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: string;
defaultContent: string;
onEdit: () => void;
requestClose: () => void;
onEdit?: () => void;
submitDelete?: AccountDataDeleteCallback;
};
function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDelete }: AccountDataViewProps) {
@ -231,9 +231,11 @@ function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDel
required
/>
</Box>
{onEdit && (
<Button variant="Secondary" size="400" radii="300" onClick={onEdit}>
<Text size="B400">Edit</Text>
</Button>
)}
{submitDelete && (
<Button
variant="Critical"
@ -269,7 +271,7 @@ function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDel
export type AccountDataEditorProps = {
type?: string;
content?: unknown;
submitChange: AccountDataSubmitCallback;
submitChange?: AccountDataSubmitCallback;
submitDelete?: AccountDataDeleteCallback;
requestClose: () => void;
};
@ -328,7 +330,7 @@ export function AccountDataEditor({
</Box>
</PageHeader>
<Box grow="Yes" direction="Column">
{edit ? (
{(edit && submitChange) ? (
<AccountDataEdit
type={data.type}
defaultContent={contentJSONStr}
@ -340,8 +342,8 @@ export function AccountDataEditor({
<AccountDataView
type={data.type}
defaultContent={contentJSONStr}
onEdit={() => setEdit(true)}
requestClose={requestClose}
onEdit={submitChange ? () => setEdit(true) : undefined}
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,
} from '../../../components/AccountDataEditor';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { CollapsibleCard } from '../../../components/CollapsibleCard';
type DeveloperToolsProps = {
requestClose: () => void;
@ -175,36 +176,12 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
}
/>
</SequenceCard>
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
<CollapsibleCard
expand={expandState}
setExpand={setExpandState}
title="Room State"
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 justifyContent="SpaceBetween">
<Text size="L400">Events</Text>
@ -310,38 +287,13 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
})}
</CutoutCard>
</Box>
)}
</SequenceCard>
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
</CollapsibleCard>
<CollapsibleCard
expand={expandAccountData}
setExpand={setExpandAccountData}
title="Account Data"
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
/>
}
description="Private personalization data stored within room"
>
<Text size="B300">{expandAccountData ? 'Collapse' : 'Expand'}</Text>
</Button>
}
/>
{expandAccountData && (
<Box direction="Column" gap="100">
<Box justifyContent="SpaceBetween">
<Text size="L400">Events</Text>
@ -383,8 +335,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
))}
</CutoutCard>
</Box>
)}
</SequenceCard>
</CollapsibleCard>
</Box>
)}
</Box>

View file

@ -7,8 +7,6 @@ import {
Chip,
color,
config,
Icon,
Icons,
Input,
Spinner,
Text,
@ -33,6 +31,7 @@ import { useAlive } from '../../../hooks/useAlive';
import { StateEvent } from '../../../../types/matrix/room';
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
import { getMxIdServer } from '../../../utils/matrix';
import { CollapsibleCard } from '../../../components/CollapsibleCard';
type RoomPublishedAddressesProps = {
permissions: RoomPermissionsAPI;
@ -373,35 +372,12 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
const { localAliasesState, addLocalAlias, removeLocalAlias } = useLocalAliases(room.roomId);
return (
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
<CollapsibleCard
expand={expand}
setExpand={setExpand}
title="Local Addresses"
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 }}>
{localAliasesState.status === AsyncStatus.Loading && (
<Box gap="100">
@ -429,8 +405,7 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
</Box>
)}
</CutoutCard>
)}
{expand && <LocalAddressInput addLocalAlias={addLocalAlias} />}
</SequenceCard>
</CollapsibleCard>
);
}

View file

@ -1,46 +1,16 @@
import React from 'react';
import { Box, Text, Icon, Icons, Button, MenuItem } from 'folds';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile';
import { Box, Text, Icon, Icons, MenuItem } from 'folds';
import { CutoutCard } from '../../../components/cutout-card';
type AccountDataListProps = {
title?: string;
description?: string;
expand: boolean;
setExpand: (expand: boolean) => void;
types: string[];
onSelect: (type: string | null) => void;
};
export function AccountDataList({ types, onSelect, expand, setExpand, title, description }: AccountDataListProps) {
export function AccountDataList({
types,
onSelect,
}: AccountDataListProps) {
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 justifyContent="SpaceBetween">
<Text size="L400">Fields</Text>
@ -80,7 +50,5 @@ export function AccountDataList({ types, onSelect, expand, setExpand, title, des
))}
</CutoutCard>
</Box>
)}
</SequenceCard>
);
}

View file

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

View file

@ -11,6 +11,7 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { decryptMegolmKeyFile, encryptMegolmKeyFile } from '../../../../util/cryptE2ERoomKeys';
import { useAlive } from '../../../hooks/useAlive';
import { useFilePicker } from '../../../hooks/useFilePicker';
import { CollapsibleCard } from '../../../components/CollapsibleCard';
function ExportKeys() {
const mx = useMatrixClient();
@ -121,37 +122,18 @@ function ExportKeys() {
);
}
function ExportKeysTile() {
function ExportKeysCard() {
const [expand, setExpand] = useState(false);
return (
<>
<SettingTile
<CollapsibleCard
expand={expand}
setExpand={setExpand}
title="Export Messages Data"
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>
{expand ? 'Collapse' : 'Expand'}
</Text>
</Button>
</Box>
}
/>
{expand && <ExportKeys />}
</>
<ExportKeys />
</CollapsibleCard>
);
}
@ -304,14 +286,7 @@ export function LocalBackup() {
return (
<Box direction="Column" gap="100">
<Text size="L400">Local Backup</Text>
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<ExportKeysTile />
</SequenceCard>
<ExportKeysCard />
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"