Use a common CollapsibleCard element for collapsible settings cards

This commit is contained in:
Ginger 2025-10-06 12:21:01 -04:00
parent af9460ef8b
commit d42bcc6e3d
No known key found for this signature in database
7 changed files with 322 additions and 390 deletions

View file

@ -195,8 +195,8 @@ function AccountDataEdit({
type AccountDataViewProps = { type AccountDataViewProps = {
type: string; type: string;
defaultContent: string; defaultContent: string;
onEdit: () => void;
requestClose: () => void; requestClose: () => void;
onEdit?: () => void;
submitDelete?: AccountDataDeleteCallback; submitDelete?: AccountDataDeleteCallback;
}; };
function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDelete }: AccountDataViewProps) { function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDelete }: AccountDataViewProps) {
@ -231,9 +231,11 @@ function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDel
required required
/> />
</Box> </Box>
{onEdit && (
<Button variant="Secondary" size="400" radii="300" onClick={onEdit}> <Button variant="Secondary" size="400" radii="300" onClick={onEdit}>
<Text size="B400">Edit</Text> <Text size="B400">Edit</Text>
</Button> </Button>
)}
{submitDelete && ( {submitDelete && (
<Button <Button
variant="Critical" variant="Critical"
@ -269,7 +271,7 @@ function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDel
export type AccountDataEditorProps = { export type AccountDataEditorProps = {
type?: string; type?: string;
content?: unknown; content?: unknown;
submitChange: AccountDataSubmitCallback; submitChange?: AccountDataSubmitCallback;
submitDelete?: AccountDataDeleteCallback; submitDelete?: AccountDataDeleteCallback;
requestClose: () => void; requestClose: () => void;
}; };
@ -328,7 +330,7 @@ export function AccountDataEditor({
</Box> </Box>
</PageHeader> </PageHeader>
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
{edit ? ( {(edit && submitChange) ? (
<AccountDataEdit <AccountDataEdit
type={data.type} type={data.type}
defaultContent={contentJSONStr} defaultContent={contentJSONStr}
@ -340,8 +342,8 @@ export function AccountDataEditor({
<AccountDataView <AccountDataView
type={data.type} type={data.type}
defaultContent={contentJSONStr} defaultContent={contentJSONStr}
onEdit={() => setEdit(true)}
requestClose={requestClose} requestClose={requestClose}
onEdit={submitChange ? () => setEdit(true) : undefined}
submitDelete={submitDelete} submitDelete={submitDelete}
/> />
)} )}

View file

@ -0,0 +1,54 @@
import React, { ReactNode } from 'react';
import { Button, Icon, Icons, Text } from 'folds';
import { SequenceCard } from './sequence-card';
import { SequenceCardStyle } from '../features/settings/styles.css';
import { SettingTile } from './setting-tile';
type CollapsibleCardProps = {
expand: boolean;
setExpand: (expand: boolean) => void;
title?: ReactNode;
description?: ReactNode;
before?: ReactNode;
children?: ReactNode;
};
export function CollapsibleCard({
expand,
setExpand,
title,
description,
before,
children,
}: CollapsibleCardProps) {
return (
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
title={title}
description={description}
before={before}
after={
<Button
onClick={() => setExpand(!expand)}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
before={
<Icon src={expand ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
}
>
<Text size="B300">{expand ? 'Collapse' : 'Expand'}</Text>
</Button>
}
/>
{expand && children}
</SequenceCard>
);
}

View file

@ -30,6 +30,7 @@ import {
AccountDataSubmitCallback, AccountDataSubmitCallback,
} from '../../../components/AccountDataEditor'; } from '../../../components/AccountDataEditor';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { CollapsibleCard } from '../../../components/CollapsibleCard';
type DeveloperToolsProps = { type DeveloperToolsProps = {
requestClose: () => void; requestClose: () => void;
@ -175,36 +176,12 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
} }
/> />
</SequenceCard> </SequenceCard>
<SequenceCard <CollapsibleCard
className={SequenceCardStyle} expand={expandState}
variant="SurfaceVariant" setExpand={setExpandState}
direction="Column"
gap="400"
>
<SettingTile
title="Room State" title="Room State"
description="State events of the room." description="State events of the room."
after={
<Button
onClick={() => setExpandState(!expandState)}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
before={
<Icon
src={expandState ? Icons.ChevronTop : Icons.ChevronBottom}
size="100"
filled
/>
}
> >
<Text size="B300">{expandState ? 'Collapse' : 'Expand'}</Text>
</Button>
}
/>
{expandState && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box justifyContent="SpaceBetween"> <Box justifyContent="SpaceBetween">
<Text size="L400">Events</Text> <Text size="L400">Events</Text>
@ -310,38 +287,13 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
})} })}
</CutoutCard> </CutoutCard>
</Box> </Box>
)} </CollapsibleCard>
</SequenceCard> <CollapsibleCard
<SequenceCard expand={expandAccountData}
className={SequenceCardStyle} setExpand={setExpandAccountData}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
title="Account Data" title="Account Data"
description="Private personalization data stored within room." description="Private personalization data stored within room"
after={
<Button
onClick={() => setExpandAccountData(!expandAccountData)}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
before={
<Icon
src={expandAccountData ? Icons.ChevronTop : Icons.ChevronBottom}
size="100"
filled
/>
}
> >
<Text size="B300">{expandAccountData ? 'Collapse' : 'Expand'}</Text>
</Button>
}
/>
{expandAccountData && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box justifyContent="SpaceBetween"> <Box justifyContent="SpaceBetween">
<Text size="L400">Events</Text> <Text size="L400">Events</Text>
@ -383,8 +335,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
))} ))}
</CutoutCard> </CutoutCard>
</Box> </Box>
)} </CollapsibleCard>
</SequenceCard>
</Box> </Box>
)} )}
</Box> </Box>

