style(settings): group the General options into a Dawn hairline-divided section in room and space settings

This commit is contained in:
heaven 2026-06-04 01:40:27 +03:00
parent a3a8655487
commit aab65b573a
6 changed files with 191 additions and 227 deletions

View file

@ -20,8 +20,6 @@ import { MatrixError } from 'matrix-js-sdk';
import type { StateEvents } from 'matrix-js-sdk'; import type { StateEvents } from 'matrix-js-sdk';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { StateEvent } from '../../../../types/matrix/room'; import { StateEvent } from '../../../../types/matrix/room';
@ -65,83 +63,76 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
}; };
return ( return (
<SequenceCard <SettingTile
className={SequenceCardStyle} title={t('RoomSettings.room_encryption')}
variant="SurfaceVariant" description={
direction="Column" enabled
gap="400" ? t('RoomSettings.encryption_enabled_desc')
: t('RoomSettings.encryption_disabled_desc')
}
after={
enabled ? (
<Badge size="500" variant="Success" fill="Solid" radii="300">
<Text size="L400">{t('RoomSettings.enabled')}</Text>
</Badge>
) : (
<Button
size="300"
variant="Primary"
fill="Solid"
radii="300"
disabled={!canEnable}
onClick={() => setPrompt(true)}
before={enabling && <Spinner size="100" variant="Primary" fill="Solid" />}
>
<Text size="B300">{t('RoomSettings.enable')}</Text>
</Button>
)
}
> >
<SettingTile {enableState.status === AsyncStatus.Error && (
title={t('RoomSettings.room_encryption')} <Text style={{ color: color.Critical.Main }} size="T200">
description={ {(enableState.error as MatrixError).message}
enabled </Text>
? t('RoomSettings.encryption_enabled_desc') )}
: t('RoomSettings.encryption_disabled_desc') {prompt && (
} <Overlay open backdrop={<OverlayBackdrop />}>
after={ <OverlayCenter>
enabled ? ( <FocusTrap
<Badge size="500" variant="Success" fill="Solid" radii="300"> focusTrapOptions={{
<Text size="L400">{t('RoomSettings.enabled')}</Text> initialFocus: false,
</Badge> onDeactivate: () => setPrompt(false),
) : ( clickOutsideDeactivates: true,
<Button escapeDeactivates: stopPropagation,
size="300" }}
variant="Primary"
fill="Solid"
radii="300"
disabled={!canEnable}
onClick={() => setPrompt(true)}
before={enabling && <Spinner size="100" variant="Primary" fill="Solid" />}
> >
<Text size="B300">{t('RoomSettings.enable')}</Text> <Dialog variant="Surface">
</Button> <Header
) style={{
} padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
> borderBottomWidth: config.borderWidth.B300,
{enableState.status === AsyncStatus.Error && ( }}
<Text style={{ color: color.Critical.Main }} size="T200"> variant="Surface"
{(enableState.error as MatrixError).message} size="500"
</Text> >
)} <Box grow="Yes">
{prompt && ( <Text size="H4">{t('RoomSettings.enable_encryption')}</Text>
<Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: () => setPrompt(false),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface">
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
borderBottomWidth: config.borderWidth.B300,
}}
variant="Surface"
size="500"
>
<Box grow="Yes">
<Text size="H4">{t('RoomSettings.enable_encryption')}</Text>
</Box>
<IconButton size="300" onClick={() => setPrompt(false)} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Text priority="400">{t('RoomSettings.enable_encryption_confirm')}</Text>
<Button type="submit" variant="Primary" onClick={handleEnable}>
<Text size="B400">{t('RoomSettings.enable_e2e_encryption')}</Text>
</Button>
</Box> </Box>
</Dialog> <IconButton size="300" onClick={() => setPrompt(false)} radii="300">
</FocusTrap> <Icon src={Icons.Cross} />
</OverlayCenter> </IconButton>
</Overlay> </Header>
)} <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
</SettingTile> <Text priority="400">{t('RoomSettings.enable_encryption_confirm')}</Text>
</SequenceCard> <Button type="submit" variant="Primary" onClick={handleEnable}>
<Text size="B400">{t('RoomSettings.enable_e2e_encryption')}</Text>
</Button>
</Box>
</Dialog>
</FocusTrap>
</OverlayCenter>
</Overlay>
)}
</SettingTile>
); );
} }

