add setting to override theme primary shade (#1791)

This commit is contained in:
jeffvli
2026-03-04 20:58:30 -08:00
parent 884dcde289
commit 93791aea15
5 changed files with 80 additions and 4 deletions
+4
View File
@@ -724,6 +724,10 @@
"accentColor": "accent color",
"useThemeAccentColor": "use theme accent color",
"useThemeAccentColor_description": "use the primary color defined in the selected theme instead of the custom accent color",
"useThemePrimaryShade": "use theme primary shade",
"useThemePrimaryShade_description": "use the primary shade defined in the selected theme for primary color variants",
"primaryShade": "primary shade",
"primaryShade_description": "override the primary shade (09) used for buttons, links, and other primary-colored elements",
"albumBackground_description": "adds a background image for album pages containing the album art",
"albumBackground": "album background image",
"albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image",
@@ -103,6 +103,7 @@ type SettingsProperties = {
'settings.themeLight': string;
'settings.tray': boolean;
'settings.useThemeAccentColor': boolean;
'settings.useThemePrimaryShade': boolean;
'settings.windowBarStyle': Platform;
'settings.zoomFactor': number;
};
@@ -192,6 +193,7 @@ const getSettingsProperties = (): SettingsProperties => {
'settings.themeLight': settings.general.themeLight,
'settings.tray': ignoreWeb(settings.window.tray),
'settings.useThemeAccentColor': settings.general.useThemeAccentColor,
'settings.useThemePrimaryShade': settings.general.useThemePrimaryShade,
'settings.windowBarStyle': ignoreWeb(settings.window.windowBarStyle),
'settings.zoomFactor': ignoreWeb(settings.general.zoomFactor),
} as any;
@@ -13,6 +13,7 @@ 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 { Slider } from '/@/shared/components/slider/slider';
import { Stack } from '/@/shared/components/stack/stack';
import { Switch } from '/@/shared/components/switch/switch';
import { getAppTheme } from '/@/shared/themes/app-theme';
@@ -253,6 +254,51 @@ export const ThemeSettings = memo(() => {
}),
title: t('setting.accentColor', { postProcess: 'sentenceCase' }),
},
{
control: (
<Switch
checked={settings.useThemePrimaryShade}
onChange={(e) => {
setSettings({
general: {
useThemePrimaryShade: e.currentTarget.checked,
},
});
}}
/>
),
description: t('setting.useThemePrimaryShade', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.useThemePrimaryShade', { postProcess: 'sentenceCase' }),
},
{
control: (
<Slider
defaultValue={settings.primaryShade}
label={(value) => value}
max={9}
min={0}
onChangeEnd={(value) => {
setSettings({
general: {
primaryShade: value,
},
});
}}
step={1}
w={120}
/>
),
description: t('setting.primaryShade', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: settings.useThemePrimaryShade,
title: t('setting.primaryShade', { postProcess: 'sentenceCase' }),
},
];
return (
+6
View File
@@ -462,6 +462,7 @@ export const GeneralSettingsSchema = z.object({
playerbarOpenDrawer: z.boolean(),
playerbarSlider: PlayerbarSliderSchema,
playlistTarget: PlaylistTargetSchema,
primaryShade: z.number().min(0).max(9),
resume: z.boolean(),
showLyricsInSidebar: z.boolean(),
showRatings: z.boolean(),
@@ -479,6 +480,7 @@ export const GeneralSettingsSchema = z.object({
themeDark: z.nativeEnum(AppTheme),
themeLight: z.nativeEnum(AppTheme),
useThemeAccentColor: z.boolean(),
useThemePrimaryShade: z.boolean(),
volumeWheelStep: z.number(),
volumeWidth: z.number(),
zoomFactor: z.number(),
@@ -1051,6 +1053,7 @@ const initialState: SettingsState = {
type: PlayerbarSliderType.SLIDER,
},
playlistTarget: PlaylistTarget.TRACK,
primaryShade: 6,
resume: true,
showLyricsInSidebar: true,
showRatings: true,
@@ -1072,6 +1075,7 @@ const initialState: SettingsState = {
themeDark: AppTheme.DEFAULT_DARK,
themeLight: AppTheme.DEFAULT_LIGHT,
useThemeAccentColor: false,
useThemePrimaryShade: true,
volumeWheelStep: 5,
volumeWidth: 70,
zoomFactor: 100,
@@ -2371,10 +2375,12 @@ export const useThemeSettings = () =>
useSettingsStore(
(state) => ({
followSystemTheme: state.general.followSystemTheme,
primaryShade: state.general.primaryShade,
theme: state.general.theme,
themeDark: state.general.themeDark,
themeLight: state.general.themeLight,
useThemeAccentColor: state.general.useThemeAccentColor,
useThemePrimaryShade: state.general.useThemePrimaryShade,
}),
shallow,
);
+22 -4
View File
@@ -52,7 +52,7 @@ export const useAppTheme = (overrideTheme?: AppTheme) => {
const themeInlineStylesRef = useRef<HTMLStyleElement | null>(null);
const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
const [isDarkTheme, setIsDarkTheme] = useState(getCurrentTheme());
const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } =
const { followSystemTheme, primaryShade, theme, themeDark, themeLight, useThemeAccentColor, useThemePrimaryShade } =
useThemeSettings();
const mqListener = (e: any) => {
@@ -144,14 +144,23 @@ export const useAppTheme = (overrideTheme?: AppTheme) => {
? themeProperties.colors?.primary || themeProperties.colors?.['state-info'] || accent
: accent;
// Use theme's primary shade if useThemePrimaryShade is enabled, otherwise use slider value
const effectivePrimaryShade = useThemePrimaryShade
? themeProperties.mantineOverride?.primaryShade
: { dark: primaryShade, light: primaryShade };
return {
...themeProperties,
colors: {
...themeProperties.colors,
primary: primaryColor,
},
mantineOverride: {
...themeProperties.mantineOverride,
...(effectivePrimaryShade != null && { primaryShade: effectivePrimaryShade }),
},
};
}, [accent, selectedTheme, useThemeAccentColor]);
}, [accent, primaryShade, selectedTheme, useThemeAccentColor, useThemePrimaryShade]);
useEffect(() => {
const root = document.documentElement;
@@ -241,7 +250,7 @@ export const useAppThemeColors = () => {
const accent = useAccent();
const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
const [isDarkTheme] = useState(getCurrentTheme());
const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } =
const { followSystemTheme, primaryShade, theme, themeDark, themeLight, useThemeAccentColor, useThemePrimaryShade } =
useThemeSettings();
const getSelectedTheme = () => {
@@ -262,14 +271,23 @@ export const useAppThemeColors = () => {
? themeProperties.colors?.primary || themeProperties.colors?.['state-info'] || accent
: accent;
// Use theme's primary shade if useThemePrimaryShade is enabled, otherwise use slider value
const effectivePrimaryShade = useThemePrimaryShade
? themeProperties.mantineOverride?.primaryShade
: { dark: primaryShade, light: primaryShade };
return {
...themeProperties,
colors: {
...themeProperties.colors,
primary: primaryColor,
},
mantineOverride: {
...themeProperties.mantineOverride,
...(effectivePrimaryShade != null && { primaryShade: effectivePrimaryShade }),
},
};
}, [accent, selectedTheme, useThemeAccentColor]);
}, [accent, primaryShade, selectedTheme, useThemeAccentColor, useThemePrimaryShade]);
const themeVars = useMemo(() => {
return Object.entries(appTheme?.app ?? {})