View file

@ -7,8 +7,6 @@ import {
Chip, Chip,
color, color,
config, config,
Icon,
Icons,
Input, Input,
Spinner, Spinner,
Text, Text,
@ -33,6 +31,7 @@ import { useAlive } from '../../../hooks/useAlive';
import { StateEvent } from '../../../../types/matrix/room'; import { StateEvent } from '../../../../types/matrix/room';
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
import { getMxIdServer } from '../../../utils/matrix'; import { getMxIdServer } from '../../../utils/matrix';
import { CollapsibleCard } from '../../../components/CollapsibleCard';
type RoomPublishedAddressesProps = { type RoomPublishedAddressesProps = {
permissions: RoomPermissionsAPI; permissions: RoomPermissionsAPI;
@ -373,35 +372,12 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
const { localAliasesState, addLocalAlias, removeLocalAlias } = useLocalAliases(room.roomId); const { localAliasesState, addLocalAlias, removeLocalAlias } = useLocalAliases(room.roomId);
return ( return (
<SequenceCard <CollapsibleCard
className={SequenceCardStyle} expand={expand}
variant="SurfaceVariant" setExpand={setExpand}
direction="Column"
gap="400"
>
<SettingTile
title="Local Addresses" title="Local Addresses"
description="Set local address so users can join through your homeserver." description="Set local address so users can join through your homeserver."
after={
<Button
type="button"
onClick={() => setExpand(!expand)}
size="300"
variant="Secondary"
fill="Soft"
outlined
radii="300"
before={
<Icon size="100" src={expand ? Icons.ChevronTop : Icons.ChevronBottom} filled />
}
> >
<Text as="span" size="B300" truncate>
{expand ? 'Collapse' : 'Expand'}
</Text>
</Button>
}
/>
{expand && (
<CutoutCard variant="Surface" style={{ padding: config.space.S300 }}> <CutoutCard variant="Surface" style={{ padding: config.space.S300 }}>
{localAliasesState.status === AsyncStatus.Loading && ( {localAliasesState.status === AsyncStatus.Loading && (
<Box gap="100"> <Box gap="100">
@ -429,8 +405,7 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
</Box> </Box>
)} )}
</CutoutCard> </CutoutCard>
)}
{expand && <LocalAddressInput addLocalAlias={addLocalAlias} />} {expand && <LocalAddressInput addLocalAlias={addLocalAlias} />}
</SequenceCard> </CollapsibleCard>
); );
} }

View file

@ -1,46 +1,16 @@
import React from 'react'; import React from 'react';
import { Box, Text, Icon, Icons, Button, MenuItem } from 'folds'; import { Box, Text, Icon, Icons, MenuItem } from 'folds';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile';
import { CutoutCard } from '../../../components/cutout-card'; import { CutoutCard } from '../../../components/cutout-card';
type AccountDataListProps = { type AccountDataListProps = {
title?: string;
description?: string;
expand: boolean;
setExpand: (expand: boolean) => void;
types: string[]; types: string[];
onSelect: (type: string | null) => void; onSelect: (type: string | null) => void;
}; };
export function AccountDataList({ types, onSelect, expand, setExpand, title, description }: AccountDataListProps) { export function AccountDataList({
types,
onSelect,
}: AccountDataListProps) {
return ( return (
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
title={title}
description={description}
after={
<Button
onClick={() => setExpand(!expand)}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
before={
<Icon src={expand ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
}
>
<Text size="B300">{expand ? 'Collapse' : 'Expand'}</Text>
</Button>
}
/>
{expand && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box justifyContent="SpaceBetween"> <Box justifyContent="SpaceBetween">
<Text size="L400">Fields</Text> <Text size="L400">Fields</Text>
@ -80,7 +50,5 @@ export function AccountDataList({ types, onSelect, expand, setExpand, title, des
))} ))}
</CutoutCard> </CutoutCard>
</Box> </Box>
)}
</SequenceCard>
); );
} }

