Files
crewli-old/apps/app/src/components-v2/layout/AppTopbar.stories.ts
bert.hausmans d0dd45c03a refactor(theme): Plan 2.5 P3 — dark mode class on <html> (AD-2.5-D1 + Fix 6)
Per RFC-WS-PRIMEVUE-PLAN-2-5 §4 AD-2.5-D1 and §5.6 Fix 6. Single class
on <html> drives both PrimeVue darkModeSelector and Tailwind v4
@custom-variant dark — one toggle, two ecosystems react.

Audit findings (pre-change):
- applyDomAttributes was writing BOTH data-theme="dark" AND .dark on
  documentElement. The historic data-theme write is the design-doc §4
  mechanism that AD-2.5-D1 supersedes; the .dark toggle was already
  correct (and is already paired with PrimeVue darkModeSelector: '.dark'
  in plugins/primevue/index.ts:31, verified in P1).
- tailwind.css had NO @custom-variant dark directive — Tailwind v4
  default is `prefers-color-scheme` (OS-controlled), so utility
  `dark:` variants would have ignored the topbar toggle entirely.
- One stray .dark subtree wrapper in AppTopbar.stories.ts:56
  (DarkTheme story) — deliberate Storybook isolation per its comment,
  but in violation of AD-2.5-D1's single-source-of-truth rule.

Changes:
- useShellUiStore.applyDomAttributes(): removed data-theme write,
  kept .dark class toggle on document.documentElement, kept
  data-density (P6 wires density-toggle UI; density is an
  orthogonal axis and unaffected). File-header comment updated to
  cite AD-2.5-D1 + reference the Tailwind & PrimeVue mirror sites.
- assets/styles/tailwind.css: added
  `@custom-variant dark (&:where(.dark, .dark *))` so utility
  `dark:` classes resolve via the same .dark trigger.
- components-v2/layout/AppTopbar.stories.ts: stripped class="dark"
  from the DarkTheme story's render wrapper. Story comment updated
  to flag that visual confirmation now comes via parity-batch
  Playwright (after Plan 2.5 closes), not Storybook autodocs. A
  proper documentElement-mutating decorator is a backlog item.
- stores/__tests__/useShellUiStore.spec.ts: updated the existing
  applyDomAttributes assertion to drop the data-theme expectation
  (the write is gone); added a new `describe('applyDomAttributes
  — dark mode (AD-2.5-D1)', …)` block with 2 specs (class toggle
  reactive, no data-theme attribute written).

Re-grep verification — all three return 0 hits:
- stray .dark in v2 (excluding `dark:` utility prefixes)
- data-theme setAttribute calls in stores/
- [data-theme=…] CSS selectors anywhere

Suite delta: 573 → 575 (+2). vue-tsc clean. Scoped ESLint clean.

Note: darkModeSelector: '.dark' was already set in
plugins/primevue/index.ts:31 (verified in P1 audit) — the config
dimension of AD-2.5-D1 was satisfied before this commit; P3 closes
the store-side, Tailwind-side, and stray-class dimensions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:31:30 +02:00

76 lines
2.1 KiB
TypeScript

import type { Meta, StoryObj } from '@storybook/vue3-vite'
import { orgA, userFixture, withPinia } from '@/stories/v2/_helpers'
import AppTopbar from '@/components-v2/layout/AppTopbar.vue'
import { useAuthStore } from '@/stores/useAuthStore'
import { useShellUiStore } from '@/stores/useShellUiStore'
/**
* AppTopbar reads useShellUiStore (theme / density), useAuthStore (user +
* currentOrganisation) and useBreadcrumb (route-driven — router is global
* in preview.ts). Each story seeds both stores on a fresh Pinia.
*/
function seedAuth(): void {
const auth = useAuthStore()
auth.user = userFixture
auth.organisations = [orgA]
}
const meta: Meta<typeof AppTopbar> = {
title: 'v2 Shell/AppTopbar',
component: AppTopbar,
tags: ['autodocs'],
render: () => ({
components: { AppTopbar },
template: `
<div class="min-h-[200px]">
<AppTopbar />
</div>
`,
}),
}
export default meta
type Story = StoryObj<typeof AppTopbar>
export const Default: Story = {
decorators: [withPinia(seedAuth)],
}
/**
* Dark variant sets useShellUiStore.theme = 'dark' on the seed, but the
* DOM is not mutated here. Per AD-2.5-D1 (RFC-WS-PRIMEVUE-PLAN-2-5 §4),
* `.dark` lives only on document.documentElement; subtree-scoped
* wrappers are forbidden (single source of truth). A proper Storybook
* decorator that toggles documentElement on mount + cleans up on
* unmount is a Plan 2.5-PARITY-BATCH backlog item — until it lands,
* this story exists to lock the store-state shape; visual confirmation
* comes from the parity-batch Playwright captures (after Plan 2.5
* closes), not from Storybook autodocs.
*/
export const DarkTheme: Story = {
decorators: [
withPinia(() => {
seedAuth()
useShellUiStore().setTheme('dark')
}),
],
render: () => ({
components: { AppTopbar },
template: `
<div class="min-h-[200px] bg-[var(--p-content-background)]">
<AppTopbar />
</div>
`,
}),
}
export const CompactDensity: Story = {
decorators: [
withPinia(() => {
seedAuth()
useShellUiStore().setDensity('compact')
}),
],
}