big refactor

This commit is contained in:
2026-04-29 16:25:11 -05:00
parent 6c411ce2b6
commit 8265fb689a
156 changed files with 15845 additions and 50373 deletions

View File

@@ -1,7 +1,4 @@
<script setup lang="ts">
import type { AppThemeId } from '~/types/app-theme'
import { APP_THEME_OPTIONS } from '~/types/app-theme'
defineProps<{
sidebarCollapsed: boolean
brandTitle?: string
@@ -15,19 +12,18 @@ const emit = defineEmits<{
const route = useRoute()
const router = useRouter()
const isHome = computed(() => route.path === '/')
const { themeId, applyTheme } = useAppTheme()
const themeIcons: Record<string, string> = {
light: 'i-heroicons-sun',
purple: 'i-heroicons-sparkles',
dark: 'i-heroicons-moon',
'dark-purple': 'i-heroicons-star',
}
const colorMode = useColorMode()
const isDark = computed({
get () {
return colorMode.value === 'dark'
},
set () {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const userMenuOpen = ref(false)
const userMenuRoot = ref<HTMLElement | null>(null)
const themeMenuOpen = ref(false)
const themeMenuRoot = ref<HTMLElement | null>(null)
function closeUserMenu() {
userMenuOpen.value = false
@@ -38,10 +34,6 @@ function onDocClick(e: MouseEvent) {
if (userEl && userMenuOpen.value && !userEl.contains(e.target as Node)) {
userMenuOpen.value = false
}
const themeEl = themeMenuRoot.value
if (themeEl && themeMenuOpen.value && !themeEl.contains(e.target as Node)) {
themeMenuOpen.value = false
}
}
onMounted(() => document.addEventListener('click', onDocClick))
@@ -107,58 +99,19 @@ onUnmounted(() => document.removeEventListener('click', onDocClick))
<UIcon name="i-heroicons-arrow-path" style="width: 16px; height: 16px;" />
</button>
<!-- Quick theme switcher -->
<div ref="themeMenuRoot" class="relative">
<button
type="button"
class="app-topbar-icon-btn"
title="Switch theme"
<!-- Theme toggle -->
<ClientOnly>
<UButton
:icon="isDark ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
color="gray"
variant="ghost"
aria-label="Theme"
@click.stop="themeMenuOpen = !themeMenuOpen"
>
<UIcon :name="themeIcons[themeId] ?? 'i-heroicons-swatch'" style="width: 16px; height: 16px;" />
</button>
<Transition
enter-active-class="transition duration-150 ease-out"
enter-from-class="opacity-0 scale-95 translate-y-1"
enter-to-class="opacity-100 scale-100 translate-y-0"
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0 scale-95"
>
<div
v-show="themeMenuOpen"
class="absolute right-0 top-[calc(100%+8px)] z-50 w-52 overflow-hidden rounded-xl border border-[var(--sidebar-border)] bg-[var(--surface)] py-1.5 shadow-xl ring-1 ring-black/5"
>
<p class="px-3 py-1 text-[10px] font-semibold uppercase tracking-wider text-[var(--text-muted)]">Theme</p>
<button
v-for="opt in APP_THEME_OPTIONS"
:key="opt.id"
type="button"
class="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm transition hover:bg-[var(--brand-faint)]"
:class="themeId === opt.id ? 'text-[var(--brand)] font-medium' : 'text-[var(--text-primary)]'"
@click="applyTheme(opt.id as AppThemeId); themeMenuOpen = false"
>
<UIcon :name="themeIcons[opt.id]" class="h-4 w-4 shrink-0" :class="themeId === opt.id ? 'text-[var(--brand)]' : 'opacity-60'" />
<span class="flex-1">{{ opt.label }}</span>
<UIcon
v-if="themeId === opt.id"
name="i-heroicons-check"
class="h-3.5 w-3.5 text-[var(--brand)]"
/>
</button>
<div class="mx-3 my-1.5 border-t border-[var(--sidebar-border)]" />
<NuxtLink
to="/account"
class="flex items-center gap-2.5 px-3 py-1.5 text-xs text-[var(--text-muted)] transition hover:text-[var(--brand)]"
@click="themeMenuOpen = false"
>
<UIcon name="i-heroicons-cog-6-tooth" class="h-3.5 w-3.5" />
All appearance settings
</NuxtLink>
</div>
</Transition>
</div>
@click="isDark = !isDark"
/>
<template #fallback>
<div class="w-8 h-8" />
</template>
</ClientOnly>
<NuxtLink to="/settings" class="inline-flex" title="Software settings">
<span class="app-topbar-icon-btn">