Backgrounds: theme-aware patterns and visual preview grid

This commit is contained in:
root
2026-05-13 21:51:19 -04:00
parent 77f0c0d4ca
commit f2bcd65a9b
4 changed files with 206 additions and 144 deletions
+39 -73
View File
@@ -32,7 +32,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, MessageLayout, MessageSpacing, settingsAtom } from '../../../state/settings';
import { ChatBackground, DateFormat, MessageLayout, MessageSpacing, settingsAtom } from '../../../state/settings';
import { SettingTile } from '../../../components/setting-tile';
import { KeySymbol } from '../../../utils/key-symbol';
import { isMacOS } from '../../../utils/user-agent';
@@ -42,10 +42,12 @@ import {
Theme,
ThemeKind,
useSystemThemeKind,
useTheme,
useThemeNames,
useThemes,
} from '../../../hooks/useTheme';
import { stopPropagation } from '../../../utils/keyboard';
import { BG_OPTIONS, getChatBg } from '../../lotus/chatBackground';
import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
import { useDateFormatItems } from '../../../hooks/useDateFormat';
@@ -352,12 +354,14 @@ function Appearance() {
<SettingTile title="Page Zoom" after={<PageZoomInput />} />
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column" gap="200">
<SettingTile
title="Chat Background"
description="Pattern applied behind the message timeline."
after={<SelectChatBackground />}
/>
<Box style={{ padding: `0 ${config.space.S400} ${config.space.S300}` }}>
<ChatBgGrid />
</Box>
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
@@ -759,81 +763,43 @@ function Editor() {
}
const BG_OPTIONS: { value: ChatBackground; label: string }[] = [
{ value: 'none', label: 'None' },
{ value: 'blueprint', label: 'Blueprint' },
{ value: 'carbon', label: 'Carbon' },
{ value: 'stars', label: 'Stars' },
{ value: 'topographic', label: 'Topographic' },
{ value: 'herringbone', label: 'Herringbone' },
{ value: 'crosshatch', label: 'Crosshatch' },
];
function SelectChatBackground() {
const [menuCords, setMenuCords] = useState<RectCords>();
function ChatBgGrid() {
const [chatBackground, setChatBackground] = useSetting(settingsAtom, 'chatBackground');
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuCords(evt.currentTarget.getBoundingClientRect());
};
const handleSelect = (value: ChatBackground) => {
setChatBackground(value);
setMenuCords(undefined);
};
const theme = useTheme();
const isDark = theme.kind === ThemeKind.Dark;
return (
<>
<Button
size="300"
variant="Secondary"
outlined
fill="Soft"
radii="300"
after={<Icon size="300" src={Icons.ChevronBottom} />}
onClick={handleMenu}
>
<Text size="T300">
{BG_OPTIONS.find((o) => o.value === chatBackground)?.label ?? 'None'}
</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,
<Box wrap="Wrap" gap="200">
{BG_OPTIONS.map((opt) => (
<Box key={opt.value} direction="Column" gap="100" style={{ alignItems: 'center' }}>
<button
type="button"
aria-label={opt.label}
aria-pressed={chatBackground === opt.value}
onClick={() => setChatBackground(opt.value as ChatBackground)}
style={{
display: 'block',
width: toRem(76),
height: toRem(50),
borderRadius: toRem(8),
cursor: 'pointer',
border: chatBackground === opt.value
? '2px solid #980000'
: '2px solid rgba(128,128,128,0.25)',
padding: 0,
overflow: 'hidden',
...getChatBg(opt.value as ChatBackground, isDark),
}}
/>
<Text
size="T200"
style={chatBackground === opt.value ? { color: '#980000' } : undefined}
>
<Menu>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
{BG_OPTIONS.map((opt) => (
<MenuItem
key={opt.value}
size="300"
variant={chatBackground === opt.value ? 'Primary' : 'Surface'}
radii="300"
onClick={() => handleSelect(opt.value)}
>
<Text size="T300">{opt.label}</Text>
</MenuItem>
))}
</Box>
</Menu>
</FocusTrap>
}
/>
</>
{opt.label}
</Text>
</Box>
))}
</Box>
);
}