Files
flxn-app/src/features/settings/components/theme-picker.tsx
T
2026-08-23 17:17:04 -07:00

112 lines
3.4 KiB
TypeScript

import {
Box,
SimpleGrid,
Stack,
Text,
UnstyledButton,
useComputedColorScheme,
} from "@mantine/core";
import { CaretRightIcon } from "@phosphor-icons/react";
import { Trans, useLingui } from "@lingui/react/macro";
import Sheet from "@/components/sheet/sheet";
import { useSheet } from "@/hooks/use-sheet";
import { updateUserTheme } from "@/features/settings/server";
import { useAuth } from "@/contexts/auth-context";
import { DEFAULT_THEME_ID, THEME_LIST, getTheme } from "@/themes/registry";
import ThemePreview from "./theme-preview";
const ThemePicker = () => {
const { metadata, user, set } = useAuth();
const { t } = useLingui();
const sheet = useSheet();
const scheme = useComputedColorScheme("dark");
const currentId = metadata.theme || DEFAULT_THEME_ID;
const current = getTheme(currentId);
const handleSelect = async (id: string) => {
if (!user || id === currentId) return;
const previous = metadata;
set({ metadata: { ...metadata, theme: id } });
try {
await updateUserTheme({ data: id });
} catch {
set({ metadata: previous });
}
};
return (
<Box>
<Text fw={500} size="sm" mb="xs">
<Trans>Theme</Trans>
</Text>
<UnstyledButton
onClick={sheet.open}
w="100%"
style={{
display: "flex",
alignItems: "center",
gap: "var(--mantine-spacing-sm)",
padding: "var(--mantine-spacing-xs)",
border: "1px solid var(--mantine-color-default-border)",
borderRadius: "var(--mantine-radius-md)",
}}
>
<Box w="3.25rem" style={{ flexShrink: 0 }}>
<ThemePreview
manifest={current}
scheme={scheme}
height="2.25rem"
scale={0.5}
/>
</Box>
<Text size="sm" fw={500} style={{ flex: 1, textAlign: "left" }}>
{t(current.label)}
</Text>
<CaretRightIcon size={16} color="var(--mantine-color-dimmed)" />
</UnstyledButton>
<Sheet title={t`Theme`} {...sheet.props}>
<SimpleGrid cols={3} spacing="sm" py="sm">
{[...THEME_LIST]
.sort(
(a, b) =>
(a.order ?? 100) - (b.order ?? 100) ||
t(a.label).localeCompare(t(b.label))
)
.map((manifest) => {
const isSelected = manifest.id === currentId;
return (
<UnstyledButton
key={manifest.id}
onClick={() => handleSelect(manifest.id)}
>
<Stack gap={6} align="center">
<Box
w="100%"
p="0.125rem"
bd={
isSelected
? "0.125rem solid var(--mantine-color-bright)"
: "0.125rem solid transparent"
}
style={{
borderRadius: "calc(var(--mantine-radius-md) + 0.25rem)",
}}
>
<ThemePreview manifest={manifest} scheme={scheme} />
</Box>
<Text size="xs" fw={isSelected ? 700 : 400} ta="center">
{t(manifest.label)}
</Text>
</Stack>
</UnstyledButton>
);
})}
</SimpleGrid>
</Sheet>
</Box>
);
};
export default ThemePicker;