View file

@ -17,8 +17,6 @@ import type { StateEvents } from 'matrix-js-sdk';
import { RoomHistoryVisibilityEventContent } from 'matrix-js-sdk/lib/types'; import { RoomHistoryVisibilityEventContent } from 'matrix-js-sdk/lib/types';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useRoom } from '../../../hooks/useRoom'; import { useRoom } from '../../../hooks/useRoom';
@ -98,77 +96,70 @@ export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProp
}; };
return ( return (
<SequenceCard <SettingTile
className={SequenceCardStyle} title={t('RoomSettings.history_visibility')}
variant="SurfaceVariant" description={t('RoomSettings.history_visibility_desc')}
direction="Column" after={
gap="400" <PopOut
> anchor={menuAnchor}
<SettingTile position="Bottom"
title={t('RoomSettings.history_visibility')} align="End"
description={t('RoomSettings.history_visibility_desc')} content={
after={ <FocusTrap
<PopOut focusTrapOptions={{
anchor={menuAnchor} initialFocus: false,
position="Bottom" returnFocusOnDeactivate: false,
align="End" onDeactivate: () => setMenuAnchor(undefined),
content={ clickOutsideDeactivates: true,
<FocusTrap isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
focusTrapOptions={{ isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
initialFocus: false, escapeDeactivates: stopPropagation,
returnFocusOnDeactivate: false, }}
onDeactivate: () => setMenuAnchor(undefined), >
clickOutsideDeactivates: true, <Menu style={{ padding: config.space.S100 }}>
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', {visibilityMenu.map((visibility) => (
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', <MenuItem
escapeDeactivates: stopPropagation, key={visibility}
}} size="300"
> radii="300"
<Menu style={{ padding: config.space.S100 }}> onClick={() => handleChange(visibility)}
{visibilityMenu.map((visibility) => ( aria-pressed={visibility === historyVisibility}
<MenuItem >
key={visibility} <Text as="span" size="T300" truncate>
size="300" {visibilityStr[visibility]}
radii="300" </Text>
onClick={() => handleChange(visibility)} </MenuItem>
aria-pressed={visibility === historyVisibility} ))}
> </Menu>
<Text as="span" size="T300" truncate> </FocusTrap>
{visibilityStr[visibility]} }
</Text> >
</MenuItem> <Button
))} variant="Secondary"
</Menu> fill="Soft"
</FocusTrap> size="300"
radii="300"
outlined
disabled={!canEdit || submitting}
onClick={handleOpenMenu}
after={
submitting ? (
<Spinner size="100" variant="Secondary" />
) : (
<Icon size="100" src={Icons.ChevronBottom} />
)
} }
> >
<Button <Text size="B300">{visibilityStr[historyVisibility]}</Text>
variant="Secondary" </Button>
fill="Soft" </PopOut>
size="300" }
radii="300" >
outlined {submitState.status === AsyncStatus.Error && (
disabled={!canEdit || submitting} <Text style={{ color: color.Critical.Main }} size="T200">
onClick={handleOpenMenu} {(submitState.error as MatrixError).message}
after={ </Text>
submitting ? ( )}
<Spinner size="100" variant="Secondary" /> </SettingTile>
) : (
<Icon size="100" src={Icons.ChevronBottom} />
)
}
>
<Text size="B300">{visibilityStr[historyVisibility]}</Text>
</Button>
</PopOut>
}
>
{submitState.status === AsyncStatus.Error && (
<Text style={{ color: color.Critical.Main }} size="T200">
{(submitState.error as MatrixError).message}
</Text>
)}
</SettingTile>
</SequenceCard>
); );
} }

