refactor: enhance app configuration and notification components for improved UI consistency

- modified vite.config.ts to integrate app configuration into UI setup
- updated app.config.ts to include new button, tabs, and slideover variants for better theming
- cleaned up main.css by removing unused styles and ensuring proper imports
- refactored notification components to streamline structure and improve readability
This commit is contained in:
Ajit Mehrotra
2025-12-01 21:20:26 -05:00
parent 7679d71c3e
commit e80ea795fe
7 changed files with 73 additions and 88 deletions

View File

@@ -1,8 +1,36 @@
export default {
ui: {
colors: {
primary: 'blue',
neutral: 'gray',
// overrided by tailwind-shared/css-variables.css
// these shared tailwind styles and colors are imported in src/assets/main.css
},
// https://ui.nuxt.com/docs/components/button#theme
button: {
//keep in mind, there is a "variant" AND a "variants" property
variants: {
variant: {
ghost: '',
link: 'hover:underline focus:underline',
},
},
},
// https://ui.nuxt.com/docs/components/tabs#theme
tabs: {
variants: {
pill: {},
},
},
// https://ui.nuxt.com/docs/components/slideover#theme
slideover: {
slots: {
// title: 'text-3xl font-normal',
},
variants: {
right: {},
},
},
},
toaster: {

View File

@@ -92,4 +92,4 @@ iframe#progressFrame {
.has-banner-gradient #header.image > * {
position: relative;
z-index: 1;
}
}

View File

