mirror of
https://github.com/unraid/api.git
synced 2025-12-31 05:29:48 -06:00
feat: update theme application logic and color picker (#1181)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Upgraded the theme customization interface with a dropdown that lets you choose from multiple themes (Light, Dark, Azure, Gray). Users can now adjust options like text colors, background color, gradients, and banner display more intuitively. - Introduced a structured approach to theme variables, enhancing compatibility and customization options. - **Style** - Enhanced the header’s visual presentation by introducing dynamic background imagery and refined layout adjustments for a more polished look. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,92 +1,77 @@
|
||||
<script lang="ts" setup>
|
||||
import { Input, Label, Switch } from '@unraid/ui';
|
||||
|
||||
import type { Theme } from '~/store/theme';
|
||||
|
||||
import { defaultColors, useThemeStore } from '~/store/theme';
|
||||
import { Input, Label, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@unraid/ui';
|
||||
import type { Theme } from '~/themes/types';
|
||||
import { useThemeStore } from '~/store/theme';
|
||||
import { defaultColors } from '~/themes/default';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { darkMode } = toRefs(themeStore);
|
||||
|
||||
const setDarkMode = ref<boolean>(false);
|
||||
const setGradient = ref<boolean>(false);
|
||||
const setDescription = ref<boolean>(true);
|
||||
const setBanner = ref<boolean>(true);
|
||||
|
||||
const toggleSwitch = (value: boolean) => {
|
||||
setDarkMode.value = value;
|
||||
};
|
||||
const toggleGradient = (value: boolean) => {
|
||||
setGradient.value = value;
|
||||
};
|
||||
const toggleDescription = (value: boolean) => {
|
||||
setDescription.value = value;
|
||||
};
|
||||
const toggleBanner = (value: boolean) => {
|
||||
setBanner.value = value;
|
||||
};
|
||||
|
||||
const textPrimary = ref<string>('');
|
||||
const textSecondary = ref<string>('');
|
||||
const bgColor = ref<string>('');
|
||||
|
||||
const textPrimaryToSet = computed(() => {
|
||||
if (textPrimary.value) {
|
||||
return textPrimary.value;
|
||||
}
|
||||
return darkMode.value ? defaultColors.dark.headerTextPrimary : defaultColors.light.headerTextPrimary;
|
||||
// Form state
|
||||
const form = reactive({
|
||||
selectedTheme: 'white',
|
||||
gradient: false,
|
||||
description: true,
|
||||
banner: true,
|
||||
textPrimary: '',
|
||||
textSecondary: '',
|
||||
bgColor: ''
|
||||
});
|
||||
|
||||
const textSecondaryToSet = computed(() => {
|
||||
if (textSecondary.value) {
|
||||
return textSecondary.value;
|
||||
// Watch for changes and update theme
|
||||
watch([form], () => {
|
||||
// Enable gradient if banner is enabled
|
||||
if (form.banner && !form.gradient) {
|
||||
form.gradient = true;
|
||||
}
|
||||
return darkMode.value
|
||||
? defaultColors.dark.headerTextSecondary
|
||||
: defaultColors.light.headerTextSecondary;
|
||||
});
|
||||
|
||||
const bgColorToSet = computed(() => {
|
||||
if (bgColor.value) {
|
||||
return bgColor.value;
|
||||
}
|
||||
return darkMode.value
|
||||
? defaultColors.dark.headerBackgroundColor
|
||||
: defaultColors.light.headerBackgroundColor;
|
||||
});
|
||||
|
||||
watch([setDarkMode, bgColorToSet, textSecondaryToSet, textPrimaryToSet], (newVal) => {
|
||||
console.log(newVal);
|
||||
const themeToSet: Theme = {
|
||||
banner: setBanner.value,
|
||||
bannerGradient: setGradient.value,
|
||||
descriptionShow: setDescription.value,
|
||||
textColor: textPrimaryToSet.value,
|
||||
metaColor: textSecondaryToSet.value,
|
||||
bgColor: bgColorToSet.value,
|
||||
name: setDarkMode.value ? 'black' : 'light',
|
||||
banner: form.banner,
|
||||
bannerGradient: form.gradient,
|
||||
descriptionShow: form.description,
|
||||
textColor: form.textPrimary ?? defaultColors[form.selectedTheme]['--header-text-primary']!,
|
||||
metaColor: form.textSecondary ?? defaultColors[form.selectedTheme]['--header-text-secondary']!,
|
||||
bgColor: form.bgColor ?? defaultColors[form.selectedTheme]['--header-background-color']!,
|
||||
name: form.selectedTheme
|
||||
};
|
||||
themeStore.setTheme(themeToSet);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 border-solid border-2 p-2 border-r-2">
|
||||
<h1 class="text-lg">Color Theme Customization</h1>
|
||||
|
||||
<Label for="theme-select">Theme</Label>
|
||||
<Select v-model="form.selectedTheme">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a theme" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="white">Light</SelectItem>
|
||||
<SelectItem value="black">Dark</SelectItem>
|
||||
<SelectItem value="azure">Azure</SelectItem>
|
||||
<SelectItem value="gray">Gray</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Label for="primary-text-color">Header Primary Text Color</Label>
|
||||
<Input id="primary-text-color" v-model="textPrimary" />
|
||||
<Label for="primary-text-color">Header Secondary Text Color</Label>
|
||||
<Input id="primary-text-color" v-model="textSecondary" />
|
||||
<Label for="primary-text-color">Header Background Color</Label>
|
||||
<Input id="primary-text-color" v-model="bgColor" />
|
||||
<Label for="dark-mode">Dark Mode</Label>
|
||||
<Switch id="dark-mode" @update:checked="toggleSwitch" />
|
||||
<Input id="primary-text-color" v-model="form.textPrimary" />
|
||||
|
||||
<Label for="secondary-text-color">Header Secondary Text Color</Label>
|
||||
<Input id="secondary-text-color" v-model="form.textSecondary" />
|
||||
|
||||
<Label for="background-color">Header Background Color</Label>
|
||||
<Input id="background-color" v-model="form.bgColor" />
|
||||
|
||||
<Label for="gradient">Gradient</Label>
|
||||
<Switch id="gradient" @update:checked="toggleGradient" />
|
||||
<Switch id="gradient" v-model:checked="form.gradient" />
|
||||
|
||||
<Label for="description">Description</Label>
|
||||
<Switch id="description" @update:checked="toggleDescription" />
|
||||
<Switch id="description" v-model:checked="form.description" />
|
||||
|
||||
<Label for="banner">Banner</Label>
|
||||
<Switch id="banner" @update:checked="toggleBanner" />
|
||||
<Switch id="banner" v-model:checked="form.banner" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ import AES from 'crypto-js/aes';
|
||||
import type { SendPayloads } from '~/store/callback';
|
||||
|
||||
import SsoButtonCe from '~/components/SsoButton.ce.vue';
|
||||
import { useThemeStore } from '~/store/theme';
|
||||
|
||||
const serverStore = useDummyServerStore();
|
||||
const { serverState } = storeToRefs(serverStore);
|
||||
const { registerEntry } = useCustomElements();
|
||||
const { theme } = storeToRefs(useThemeStore());
|
||||
onBeforeMount(() => {
|
||||
registerEntry('UnraidComponents');
|
||||
});
|
||||
@@ -75,6 +77,13 @@ onMounted(() => {
|
||||
'forUpc'
|
||||
);
|
||||
});
|
||||
|
||||
const bannerImage = watch(theme, () => {
|
||||
if (theme.value.banner) {
|
||||
return `url(https://picsum.photos/1920/200?${Math.round(Math.random() * 100)})`;
|
||||
}
|
||||
return 'none';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -86,7 +95,14 @@ onMounted(() => {
|
||||
<ColorSwitcherCe />
|
||||
<h2 class="text-xl font-semibold font-mono">Vue Components</h2>
|
||||
<h3 class="text-lg font-semibold font-mono">UserProfileCe</h3>
|
||||
<header class="bg-header-background-color py-4 flex flex-row justify-between items-center">
|
||||
<header
|
||||
class="bg-header-background-color flex justify-between items-center"
|
||||
:style="{
|
||||
backgroundImage: bannerImage,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
}"
|
||||
>
|
||||
<div class="inline-flex flex-col gap-4 items-start px-4">
|
||||
<a href="https://unraid.net" target="_blank">
|
||||
<BrandLogo class="w-[100px] sm:w-[150px]" />
|
||||
|
||||
@@ -1,129 +1,33 @@
|
||||
import { createPinia, defineStore, setActivePinia } from 'pinia';
|
||||
|
||||
import { defaultColors } from '~/themes/default';
|
||||
import hexToRgba from 'hex-to-rgba';
|
||||
|
||||
import type { Theme, ThemeVariables } from '~/themes/types';
|
||||
|
||||
/**
|
||||
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
|
||||
* @see https://github.com/vuejs/pinia/discussions/1085
|
||||
*/
|
||||
setActivePinia(createPinia());
|
||||
|
||||
export interface Theme {
|
||||
banner: boolean;
|
||||
bannerGradient: boolean;
|
||||
bgColor: string;
|
||||
descriptionShow: boolean;
|
||||
metaColor: string;
|
||||
name: string;
|
||||
textColor: string;
|
||||
}
|
||||
|
||||
interface ThemeVariables {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export const defaultColors: Record<string, ThemeVariables> = {
|
||||
dark: {
|
||||
'--background': '0 0% 3.9%',
|
||||
'--foreground': '0 0% 98%',
|
||||
'--muted': '0 0% 14.9%',
|
||||
'--muted-foreground': '0 0% 63.9%',
|
||||
'--popover': '0 0% 3.9%',
|
||||
'--popover-foreground': '0 0% 98%',
|
||||
'--card': '0 0% 14.9%',
|
||||
'--card-foreground': '0 0% 98%',
|
||||
'--border': '0 0% 20%',
|
||||
'--input': '0 0% 14.9%',
|
||||
'--primary': '24 100% 50%',
|
||||
'--primary-foreground': '0 0% 98%',
|
||||
'--secondary': '0 0% 14.9%',
|
||||
'--secondary-foreground': '0 0% 77%',
|
||||
'--accent': '0 0% 14.9%',
|
||||
'--accent-foreground': '0 0% 98%',
|
||||
'--destructive': '0 62.8% 30.6%',
|
||||
'--destructive-foreground': '0 0% 98%',
|
||||
'--ring': '0 0% 83.1%',
|
||||
'--header-text-primary': '#1c1c1c',
|
||||
'--header-text-secondary': '#999999',
|
||||
'--header-background-color': '#f2f2f2',
|
||||
'--gradient-start': 'rgba(0, 0, 0, 0)',
|
||||
'--gradient-end': 'var(--header-background-color)',
|
||||
},
|
||||
light: {
|
||||
'--background': '0 0% 100%',
|
||||
'--foreground': '0 0% 3.9%',
|
||||
'--muted': '0 0% 96.1%',
|
||||
'--muted-foreground': '0 0% 45.1%',
|
||||
'--popover': '0 0% 100%',
|
||||
'--popover-foreground': '0 0% 3.9%',
|
||||
'--card': '0 0% 100%',
|
||||
'--card-foreground': '0 0% 3.9%',
|
||||
'--border': '0 0% 89.8%',
|
||||
'--input': '0 0% 89.8%',
|
||||
'--primary': '24 100% 50%',
|
||||
'--primary-foreground': '0 0% 98%',
|
||||
'--secondary': '0 0% 96.1%',
|
||||
'--secondary-foreground': '0 0% 45%',
|
||||
'--accent': '0 0% 96.1%',
|
||||
'--accent-foreground': '0 0% 9%',
|
||||
'--destructive': '0 84.2% 60.2%',
|
||||
'--destructive-foreground': '0 0% 98%',
|
||||
'--ring': '0 0% 3.9%',
|
||||
'--radius': '0.5rem',
|
||||
'--header-text-primary': '#f2f2f2',
|
||||
'--header-text-secondary': '#999999',
|
||||
'--header-background-color': '#1c1b1b',
|
||||
'--gradient-start': 'rgba(0, 0, 0, 0)',
|
||||
'--gradient-end': 'var(--header-background-color)',
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Unraid default theme colors do not have consistent colors for the header.
|
||||
* This is a workaround to set the correct colors for the header.
|
||||
* DARK THEMES: black, gray
|
||||
* DARK HEADER THEMES: white, gray
|
||||
* LIGHT THEMES: white, gray
|
||||
* LIGHT HEADER THEMES: black, gray
|
||||
*/
|
||||
export const defaultAzureGrayHeaderColors: ThemeVariables = {
|
||||
// azure and gray header colors are the same but the background color is different
|
||||
'--header-text-primary': '#39587f',
|
||||
'--header-text-secondary': '#606e7f',
|
||||
};
|
||||
export const defaultHeaderColors: Record<string, ThemeVariables> = {
|
||||
azure: {
|
||||
...defaultAzureGrayHeaderColors,
|
||||
'--header-background-color': '#1c1b1b',
|
||||
},
|
||||
black: {
|
||||
'--header-text-primary': '#1c1c1c',
|
||||
'--header-text-secondary': '#999999',
|
||||
'--header-background-color': '#f2f2f2',
|
||||
},
|
||||
gray: {
|
||||
...defaultAzureGrayHeaderColors,
|
||||
'--header-background-color': '#f2f2f2',
|
||||
},
|
||||
white: {
|
||||
'--header-text-primary': '#f2f2f2',
|
||||
'--header-text-secondary': '#999999',
|
||||
'--header-background-color': '#1c1b1b',
|
||||
},
|
||||
};
|
||||
|
||||
// used to swap the UPC text color when using the azure or gray theme
|
||||
export const DARK_THEMES = ['black', 'gray'] as const;
|
||||
|
||||
export const useThemeStore = defineStore('theme', () => {
|
||||
// State
|
||||
const theme = ref<Theme | undefined>();
|
||||
|
||||
const activeColorVariables = ref<ThemeVariables>({
|
||||
...defaultColors.light,
|
||||
...defaultHeaderColors['white'],
|
||||
const theme = ref<Theme>({
|
||||
name: 'white',
|
||||
banner: false,
|
||||
bannerGradient: false,
|
||||
bgColor: '',
|
||||
descriptionShow: false,
|
||||
metaColor: '',
|
||||
textColor: '',
|
||||
});
|
||||
|
||||
const activeColorVariables = ref<ThemeVariables>(defaultColors.white);
|
||||
|
||||
// Getters
|
||||
const darkMode = computed<boolean>(
|
||||
() => DARK_THEMES.includes(theme.value?.name as (typeof DARK_THEMES)[number]) ?? false
|
||||
@@ -133,8 +37,8 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
if (!theme.value?.banner || !theme.value?.bannerGradient) {
|
||||
return undefined;
|
||||
}
|
||||
const start = theme.value?.bgColor ? 'var(--gradient-start)' : 'rgba(0, 0, 0, 0)';
|
||||
const end = theme.value?.bgColor ? 'var(--gradient-end)' : 'var(--header-background-color)';
|
||||
const start = theme.value?.bgColor ? 'var(--header-gradient-start)' : 'rgba(0, 0, 0, 0)';
|
||||
const end = theme.value?.bgColor ? 'var(--header-gradient-end)' : 'var(--header-background-color)';
|
||||
return `background-image: linear-gradient(90deg, ${start} 0, ${end} 30%);`;
|
||||
});
|
||||
// Actions
|
||||
@@ -143,74 +47,68 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
};
|
||||
|
||||
const setCssVars = () => {
|
||||
const customColorVariables = structuredClone(defaultColors);
|
||||
const body = document.body;
|
||||
const selectedMode = darkMode.value ? 'dark' : 'light';
|
||||
|
||||
// set the default header colors for the current theme
|
||||
const themeName = theme.value?.name;
|
||||
if (themeName && themeName in defaultHeaderColors) {
|
||||
customColorVariables[selectedMode] = {
|
||||
...customColorVariables[selectedMode],
|
||||
...defaultHeaderColors[themeName],
|
||||
};
|
||||
}
|
||||
|
||||
const selectedTheme = theme.value.name;
|
||||
const customTheme = { ...defaultColors[selectedTheme] };
|
||||
// Set banner gradient if enabled
|
||||
if (theme.value?.banner && theme.value?.bannerGradient) {
|
||||
const start = theme.value?.bgColor
|
||||
if (theme.value.banner && theme.value.bannerGradient) {
|
||||
const start = theme.value.bgColor
|
||||
? hexToRgba(theme.value.bgColor, 0)
|
||||
: customColorVariables[selectedMode]['--gradient-start'];
|
||||
const end = theme.value?.bgColor
|
||||
: customTheme['--header-gradient-start'];
|
||||
const end = theme.value.bgColor
|
||||
? hexToRgba(theme.value.bgColor, 0.7)
|
||||
: customColorVariables[selectedMode]['--gradient-end'];
|
||||
body.style.setProperty('--banner-gradient', `linear-gradient(90deg, ${start} 0, ${end} 30%)`);
|
||||
} else {
|
||||
body.style.removeProperty('--banner-gradient');
|
||||
: customTheme['--header-gradient-end'];
|
||||
|
||||
// set the banner gradient
|
||||
customTheme['--banner-gradient'] = `linear-gradient(90deg, ${start} 0, ${end} 30%)`;
|
||||
}
|
||||
|
||||
// overwrite with hex colors set in webGUI @ /Settings/DisplaySettings
|
||||
if (theme.value?.textColor) {
|
||||
body.style.setProperty('--header-text-primary', theme.value.textColor);
|
||||
} else {
|
||||
body.style.setProperty(
|
||||
'--header-text-primary',
|
||||
customColorVariables[selectedMode]['--header-text-primary']
|
||||
);
|
||||
if (theme.value.textColor) {
|
||||
customTheme['--header-text-primary'] = theme.value.textColor;
|
||||
}
|
||||
if (theme.value?.metaColor) {
|
||||
body.style.setProperty('--header-text-secondary', theme.value.metaColor);
|
||||
} else {
|
||||
body.style.setProperty(
|
||||
'--header-text-secondary',
|
||||
customColorVariables[selectedMode]['--header-text-secondary']
|
||||
);
|
||||
}
|
||||
if (theme.value?.bgColor) {
|
||||
body.style.setProperty('--header-background-color', theme.value.bgColor);
|
||||
body.style.setProperty('--gradient-start', hexToRgba(theme.value.bgColor, 0));
|
||||
body.style.setProperty('--gradient-end', hexToRgba(theme.value.bgColor, 0.7));
|
||||
} else {
|
||||
body.style.setProperty(
|
||||
'--header-background-color',
|
||||
customColorVariables[selectedMode]['--header-background-color']
|
||||
);
|
||||
if (theme.value.metaColor) {
|
||||
customTheme['--header-text-secondary'] = theme.value.metaColor;
|
||||
}
|
||||
|
||||
// Apply all other CSS variables
|
||||
Object.entries(customColorVariables[selectedMode]).forEach(([key, value]) => {
|
||||
if (!key.startsWith('--header-')) {
|
||||
body.style.setProperty(key, value);
|
||||
if (theme.value.bgColor) {
|
||||
customTheme['--header-background-color'] = theme.value.bgColor;
|
||||
customTheme['--header-gradient-start'] = hexToRgba(theme.value.bgColor, 0);
|
||||
customTheme['--header-gradient-end'] = hexToRgba(theme.value.bgColor, 0.7);
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (darkMode.value) {
|
||||
document.body.classList.add('dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
}
|
||||
|
||||
document.body.style.cssText = createCssText(customTheme, document.body);
|
||||
activeColorVariables.value = customTheme;
|
||||
});
|
||||
};
|
||||
|
||||
if (darkMode.value) {
|
||||
document.body.classList.add('dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
}
|
||||
/**
|
||||
* Creates a string of CSS rules preserving existing rules that are not defined in the theme variables
|
||||
* @param themeVariables - The theme variables to apply
|
||||
* @param body - The body element to apply the CSS to
|
||||
* @returns A string of CSS rules
|
||||
*/
|
||||
const createCssText = (themeVariables: ThemeVariables, body: HTMLElement) => {
|
||||
const existingStyles = body.style.cssText
|
||||
.split(';')
|
||||
.filter((rule) => rule.trim())
|
||||
.filter((rule) => {
|
||||
// Keep rules that aren't in our theme variables
|
||||
return !Object.keys(themeVariables).some((themeVar) => rule.startsWith(themeVar));
|
||||
});
|
||||
|
||||
activeColorVariables.value = customColorVariables[selectedMode];
|
||||
const themeStyles = Object.entries(themeVariables).reduce((acc, [key, value]) => {
|
||||
if (value) acc.push(`${key}: ${value}`);
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
|
||||
return [...existingStyles, ...themeStyles].join(';');
|
||||
};
|
||||
|
||||
watch(theme, () => {
|
||||
|
||||
104
web/themes/default.ts
Normal file
104
web/themes/default.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { LegacyThemeVariables, ThemeVariables } from '~/themes/types';
|
||||
|
||||
/**
|
||||
* Defines legacy colors that are kept for backwards compatibility
|
||||
*
|
||||
* Allows theme-engine to be updated without breaking existing themes
|
||||
*/
|
||||
export const legacyColors = {
|
||||
'--color-alpha': 'var(--header-background-color)',
|
||||
'--color-beta': 'var(--header-text-primary)',
|
||||
'--color-gamma': 'var(--header-text-secondary)',
|
||||
'--color-gamma-opaque': 'rgba(153, 153, 153, .5)',
|
||||
'--color-customgradient-start': 'rgba(242, 242, 242, .0)',
|
||||
'--color-customgradient-end': 'rgba(242, 242, 242, .85)',
|
||||
'--shadow-beta': '0 25px 50px -12px rgba(242, 242, 242, .15)',
|
||||
} as const satisfies LegacyThemeVariables;
|
||||
|
||||
export const defaultLight: ThemeVariables = {
|
||||
'--background': '0 0% 3.9%',
|
||||
'--foreground': '0 0% 98%',
|
||||
'--muted': '0 0% 14.9%',
|
||||
'--muted-foreground': '0 0% 63.9%',
|
||||
'--popover': '0 0% 3.9%',
|
||||
'--popover-foreground': '0 0% 98%',
|
||||
'--card': '0 0% 14.9%',
|
||||
'--card-foreground': '0 0% 98%',
|
||||
'--border': '0 0% 20%',
|
||||
'--input': '0 0% 14.9%',
|
||||
'--primary': '24 100% 50%',
|
||||
'--primary-foreground': '0 0% 98%',
|
||||
'--secondary': '0 0% 14.9%',
|
||||
'--secondary-foreground': '0 0% 77%',
|
||||
'--accent': '0 0% 14.9%',
|
||||
'--accent-foreground': '0 0% 98%',
|
||||
'--destructive': '0 62.8% 30.6%',
|
||||
'--destructive-foreground': '0 0% 98%',
|
||||
'--ring': '0 0% 83.1%',
|
||||
'--radius': '0.5rem',
|
||||
'--header-text-primary': '#f2f2f2',
|
||||
'--header-text-secondary': '#999999',
|
||||
'--header-background-color': '#1c1b1b',
|
||||
'--header-gradient-start': 'rgba(0, 0, 0, 0)',
|
||||
'--header-gradient-end': 'var(--header-background-color)',
|
||||
'--banner-gradient': null,
|
||||
...legacyColors,
|
||||
} as const satisfies ThemeVariables;
|
||||
|
||||
export const defaultDark: ThemeVariables = {
|
||||
'--background': '0 0% 100%',
|
||||
'--foreground': '0 0% 3.9%',
|
||||
'--muted': '0 0% 96.1%',
|
||||
'--muted-foreground': '0 0% 45.1%',
|
||||
'--popover': '0 0% 100%',
|
||||
'--popover-foreground': '0 0% 3.9%',
|
||||
'--card': '0 0% 100%',
|
||||
'--card-foreground': '0 0% 3.9%',
|
||||
'--border': '0 0% 89.8%',
|
||||
'--input': '0 0% 89.8%',
|
||||
'--primary': '24 100% 50%',
|
||||
'--primary-foreground': '0 0% 98%',
|
||||
'--secondary': '0 0% 96.1%',
|
||||
'--secondary-foreground': '0 0% 45%',
|
||||
'--accent': '0 0% 96.1%',
|
||||
'--accent-foreground': '0 0% 9%',
|
||||
'--destructive': '0 84.2% 60.2%',
|
||||
'--destructive-foreground': '0 0% 98%',
|
||||
'--ring': '0 0% 3.9%',
|
||||
'--radius': '0.5rem',
|
||||
'--header-text-primary': '#1c1c1c',
|
||||
'--header-text-secondary': '#999999',
|
||||
'--header-background-color': '#f2f2f2',
|
||||
'--header-gradient-start': 'rgba(0, 0, 0, 0)',
|
||||
'--header-gradient-end': 'var(--header-background-color)',
|
||||
'--banner-gradient': null,
|
||||
...legacyColors,
|
||||
} as const satisfies ThemeVariables;
|
||||
|
||||
/**
|
||||
* Color Explanation:
|
||||
* White (base light theme): has dark header background and light text
|
||||
* Black (base dark theme): has light header background and dark text
|
||||
* Gray (base dark theme): has dark header background and light text
|
||||
* Azure (base light theme): has light header background and dark text
|
||||
*/
|
||||
export const defaultColors: Record<string, ThemeVariables> = {
|
||||
white: {
|
||||
...defaultLight,
|
||||
},
|
||||
black: {
|
||||
...defaultDark,
|
||||
},
|
||||
gray: {
|
||||
...defaultDark,
|
||||
'--header-text-primary': '#39587f',
|
||||
'--header-text-secondary': '#606e7f',
|
||||
'--header-background-color': '#1c1b1b',
|
||||
},
|
||||
azure: {
|
||||
...defaultDark,
|
||||
'--header-text-primary': '#39587f',
|
||||
'--header-text-secondary': '#606e7f',
|
||||
'--header-background-color': '#f2f2f2',
|
||||
},
|
||||
} as const satisfies Record<string, ThemeVariables>;
|
||||
50
web/themes/types.d.ts
vendored
Normal file
50
web/themes/types.d.ts
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
export interface Theme {
|
||||
banner: boolean;
|
||||
bannerGradient: boolean;
|
||||
bgColor: string;
|
||||
descriptionShow: boolean;
|
||||
metaColor: string;
|
||||
name: string;
|
||||
textColor: string;
|
||||
}
|
||||
|
||||
type BaseThemeVariables = {
|
||||
'--background': string;
|
||||
'--foreground': string;
|
||||
'--muted': string;
|
||||
'--muted-foreground': string;
|
||||
'--popover': string;
|
||||
'--popover-foreground': string;
|
||||
'--card': string;
|
||||
'--card-foreground': string;
|
||||
'--border': string;
|
||||
'--input': string;
|
||||
'--primary': string;
|
||||
'--primary-foreground': string;
|
||||
'--secondary': string;
|
||||
'--secondary-foreground': string;
|
||||
'--accent': string;
|
||||
'--accent-foreground': string;
|
||||
'--destructive': string;
|
||||
'--destructive-foreground': string;
|
||||
'--ring': string;
|
||||
'--radius': string;
|
||||
'--header-text-primary': string;
|
||||
'--header-text-secondary': string;
|
||||
'--header-background-color': string;
|
||||
'--header-gradient-start': string;
|
||||
'--header-gradient-end': string;
|
||||
'--banner-gradient': string | null;
|
||||
};
|
||||
|
||||
type LegacyThemeVariables = {
|
||||
'--color-alpha': string;
|
||||
'--color-beta': string;
|
||||
'--color-gamma': string;
|
||||
'--color-gamma-opaque': string;
|
||||
'--color-customgradient-start': string;
|
||||
'--color-customgradient-end': string;
|
||||
'--shadow-beta': string;
|
||||
};
|
||||
|
||||
export type ThemeVariables = BaseThemeVariables & LegacyThemeVariables;
|
||||
Reference in New Issue
Block a user