feat: improved structure of settings
This commit is contained in:
104
src/components/Settings/Customization/Theme/ColorSelector.tsx
Normal file
104
src/components/Settings/Customization/Theme/ColorSelector.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
ColorSwatch,
|
||||
Grid,
|
||||
Group,
|
||||
MantineTheme,
|
||||
Popover,
|
||||
Text,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
import { useColorTheme } from '../../../../tools/color';
|
||||
|
||||
interface ColorControlProps {
|
||||
defaultValue: MantineTheme['primaryColor'] | undefined;
|
||||
type: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
export function ColorSelector({ type, defaultValue }: ColorControlProps) {
|
||||
const { t } = useTranslation('settings/customization/color-selector');
|
||||
const [color, setColor] = useState(defaultValue);
|
||||
const [popoverOpened, popover] = useDisclosure(false);
|
||||
const { setPrimaryColor, setSecondaryColor } = useColorTheme();
|
||||
const { name: configName } = useConfigContext();
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
|
||||
const theme = useMantineTheme();
|
||||
const colors = Object.keys(theme.colors).map((color) => ({
|
||||
swatch: theme.colors[color][6],
|
||||
color,
|
||||
}));
|
||||
|
||||
if (!color || !configName) return null;
|
||||
|
||||
const handleSelection = (color: MantineTheme['primaryColor']) => {
|
||||
setColor(color);
|
||||
if (type === 'primary') setPrimaryColor(color);
|
||||
else setSecondaryColor(color);
|
||||
updateConfig(configName, (prev) => {
|
||||
const { colors } = prev.settings.customization;
|
||||
colors[type] = color;
|
||||
return {
|
||||
...prev,
|
||||
settings: {
|
||||
...prev.settings,
|
||||
customization: {
|
||||
...prev.settings.customization,
|
||||
colors,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const swatches = colors.map(({ color, swatch }) => (
|
||||
<Grid.Col span={2} key={color}>
|
||||
<ColorSwatch
|
||||
component="button"
|
||||
type="button"
|
||||
onClick={() => handleSelection(color)}
|
||||
color={swatch}
|
||||
size={22}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
));
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<Popover
|
||||
width={250}
|
||||
withinPortal
|
||||
opened={popoverOpened}
|
||||
onClose={popover.close}
|
||||
position="left"
|
||||
withArrow
|
||||
>
|
||||
<Popover.Target>
|
||||
<ColorSwatch
|
||||
component="button"
|
||||
type="button"
|
||||
color={theme.colors[color][6]}
|
||||
onClick={popover.toggle}
|
||||
size={22}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Grid gutter="lg" columns={14}>
|
||||
{swatches}
|
||||
</Grid>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
<Text>
|
||||
{t('suffix', {
|
||||
color: type[0].toUpperCase() + type.slice(1),
|
||||
})}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Textarea } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ChangeEventHandler, useState } from 'react';
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
|
||||
interface CustomCssChangerProps {
|
||||
defaultValue: string | undefined;
|
||||
}
|
||||
|
||||
export const CustomCssChanger = ({ defaultValue }: CustomCssChangerProps) => {
|
||||
const { t } = useTranslation('settings/customization/page-appearance');
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
const { name: configName } = useConfigContext();
|
||||
const [customCss, setCustomCss] = useState(defaultValue);
|
||||
|
||||
if (!configName) return null;
|
||||
|
||||
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = (ev) => {
|
||||
const { value } = ev.currentTarget;
|
||||
const customCss = value.trim().length === 0 ? undefined : value;
|
||||
setCustomCss(customCss);
|
||||
updateConfig(configName, (prev) => ({
|
||||
...prev,
|
||||
settings: {
|
||||
...prev.settings,
|
||||
customization: {
|
||||
...prev.settings.customization,
|
||||
customCss,
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
minRows={5}
|
||||
label={t('customCSS.label')}
|
||||
placeholder={t('customCSS.placeholder')}
|
||||
value={customCss}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Slider, Stack, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
|
||||
interface OpacitySelectorProps {
|
||||
defaultValue: number | undefined;
|
||||
}
|
||||
|
||||
export function OpacitySelector({ defaultValue }: OpacitySelectorProps) {
|
||||
const [opacity, setOpacity] = useState(defaultValue || 100);
|
||||
const { t } = useTranslation('settings/customization/opacity-selector');
|
||||
|
||||
const { name: configName } = useConfigContext();
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
|
||||
if (!configName) return null;
|
||||
|
||||
const handleChange = (opacity: number) => {
|
||||
setOpacity(opacity);
|
||||
updateConfig(configName, (prev) => ({
|
||||
...prev,
|
||||
settings: {
|
||||
...prev.settings,
|
||||
customization: {
|
||||
...prev.settings.customization,
|
||||
appOpacity: opacity,
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing="xs">
|
||||
<Text>{t('label')}</Text>
|
||||
<Slider
|
||||
defaultValue={opacity}
|
||||
step={10}
|
||||
min={10}
|
||||
marks={MARKS}
|
||||
styles={{ markLabel: { fontSize: 'xx-small' } }}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const MARKS = [
|
||||
{ value: 10, label: '10' },
|
||||
{ value: 20, label: '20' },
|
||||
{ value: 30, label: '30' },
|
||||
{ value: 40, label: '40' },
|
||||
{ value: 50, label: '50' },
|
||||
{ value: 60, label: '60' },
|
||||
{ value: 70, label: '70' },
|
||||
{ value: 80, label: '80' },
|
||||
{ value: 90, label: '90' },
|
||||
{ value: 100, label: '100' },
|
||||
];
|
||||
101
src/components/Settings/Customization/Theme/ShadeSelector.tsx
Normal file
101
src/components/Settings/Customization/Theme/ShadeSelector.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
ColorSwatch,
|
||||
Grid,
|
||||
Group,
|
||||
MantineTheme,
|
||||
Popover,
|
||||
Stack,
|
||||
Text,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
import { useColorTheme } from '../../../../tools/color';
|
||||
|
||||
interface ShadeSelectorProps {
|
||||
defaultValue: MantineTheme['primaryShade'] | undefined;
|
||||
}
|
||||
|
||||
export function ShadeSelector({ defaultValue }: ShadeSelectorProps) {
|
||||
const { t } = useTranslation('settings/customization/shade-selector');
|
||||
const [shade, setShade] = useState(defaultValue);
|
||||
const [popoverOpened, popover] = useDisclosure(false);
|
||||
const { primaryColor, setPrimaryShade } = useColorTheme();
|
||||
|
||||
const { name: configName } = useConfigContext();
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
|
||||
const theme = useMantineTheme();
|
||||
const primaryShades = theme.colors[primaryColor].map((s, i) => ({
|
||||
swatch: theme.colors[primaryColor][i],
|
||||
shade: i as MantineTheme['primaryShade'],
|
||||
}));
|
||||
|
||||
if (shade === undefined || !configName) return null;
|
||||
|
||||
const handleSelection = (shade: MantineTheme['primaryShade']) => {
|
||||
setPrimaryShade(shade);
|
||||
setShade(shade);
|
||||
updateConfig(configName, (prev) => ({
|
||||
...prev,
|
||||
settings: {
|
||||
...prev.settings,
|
||||
customization: {
|
||||
...prev.settings.customization,
|
||||
colors: {
|
||||
...prev.settings.customization.colors,
|
||||
shade,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const primarySwatches = primaryShades.map(({ swatch, shade }) => (
|
||||
<Grid.Col span={1} key={Number(shade)}>
|
||||
<ColorSwatch
|
||||
component="button"
|
||||
type="button"
|
||||
onClick={() => handleSelection(shade)}
|
||||
color={swatch}
|
||||
size={22}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
));
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<Popover
|
||||
width={350}
|
||||
withinPortal
|
||||
opened={popoverOpened}
|
||||
onClose={popover.close}
|
||||
position="left"
|
||||
withArrow
|
||||
>
|
||||
<Popover.Target>
|
||||
<ColorSwatch
|
||||
component="button"
|
||||
type="button"
|
||||
color={theme.colors[primaryColor][Number(shade)]}
|
||||
onClick={popover.toggle}
|
||||
size={22}
|
||||
style={{ display: 'block', cursor: 'pointer' }}
|
||||
/>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Stack spacing="xs">
|
||||
<Grid gutter="lg" columns={10}>
|
||||
{primarySwatches}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
<Text>{t('label')}</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user