style(settings): redesign the room and space settings nav rail with a violet active accent and eyebrow header

This commit is contained in:
heaven 2026-06-04 02:39:23 +03:00
parent 18eddec405
commit 0beb98e4d1
6 changed files with 162 additions and 46 deletions

View file

@ -918,7 +918,9 @@
"perm_space_name": "Space Name",
"perm_space_topic": "Space Topic",
"perm_change_space_access": "Change Space Access",
"perm_upgrade_space": "Upgrade Space"
"perm_upgrade_space": "Upgrade Space",
"settings": "Settings",
"sections": "Sections"
},
"Push": {
"new_message": "New message",

View file

@ -936,7 +936,9 @@
"perm_space_name": "Название пространства",
"perm_space_topic": "Тема пространства",
"perm_change_space_access": "Изменение доступа к пространству",
"perm_upgrade_space": "Обновить пространство"
"perm_upgrade_space": "Обновить пространство",
"settings": "Настройки",
"sections": "Разделы"
},
"Push": {
"new_message": "Новое сообщение",

View 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,
},
},
});

View 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>;
}

View file

@ -1,8 +1,13 @@
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 { JoinRule } from 'matrix-js-sdk';
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
import {
SettingsNavEyebrow,
SettingsNavItem,
SettingsNavSection,
} from '../common-settings/SettingsNav';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { mxcUrlToHttp } from '../../utils/matrix';
@ -63,6 +68,7 @@ type RoomSettingsProps = {
requestClose: () => void;
};
export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
const { t } = useTranslation();
const room = useRoom();
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
@ -99,8 +105,8 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
<PageNav size="300">
<PageNavHeader outlined={false}>
<Box grow="Yes" gap="200">
<Avatar size="200" radii="300">
<Box grow="Yes" gap="300" alignItems="Center">
<Avatar size="300" radii="300">
<RoomAvatar
roomId={room.roomId}
src={avatarUrl}
@ -115,9 +121,12 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
)}
/>
</Avatar>
<Text size="H4" truncate>
{roomName}
</Text>
<Box direction="Column" grow="Yes">
<SettingsNavEyebrow>{t('RoomSettings.settings')}</SettingsNavEyebrow>
<Text size="H4" truncate>
{roomName}
</Text>
</Box>
</Box>
<Box shrink="No">
{screenSize === ScreenSize.Mobile && (
@ -130,25 +139,15 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
<Box grow="Yes" direction="Column">
<PageNavContent>
<div style={{ flexGrow: 1 }}>
<SettingsNavSection>{t('RoomSettings.sections')}</SettingsNavSection>
{menuItems.map((item) => (
<MenuItem
<SettingsNavItem
key={item.name}
variant="Background"
radii="400"
aria-pressed={activePage === item.page}
before={<Icon src={item.icon} size="100" filled={activePage === item.page} />}
icon={item.icon}
label={item.name}
active={activePage === item.page}
onClick={() => setActivePage(item.page)}
>
<Text
style={{
fontWeight: activePage === item.page ? config.fontWeight.W600 : undefined,
}}
size="T300"
truncate
>
{item.name}
</Text>
</MenuItem>
/>
))}
</div>
</PageNavContent>

View file

@ -1,8 +1,13 @@
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 { useTranslation } from 'react-i18next';
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
import {
SettingsNavEyebrow,
SettingsNavItem,
SettingsNavSection,
} from '../common-settings/SettingsNav';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { mxcUrlToHttp } from '../../utils/matrix';
@ -63,6 +68,7 @@ type SpaceSettingsProps = {
requestClose: () => void;
};
export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps) {
const { t } = useTranslation();
const room = useRoom();
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
@ -98,8 +104,8 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
<PageNav size="300">
<PageNavHeader outlined={false}>
<Box grow="Yes" gap="200">
<Avatar size="200" radii="300">
<Box grow="Yes" gap="300" alignItems="Center">
<Avatar size="300" radii="300">
<RoomAvatar
roomId={room.roomId}
src={avatarUrl}
@ -114,9 +120,12 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
)}
/>
</Avatar>
<Text size="H4" truncate>
{roomName}
</Text>
<Box direction="Column" grow="Yes">
<SettingsNavEyebrow>{t('RoomSettings.settings')}</SettingsNavEyebrow>
<Text size="H4" truncate>
{roomName}
</Text>
</Box>
</Box>
<Box shrink="No">
{screenSize === ScreenSize.Mobile && (
@ -129,25 +138,15 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
<Box grow="Yes" direction="Column">
<PageNavContent>
<div style={{ flexGrow: 1 }}>
<SettingsNavSection>{t('RoomSettings.sections')}</SettingsNavSection>
{menuItems.map((item) => (
<MenuItem
<SettingsNavItem
key={item.name}
variant="Background"
radii="400"
aria-pressed={activePage === item.page}
before={<Icon src={item.icon} size="100" filled={activePage === item.page} />}
icon={item.icon}
label={item.name}
active={activePage === item.page}
onClick={() => setActivePage(item.page)}
>
<Text
style={{
fontWeight: activePage === item.page ? config.fontWeight.W600 : undefined,
}}
size="T300"
truncate
>
{item.name}
</Text>
</MenuItem>
/>
))}
</div>
</PageNavContent>