Files
crewli/apps/app/src/stores/useShellUiStore.ts
bert.hausmans 1ba5d5bb9b docs(plan-2.5): closure — RFC supersession, BACKLOG landing, applyDomAttributes dedupe
Plan 2.5 final phase (P8). Closes the PrimeVue shell-migration workstream.

- RFC-WS-PRIMEVUE-PLAN-2-5: added Supersessions section recording the
  governing-RFC divergences (§4 dark mode `<body>`→`<html>` per AD-2.5-D1;
  §7.4 workspace sub option A reversed to placeholder after visual
  review). Added closure summary (phases, ADs, brand-square recipe,
  suite delta, lessons). Status → COMPLETE.
- BACKLOG: landed 8 items surfaced during Plan 2.5 (MOBILE-SHELL-PARITY,
  WORKSPACE-DROPDOWN-SUB-CONTENT, DENSITY-AWARE-SPACING, TOPBAR-H-VAR-
  DECLARE, CSP-FONT-SRC-LOCKDOWN, AUTO-IMPORTS-V2-SCAN, PNPM-RESOLUTIONS-
  ROOT, SHELLUI-STALE-DATA-THEME-CLEANUP). Marked GRADIENT-BRAND-
  ALIGNMENT as resolved.
- useShellUiStore.toggleDensity: removed redundant applyDomAttributes()
  call (the AppShellV2 watch already covers density changes). Moved the
  DOM-write assertion to AppShellV2 watcher-coverage specs.

Plan 2.5 status: COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 02:23:39 +02:00

104 lines
3.2 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref } from 'vue'
// v2 shell UI state ONLY (RFC-WS-GUI-REDESIGN AD-G4). No tenant/org
// state — that stays in useAuthStore/useOrganisationStore. Owns the
// writes to <html data-density>/.dark; v2 bypasses Vuexy useSkins.ts.
//
// AD-2.5-D1 (RFC-WS-PRIMEVUE-PLAN-2-5 §4): dark mode is the single
// class `.dark` on document.documentElement. Tailwind v4's
// @custom-variant dark (in assets/styles/tailwind.css) and PrimeVue's
// darkModeSelector (in plugins/primevue/index.ts:31) both react to
// that class — one toggle, both ecosystems. The historic
// <html data-theme="..."> mechanism is superseded and removed.
export type ShellTheme = 'light' | 'dark'
export type ShellDensity = 'comfortable' | 'compact'
export interface ShellDrawerState {
isOpen: boolean
component: string | null
props: Record<string, unknown>
}
export const useShellUiStore = defineStore('shellUi', () => {
const sidebarCollapsed = ref(false)
const mobileOpen = ref(false)
const density = ref<ShellDensity>('comfortable')
const theme = ref<ShellTheme>('light')
const drawer = ref<ShellDrawerState>({ isOpen: false, component: null, props: {} })
function toggleSidebar(): void {
sidebarCollapsed.value = !sidebarCollapsed.value
}
function setMobileOpen(v: boolean): void {
mobileOpen.value = v
}
function setTheme(next: ShellTheme): void {
theme.value = next
}
function setDensity(next: ShellDensity): void {
density.value = next
}
/**
* Plan 2.5 P6 Fix 10: binary UI toggle between the two density extremes
* the topbar exposes. Pure state mutation — the <html data-density> write
* is owned by AppShellV2's `watch([theme, density])`, the single
* applyDomAttributes() authority. Mirrors setTheme(), which has always
* relied on that same watcher.
*
* Plan 2.5 P8 dedupe: this previously also called applyDomAttributes()
* directly, so a density toggle fired it twice (idempotent but redundant —
* once here, once via the watch). The direct call was removed; the watch
* still covers the change.
*/
function toggleDensity(): void {
density.value = density.value === 'compact' ? 'comfortable' : 'compact'
}
function applyDomAttributes(): void {
const el = document.documentElement
// AD-2.5-D1: dark mode is `.dark` on <html>. PrimeVue
// darkModeSelector and Tailwind v4 @custom-variant dark both
// resolve to this class — see file header.
if (theme.value === 'dark')
el.classList.add('dark')
else
el.classList.remove('dark')
// Density continues to use data-attribute (orthogonal axis to
// colour scheme; coexists with the .dark class on the same root).
// Density toggle UI lands in P6 Fix 10.
el.setAttribute('data-density', density.value)
}
function openDrawer(component: string, props: Record<string, unknown> = {}): void {
drawer.value = { isOpen: true, component, props }
}
function closeDrawer(): void {
drawer.value = { isOpen: false, component: null, props: {} }
}
return {
sidebarCollapsed,
mobileOpen,
density,
theme,
drawer,
toggleSidebar,
setMobileOpen,
setTheme,
setDensity,
toggleDensity,
applyDomAttributes,
openDrawer,
closeDrawer,
}
})