feat: implement room list reordering and animation hooks; add scroll to latest behavior settings

This commit is contained in:
2026-04-23 14:53:00 +10:00
parent 643608f25d
commit 35ccdc0a22
9 changed files with 414 additions and 32 deletions

View File

@@ -34,7 +34,7 @@ import FocusTrap from 'focus-trap-react';
import { Page, PageContent, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card';
import { useSetting } from '../../../state/hooks/settings';
import { DateFormat, EmojiStyle, MessageLayout, MessageSpacing, settingsAtom } from '../../../state/settings';
import { DateFormat, EmojiStyle, MessageLayout, MessageSpacing, ScrollToLatestBehavior, settingsAtom } from '../../../state/settings';
import { SettingTile } from '../../../components/setting-tile';
import { KeySymbol } from '../../../utils/key-symbol';
import { isMacOS } from '../../../utils/user-agent';
@@ -984,6 +984,83 @@ function SelectMessageSpacing() {
);
}
function SelectScrollToLatestBehavior() {
const [menuCords, setMenuCords] = useState<RectCords>();
const [scrollToLatestBehavior, setScrollToLatestBehavior] = useSetting(
settingsAtom,
'scrollToLatestBehavior'
);
const behaviors = [
{ value: ScrollToLatestBehavior.Always, label: 'Always' },
{ value: ScrollToLatestBehavior.OnNewMessage, label: 'On New Message' },
{ value: ScrollToLatestBehavior.Never, label: 'Never' },
];
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuCords(evt.currentTarget.getBoundingClientRect());
};
const handleSelect = (behavior: ScrollToLatestBehavior) => {
setScrollToLatestBehavior(behavior);
setMenuCords(undefined);
};
const currentLabel = behaviors.find((b) => b.value === scrollToLatestBehavior)?.label ?? 'Always';
return (
<>
<Button
size="300"
variant="Secondary"
outlined
fill="Soft"
radii="300"
after={<Icon size="300" src={Icons.ChevronBottom} />}
onClick={handleMenu}
>
<Text size="T300">{currentLabel}</Text>
</Button>
<PopOut
anchor={menuCords}
offset={5}
position="Bottom"
align="End"
content={
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: () => setMenuCords(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) =>
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
isKeyBackward: (evt: KeyboardEvent) =>
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
escapeDeactivates: stopPropagation,
}}
>
<Menu>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
{behaviors.map((item) => (
<MenuItem
key={item.value}
size="300"
variant={scrollToLatestBehavior === item.value ? 'Primary' : 'Surface'}
radii="300"
onClick={() => handleSelect(item.value)}
>
<Text size="T300">{item.label}</Text>
</MenuItem>
))}
</Box>
</Menu>
</FocusTrap>
}
/>
</>
);
}
function Messages() {
const [legacyUsernameColor, setLegacyUsernameColor] = useSetting(
settingsAtom,
@@ -1011,6 +1088,12 @@ function Messages() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Message Spacing" after={<SelectMessageSpacing />} />
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile
title="Scroll to Latest on Room Reselect"
after={<SelectScrollToLatestBehavior />}
/>
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile
title="Legacy Username Color"