Per RFC-WS-PRIMEVUE-PLAN-2-5 §4 AD-2.5-W1 and AD-2.5-B1, §5.4 Fix 4. Changes: - WorkspaceSwitcher: sub field removed from template, WorkspaceDisplay type, and buildDisplay derivation. Stories did not carry sub args (auto-derived from seeded org.role); no WithSub story existed. New regression spec (WorkspaceSwitcher.spec.ts) locks the no-sub render. - SidebarNav: now consumes APP_NAVIGATION from src/config/navigation.ts as the single source of truth (shared with breadcrumb derivation in useNavBreadcrumb). The groups: V2NavGroup[] prop is removed; render walks top-level NavItems (branch nodes render label-heading + children; leaf nodes render as rows; items without routeName render as non-clickable dormant placeholders). Previous nav data source: groups prop fed by useV2Nav(orgNavItems) in OrganizerLayoutV2. - APP_NAVIGATION expanded with 7 entries to preserve visual sidebar continuity (Evenementen at top-level + Beheer branch with 5 children). All new entries use routeName: undefined until the corresponding v2 page lands (TODOs noted per entry); only Dashboard maps to v2-dashboard. - AppSidebar: groups prop removed; passes only :collapsed to SidebarNav. - OrganizerLayoutV2: useV2Nav(orgNavItems) plumbing retired; the layout now renders <AppSidebar /> with no nav-data wiring. - Tests: AppSidebar.spec drops the "passes groups prop to SidebarNav" assertion; OrganizerLayoutV2.spec drops the "forwards orgNavItems" assertion. New WorkspaceSwitcher no-sub regression spec (+2 tests). - Storybook: SidebarNav.stories and AppSidebar.stories updated to no longer thread navFixture/groups; WithActiveItem pushes v2-dashboard. Position of WorkspaceSwitcher (Fix 3), workspace dropdown panel (Fix 5), and AppBreadcrumb wiring (Fix 2) remain unchanged in P4 — both lands in P5. The legacy useBreadcrumb composable also remains untouched until P5 (atomic with AppTopbar refactor). Orphans flagged for follow-up cleanup (intentionally not deleted in P4): useV2Nav composable + spec, V2NavGroup/V2NavItem types, sidebarNavActive helper + spec, navFixture in stories/v2/_helpers.ts. Suite delta: 575 → 575 (+2 WorkspaceSwitcher no-sub spec, -1 AppSidebar groups-prop assertion, -1 OrganizerLayoutV2 groups-forward assertion). vue-tsc clean. Scoped ESLint clean (0 errors). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
117 lines
5.2 KiB
Vue
117 lines
5.2 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* AppSidebar — composes SidebarHeader + SidebarNav + WorkspaceSwitcher.
|
|
*
|
|
* Two rendering modes (RFC §4):
|
|
*
|
|
* DESKTOP (≥ lg / 1024px): a permanent <aside> (hidden lg:flex column).
|
|
* Width responds to sidebarCollapsed: expanded → w-64 (256px), collapsed → w-16 (64px).
|
|
* These match crewli-starter's --sidebar-w / --sidebar-w-collapsed CSS variables.
|
|
*
|
|
* MOBILE (< lg): a PrimeVue <Drawer> bound to shell.mobileOpen via a writable
|
|
* computed (`mobileVisible`). The Drawer contains the same 3 children as the
|
|
* <aside> — a small amount of template duplication kept intentional for
|
|
* readability (no render-fn or dynamic component indirection needed at this scale).
|
|
*
|
|
* The Drawer is conditionally MOUNTED (v-if="isMobile") rather than hidden via
|
|
* a CSS class. PrimeVue Drawer teleports its overlay to <body> via an internal
|
|
* Portal; a `lg:hidden` class on the component node does NOT suppress the
|
|
* teleported overlay. By unmounting on desktop we guarantee no modal backdrop /
|
|
* focus-trapped dialog can appear on wide viewports regardless of mobileOpen state.
|
|
*
|
|
* Nav data: as of Plan 2.5 P4, SidebarNav reads APP_NAVIGATION from
|
|
* @/config/navigation directly — no prop is threaded through AppSidebar.
|
|
* The components-v2 import boundary still applies to @/navigation (the v1
|
|
* source), but @/config/navigation is the unclassified central registry
|
|
* and is consumed in-place by SidebarNav.
|
|
*
|
|
* Deliberate simplification: crewli-starter's bespoke Teleport tooltip (shown in
|
|
* collapsed mode for nav items) is NOT ported here. SidebarNav already provides
|
|
* native `:title` tooltips in collapsed mode, which is functionally equivalent
|
|
* for keyboard/mouse users and avoids the bespoke fixed-position Teleport mechanism.
|
|
* Tracked for re-evaluation if custom tooltip styling is required later.
|
|
*
|
|
* CSS translation (main.css → Tailwind):
|
|
* .sidebar (desktop) → flex flex-col overflow-hidden bg-[var(--p-surface-card)]
|
|
* border-e border-[var(--p-content-border-color)] z-40
|
|
* transition-[width] duration-200
|
|
* w-64 (expanded) | w-16 (collapsed)
|
|
* .sidebar (mobile) → full-height column inside the Drawer
|
|
* .drawer-open (blur) → handled by parent layout; not in scope here
|
|
*/
|
|
|
|
import { computed } from 'vue'
|
|
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
|
|
import Drawer from 'primevue/drawer'
|
|
import { useShellUiStore } from '@/stores/useShellUiStore'
|
|
import SidebarHeader from '@/components-v2/layout/SidebarHeader.vue'
|
|
import SidebarNav from '@/components-v2/layout/SidebarNav.vue'
|
|
import WorkspaceSwitcher from '@/components-v2/layout/WorkspaceSwitcher.vue'
|
|
|
|
const shell = useShellUiStore()
|
|
|
|
// Shared breakpoint signal — must match SidebarHeader and the CSS `lg:` boundary (1024px).
|
|
// useBreakpoints(breakpointsTailwind).smaller('lg') is true below 1024px, equivalent to
|
|
// max-width: 1023px. Both components import from the same Tailwind constant so the
|
|
// desktop/mobile boundary cannot drift.
|
|
const isMobile = useBreakpoints(breakpointsTailwind).smaller('lg')
|
|
|
|
/**
|
|
* Writable computed so we can use v-model:visible on PrimeVue Drawer
|
|
* without mutating the store ref directly.
|
|
*/
|
|
const mobileVisible = computed<boolean>({
|
|
get: () => shell.mobileOpen,
|
|
set: (v: boolean) => shell.setMobileOpen(v),
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<!--
|
|
DESKTOP: permanent <aside>, hidden below lg, flex column above lg.
|
|
Width transitions between w-64 (expanded) and w-16 (collapsed).
|
|
`hidden lg:flex` is effective here because <aside> is not teleported.
|
|
-->
|
|
<!-- desktop -->
|
|
<aside
|
|
class="hidden lg:flex flex-col overflow-hidden bg-[var(--p-surface-card)] border-e border-[var(--p-content-border-color)] z-40 transition-[width] duration-200 flex-shrink-0"
|
|
:class="shell.sidebarCollapsed ? 'w-16' : 'w-64'"
|
|
>
|
|
<SidebarHeader />
|
|
<SidebarNav :collapsed="shell.sidebarCollapsed" />
|
|
<WorkspaceSwitcher :collapsed="shell.sidebarCollapsed" />
|
|
</aside>
|
|
|
|
<!--
|
|
MOBILE: PrimeVue Drawer, conditionally MOUNTED only when isMobile is true.
|
|
v-if (not v-show / CSS) is required because PrimeVue Drawer teleports its
|
|
overlay to <body> — a CSS class on the component node does NOT reach the
|
|
teleported portal. Unmounting on desktop ensures the overlay and focus-trap
|
|
are never created on wide viewports, regardless of shell.mobileOpen state.
|
|
|
|
The Drawer's own overlay handles backdrop / close-on-outside-click.
|
|
position="left" gives the sidebar-style panel. pt (passthrough) removes
|
|
Drawer's default padding so our children control their own spacing exactly
|
|
as on desktop.
|
|
|
|
The children are intentionally repeated (not factored into a slot or
|
|
render-fn) — the 3-component block is short and the duplication is
|
|
preferable to the indirection of a dynamic component or slot wiring.
|
|
-->
|
|
<!-- mobile -->
|
|
<Drawer
|
|
v-if="isMobile"
|
|
v-model:visible="mobileVisible"
|
|
position="left"
|
|
class="!w-64"
|
|
:pt="{
|
|
content: { class: 'flex flex-col p-0 overflow-hidden h-full' },
|
|
header: { class: 'hidden' },
|
|
}"
|
|
>
|
|
<SidebarHeader />
|
|
<SidebarNav :collapsed="false" />
|
|
<WorkspaceSwitcher :collapsed="false" />
|
|
</Drawer>
|
|
</template>
|