@@ -44,7 +44,7 @@ const icon = computed<{ name: string; color: string } | null>(() => {
<template>
<div class="relative flex items-center justify-center">
<UIcon name="i-heroicons-bell-20-solid" class="text-header-text-primary h-6 w-6" />
<UIcon name="i-heroicons-bell-20-solid" class="h-6 w-6" />
<div
v-if="!seen && indicatorLevel === 'UNREAD'"
class="border-muted bg-unraid-green absolute top-0 right-0 size-2.5 rounded-full border"

View File

@@ -107,35 +107,27 @@ const reformattedTimestamp = computed<string>(() => {
<div class="" v-html="descriptionMarkup" />
</div>
<p v-if="mutationError" class="text-red-600">{{ t('common.error') }}: {{ mutationError }}</p>
<p v-if="mutationError" class="text-destructive">{{ t('common.error') }}: {{ mutationError }}</p>
<div class="flex items-baseline justify-end gap-4">
<UButton
v-if="link"
:to="link"
variant="link"
class="text-primary inline-flex items-center justify-center p-0 text-sm font-medium hover:underline focus:underline"
icon="i-heroicons-link-20-solid"
>
<span class="text-sm">{{ t('notifications.item.viewLink') }}</span>
<UButton v-if="link" :to="link" variant="link" icon="i-heroicons-link-20-solid">
{{ t('notifications.item.viewLink') }}
</UButton>
<UButton
v-if="type === NotificationType.UNREAD"
:loading="archive.loading"
icon="i-heroicons-archive-box-20-solid"
class="!bg-none"
@click="() => void archive.mutate({ id: props.id })"
>
<span class="text-sm">{{ t('notifications.item.archive') }}</span>
{{ t('notifications.item.archive') }}
</UButton>
<UButton
v-if="type === NotificationType.ARCHIVE"
:loading="deleteNotification.loading"
icon="i-heroicons-trash-20-solid"
class="!bg-none"
@click="() => void deleteNotification.mutate({ id: props.id, type: props.type })"
>
<span class="text-sm">{{ t('notifications.item.delete') }}</span>
{{ t('notifications.item.delete') }}
</UButton>
</div>
</div>

View File

@@ -221,9 +221,8 @@ const displayErrorMessage = computed(() => {
</div>
</div>
<!-- nextui replacement for LoadingError -->
<!-- USkeleton for loading and error states -->
<div v-else class="flex h-full flex-col items-center justify-center gap-3 px-3">
<!-- Loading (centered, like LoadingError) -->
<div v-if="loading" class="w-full max-w-md space-y-4">
<div v-for="n in 3" :key="n" class="py-1.5">
<div class="flex items-center gap-2">
@@ -240,7 +239,7 @@ const displayErrorMessage = computed(() => {
<!-- Error (centered, icon + title + message + full-width button) -->
<div v-else-if="offlineError || error" class="w-full max-w-sm space-y-3">
<div class="flex justify-center">
<UIcon name="i-heroicons-shield-exclamation-20-solid" class="size-10 text-red-600" />
<UIcon name="i-heroicons-shield-exclamation-20-solid" class="text-destructive size-10" />
</div>
<div class="text-center">
<h3 class="font-bold">Error</h3>
@@ -251,7 +250,7 @@ const displayErrorMessage = computed(() => {
<!-- Default (empty state) -->
<div v-else class="contents">
<CheckIcon class="h-10 translate-y-3 text-green-600" />
<CheckIcon class="text-unraid-green h-10 translate-y-3" />
{{ noNotificationsMessage }}
</div>
</div>

View File

@@ -30,8 +30,15 @@ const importance = ref<Importance | undefined>(undefined);
const { t } = useI18n();
const filterOptions = computed<Array<{ label: string; value?: Importance }>>(() => [
{ label: t('notifications.sidebar.filters.all') },
const activeFilter = computed({
get: () => importance.value ?? 'all',
set: (val) => {
importance.value = val === 'all' ? undefined : (val as Importance);
},
});
const filterTabs = computed(() => [
{ label: t('notifications.sidebar.filters.all'), value: 'all' as const },
{ label: t('notifications.sidebar.filters.alert'), value: Importance.ALERT },
{ label: t('notifications.sidebar.filters.info'), value: Importance.INFO },
{ label: t('notifications.sidebar.filters.warning'), value: Importance.WARNING },
@@ -131,25 +138,24 @@ const activeTab = ref<'unread' | 'archived'>('unread');
const tabs = computed(() => [
{
id: 'unread',
label: t('notifications.sidebar.unreadTab'),
count: overview.value?.unread.total,
value: 'unread' as const,
badge: overview.value?.unread.total ?? 0,
},
{
id: 'archived',
label: t('notifications.sidebar.archivedTab'),
count: readArchivedCount.value,
value: 'archived' as const,
badge: readArchivedCount.value ?? 0,
},
]);
</script>
<!-- totally scuffed but we use: !bg-transparent, !bg-none, hover:text-current to override conflicting webgui/api styles -->
<template>
<div>
<UButton
variant="ghost"
color="neutral"
class="!bg-transparent"
class="text-inverted hover:text-current"
@click="
() => {
isOpen = true;
@@ -161,20 +167,7 @@ const tabs = computed(() => [
<NotificationsIndicator :overview="overview" :seen="haveSeenNotifications" />
</UButton>
<USlideover
v-model:open="isOpen"
side="right"
:title="t('notifications.sidebar.title')"
:close="{
color: 'neutral',
variant: 'ghost',
class: 'rounded-md !bg-none hover:text-current',
}"
:ui="{
content: 'w-screen max-w-screen sm:max-w-[540px]',
title: 'text-3xl font-normal',
}"
>
<USlideover v-model:open="isOpen" side="right" :title="t('notifications.sidebar.title')">
<template #body>
<div class="flex h-full flex-col">
<div class="flex flex-1 flex-col overflow-hidden">
@@ -182,34 +175,19 @@ const tabs = computed(() => [
<div class="flex flex-col gap-3 px-0 py-3">
<!-- Tabs & Action Button Row -->
<div class="flex items-center justify-between gap-3">
<!-- Custom Pill Tabs -->
<div class="dark:bg-muted flex shrink-0 gap-1 rounded-lg bg-gray-100 p-2">
<UButton
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id as 'unread' | 'archived'"
:color="activeTab === tab.id ? 'primary' : 'neutral'"
:variant="activeTab === tab.id ? 'solid' : 'ghost'"
size="sm"
class="!bg-none transition-colors"
:class="[
activeTab === tab.id
? 'text-white'
: 'text-gray-500 hover:bg-transparent hover:text-gray-700 dark:text-gray-400 dark:hover:bg-transparent dark:hover:text-gray-200',
]"
>
<span>{{ tab.label }}</span>
<span v-if="tab.count !== undefined" class="opacity-90">({{ tab.count }})</span>
</UButton>
</div>
<UTabs
v-model="activeTab"
:items="tabs"
:content="false"
variant="pill"
color="primary"
/>
<!-- Action Button -->
<UButton
v-if="activeTab === 'unread'"
:disabled="loadingArchiveAll"
variant="link"
color="neutral"
class="hover:text-primary h-auto !bg-none p-0 font-normal hover:underline"
@click="confirmAndArchiveAll"
>
{{ t('notifications.sidebar.archiveAllAction') }}
@@ -219,7 +197,6 @@ const tabs = computed(() => [
:disabled="loadingDeleteAll"
variant="link"
color="neutral"
class="text-foreground hover:text-destructive h-auto !bg-none p-0 font-normal transition-colors hover:underline"
@click="confirmAndDeleteArchives"
>
{{ t('notifications.sidebar.deleteAllAction') }}
@@ -229,26 +206,13 @@ const tabs = computed(() => [
<!-- Filters & Settings Row -->
<div class="flex items-center justify-between gap-3">
<!-- Filter Button Group -->
<div
class="dark:bg-muted flex items-center gap-1 overflow-x-auto rounded-lg bg-gray-100 p-1"
>
<UButton
v-for="option in filterOptions"
:key="option.value ?? 'all'"
@click="importance = option.value"
color="neutral"
variant="ghost"
size="xs"
class="!bg-none whitespace-nowrap transition-colors"
:class="[
importance === option.value
? 'dark:bg-accented bg-white text-gray-900 shadow-sm ring-1 ring-gray-200 hover:bg-white hover:text-gray-900 dark:text-white dark:ring-gray-600 dark:hover:bg-gray-700 dark:hover:text-white'
: 'text-muted-foreground hover:text-foreground hover:bg-transparent hover:ring-1 hover:ring-gray-300 dark:hover:ring-gray-600',
]"
>
{{ option.label }}
</UButton>
</div>
<UTabs
v-model="activeFilter"
:items="filterTabs"
:content="false"
variant="pill"
color="neutral"
/>
<!-- Settings Icon -->
<UTooltip
:delay-duration="0"
@@ -263,7 +227,6 @@ const tabs = computed(() => [
variant="ghost"
color="neutral"
icon="i-heroicons-cog-6-tooth-20-solid"
class="h-8 w-8 !bg-none hover:text-current"
@click="openSettings"
/>
</UTooltip>

View File

@@ -8,6 +8,7 @@ import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import removeConsole from 'vite-plugin-remove-console';
import appConfig from './app.config';
import scopeTailwindToUnapi from './postcss/scopeTailwindToUnapi';
import { serveStaticHtml } from './vite-plugin-serve-static';
@@ -72,7 +73,9 @@ export default defineConfig({
},
},
}),
ui(),
ui({
ui: appConfig.ui,
}),
serveStaticHtml(), // Serve static test pages
// Remove console logs in production
...(dropConsole