feat: lots of progress on colors

This commit is contained in:
Eli Bosley
2024-12-06 13:24:36 -05:00
parent 24435613f8
commit c6547a51fc
12 changed files with 188 additions and 101 deletions

View File

@@ -0,0 +1,74 @@
<script lang="ts" setup>
import Input from '~/components/shadcn/input/Input.vue';
import Label from '~/components/shadcn/label/Label.vue';
import { defaultColors, useThemeStore, type Theme } from '~/store/theme';
const themeStore = useThemeStore();
const { darkMode, theme } = toRefs(themeStore);
const setDarkMode = ref<boolean>(false);
const toggleSwitch= (value: boolean) => {
setDarkMode.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;
})
const textSecondaryToSet = computed(() => {
if (textSecondary.value) {
return textSecondary.value;
}
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: true,
bannerGradient: true,
descriptionShow: true,
textColor: textPrimaryToSet.value,
metaColor: textSecondaryToSet.value,
bgColor: bgColorToSet.value,
name: setDarkMode.value ? 'black' : 'light',
}
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="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"/>
</div>
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

View File

@@ -16,7 +16,6 @@ import { useUpdateOsStore } from '~/store/updateOs';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
import type { UserProfileLink } from '~/types/userProfile';
import type { UiBadgeProps, UiBadgePropsColor } from '~/types/ui/badge';
import { useThemeStore } from '~/store/theme';
const { t } = useI18n();
@@ -67,16 +66,6 @@ const updateOsStatus = computed(() => {
return null;
});
const themeStore = useThemeStore();
const { darkMode, theme } = toRefs(themeStore);
const toggleDarkMode = () => {
const themeNameToSet = darkMode.value ? 'light' : 'black';
if (theme.value) {
themeStore.setTheme({ ...theme.value, name: themeNameToSet });
}
};
</script>
<template>
@@ -89,17 +78,13 @@ const toggleDarkMode = () => {
<UiBadge
color="custom"
:icon="InformationCircleIcon"
icon-styles="text-gamma"
icon-styles="text-header-text-secondary"
size="14px"
class="text-gamma group-hover:text-orange-dark group-focus:text-orange-dark group-hover:underline group-focus:underline"
class="text-header-text-secondary group-hover:text-orange-dark group-focus:text-orange-dark group-hover:underline group-focus:underline"
>
{{ osVersion }}
</UiBadge>
</button>
<button @click="toggleDarkMode">
Toggle Dark / Light Theme - Current is: {{ darkMode ? 'Dark' : 'Light' }}
</button>
<component
:is="updateOsStatus.href ? 'a' : 'button'"
v-if="updateOsStatus"

View File

@@ -42,9 +42,6 @@ const closeModal = () => {
const ariaLablledById = computed((): string|undefined => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined);
/**
* @todo when providing custom colors for theme we should invert text-beta bg-alpha to text-alpha bg-beta
*/
</script>
<template>

View File

@@ -39,16 +39,24 @@ const icon = computed<{ component: Component; color: string } | null>(() => {
</script>
<template>
<div class="relative">
<BellIcon class="w-6 h-6" />
<div
v-if="indicatorLevel === 'UNREAD'"
class="absolute top-0 right-0 size-2.5 rounded-full border border-neutral-800 bg-unraid-green"
/>
<component
:is="icon.component"
v-else-if="icon && indicatorLevel"
:class="cn('absolute -top-1 -right-1 size-4 rounded-full', icon.color)"
/>
<div class="flex items-end gap-1 text-header-text-primary">
<div class="relative">
<BellIcon class="w-6 h-6" />
<div
v-if="indicatorLevel"
:class="
cn('absolute top-0 right-0 size-2.5 rounded-full border border-neutral-800', {
'bg-unraid-red': indicatorLevel === Importance.Alert,
'bg-yellow-accent': indicatorLevel === Importance.Warning,
'bg-unraid-green': indicatorLevel === 'UNREAD',
})
"
/>
<div
v-if="hasNewNotifications || indicatorLevel === Importance.Alert"
class="absolute top-0 right-0 size-2.5 rounded-full bg-unraid-red animate-ping"
/>
</div>
<component :is="icon.component" v-if="icon" :class="cn('size-6', icon.color)" />
</div>
</template>

View File

@@ -97,21 +97,21 @@ onBeforeMount(() => {
<template>
<div
id="UserProfile"
class="text-alpha relative z-20 flex flex-col h-full gap-y-4px pt-4px pr-16px pl-40px"
class="text-primary relative z-20 flex flex-col h-full gap-y-4px pt-4px pr-16px pl-40px"
>
<div v-if="bannerGradient" class="absolute z-0 w-[125%] top-0 bottom-0 right-0" :style="bannerGradient" />
<div class="text-xs text-gamma text-right font-semibold leading-normal relative z-10 flex flex-col items-end justify-end gap-x-4px xs:flex-row xs:items-baseline xs:gap-x-12px">
<div class="text-xs text-header-text-secondary text-right font-semibold leading-normal relative z-10 flex flex-col items-end justify-end gap-x-4px xs:flex-row xs:items-baseline xs:gap-x-12px">
<UpcUptimeExpire :t="t" />
<span class="hidden xs:block">&bull;</span>
<UpcServerState :t="t" />
</div>
<div class="relative z-10 flex flex-row items-center justify-end gap-x-16px h-full">
<h1 class="text-14px sm:text-18px relative flex flex-col-reverse items-end md:flex-row border-0">
<h1 class="text-14px sm:text-18px relative flex flex-col-reverse items-end md:flex-row border-0 text-header-text-primary">
<template v-if="description && theme?.descriptionShow">
<span class="text-right text-12px sm:text-18px hidden 2xs:block" v-html="description" />
<span class="text-gamma hidden md:inline-block px-8px">&bull;</span>
<span class="text-header-text-secondary hidden md:inline-block px-8px">&bull;</span>
</template>
<button :title="t('Click to Copy LAN IP {0}', [lanIp])" class="opacity-100 hover:opacity-75 focus:opacity-75 transition-opacity" @click="copyLanIp()">
{{ name }}
@@ -155,7 +155,7 @@ onBeforeMount(() => {
top: -10px;
right: 42px;
border-right: 11px solid transparent;
border-bottom: 11px solid var(--color-alpha);
border-bottom: 11px solid var(--color-headerTextPrimary);
border-left: 11px solid transparent;
}
}

View File

@@ -40,7 +40,7 @@ const title = computed((): string => {
<template>
<button
class="group text-18px border-0 relative flex flex-row justify-end items-center h-full gap-x-8px opacity-100 hover:opacity-75 focus:opacity-75 transition-opacity"
class="group text-18px border-0 relative flex flex-row justify-end items-center h-full gap-x-8px opacity-100 hover:opacity-75 focus:opacity-75 transition-opacity text-header-text-primary"
:title="title"
@click="dropdownStore.dropdownToggle()"
>
@@ -49,7 +49,7 @@ const title = computed((): string => {
<ExclamationTriangleIcon v-if="errors[0].level === 'warning'" class="text-unraid-red fill-current relative w-24px h-24px" />
<ShieldExclamationIcon v-if="errors[0].level === 'error'" class="text-unraid-red fill-current relative w-24px h-24px" />
</template>
<span v-if="text" class="relative leading-none">
<span v-if="text" class="relative leading-none ">
<span>{{ text }}</span>
<span class="absolute bottom-[-3px] inset-x-0 h-2px w-full bg-gradient-to-r from-unraid-red to-orange rounded opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-opacity" />
</span>

View File

@@ -10,7 +10,7 @@ export { default as SheetDescription } from './SheetDescription.vue'
export { default as SheetFooter } from './SheetFooter.vue'
export const sheetVariants = cva(
'fixed z-50 gap-4 bg-beta shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
'fixed z-50 gap-4 bg-alpha shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
{
variants: {
side: {

View File

@@ -8,7 +8,7 @@
"lint:fix": "eslint . --fix",
"type-check": "nuxi typecheck",
"prebuild:dev": "./scripts/prebuild-webgui-set-env.sh .env.staging",
"build:dev": "npm run type-check && nuxt build && npm run manifest-ts && npm run deploy-to-unraid:dev",
"build:dev": "nuxt build && npm run manifest-ts && npm run deploy-to-unraid:dev",
"postbuild:dev": "./scripts/postbuild-webgui-restore-env.sh",
"prebuild:webgui": "./scripts/prebuild-webgui-set-env.sh",
"build:webgui": "npm run type-check && nuxt build && npm run manifest-ts && npm run copy-to-webgui-repo",

View File

@@ -1,7 +1,6 @@
<script lang="ts" setup>
import { serverState } from '~/_data/serverState';
import type { SendPayloads } from '~/store/callback';
import { useThemeStore, type Theme } from '~/store/theme';
import AES from 'crypto-js/aes';
const { registerEntry } = useCustomElements();
@@ -49,16 +48,7 @@ onMounted(() => {
);
});
const isDarkMode = ref(false);
const themeStore = useThemeStore();
const { darkMode, theme } = toRefs(themeStore);
const toggleDarkMode = () => {
const themeNameToSet = darkMode.value ? 'light' : 'black';
if (theme.value) {
themeStore.setTheme({ ...theme.value, name: themeNameToSet });
}
};
</script>
<template>
@@ -66,12 +56,10 @@ const toggleDarkMode = () => {
<div class="pb-12 mx-auto">
<client-only>
<div class="flex flex-col gap-6 p-6">
<button @click="toggleDarkMode">
Toggle Dark / Light Theme - Current is: {{ darkMode ? 'Dark' : 'Light' }}
</button>
<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-gray py-4 flex flex-row justify-between items-center">
<header class="bg-header-background-color py-4 flex flex-row justify-between items-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]" />

View File

@@ -1,5 +1,5 @@
import { defineStore, createPinia, setActivePinia } from 'pinia';
import hexToRgba from 'hex-to-rgba';
import { createPinia, defineStore, setActivePinia } from 'pinia';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
@@ -17,14 +17,46 @@ export interface Theme {
textColor: string;
}
interface ColorMode {
headerTextPrimary: string;
headerTextSecondary: string;
headerBackgroundColor: string;
alpha: string;
beta: string;
gamma: string;
}
export const defaultColors: Record<string, ColorMode> = {
dark: {
alpha: '#1c1c1c', // previously header custom text color
beta: '#f2f2f2', // previously header background color
gamma: '#999999', // previously header custom secondary text color
headerTextPrimary: '#1c1c1c',
headerBackgroundColor: '#f2f2f2',
headerTextSecondary: '#999999',
},
light: {
alpha: '#f2f2f2', // previously header custom text color
beta: '#1c1b1b', // previously header background color
gamma: '#999999', // previously header custom secondary text color
headerTextPrimary: '#f2f2f2',
headerBackgroundColor: '#1c1b1b',
headerTextSecondary: '#999999',
},
};
export const useThemeStore = defineStore('theme', () => {
// State
const theme = ref<Theme | undefined>();
// Getters
const darkMode = computed(() => (theme.value?.name === 'black' || theme.value?.name === 'azure') ?? false);
const darkMode = computed(
() => (theme.value?.name === 'black' || theme.value?.name === 'azure') ?? false
);
// used to swap the UPC text color when using the azure or gray theme
const bannerGradient = computed(() => {
if (!theme.value?.banner || !theme.value?.bannerGradient) { return undefined; }
if (!theme.value?.banner || !theme.value?.bannerGradient) {
return undefined;
}
const start = theme.value?.bgColor ? 'var(--color-customgradient-start)' : 'rgba(0, 0, 0, 0)';
const end = theme.value?.bgColor ? 'var(--color-customgradient-end)' : 'var(--color-beta)';
return `background-image: linear-gradient(90deg, ${start} 0, ${end} 30%);`;
@@ -35,39 +67,44 @@ export const useThemeStore = defineStore('theme', () => {
};
const setCssVars = () => {
const body = document.body;
const defaultColors = {
darkTheme: {
alpha: '#1c1b1b',
beta: '#f2f2f2',
gamma: '#999999',
},
lightTheme: {
alpha: '#f2f2f2',
beta: '#1c1b1b',
gamma: '#999999',
},
};
let { alpha, beta, gamma } = darkMode.value ? defaultColors.darkTheme : defaultColors.lightTheme;
let { alpha, beta, gamma, headerTextPrimary, headerTextSecondary, headerBackgroundColor } =
darkMode.value ? defaultColors.dark : defaultColors.light;
// overwrite with hex colors set in webGUI @ /Settings/DisplaySettings
if (theme.value?.textColor) { alpha = theme.value?.textColor; }
if (theme.value?.textColor) {
headerTextPrimary = theme.value?.textColor;
}
if (theme.value?.bgColor) {
beta = theme.value?.bgColor;
headerBackgroundColor = theme.value.bgColor;
body.style.setProperty('--color-customgradient-start', hexToRgba(beta, 0));
body.style.setProperty('--color-customgradient-end', hexToRgba(beta, 0.7));
}
if (theme.value?.metaColor) { gamma = theme.value?.metaColor; }
if (theme.value?.metaColor) {
headerTextSecondary = theme.value?.metaColor;
}
body.style.setProperty('--color-alpha', alpha);
body.style.setProperty('--color-beta', beta);
body.style.setProperty('--color-gamma', gamma);
body.style.setProperty('--header-text-primary', headerTextPrimary);
body.style.setProperty('--header-text-secondary', headerTextSecondary);
body.style.setProperty('--header-background-color', headerBackgroundColor);
body.style.setProperty('--color-gamma-opaque', hexToRgba(gamma, 0.25));
// box shadow
body.style.setProperty('--shadow-beta', `0 25px 50px -12px ${hexToRgba(beta, 0.15)}`);
body.style.setProperty('--ring-offset-shadow', `0 0 ${beta}`);
body.style.setProperty('--ring-shadow', `0 0 ${beta}`);
body.style.setProperty('--dev-test', `0 0 ${beta}`);
if (darkMode.value) {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
};
watch(theme, () => {
console.log(theme.value);
console.log('theme changed');
setCssVars();
});

View File

@@ -69,7 +69,7 @@ export const useUpdateOsStore = defineStore('updateOs', () => {
serverStore.setUpdateOsResponse(response as ServerUpdateOsResponse);
checkForUpdatesLoading.value = false;
} catch (error) {
throw new Error('[localCheckForUpdate] Error checking for updates');
throw new Error("[localCheckForUpdate] Error checking for updates\n" + JSON.stringify(error));
}
};

View File

@@ -2,6 +2,7 @@ import 'dotenv/config';
import type { Config } from 'tailwindcss';
import type { PluginAPI } from 'tailwindcss/types/config';
// @ts-expect-error - just trying to get this to build @fixme
export default <Partial<Config>>{
darkMode: ['selector'],
@@ -44,11 +45,6 @@ export default <Partial<Config>>{
'grey-lightest': '#f2f2f2',
white: '#ffffff',
// New Color Palette
'sidebar': '#f2f2f2',
'sidebar-dark': '#1c1b1b',
// unraid colors
'yellow-accent': '#E9BF41',
'orange-dark': '#f15a2c',
@@ -88,40 +84,42 @@ export default <Partial<Config>>{
beta: 'var(--color-beta)',
gamma: 'var(--color-gamma)',
'gamma-opaque': 'var(--color-gamma-opaque)',
// shadcn specific
border: 'hsl(0 0% 89.8%)',
input: 'hsl(0 0% 89.8%)',
ring: 'hsl(0 0% 3.9%)',
background: 'hsl(0 0% 100%)',
foreground: 'hsl(0 0% 3.9%)',
'header-text-primary': 'var(--header-text-primary)',
'header-text-secondary': 'var(--header-text-secondary)',
'header-background-color': 'var(--header-background-color)',
// ShadCN
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(0 0% 9%)',
foreground: 'hsl(0 0% 98%)',
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(0 0% 96.1%)',
foreground: 'hsl(0 0% 9%)',
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(0 84.2% 60.2%)',
foreground: 'hsl(0 0% 98%)',
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(0 0% 96.1%)',
foreground: 'hsl(0 0% 45.1%)',
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(0 0% 96.1%)',
foreground: 'hsl(0 0% 9%)',
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(0 0% 100%)',
foreground: 'hsl(0 0% 3.9%)',
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(0 0% 100%)',
foreground: 'hsl(0 0% 3.9%)',
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
// Unfortunately due to webGUI CSS setting base HTML font-size to .65% or something we must use pixel values for web components
@@ -319,4 +317,4 @@ export default <Partial<Config>>{
newFontSize: process.env.VITE_TAILWIND_BASE_FONT_SIZE ?? 10,
}),
],
};
};