feat(quick-261016-0fm): manual theme switch on the rail

- the rail's top button cycles system, light and dark and names the
  current choice; light and dark ignore prefers-color-scheme
- usePreferences applies the cached theme before the first await of boot,
  saves the choice through PUT /account/preferences and reverts with a
  toast when the save fails; me() applies the stored preference
This commit is contained in:
Jakub Zych
2026-10-16 09:54:02 +02:00
parent 047858ae84
commit 21b0d39de0
7 changed files with 181 additions and 17 deletions

View File

@@ -1,11 +1,12 @@
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { RouterLink, useRoute } from 'vue-router'
import { PanelLeftOpen, Settings, Sun } from '@lucide/vue'
import { Moon, PanelLeftOpen, Settings, Sun, SunMoon } from '@lucide/vue'
import { t } from '../../app/i18n'
import { controllerIdFromPath } from '../../app/controllerRoutes'
import { iconFor } from '../../app/icons'
import { activeEntry, firstControllerPath, railEntries } from '../../state/useNavigation'
import { cycleTheme, themeChoice } from '../../state/usePreferences'
import { hasSettings, settingsLinkFor } from '../../state/useSettings'
import { useSidebar } from '../../state/useSidebar'
import SectionFlyout from './SectionFlyout.vue'
@@ -23,6 +24,13 @@ const FLYOUT_CLOSE_DELAY = 200
const route = useRoute()
const { collapsed, narrow, setCollapsed } = useSidebar()
// The top button cycles the theme (system, light, dark) and names the
// current choice; the icon shows it.
const themeIcon = computed(() => ({ system: SunMoon, light: Sun, dark: Moon })[themeChoice.value])
const themeLabel = computed(() =>
t('backend::lang.nav.theme', { mode: t(`backend::lang.nav.theme_${themeChoice.value}`) }),
)
const active = computed(() => activeEntry(String(route.params.vendor ?? ''), String(route.params.plugin ?? '')))
// Ustawienia (D-21) is pinned to the bottom and shown only when the admin may
@@ -152,9 +160,16 @@ onBeforeUnmount(cancelClose)
:aria-label="t('backend::lang.nav.plugins')"
class="sticky top-0 flex h-screen w-rail shrink-0 flex-col items-center gap-1.5 self-start overflow-y-auto border-r border-side-border bg-side pt-3.5 pb-3"
>
<span class="mb-3.5 flex size-10 items-center justify-center rounded-inner bg-accent-soft" title="SummerCMS">
<Sun :size="22" class="text-accent" aria-hidden="true" />
</span>
<button
type="button"
data-theme-toggle
:aria-label="themeLabel"
:title="themeLabel"
class="mb-3.5 flex size-10 shrink-0 items-center justify-center rounded-inner bg-accent-soft transition-colors duration-150 ease-out hover:bg-side-hover"
@click="cycleTheme"
>
<component :is="themeIcon" :size="22" class="text-accent" aria-hidden="true" />
</button>
<RouterLink
v-for="item in items"
:key="item.entry.code"