feat(gui-v2): decompose AppSidebar into SidebarHeader + AppSidebar
Ports crewli-starter's monolithic AppSidebar.vue into two typed production components: SidebarHeader (the .brand block) and AppSidebar (composing SidebarHeader + SidebarNav + WorkspaceSwitcher). AppSidebar renders a permanent <aside> on desktop (lg+) and a PrimeVue Drawer on mobile, both wired to useShellUiStore for collapse/mobile state. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
105
apps/app/src/components-v2/layout/AppSidebar.vue
Normal file
105
apps/app/src/components-v2/layout/AppSidebar.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<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).
|
||||
*
|
||||
* nav groups arrive as a prop — this file must NOT import @/navigation directly
|
||||
* (components-v2 import boundary; the layout page supplies nav data).
|
||||
*
|
||||
* 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 Drawer from 'primevue/drawer'
|
||||
import { useShellUiStore } from '@/stores/useShellUiStore'
|
||||
import type { V2NavGroup } from '@/types/v2/nav'
|
||||
import SidebarHeader from '@/components-v2/layout/SidebarHeader.vue'
|
||||
import SidebarNav from '@/components-v2/layout/SidebarNav.vue'
|
||||
import WorkspaceSwitcher from '@/components-v2/layout/WorkspaceSwitcher.vue'
|
||||
|
||||
defineProps<{
|
||||
/**
|
||||
* Nav groups passed in from the layout — components-v2 files may NOT
|
||||
* import from @/navigation; the parent layout supplies this.
|
||||
*/
|
||||
groups: V2NavGroup[]
|
||||
}>()
|
||||
|
||||
const shell = useShellUiStore()
|
||||
|
||||
/**
|
||||
* 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).
|
||||
-->
|
||||
<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
|
||||
:groups="groups"
|
||||
:collapsed="shell.sidebarCollapsed"
|
||||
/>
|
||||
<WorkspaceSwitcher :collapsed="shell.sidebarCollapsed" />
|
||||
</aside>
|
||||
|
||||
<!--
|
||||
MOBILE: PrimeVue Drawer rendered below lg. 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.
|
||||
-->
|
||||
<Drawer
|
||||
v-model:visible="mobileVisible"
|
||||
position="left"
|
||||
class="!w-64 lg:hidden"
|
||||
:pt="{
|
||||
content: { class: 'flex flex-col p-0 overflow-hidden h-full' },
|
||||
header: { class: 'hidden' },
|
||||
}"
|
||||
>
|
||||
<SidebarHeader />
|
||||
<SidebarNav
|
||||
:groups="groups"
|
||||
:collapsed="false"
|
||||
/>
|
||||
<WorkspaceSwitcher :collapsed="false" />
|
||||
</Drawer>
|
||||
</template>
|
||||
Reference in New Issue
Block a user