style(settings): redesign the room and space settings nav rail with a violet active accent and eyebrow header
This commit is contained in:
parent
18eddec405
commit
0beb98e4d1
6 changed files with 162 additions and 46 deletions
|
|
@ -918,7 +918,9 @@
|
||||||
"perm_space_name": "Space Name",
|
"perm_space_name": "Space Name",
|
||||||
"perm_space_topic": "Space Topic",
|
"perm_space_topic": "Space Topic",
|
||||||
"perm_change_space_access": "Change Space Access",
|
"perm_change_space_access": "Change Space Access",
|
||||||
"perm_upgrade_space": "Upgrade Space"
|
"perm_upgrade_space": "Upgrade Space",
|
||||||
|
"settings": "Settings",
|
||||||
|
"sections": "Sections"
|
||||||
},
|
},
|
||||||
"Push": {
|
"Push": {
|
||||||
"new_message": "New message",
|
"new_message": "New message",
|
||||||
|
|
|
||||||
|
|
@ -936,7 +936,9 @@
|
||||||
"perm_space_name": "Название пространства",
|
"perm_space_name": "Название пространства",
|
||||||
"perm_space_topic": "Тема пространства",
|
"perm_space_topic": "Тема пространства",
|
||||||
"perm_change_space_access": "Изменение доступа к пространству",
|
"perm_change_space_access": "Изменение доступа к пространству",
|
||||||
"perm_upgrade_space": "Обновить пространство"
|
"perm_upgrade_space": "Обновить пространство",
|
||||||
|
"settings": "Настройки",
|
||||||
|
"sections": "Разделы"
|
||||||
},
|
},
|
||||||
"Push": {
|
"Push": {
|
||||||
"new_message": "Новое сообщение",
|
"new_message": "Новое сообщение",
|
||||||
|
|
|
||||||
86
src/app/features/common-settings/SettingsNav.css.ts
Normal file
86
src/app/features/common-settings/SettingsNav.css.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
import { color, config, DefaultReset, FocusOutline, toRem } from 'folds';
|
||||||
|
|
||||||
|
// Dawn settings rail — uppercase tracked muted labels, raised active row with a
|
||||||
|
// violet accent tick, and an eyebrow over the entity name. Shared by room and
|
||||||
|
// space settings.
|
||||||
|
|
||||||
|
const MUTED = 'rgba(230, 230, 233, 0.45)';
|
||||||
|
|
||||||
|
export const NavEyebrow = style([
|
||||||
|
DefaultReset,
|
||||||
|
{
|
||||||
|
display: 'block',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
fontSize: toRem(10),
|
||||||
|
lineHeight: toRem(14),
|
||||||
|
fontWeight: config.fontWeight.W600,
|
||||||
|
color: MUTED,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const NavSection = style([
|
||||||
|
DefaultReset,
|
||||||
|
{
|
||||||
|
display: 'block',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.08em',
|
||||||
|
fontSize: toRem(11),
|
||||||
|
lineHeight: toRem(16),
|
||||||
|
fontWeight: config.fontWeight.W600,
|
||||||
|
color: MUTED,
|
||||||
|
padding: `${config.space.S400} ${config.space.S300} ${config.space.S200}`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const NavItem = style([
|
||||||
|
DefaultReset,
|
||||||
|
FocusOutline,
|
||||||
|
{
|
||||||
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: config.space.S300,
|
||||||
|
width: '100%',
|
||||||
|
padding: `${config.space.S300} ${config.space.S300}`,
|
||||||
|
marginBottom: toRem(2),
|
||||||
|
borderRadius: config.radii.R400,
|
||||||
|
color: color.Surface.OnContainer,
|
||||||
|
cursor: 'pointer',
|
||||||
|
selectors: {
|
||||||
|
'&:hover': { backgroundColor: color.Background.ContainerHover },
|
||||||
|
'&[aria-pressed=true]': { backgroundColor: color.Background.ContainerActive },
|
||||||
|
'&[aria-pressed=true]::before': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
left: toRem(5),
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
width: toRem(3),
|
||||||
|
height: '52%',
|
||||||
|
borderRadius: toRem(3),
|
||||||
|
backgroundColor: color.Primary.Main,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const NavItemIcon = style({
|
||||||
|
opacity: 0.6,
|
||||||
|
selectors: {
|
||||||
|
[`${NavItem}[aria-pressed=true] &`]: {
|
||||||
|
color: color.Primary.Main,
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NavItemLabel = style({
|
||||||
|
fontWeight: config.fontWeight.W500,
|
||||||
|
selectors: {
|
||||||
|
[`${NavItem}[aria-pressed=true] &`]: {
|
||||||
|
fontWeight: config.fontWeight.W600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
28
src/app/features/common-settings/SettingsNav.tsx
Normal file
28
src/app/features/common-settings/SettingsNav.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { Icon, IconSrc, Text } from 'folds';
|
||||||
|
import * as css from './SettingsNav.css';
|
||||||
|
|
||||||
|
type SettingsNavItemProps = {
|
||||||
|
icon: IconSrc;
|
||||||
|
label: string;
|
||||||
|
active: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
export function SettingsNavItem({ icon, label, active, onClick }: SettingsNavItemProps) {
|
||||||
|
return (
|
||||||
|
<button type="button" className={css.NavItem} aria-pressed={active} onClick={onClick}>
|
||||||
|
<Icon className={css.NavItemIcon} src={icon} size="100" filled={active} />
|
||||||
|
<Text className={css.NavItemLabel} as="span" size="T300" truncate>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsNavEyebrow({ children }: { children: ReactNode }) {
|
||||||
|
return <span className={css.NavEyebrow}>{children}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsNavSection({ children }: { children: ReactNode }) {
|
||||||
|
return <span className={css.NavSection}>{children}</span>;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds';
|
import { Avatar, Box, Icon, IconButton, Icons, IconSrc, Text } from 'folds';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { JoinRule } from 'matrix-js-sdk';
|
import { JoinRule } from 'matrix-js-sdk';
|
||||||
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
|
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
|
||||||
|
import {
|
||||||
|
SettingsNavEyebrow,
|
||||||
|
SettingsNavItem,
|
||||||
|
SettingsNavSection,
|
||||||
|
} from '../common-settings/SettingsNav';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { mxcUrlToHttp } from '../../utils/matrix';
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
|
@ -63,6 +68,7 @@ type RoomSettingsProps = {
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
};
|
};
|
||||||
export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
|
export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
@ -99,8 +105,8 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
|
||||||
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
|
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
|
||||||
<PageNav size="300">
|
<PageNav size="300">
|
||||||
<PageNavHeader outlined={false}>
|
<PageNavHeader outlined={false}>
|
||||||
<Box grow="Yes" gap="200">
|
<Box grow="Yes" gap="300" alignItems="Center">
|
||||||
<Avatar size="200" radii="300">
|
<Avatar size="300" radii="300">
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
|
|
@ -115,9 +121,12 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Text size="H4" truncate>
|
<Box direction="Column" grow="Yes">
|
||||||
{roomName}
|
<SettingsNavEyebrow>{t('RoomSettings.settings')}</SettingsNavEyebrow>
|
||||||
</Text>
|
<Text size="H4" truncate>
|
||||||
|
{roomName}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No">
|
<Box shrink="No">
|
||||||
{screenSize === ScreenSize.Mobile && (
|
{screenSize === ScreenSize.Mobile && (
|
||||||
|
|
@ -130,25 +139,15 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
|
||||||
<Box grow="Yes" direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
<PageNavContent>
|
<PageNavContent>
|
||||||
<div style={{ flexGrow: 1 }}>
|
<div style={{ flexGrow: 1 }}>
|
||||||
|
<SettingsNavSection>{t('RoomSettings.sections')}</SettingsNavSection>
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
<MenuItem
|
<SettingsNavItem
|
||||||
key={item.name}
|
key={item.name}
|
||||||
variant="Background"
|
icon={item.icon}
|
||||||
radii="400"
|
label={item.name}
|
||||||
aria-pressed={activePage === item.page}
|
active={activePage === item.page}
|
||||||
before={<Icon src={item.icon} size="100" filled={activePage === item.page} />}
|
|
||||||
onClick={() => setActivePage(item.page)}
|
onClick={() => setActivePage(item.page)}
|
||||||
>
|
/>
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontWeight: activePage === item.page ? config.fontWeight.W600 : undefined,
|
|
||||||
}}
|
|
||||||
size="T300"
|
|
||||||
truncate
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Text>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</PageNavContent>
|
</PageNavContent>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds';
|
import { Avatar, Box, Icon, IconButton, Icons, IconSrc, Text } from 'folds';
|
||||||
import { JoinRule } from 'matrix-js-sdk';
|
import { JoinRule } from 'matrix-js-sdk';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
|
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
|
||||||
|
import {
|
||||||
|
SettingsNavEyebrow,
|
||||||
|
SettingsNavItem,
|
||||||
|
SettingsNavSection,
|
||||||
|
} from '../common-settings/SettingsNav';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { mxcUrlToHttp } from '../../utils/matrix';
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
|
@ -63,6 +68,7 @@ type SpaceSettingsProps = {
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
};
|
};
|
||||||
export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps) {
|
export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
@ -98,8 +104,8 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
|
||||||
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
|
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
|
||||||
<PageNav size="300">
|
<PageNav size="300">
|
||||||
<PageNavHeader outlined={false}>
|
<PageNavHeader outlined={false}>
|
||||||
<Box grow="Yes" gap="200">
|
<Box grow="Yes" gap="300" alignItems="Center">
|
||||||
<Avatar size="200" radii="300">
|
<Avatar size="300" radii="300">
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
|
|
@ -114,9 +120,12 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Text size="H4" truncate>
|
<Box direction="Column" grow="Yes">
|
||||||
{roomName}
|
<SettingsNavEyebrow>{t('RoomSettings.settings')}</SettingsNavEyebrow>
|
||||||
</Text>
|
<Text size="H4" truncate>
|
||||||
|
{roomName}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No">
|
<Box shrink="No">
|
||||||
{screenSize === ScreenSize.Mobile && (
|
{screenSize === ScreenSize.Mobile && (
|
||||||
|
|
@ -129,25 +138,15 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
|
||||||
<Box grow="Yes" direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
<PageNavContent>
|
<PageNavContent>
|
||||||
<div style={{ flexGrow: 1 }}>
|
<div style={{ flexGrow: 1 }}>
|
||||||
|
<SettingsNavSection>{t('RoomSettings.sections')}</SettingsNavSection>
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
<MenuItem
|
<SettingsNavItem
|
||||||
key={item.name}
|
key={item.name}
|
||||||
variant="Background"
|
icon={item.icon}
|
||||||
radii="400"
|
label={item.name}
|
||||||
aria-pressed={activePage === item.page}
|
active={activePage === item.page}
|
||||||
before={<Icon src={item.icon} size="100" filled={activePage === item.page} />}
|
|
||||||
onClick={() => setActivePage(item.page)}
|
onClick={() => setActivePage(item.page)}
|
||||||
>
|
/>
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontWeight: activePage === item.page ? config.fontWeight.W600 : undefined,
|
|
||||||
}}
|
|
||||||
size="T300"
|
|
||||||
truncate
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Text>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</PageNavContent>
|
</PageNavContent>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue