diff --git a/public/res/ic/outlined/shield-user.svg b/public/res/ic/outlined/shield-user.svg
new file mode 100644
index 0000000..bd5f07c
--- /dev/null
+++ b/public/res/ic/outlined/shield-user.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/src/app/atoms/input/Input.jsx b/src/app/atoms/input/Input.jsx
index 5c1d842..b65d521 100644
--- a/src/app/atoms/input/Input.jsx
+++ b/src/app/atoms/input/Input.jsx
@@ -8,7 +8,7 @@ function Input({
id, label, name, value, placeholder,
required, type, onChange, forwardRef,
resizable, minHeight, onResize, state,
- onKeyDown,
+ onKeyDown, disabled,
}) {
return (
@@ -29,6 +29,7 @@ function Input({
onChange={onChange}
onResize={onResize}
onKeyDown={onKeyDown}
+ disabled={disabled}
/>
) : (
)}
@@ -64,6 +66,7 @@ Input.defaultProps = {
onResize: null,
state: 'normal',
onKeyDown: null,
+ disabled: false,
};
Input.propTypes = {
@@ -81,6 +84,7 @@ Input.propTypes = {
onResize: PropTypes.func,
state: PropTypes.oneOf(['normal', 'success', 'error']),
onKeyDown: PropTypes.func,
+ disabled: PropTypes.bool,
};
export default Input;
diff --git a/src/app/atoms/input/Input.scss b/src/app/atoms/input/Input.scss
index efe7da7..40fe43e 100644
--- a/src/app/atoms/input/Input.scss
+++ b/src/app/atoms/input/Input.scss
@@ -1,3 +1,5 @@
+@use '../../atoms/scroll/scrollbar';
+
.input {
display: block;
width: 100%;
@@ -13,6 +15,11 @@
letter-spacing: var(--ls-b2);
line-height: var(--lh-b2);
+ :disabled {
+ opacity: 0.4;
+ cursor: no-drop;
+ }
+
&__label {
display: inline-block;
margin-bottom: var(--sp-ultra-tight);
@@ -21,6 +28,10 @@
&--resizable {
resize: vertical !important;
+ overflow-y: auto !important;
+ @include scrollbar.scroll;
+ @include scrollbar.scroll__v;
+ @include scrollbar.scroll--auto-hide;
}
&--success {
border: 1px solid var(--bg-positive);
diff --git a/src/app/atoms/modal/RawModal.scss b/src/app/atoms/modal/RawModal.scss
index d008cc0..72a64d7 100644
--- a/src/app/atoms/modal/RawModal.scss
+++ b/src/app/atoms/modal/RawModal.scss
@@ -1,6 +1,6 @@
.ReactModal__Overlay {
opacity: 0;
- transition: opacity 200ms cubic-bezier(0.13, 0.56, 0.25, 0.99);
+ transition: opacity 200ms var(--fluid-slide-up);
}
.ReactModal__Overlay--after-open{
opacity: 1;
@@ -11,7 +11,7 @@
.ReactModal__Content {
transform: translateY(100%);
- transition: transform 200ms cubic-bezier(0.13, 0.56, 0.25, 0.99);
+ transition: transform 200ms var(--fluid-slide-up);
}
.ReactModal__Content--after-open{
diff --git a/src/app/organisms/room/Room.jsx b/src/app/organisms/room/Room.jsx
index 164e490..b8972d6 100644
--- a/src/app/organisms/room/Room.jsx
+++ b/src/app/organisms/room/Room.jsx
@@ -9,6 +9,7 @@ import RoomTimeline from '../../../client/state/RoomTimeline';
import Welcome from '../welcome/Welcome';
import RoomView from './RoomView';
+import RoomSettings from './RoomSettings';
import PeopleDrawer from './PeopleDrawer';
function Room() {
@@ -42,8 +43,11 @@ function Room() {
if (roomTimeline === null) return ;
return (
-
-
+
);
diff --git a/src/app/organisms/room/Room.scss b/src/app/organisms/room/Room.scss
index cea4bad..975acbd 100644
--- a/src/app/organisms/room/Room.scss
+++ b/src/app/organisms/room/Room.scss
@@ -1,4 +1,12 @@
-.room-container {
- display: flex;
+@use '../../partials/flex';
+
+.room {
+ @extend .cp-fx__row;
height: 100%;
+
+ &__content {
+ @extend .cp-fx__item-one;
+ position: relative;
+ overflow: hidden;
+ }
}
\ No newline at end of file
diff --git a/src/app/organisms/room/RoomSettings.jsx b/src/app/organisms/room/RoomSettings.jsx
new file mode 100644
index 0000000..82817b5
--- /dev/null
+++ b/src/app/organisms/room/RoomSettings.jsx
@@ -0,0 +1,85 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import './RoomSettings.scss';
+
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+
+import Text from '../../atoms/text/Text';
+import Header, { TitleWrapper } from '../../atoms/header/Header';
+import ScrollView from '../../atoms/scroll/ScrollView';
+import Tabs from '../../atoms/tabs/Tabs';
+import RoomProfile from '../../molecules/room-profile/RoomProfile';
+
+import SettingsIC from '../../../../public/res/ic/outlined/settings.svg';
+import SearchIC from '../../../../public/res/ic/outlined/search.svg';
+import ShieldUserIC from '../../../../public/res/ic/outlined/shield-user.svg';
+import LockIC from '../../../../public/res/ic/outlined/lock.svg';
+import InfoIC from '../../../../public/res/ic/outlined/info.svg';
+
+import { useForceUpdate } from '../../hooks/useForceUpdate';
+
+const tabItems = [{
+ iconSrc: SettingsIC,
+ text: 'General',
+ disabled: false,
+}, {
+ iconSrc: SearchIC,
+ text: 'Search',
+ disabled: false,
+}, {
+ iconSrc: ShieldUserIC,
+ text: 'Permissions',
+ disabled: false,
+}, {
+ iconSrc: LockIC,
+ text: 'Security',
+ disabled: false,
+}, {
+ iconSrc: InfoIC,
+ text: 'Advanced',
+ disabled: false,
+}];
+
+function RoomSettings({ roomId }) {
+ const [, forceUpdate] = useForceUpdate();
+
+ useEffect(() => {
+ const settingsToggle = (isVisible) => {
+ if (isVisible) forceUpdate();
+ else setTimeout(() => forceUpdate(), 200);
+ };
+ navigation.on(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle);
+ return () => {
+ navigation.removeListener(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle);
+ };
+ }, []);
+
+ if (!navigation.isRoomSettings) return null;
+
+ return (
+
+ );
+}
+
+RoomSettings.propTypes = {
+ roomId: PropTypes.string.isRequired,
+};
+
+export default RoomSettings;
diff --git a/src/app/organisms/room/RoomSettings.scss b/src/app/organisms/room/RoomSettings.scss
new file mode 100644
index 0000000..fd78f6b
--- /dev/null
+++ b/src/app/organisms/room/RoomSettings.scss
@@ -0,0 +1,39 @@
+@use '../../partials/dir';
+
+.room-settings {
+ height: 100%;
+
+ &__content {
+ padding-bottom: calc(2 * var(--sp-extra-loose));
+ position: relative;
+
+ & .room-profile {
+ margin: var(--sp-extra-loose);
+ }
+ }
+
+ & .tabs {
+ position: sticky;
+ top: 0;
+ width: 100%;
+ background-color: var(--bg-surface-low);
+ box-shadow: 0 -4px 0 var(--bg-surface-low),
+ inset 0 -1px 0 var(--bg-surface-border);
+
+ &__content {
+ padding: 0 var(--sp-normal);
+ }
+ }
+
+ &__cards-wrapper {
+ padding: var(--sp-normal);
+ @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
+ }
+
+ &__card {
+ background-color: var(--bg-surface);
+ border-radius: var(--bo-radius);
+ box-shadow: var(--bs-surface-border);
+ padding: 16px;
+ }
+}
\ No newline at end of file
diff --git a/src/app/organisms/room/RoomView.jsx b/src/app/organisms/room/RoomView.jsx
index 7b75127..7851f25 100644
--- a/src/app/organisms/room/RoomView.jsx
+++ b/src/app/organisms/room/RoomView.jsx
@@ -1,9 +1,12 @@
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './RoomView.scss';
import EventEmitter from 'events';
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+
import RoomViewHeader from './RoomViewHeader';
import RoomViewContent from './RoomViewContent';
import RoomViewFloating from './RoomViewFloating';
@@ -13,11 +16,27 @@ import RoomViewCmdBar from './RoomViewCmdBar';
const viewEvent = new EventEmitter();
function RoomView({ roomTimeline, eventId }) {
+ const roomViewRef = useRef(null);
// eslint-disable-next-line react/prop-types
const { roomId } = roomTimeline;
+ useEffect(() => {
+ const settingsToggle = (isVisible) => {
+ const roomView = roomViewRef.current;
+ roomView.classList.toggle('room-view--dropped');
+
+ const roomViewContent = roomView.children[1];
+ if (isVisible) setTimeout(() => { roomViewContent.style.visibility = 'hidden'; }, 200);
+ else roomViewContent.style.visibility = 'visible';
+ };
+ navigation.on(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle);
+ return () => {
+ navigation.removeListener(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle);
+ };
+ }, []);
+
return (
-
+
diff --git a/src/app/organisms/room/RoomView.scss b/src/app/organisms/room/RoomView.scss
index 9e1c8b4..73aac89 100644
--- a/src/app/organisms/room/RoomView.scss
+++ b/src/app/organisms/room/RoomView.scss
@@ -1,8 +1,23 @@
@use '../../partials/flex';
.room-view {
- @extend .cp-fx__item-one;
@extend .cp-fx__column;
+ background-color: var(--bg-surface);
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ top: 0;
+ z-index: 99;
+ box-shadow: none;
+
+ transition: transform 200ms var(--fluid-slide-down),
+ box-shadow 200ms var(--fluid-slide-down);
+
+ &--dropped {
+ transform: translateY(calc(100% - var(--header-height)));
+ border-radius: var(--bo-radius) var(--bo-radius) 0 0;
+ box-shadow: var(--bs-popup);
+ }
&__content-wrapper {
@extend .cp-fx__item-one;
diff --git a/src/app/organisms/room/RoomViewHeader.jsx b/src/app/organisms/room/RoomViewHeader.jsx
index e9153f5..284b306 100644
--- a/src/app/organisms/room/RoomViewHeader.jsx
+++ b/src/app/organisms/room/RoomViewHeader.jsx
@@ -1,20 +1,26 @@
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
+import './RoomViewHeader.scss';
import { twemojify } from '../../../util/twemojify';
+import { blurOnBubbling } from '../../atoms/button/script';
import initMatrix from '../../../client/initMatrix';
-import { openRoomOptions } from '../../../client/action/navigation';
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+import { toggleRoomSettings, openRoomOptions } from '../../../client/action/navigation';
import { togglePeopleDrawer } from '../../../client/action/settings';
import colorMXID from '../../../util/colorMXID';
import { getEventCords } from '../../../util/common';
import Text from '../../atoms/text/Text';
+import RawIcon from '../../atoms/system-icons/RawIcon';
import IconButton from '../../atoms/button/IconButton';
import Header, { TitleWrapper } from '../../atoms/header/Header';
import Avatar from '../../atoms/avatar/Avatar';
import UserIC from '../../../../public/res/ic/outlined/user.svg';
+import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg';
function RoomViewHeader({ roomId }) {
@@ -23,15 +29,35 @@ function RoomViewHeader({ roomId }) {
let avatarSrc = mx.getRoom(roomId).getAvatarUrl(mx.baseUrl, 36, 36, 'crop');
avatarSrc = isDM ? mx.getRoom(roomId).getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 36, 36, 'crop') : avatarSrc;
const roomName = mx.getRoom(roomId).name;
- const roomTopic = mx.getRoom(roomId).currentState.getStateEvents('m.room.topic')[0]?.getContent().topic;
+ const roomHeaderBtnRef = useRef(null);
+ useEffect(() => {
+ const settingsToggle = (isVisibile) => {
+ const rawIcon = roomHeaderBtnRef.current.lastElementChild;
+ rawIcon.style.transform = isVisibile
+ ? 'rotateX(180deg)'
+ : 'rotateX(0deg)';
+ };
+ navigation.on(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle);
+ return () => {
+ navigation.removeListener(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle);
+ };
+ }, []);
return (
-
-
- {twemojify(roomName)}
- { typeof roomTopic !== 'undefined' && {twemojify(roomTopic)}
}
-
+
openRoomOptions(getEventCords(e), roomId)}
diff --git a/src/app/organisms/room/RoomViewHeader.scss b/src/app/organisms/room/RoomViewHeader.scss
new file mode 100644
index 0000000..ddfab6c
--- /dev/null
+++ b/src/app/organisms/room/RoomViewHeader.scss
@@ -0,0 +1,27 @@
+@use '../../partials/flex';
+@use '../../partials/dir';
+
+.room-header__btn {
+ min-width: 0;
+ @extend .cp-fx__row--s-c;
+ @include dir.side(margin, 0, auto);
+ border-radius: var(--bo-radius);
+ cursor: pointer;
+
+ & .ic-raw {
+ @include dir.side(margin, 0, var(--sp-extra-tight));
+ transition: transform 200ms ease-in-out;
+ }
+ @media (hover:hover) {
+ &:hover {
+ background-color: var(--bg-surface-hover);
+ box-shadow: var(--bs-surface-outline);
+ }
+ }
+ &:focus,
+ &:active {
+ background-color: var(--bg-surface-active);
+ box-shadow: var(--bs-surface-outline);
+ outline: none;
+ }
+}
\ No newline at end of file
diff --git a/src/app/organisms/room/RoomViewInput.jsx b/src/app/organisms/room/RoomViewInput.jsx
index 87d3b5b..e8d5d39 100644
--- a/src/app/organisms/room/RoomViewInput.jsx
+++ b/src/app/organisms/room/RoomViewInput.jsx
@@ -151,7 +151,6 @@ function RoomViewInput({
navigation.on(cons.events.navigation.REPLY_TO_CLICKED, setUpReply);
if (textAreaRef?.current !== null) {
isTyping = false;
- focusInput();
textAreaRef.current.value = roomsInput.getMessage(roomId);
setAttachment(roomsInput.getAttachment(roomId));
setReplyTo(roomsInput.getReplyTo(roomId));
diff --git a/src/app/organisms/welcome/Welcome.scss b/src/app/organisms/welcome/Welcome.scss
index 7f242c9..e55bb8e 100644
--- a/src/app/organisms/welcome/Welcome.scss
+++ b/src/app/organisms/welcome/Welcome.scss
@@ -3,6 +3,7 @@
.app-welcome {
width: 100%;
height: 100%;
+ background-color: var(--bg-surface);
& > div {
@extend .cp-fx__column--c-c;
diff --git a/src/app/templates/client/Client.scss b/src/app/templates/client/Client.scss
index 546c151..45a5052 100644
--- a/src/app/templates/client/Client.scss
+++ b/src/app/templates/client/Client.scss
@@ -9,7 +9,6 @@
.room__wrapper {
flex: 1;
min-width: 0;
- background-color: var(--bg-surface);
}
diff --git a/src/client/action/navigation.js b/src/client/action/navigation.js
index 693ff7a..02ee13a 100644
--- a/src/client/action/navigation.js
+++ b/src/client/action/navigation.js
@@ -23,6 +23,13 @@ export function selectRoom(roomId, eventId) {
});
}
+export function toggleRoomSettings(roomId) {
+ appDispatcher.dispatch({
+ type: cons.actions.navigation.TOGGLE_ROOM_SETTINGS,
+ roomId,
+ });
+}
+
export function openInviteList() {
appDispatcher.dispatch({
type: cons.actions.navigation.OPEN_INVITE_LIST,
diff --git a/src/client/event/hotkeys.js b/src/client/event/hotkeys.js
index 41c93e9..ab94635 100644
--- a/src/client/event/hotkeys.js
+++ b/src/client/event/hotkeys.js
@@ -1,4 +1,4 @@
-import { openSearch } from '../action/navigation';
+import { openSearch, toggleRoomSettings } from '../action/navigation';
import navigation from '../state/navigation';
function listenKeyboard(event) {
@@ -11,12 +11,24 @@ function listenKeyboard(event) {
openSearch();
}
}
+
if (!event.ctrlKey && !event.altKey) {
+ if (event.keyCode === 38 && navigation.isRoomSettings) {
+ // close room settings
+ toggleRoomSettings();
+ return;
+ }
+ if (event.keyCode === 40 && !navigation.isRoomSettings) {
+ // open room settings
+ toggleRoomSettings();
+ return;
+ }
+
if (navigation.isRawModalVisible) return;
if (['text', 'textarea'].includes(document.activeElement.type)) {
return;
}
- if (event.keyCode < 48
+ if ((event.keyCode !== 8 && event.keyCode < 48)
|| (event.keyCode >= 91 && event.keyCode <= 93)
|| (event.keyCode >= 112 && event.keyCode <= 183)) {
return;
diff --git a/src/client/state/cons.js b/src/client/state/cons.js
index f15bd3b..9d34c39 100644
--- a/src/client/state/cons.js
+++ b/src/client/state/cons.js
@@ -30,7 +30,7 @@ const cons = {
SELECT_TAB: 'SELECT_TAB',
SELECT_SPACE: 'SELECT_SPACE',
SELECT_ROOM: 'SELECT_ROOM',
- TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER',
+ TOGGLE_ROOM_SETTINGS: 'TOGGLE_ROOM_SETTINGS',
OPEN_INVITE_LIST: 'OPEN_INVITE_LIST',
OPEN_PUBLIC_ROOMS: 'OPEN_PUBLIC_ROOMS',
OPEN_CREATE_ROOM: 'OPEN_CREATE_ROOM',
@@ -65,7 +65,7 @@ const cons = {
TAB_SELECTED: 'TAB_SELECTED',
SPACE_SELECTED: 'SPACE_SELECTED',
ROOM_SELECTED: 'ROOM_SELECTED',
- PEOPLE_DRAWER_TOGGLED: 'PEOPLE_DRAWER_TOGGLED',
+ ROOM_SETTINGS_TOGGLED: 'ROOM_SETTINGS_TOGGLED',
INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',
PUBLIC_ROOMS_OPENED: 'PUBLIC_ROOMS_OPENED',
CREATE_ROOM_OPENED: 'CREATE_ROOM_OPENED',
diff --git a/src/client/state/navigation.js b/src/client/state/navigation.js
index 44681ee..8ebf404 100644
--- a/src/client/state/navigation.js
+++ b/src/client/state/navigation.js
@@ -11,6 +11,7 @@ class Navigation extends EventEmitter {
this.selectedSpacePath = [cons.tabs.HOME];
this.selectedRoomId = null;
+ this.isRoomSettings = false;
this.recentRooms = [];
this.isRawModalVisible = false;
@@ -77,6 +78,10 @@ class Navigation extends EventEmitter {
this.removeRecentRoom(prevSelectedRoomId);
this.addRecentRoom(prevSelectedRoomId);
this.removeRecentRoom(this.selectedRoomId);
+ if (this.isRoomSettings) {
+ this.isRoomSettings = !this.isRoomSettings;
+ this.emit(cons.events.navigation.ROOM_SETTINGS_TOGGLED, this.isRoomSettings);
+ }
this.emit(
cons.events.navigation.ROOM_SELECTED,
this.selectedRoomId,
@@ -84,6 +89,10 @@ class Navigation extends EventEmitter {
action.eventId,
);
},
+ [cons.actions.navigation.TOGGLE_ROOM_SETTINGS]: () => {
+ this.isRoomSettings = !this.isRoomSettings;
+ this.emit(cons.events.navigation.ROOM_SETTINGS_TOGGLED, this.isRoomSettings);
+ },
[cons.actions.navigation.OPEN_INVITE_LIST]: () => {
this.emit(cons.events.navigation.INVITE_LIST_OPENED);
},
diff --git a/src/index.scss b/src/index.scss
index 3e78805..6807a20 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -68,6 +68,7 @@
/* system icons | --ic-[background type]-[priority]: value */
+ --ic-surface-high: #272727;
--ic-surface-normal: #626262;
--ic-surface-low: #7c7c7c;
--ic-primary-normal: #ffffff;
@@ -177,6 +178,8 @@
/* transition curves */
--fluid-push: cubic-bezier(0, 0.8, 0.67, 0.97);
+ --fluid-slide-down: cubic-bezier(0.02, 0.82, 0.4, 0.96);
+ --fluid-slide-up: cubic-bezier(0.13, 0.56, 0.25, 0.99);
--font-primary: 'Roboto', sans-serif;
--font-secondary: 'Roboto', sans-serif;
@@ -218,6 +221,7 @@
/* text color | --tc-[background type]-[priority]: value */
+ --ic-surface-high: rgb(255, 255, 255);;
--tc-surface-high: rgba(255, 255, 255, 98%);
--tc-surface-normal: rgba(255, 255, 255, 94%);
--tc-surface-normal-low: rgba(255, 255, 255, 60%);
@@ -299,7 +303,8 @@
/* system icons | --ic-[background type]-[priority]: value */
- --ic-surface-normal: rgb(255, 251, 222, 84%);
+ --ic-surface-high: rgb(255, 251, 222);
+ --ic-surface-normal: rgba(255, 251, 222, 84%);
--ic-surface-low: rgba(255, 251, 222, 64%);
}