View file

@ -11,8 +11,6 @@ import {
useJoinRuleIcons, useJoinRuleIcons,
useRoomJoinRuleLabel, useRoomJoinRuleLabel,
} from '../../../components/JoinRulesSwitcher'; } from '../../../components/JoinRulesSwitcher';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useRoom } from '../../../hooks/useRoom'; import { useRoom } from '../../../hooks/useRoom';
@ -123,37 +121,30 @@ export function RoomJoinRules({ permissions }: RoomJoinRulesProps) {
const submitting = submitState.status === AsyncStatus.Loading; const submitting = submitState.status === AsyncStatus.Loading;
return ( return (
<SequenceCard <SettingTile
className={SequenceCardStyle} title={room.isSpaceRoom() ? t('RoomSettings.space_access') : t('RoomSettings.room_access')}
variant="SurfaceVariant" description={
direction="Column" room.isSpaceRoom()
gap="400" ? t('RoomSettings.space_access_desc')
: t('RoomSettings.room_access_desc')
}
after={
<JoinRulesSwitcher
icons={icons}
labels={labels}
rules={joinRules}
value={rule}
onChange={submit}
disabled={!canEdit || submitting}
changing={submitting}
/>
}
> >
<SettingTile {submitState.status === AsyncStatus.Error && (
title={room.isSpaceRoom() ? t('RoomSettings.space_access') : t('RoomSettings.room_access')} <Text style={{ color: color.Critical.Main }} size="T200">
description={ {(submitState.error as MatrixError).message}
room.isSpaceRoom() </Text>
? t('RoomSettings.space_access_desc') )}
: t('RoomSettings.room_access_desc') </SettingTile>
}
after={
<JoinRulesSwitcher
icons={icons}
labels={labels}
rules={joinRules}
value={rule}
onChange={submit}
disabled={!canEdit || submitting}
changing={submitting}
/>
}
>
{submitState.status === AsyncStatus.Error && (
<Text style={{ color: color.Critical.Main }} size="T200">
{(submitState.error as MatrixError).message}
</Text>
)}
</SettingTile>
</SequenceCard>
); );
} }

View file

@ -3,8 +3,6 @@ import { Box, color, Spinner, Switch, Text } from 'folds';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { JoinRule, MatrixError } from 'matrix-js-sdk'; import { JoinRule, MatrixError } from 'matrix-js-sdk';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
import { useRoom } from '../../../hooks/useRoom'; import { useRoom } from '../../../hooks/useRoom';
import { useRoomDirectoryVisibility } from '../../../hooks/useRoomDirectoryVisibility'; import { useRoomDirectoryVisibility } from '../../../hooks/useRoomDirectoryVisibility';
@ -41,44 +39,37 @@ export function RoomPublish({ permissions }: RoomPublishProps) {
rule === JoinRule.Public || rule === JoinRule.Knock || rule === 'knock_restricted'; rule === JoinRule.Public || rule === JoinRule.Knock || rule === 'knock_restricted';
return ( return (
<SequenceCard <SettingTile
className={SequenceCardStyle} title={t('RoomSettings.publish_to_directory')}
variant="SurfaceVariant" description={
direction="Column" room.isSpaceRoom()
gap="400" ? t('RoomSettings.publish_space_desc')
: t('RoomSettings.publish_room_desc')
}
after={
<Box gap="200" alignItems="Center">
{loading && <Spinner variant="Secondary" />}
{!loading && visibilityState.status === AsyncStatus.Success && (
<Switch
value={visibilityState.data}
onChange={toggleVisibility}
disabled={!canEditCanonical || !validRule}
/>
)}
</Box>
}
> >
<SettingTile {visibilityState.status === AsyncStatus.Error && (
title={t('RoomSettings.publish_to_directory')} <Text style={{ color: color.Critical.Main }} size="T200">
description={ {(visibilityState.error as MatrixError).message}
room.isSpaceRoom() </Text>
? t('RoomSettings.publish_space_desc') )}
: t('RoomSettings.publish_room_desc')
}
after={
<Box gap="200" alignItems="Center">
{loading && <Spinner variant="Secondary" />}
{!loading && visibilityState.status === AsyncStatus.Success && (
<Switch
value={visibilityState.data}
onChange={toggleVisibility}
disabled={!canEditCanonical || !validRule}
/>
)}
</Box>
}
>
{visibilityState.status === AsyncStatus.Error && (
<Text style={{ color: color.Critical.Main }} size="T200">
{(visibilityState.error as MatrixError).message}
</Text>
)}
{toggleState.status === AsyncStatus.Error && ( {toggleState.status === AsyncStatus.Error && (
<Text style={{ color: color.Critical.Main }} size="T200"> <Text style={{ color: color.Critical.Main }} size="T200">
{(toggleState.error as MatrixError).message} {(toggleState.error as MatrixError).message}
</Text> </Text>
)} )}
</SettingTile> </SettingTile>
</SequenceCard>
); );
} }