View file

@ -17,6 +17,7 @@ import { copyToClipboard } from '../../../utils/dom';
import { AccountDataList } from './AccountDataList'; import { AccountDataList } from './AccountDataList';
import { useExtendedProfile } from '../../../hooks/useExtendedProfile'; import { useExtendedProfile } from '../../../hooks/useExtendedProfile';
import { useAccountDataCallback } from '../../../hooks/useAccountDataCallback'; import { useAccountDataCallback } from '../../../hooks/useAccountDataCallback';
import { CollapsibleCard } from '../../../components/CollapsibleCard';
type DeveloperToolsPage = type DeveloperToolsPage =
| { name: 'index' } | { name: 'index' }
@ -165,23 +166,29 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
{developerTools && ( {developerTools && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Account Data</Text> <Text size="L400">Account Data</Text>
<AccountDataList <CollapsibleCard
title="Account"
description="Private data stored in your account."
expand={globalExpand} expand={globalExpand}
setExpand={setGlobalExpand} setExpand={setGlobalExpand}
title="Account"
description="Private data stored in your account."
>
<AccountDataList
types={accountDataTypes} types={accountDataTypes}
onSelect={(type) => setPage({ name: 'account-data', type })} onSelect={(type) => setPage({ name: 'account-data', type })}
/> />
</CollapsibleCard>
{extendedProfile && ( {extendedProfile && (
<AccountDataList <CollapsibleCard
title="Profile"
description="Public data attached to your Matrix profile."
expand={profileExpand} expand={profileExpand}
setExpand={setProfileExpand} setExpand={setProfileExpand}
title="Profile"
description="Public data attached to your Matrix profile."
>
<AccountDataList
types={Object.keys(extendedProfile)} types={Object.keys(extendedProfile)}
onSelect={(type) => setPage({ name: 'profile-field', type })} onSelect={(type) => setPage({ name: 'profile-field', type })}
/> />
</CollapsibleCard>
)} )}
</Box> </Box>
)} )}

View file

@ -11,6 +11,7 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { decryptMegolmKeyFile, encryptMegolmKeyFile } from '../../../../util/cryptE2ERoomKeys'; import { decryptMegolmKeyFile, encryptMegolmKeyFile } from '../../../../util/cryptE2ERoomKeys';
import { useAlive } from '../../../hooks/useAlive'; import { useAlive } from '../../../hooks/useAlive';
import { useFilePicker } from '../../../hooks/useFilePicker'; import { useFilePicker } from '../../../hooks/useFilePicker';
import { CollapsibleCard } from '../../../components/CollapsibleCard';
function ExportKeys() { function ExportKeys() {
const mx = useMatrixClient(); const mx = useMatrixClient();
@ -121,37 +122,18 @@ function ExportKeys() {
); );
} }
function ExportKeysTile() { function ExportKeysCard() {
const [expand, setExpand] = useState(false); const [expand, setExpand] = useState(false);
return ( return (
<> <CollapsibleCard
<SettingTile expand={expand}
setExpand={setExpand}
title="Export Messages Data" title="Export Messages Data"
description="Save password protected copy of encryption data on your device to decrypt messages later." description="Save password protected copy of encryption data on your device to decrypt messages later."
after={
<Box>
<Button
type="button"
onClick={() => setExpand(!expand)}
size="300"
variant="Secondary"
fill="Soft"
outlined
radii="300"
before={
<Icon size="100" src={expand ? Icons.ChevronTop : Icons.ChevronBottom} filled />
}
> >
<Text as="span" size="B300" truncate> <ExportKeys />
{expand ? 'Collapse' : 'Expand'} </CollapsibleCard>
</Text>
</Button>
</Box>
}
/>
{expand && <ExportKeys />}
</>
); );
} }
@ -304,14 +286,7 @@ export function LocalBackup() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Local Backup</Text> <Text size="L400">Local Backup</Text>
<SequenceCard <ExportKeysCard />
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<ExportKeysTile />
</SequenceCard>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"