112 lines
3.4 KiB
TypeScript
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;
|