add new app themes

This commit is contained in:
jeffvli
2025-12-14 06:06:27 -08:00
parent 0f4534c34c
commit 69de9a98f0
32 changed files with 943 additions and 88 deletions
@@ -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({