mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 13:00:13 +02:00
add new app themes
This commit is contained in:
@@ -187,19 +187,10 @@
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgb(0 0 0 / 50%) 0%,
|
||||
rgb(0 0 0 / 5%) 50%,
|
||||
darken(var(--theme-colors-background), 10%) 0%,
|
||||
darken(var(--theme-colors-background), 5%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
@mixin light {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--theme-colors-background) 0%,
|
||||
alpha(var(--theme-colors-background), 0.05) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.item-table-left-scroll-shadow {
|
||||
@@ -210,18 +201,21 @@
|
||||
z-index: 1;
|
||||
width: 8px;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgb(0 0 0 / 50%) 0%,
|
||||
rgb(0 0 0 / 5%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
@mixin dark {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
darken(var(--theme-colors-background), 10%) 0%,
|
||||
darken(var(--theme-colors-background), 5%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@mixin light {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
var(--theme-colors-background) 0%,
|
||||
alpha(var(--theme-colors-background), 0.05) 50%,
|
||||
darken(var(--theme-colors-background), 5%) 0%,
|
||||
darken(var(--theme-colors-background), 3%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
@@ -235,18 +229,21 @@
|
||||
z-index: 1;
|
||||
width: 8px;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
rgb(0 0 0 / 50%) 0%,
|
||||
rgb(0 0 0 / 5%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
@mixin dark {
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
darken(var(--theme-colors-background), 10%) 0%,
|
||||
darken(var(--theme-colors-background), 5%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@mixin light {
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
var(--theme-colors-background) 0%,
|
||||
alpha(var(--theme-colors-background), 0.05) 50%,
|
||||
darken(var(--theme-colors-background), 5%) 0%,
|
||||
darken(var(--theme-colors-background), 3%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
@@ -260,18 +257,21 @@
|
||||
z-index: 1;
|
||||
height: 8px;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgb(0 0 0 / 50%) 0%,
|
||||
rgb(0 0 0 / 5%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
@mixin dark {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
darken(var(--theme-colors-background), 10%) 0%,
|
||||
darken(var(--theme-colors-background), 5%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@mixin light {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--theme-colors-background) 0%,
|
||||
alpha(var(--theme-colors-background), 0.05) 50%,
|
||||
darken(var(--theme-colors-background), 5%) 0%,
|
||||
darken(var(--theme-colors-background), 3%) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,8 @@ import {
|
||||
usePlaybackSettings,
|
||||
usePlayerSong,
|
||||
usePlayerTimestamp,
|
||||
usePrimaryColor,
|
||||
} from '/@/renderer/store';
|
||||
import { useColorScheme } from '/@/renderer/themes/use-app-theme';
|
||||
import { useAppThemeColors, useColorScheme } from '/@/renderer/themes/use-app-theme';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
|
||||
@@ -39,7 +38,8 @@ export const PlayerbarWaveform = () => {
|
||||
|
||||
const streamUrl = useSongUrl(currentSong, true, transcode);
|
||||
|
||||
const primaryColor = usePrimaryColor();
|
||||
const { color } = useAppThemeColors();
|
||||
const primaryColor = (color['--theme-colors-primary'] as string) || 'rgb(53, 116, 252)';
|
||||
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -7,7 +6,6 @@ import { useCssSettings, useSettingsStoreActions } from '/@/renderer/store';
|
||||
import { sanitizeCss } from '/@/renderer/utils/sanitize';
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Code } from '/@/shared/components/code/code';
|
||||
import { ConfirmModal } from '/@/shared/components/modal/modal';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Textarea } from '/@/shared/components/textarea/textarea';
|
||||
@@ -30,16 +28,6 @@ export const StylesSettings = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleResetToDefault = () => {
|
||||
setSettings({
|
||||
css: {
|
||||
content,
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
closeAllModals();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (content !== css) {
|
||||
setCss(content);
|
||||
@@ -47,19 +35,6 @@ export const StylesSettings = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- Reason: This is to only fire if an external source updates the stores css.content
|
||||
}, [content]);
|
||||
|
||||
const openConfirmModal = () => {
|
||||
openModal({
|
||||
children: (
|
||||
<ConfirmModal onConfirm={handleResetToDefault}>
|
||||
<Text color="red !important">
|
||||
{t('setting.customCssNotice', { postProcess: 'sentenceCase' })}
|
||||
</Text>
|
||||
</ConfirmModal>
|
||||
),
|
||||
title: t('setting.customCssEnable', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsOptions
|
||||
@@ -67,16 +42,12 @@ export const StylesSettings = () => {
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={(e) => {
|
||||
if (!e.currentTarget.checked) {
|
||||
setSettings({
|
||||
css: {
|
||||
content,
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
openConfirmModal();
|
||||
}
|
||||
setSettings({
|
||||
css: {
|
||||
content,
|
||||
enabled: e.currentTarget.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
@@ -84,6 +55,7 @@ export const StylesSettings = () => {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
note={t('setting.customCssNotice', { postProcess: 'sentenceCase' })}
|
||||
title={t('setting.customCssEnable', { postProcess: 'sentenceCase' })}
|
||||
/>
|
||||
{enabled && (
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { StylesSettings } from '/@/renderer/features/settings/components/advanced/styles-settings';
|
||||
import {
|
||||
SettingOption,
|
||||
@@ -9,19 +11,83 @@ import {
|
||||
import { useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
|
||||
import { THEME_DATA, useSetColorScheme } from '/@/renderer/themes/use-app-theme';
|
||||
import { ColorInput } from '/@/shared/components/color-input/color-input';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Select } from '/@/shared/components/select/select';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { getAppTheme } from '/@/shared/themes/app-theme';
|
||||
import { AppTheme } from '/@/shared/themes/app-theme-types';
|
||||
|
||||
const localSettings = isElectron() ? window.api.localSettings : null;
|
||||
|
||||
const getThemeSwatchColors = (theme: AppTheme) => {
|
||||
const themeConfig = getAppTheme(theme);
|
||||
return {
|
||||
background: themeConfig.colors?.background || 'rgb(0, 0, 0)',
|
||||
foreground: themeConfig.colors?.foreground || 'rgb(255, 255, 255)',
|
||||
surface: themeConfig.colors?.surface || themeConfig.colors?.background || 'rgb(0, 0, 0)',
|
||||
};
|
||||
};
|
||||
|
||||
const getGroupedThemeData = () => {
|
||||
const darkThemes = THEME_DATA.filter((theme) => theme.type === 'dark').sort((a, b) =>
|
||||
a.label.localeCompare(b.label),
|
||||
);
|
||||
const lightThemes = THEME_DATA.filter((theme) => theme.type === 'light').sort((a, b) =>
|
||||
a.label.localeCompare(b.label),
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
group: i18n.t('setting.themeDark', { postProcess: 'sentenceCase' }),
|
||||
items: darkThemes,
|
||||
},
|
||||
{
|
||||
group: i18n.t('setting.themeLight', { postProcess: 'sentenceCase' }),
|
||||
items: lightThemes,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const ColorSwatch = ({ color }: { color: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
border: '1px solid rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '3px',
|
||||
boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||
height: '14px',
|
||||
width: '14px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderThemeOption = ({ option }: { option: { label: string; value: string } }) => {
|
||||
const themeValue = option.value as AppTheme;
|
||||
const colors = getThemeSwatchColors(themeValue);
|
||||
|
||||
return (
|
||||
<Group gap="sm" style={{ alignItems: 'center', flex: 1 }}>
|
||||
<Group gap={4} style={{ alignItems: 'center', flexShrink: 0 }}>
|
||||
<ColorSwatch color={String(colors.background)} />
|
||||
<ColorSwatch color={String(colors.surface)} />
|
||||
<ColorSwatch color={String(colors.foreground)} />
|
||||
</Group>
|
||||
<span style={{ flex: 1 }}>{option.label}</span>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThemeSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const settings = useGeneralSettings();
|
||||
const { setSettings } = useSettingsStoreActions();
|
||||
const { setColorScheme } = useSetColorScheme();
|
||||
|
||||
const groupedThemeData = useMemo(() => getGroupedThemeData(), []);
|
||||
|
||||
const themeOptions: SettingOption[] = [
|
||||
{
|
||||
control: (
|
||||
@@ -57,7 +123,7 @@ export const ThemeSettings = () => {
|
||||
{
|
||||
control: (
|
||||
<Select
|
||||
data={THEME_DATA}
|
||||
data={groupedThemeData}
|
||||
defaultValue={settings.theme}
|
||||
onChange={(e) => {
|
||||
const theme = e as AppTheme;
|
||||
@@ -77,6 +143,7 @@ export const ThemeSettings = () => {
|
||||
localSettings.themeSet(colorScheme);
|
||||
}
|
||||
}}
|
||||
renderOption={renderThemeOption}
|
||||
/>
|
||||
),
|
||||
description: t('setting.theme', {
|
||||
@@ -89,7 +156,7 @@ export const ThemeSettings = () => {
|
||||
{
|
||||
control: (
|
||||
<Select
|
||||
data={THEME_DATA}
|
||||
data={groupedThemeData}
|
||||
defaultValue={settings.themeDark}
|
||||
onChange={(e) => {
|
||||
setSettings({
|
||||
@@ -99,6 +166,7 @@ export const ThemeSettings = () => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
renderOption={renderThemeOption}
|
||||
/>
|
||||
),
|
||||
description: t('setting.themeDark', {
|
||||
@@ -111,7 +179,7 @@ export const ThemeSettings = () => {
|
||||
{
|
||||
control: (
|
||||
<Select
|
||||
data={THEME_DATA}
|
||||
data={groupedThemeData}
|
||||
defaultValue={settings.themeLight}
|
||||
onChange={(e) => {
|
||||
setSettings({
|
||||
@@ -121,6 +189,7 @@ export const ThemeSettings = () => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
renderOption={renderThemeOption}
|
||||
/>
|
||||
),
|
||||
description: t('setting.themeLight', {
|
||||
@@ -130,11 +199,33 @@ export const ThemeSettings = () => {
|
||||
isHidden: !settings.followSystemTheme,
|
||||
title: t('setting.themeLight', { postProcess: 'sentenceCase' }),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
checked={settings.useThemeAccentColor}
|
||||
onChange={(e) => {
|
||||
setSettings({
|
||||
general: {
|
||||
...settings,
|
||||
useThemeAccentColor: e.currentTarget.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
description: t('setting.useThemeAccentColor', {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
isHidden: false,
|
||||
title: t('setting.useThemeAccentColor', { postProcess: 'sentenceCase' }),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Stack align="center">
|
||||
<ColorInput
|
||||
defaultValue={settings.accent}
|
||||
disabled={settings.useThemeAccentColor}
|
||||
format="rgb"
|
||||
onChangeEnd={(e) => {
|
||||
setSettings({
|
||||
|
||||
@@ -257,6 +257,7 @@ const GeneralSettingsSchema = z.object({
|
||||
theme: z.nativeEnum(AppTheme),
|
||||
themeDark: z.nativeEnum(AppTheme),
|
||||
themeLight: z.nativeEnum(AppTheme),
|
||||
useThemeAccentColor: z.boolean(),
|
||||
volumeWheelStep: z.number(),
|
||||
volumeWidth: z.number(),
|
||||
zoomFactor: z.number(),
|
||||
@@ -743,6 +744,7 @@ const initialState: SettingsState = {
|
||||
theme: AppTheme.DEFAULT_DARK,
|
||||
themeDark: AppTheme.DEFAULT_DARK,
|
||||
themeLight: AppTheme.DEFAULT_LIGHT,
|
||||
useThemeAccentColor: false,
|
||||
volumeWheelStep: 5,
|
||||
volumeWidth: 70,
|
||||
zoomFactor: 100,
|
||||
|
||||
@@ -10,6 +10,29 @@ import { FontType } from '/@/shared/types/types';
|
||||
export const THEME_DATA = [
|
||||
{ label: 'Default Dark', type: 'dark', value: AppTheme.DEFAULT_DARK },
|
||||
{ label: 'Default Light', type: 'light', value: AppTheme.DEFAULT_LIGHT },
|
||||
{ label: 'Nord', type: 'dark', value: AppTheme.NORD },
|
||||
{ label: 'Dracula', type: 'dark', value: AppTheme.DRACULA },
|
||||
{ label: 'One Dark', type: 'dark', value: AppTheme.ONE_DARK },
|
||||
{ label: 'Solarized Dark', type: 'dark', value: AppTheme.SOLARIZED_DARK },
|
||||
{ label: 'Solarized Light', type: 'light', value: AppTheme.SOLARIZED_LIGHT },
|
||||
{ label: 'GitHub Dark', type: 'dark', value: AppTheme.GITHUB_DARK },
|
||||
{ label: 'GitHub Light', type: 'light', value: AppTheme.GITHUB_LIGHT },
|
||||
{ label: 'Monokai', type: 'dark', value: AppTheme.MONOKAI },
|
||||
{ label: 'High Contrast Dark', type: 'dark', value: AppTheme.HIGH_CONTRAST_DARK },
|
||||
{ label: 'High Contrast Light', type: 'light', value: AppTheme.HIGH_CONTRAST_LIGHT },
|
||||
{ label: 'Tokyo Night', type: 'dark', value: AppTheme.TOKYO_NIGHT },
|
||||
{ label: 'Catppuccin Mocha', type: 'dark', value: AppTheme.CATPPUCCIN_MOCHA },
|
||||
{ label: 'Catppuccin Latte', type: 'light', value: AppTheme.CATPPUCCIN_LATTE },
|
||||
{ label: 'Gruvbox Dark', type: 'dark', value: AppTheme.GRUVBOX_DARK },
|
||||
{ label: 'Gruvbox Light', type: 'light', value: AppTheme.GRUVBOX_LIGHT },
|
||||
{ label: 'Night Owl', type: 'dark', value: AppTheme.NIGHT_OWL },
|
||||
{ label: 'Material Dark', type: 'dark', value: AppTheme.MATERIAL_DARK },
|
||||
{ label: 'Material Light', type: 'light', value: AppTheme.MATERIAL_LIGHT },
|
||||
{ label: 'Ayu Dark', type: 'dark', value: AppTheme.AYU_DARK },
|
||||
{ label: 'Ayu Light', type: 'light', value: AppTheme.AYU_LIGHT },
|
||||
{ label: 'Shades of Purple', type: 'dark', value: AppTheme.SHADES_OF_PURPLE },
|
||||
{ label: 'VS Code Dark+', type: 'dark', value: AppTheme.VSCODE_DARK_PLUS },
|
||||
{ label: 'VS Code Light+', type: 'light', value: AppTheme.VSCODE_LIGHT_PLUS },
|
||||
];
|
||||
|
||||
export const useAppTheme = (overrideTheme?: AppTheme) => {
|
||||
@@ -20,9 +43,8 @@ export const useAppTheme = (overrideTheme?: AppTheme) => {
|
||||
const loadedStylesheetsRef = useRef<Set<string>>(new Set());
|
||||
const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const [isDarkTheme, setIsDarkTheme] = useState(getCurrentTheme());
|
||||
const { followSystemTheme, theme, themeDark, themeLight } = useSettingsStore(
|
||||
(state) => state.general,
|
||||
);
|
||||
const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } =
|
||||
useSettingsStore((state) => state.general);
|
||||
|
||||
const mqListener = (e: any) => {
|
||||
setIsDarkTheme(e.matches);
|
||||
@@ -145,19 +167,28 @@ export const useAppTheme = (overrideTheme?: AppTheme) => {
|
||||
const appTheme: AppThemeConfiguration = useMemo(() => {
|
||||
const themeProperties = getAppTheme(selectedTheme);
|
||||
|
||||
// Use theme's primary color if useThemeAccentColor is enabled, otherwise use custom accent
|
||||
const primaryColor = useThemeAccentColor
|
||||
? themeProperties.colors?.primary || themeProperties.colors?.['state-info'] || accent
|
||||
: accent;
|
||||
|
||||
return {
|
||||
...themeProperties,
|
||||
colors: {
|
||||
...themeProperties.colors,
|
||||
primary: accent,
|
||||
primary: primaryColor,
|
||||
},
|
||||
};
|
||||
}, [accent, selectedTheme]);
|
||||
}, [accent, selectedTheme, useThemeAccentColor]);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--theme-colors-primary', accent);
|
||||
}, [accent]);
|
||||
const themeProperties = getAppTheme(selectedTheme);
|
||||
const primaryColor = useThemeAccentColor
|
||||
? themeProperties.colors?.primary || themeProperties.colors?.['state-info'] || accent
|
||||
: accent;
|
||||
root.style.setProperty('--theme-colors-primary', primaryColor);
|
||||
}, [accent, selectedTheme, useThemeAccentColor]);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
@@ -235,9 +266,8 @@ export const useAppThemeColors = () => {
|
||||
const accent = useSettingsStore((store) => store.general.accent);
|
||||
const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const [isDarkTheme] = useState(getCurrentTheme());
|
||||
const { followSystemTheme, theme, themeDark, themeLight } = useSettingsStore(
|
||||
(state) => state.general,
|
||||
);
|
||||
const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } =
|
||||
useSettingsStore((state) => state.general);
|
||||
|
||||
const getSelectedTheme = () => {
|
||||
if (followSystemTheme) {
|
||||
@@ -252,14 +282,19 @@ export const useAppThemeColors = () => {
|
||||
const appTheme: AppThemeConfiguration = useMemo(() => {
|
||||
const themeProperties = getAppTheme(selectedTheme);
|
||||
|
||||
// Use theme's primary color if useThemeAccentColor is enabled, otherwise use custom accent
|
||||
const primaryColor = useThemeAccentColor
|
||||
? themeProperties.colors?.primary || themeProperties.colors?.['state-info'] || accent
|
||||
: accent;
|
||||
|
||||
return {
|
||||
...themeProperties,
|
||||
colors: {
|
||||
...themeProperties.colors,
|
||||
primary: accent,
|
||||
primary: primaryColor,
|
||||
},
|
||||
};
|
||||
}, [accent, selectedTheme]);
|
||||
}, [accent, selectedTheme, useThemeAccentColor]);
|
||||
|
||||
const themeVars = useMemo(() => {
|
||||
return Object.entries(appTheme?.app ?? {})
|
||||
|
||||
Reference in New Issue
Block a user