View file

@ -2,6 +2,7 @@ import React from 'react';
import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { SettingsSection } from '../../settings/SettingsSection';
import { usePowerLevels } from '../../../hooks/usePowerLevels'; import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useRoom } from '../../../hooks/useRoom'; import { useRoom } from '../../../hooks/useRoom';
import { import {
@ -48,13 +49,12 @@ export function General({ requestClose }: GeneralProps) {
<PageContent> <PageContent>
<Box direction="Column" gap="700"> <Box direction="Column" gap="700">
<RoomProfile permissions={permissions} /> <RoomProfile permissions={permissions} />
<Box direction="Column" gap="100"> <SettingsSection label={t('RoomSettings.options')}>
<Text size="L400">{t('RoomSettings.options')}</Text>
<RoomJoinRules permissions={permissions} /> <RoomJoinRules permissions={permissions} />
<RoomHistoryVisibility permissions={permissions} /> <RoomHistoryVisibility permissions={permissions} />
<RoomEncryption permissions={permissions} /> <RoomEncryption permissions={permissions} />
<RoomPublish permissions={permissions} /> <RoomPublish permissions={permissions} />
</Box> </SettingsSection>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">{t('RoomSettings.addresses')}</Text> <Text size="L400">{t('RoomSettings.addresses')}</Text>
<RoomPublishedAddresses permissions={permissions} /> <RoomPublishedAddresses permissions={permissions} />

View file

@ -2,6 +2,7 @@ import React from 'react';
import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { SettingsSection } from '../../settings/SettingsSection';
import { usePowerLevels } from '../../../hooks/usePowerLevels'; import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useRoom } from '../../../hooks/useRoom'; import { useRoom } from '../../../hooks/useRoom';
import { import {
@ -46,11 +47,10 @@ export function General({ requestClose }: GeneralProps) {
<PageContent> <PageContent>
<Box direction="Column" gap="700"> <Box direction="Column" gap="700">
<RoomProfile permissions={permissions} /> <RoomProfile permissions={permissions} />
<Box direction="Column" gap="100"> <SettingsSection label={t('RoomSettings.options')}>
<Text size="L400">{t('RoomSettings.options')}</Text>
<RoomJoinRules permissions={permissions} /> <RoomJoinRules permissions={permissions} />
<RoomPublish permissions={permissions} /> <RoomPublish permissions={permissions} />
</Box> </SettingsSection>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">{t('RoomSettings.addresses')}</Text> <Text size="L400">{t('RoomSettings.addresses')}</Text>
<RoomPublishedAddresses permissions={permissions} /> <RoomPublishedAddresses permissions={permissions} />