@open-mercato/ui 0.5.1-develop.2975.ccbadc8198 → 0.5.1-develop.2996.ce62fd491c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/backend/AppShell.js +274 -697
  3. package/dist/backend/AppShell.js.map +3 -3
  4. package/dist/backend/CrudForm.js +1 -1
  5. package/dist/backend/CrudForm.js.map +2 -2
  6. package/dist/backend/crud/CollapsibleZoneLayout.js +23 -3
  7. package/dist/backend/crud/CollapsibleZoneLayout.js.map +2 -2
  8. package/dist/backend/section-page/SectionNav.js +10 -8
  9. package/dist/backend/section-page/SectionNav.js.map +2 -2
  10. package/dist/backend/section-page/SectionPage.js +2 -2
  11. package/dist/backend/section-page/SectionPage.js.map +2 -2
  12. package/dist/backend/sidebar/SidebarCustomizationEditor.js +1303 -0
  13. package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +7 -0
  14. package/dist/backend/sidebar/customization-helpers.js +150 -0
  15. package/dist/backend/sidebar/customization-helpers.js.map +7 -0
  16. package/dist/primitives/switch.js +1 -2
  17. package/dist/primitives/switch.js.map +2 -2
  18. package/jest.setup.ts +13 -0
  19. package/package.json +3 -3
  20. package/src/backend/AppShell.tsx +245 -732
  21. package/src/backend/CrudForm.tsx +1 -1
  22. package/src/backend/__tests__/AppShell.test.tsx +1 -1
  23. package/src/backend/__tests__/CollapsibleZoneLayout.test.tsx +101 -0
  24. package/src/backend/__tests__/CrudForm.navigation.test.tsx +42 -0
  25. package/src/backend/__tests__/SidebarCustomizationEditor.test.tsx +200 -0
  26. package/src/backend/crud/CollapsibleZoneLayout.tsx +28 -3
  27. package/src/backend/section-page/SectionNav.tsx +14 -10
  28. package/src/backend/section-page/SectionPage.tsx +15 -10
  29. package/src/backend/sidebar/SidebarCustomizationEditor.tsx +1562 -0
  30. package/src/backend/sidebar/customization-helpers.ts +203 -0
  31. package/src/primitives/switch.tsx +1 -2
@@ -83,9 +83,29 @@ function CollapsibleZoneLayout({
83
83
  setExpandedWhileConstrained(!canShowSideBySide);
84
84
  requestAnimationFrame(() => {
85
85
  const target = document.getElementById(section.targetId ?? `collapsible-group-wrapper-${section.id}`) ?? document.getElementById(`collapsible-group-${section.id}`);
86
- const headingButton = target?.querySelector("button[aria-controls]");
87
- target?.scrollIntoView({ behavior: "smooth", block: "start" });
88
- headingButton?.focus({ preventScroll: true });
86
+ if (!target) return;
87
+ const headingButton = target.querySelector("button[aria-controls]");
88
+ if (headingButton?.getAttribute("aria-expanded") === "false") {
89
+ headingButton.click();
90
+ }
91
+ target.scrollIntoView({ behavior: "smooth", block: "start" });
92
+ requestAnimationFrame(() => {
93
+ const focusables = Array.from(
94
+ target.querySelectorAll(
95
+ 'input:not([type="hidden"]), textarea, select, [contenteditable="true"]'
96
+ )
97
+ );
98
+ const firstInput = focusables.find((el) => {
99
+ if (el.hasAttribute("disabled") || el.getAttribute("aria-hidden") === "true") return false;
100
+ if (el instanceof HTMLInputElement && el.readOnly) return false;
101
+ return true;
102
+ });
103
+ if (firstInput) {
104
+ firstInput.focus({ preventScroll: true });
105
+ return;
106
+ }
107
+ headingButton?.focus({ preventScroll: true });
108
+ });
89
109
  });
90
110
  }, [canCollapse, canShowSideBySide, setCollapsed]);
91
111
  const handleCollapse = React.useCallback(() => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/crud/CollapsibleZoneLayout.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { ChevronsLeft, ChevronsRight } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { useZoneCollapse } from './useZoneCollapse'\nimport type { LucideIcon } from 'lucide-react'\n\nconst SIDE_BY_SIDE_MIN_WIDTH = 1280\n\nexport interface ZoneSectionDescriptor {\n id: string\n icon: LucideIcon\n label: string\n targetId?: string\n ariaLabel?: string\n errorCount?: number\n}\n\nexport interface CollapsibleZoneLayoutProps {\n zone1: React.ReactNode\n zone2: React.ReactNode\n entityName: string\n pageType: string\n zone1DefaultWidth?: string\n errorCount?: number\n isDirty?: boolean\n /** Section descriptors for the collapsed rail icon sidebar. When omitted the rail shows the legacy minimal view. */\n sections?: ZoneSectionDescriptor[]\n}\n\nfunction subscribeViewport(callback: () => void) {\n const mediaQuery = window.matchMedia('(min-width: 1024px)')\n mediaQuery.addEventListener('change', callback)\n return () => mediaQuery.removeEventListener('change', callback)\n}\n\nfunction getViewportSnapshot() {\n return window.matchMedia('(min-width: 1024px)').matches\n}\n\nfunction getViewportServerSnapshot() {\n return false\n}\n\nexport function CollapsibleZoneLayout({\n zone1,\n zone2,\n entityName,\n pageType,\n zone1DefaultWidth,\n errorCount = 0,\n isDirty = false,\n sections,\n}: CollapsibleZoneLayoutProps) {\n const t = useT()\n const { collapsed, setCollapsed, isHydrated } = useZoneCollapse(pageType)\n const canCollapse = React.useSyncExternalStore(\n subscribeViewport,\n getViewportSnapshot,\n getViewportServerSnapshot,\n )\n const layoutRef = React.useRef<HTMLDivElement>(null)\n const expandButtonRef = React.useRef<HTMLButtonElement>(null)\n const [containerWidth, setContainerWidth] = React.useState(() => (typeof window === 'undefined' ? 0 : window.innerWidth))\n const [expandedWhileConstrained, setExpandedWhileConstrained] = React.useState(false)\n\n React.useEffect(() => {\n const node = layoutRef.current\n if (!node) return\n\n const updateWidth = (nextWidth: number) => {\n setContainerWidth((prev) => (Math.abs(prev - nextWidth) < 1 ? prev : nextWidth))\n }\n\n const measure = () => {\n updateWidth(node.getBoundingClientRect().width || window.innerWidth)\n }\n\n measure()\n\n if (typeof ResizeObserver === 'undefined') {\n window.addEventListener('resize', measure)\n return () => window.removeEventListener('resize', measure)\n }\n\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n updateWidth(entry.contentRect.width)\n })\n\n observer.observe(node)\n return () => observer.disconnect()\n }, [])\n\n const canShowSideBySide = containerWidth >= SIDE_BY_SIDE_MIN_WIDTH\n\n React.useEffect(() => {\n if (canShowSideBySide) {\n setExpandedWhileConstrained(false)\n }\n }, [canShowSideBySide])\n\n const showCollapsedRail = canCollapse && (collapsed || (!canShowSideBySide && !expandedWhileConstrained))\n const showStackedExpanded = !showCollapsedRail && !canShowSideBySide\n const layoutMode = showCollapsedRail ? 'collapsed' : showStackedExpanded ? 'stacked' : 'side-by-side'\n const zone1SideBySideStyle = zone1DefaultWidth\n ? { width: zone1DefaultWidth, flexBasis: zone1DefaultWidth }\n : undefined\n\n const handleExpand = React.useCallback(() => {\n if (!canCollapse) return\n setCollapsed(false)\n setExpandedWhileConstrained(!canShowSideBySide)\n }, [canCollapse, canShowSideBySide, setCollapsed])\n\n const handleSectionActivate = React.useCallback((section: ZoneSectionDescriptor) => {\n if (!canCollapse) return\n setCollapsed(false)\n setExpandedWhileConstrained(!canShowSideBySide)\n requestAnimationFrame(() => {\n const target =\n document.getElementById(section.targetId ?? `collapsible-group-wrapper-${section.id}`)\n ?? document.getElementById(`collapsible-group-${section.id}`)\n const headingButton = target?.querySelector<HTMLButtonElement>('button[aria-controls]')\n target?.scrollIntoView({ behavior: 'smooth', block: 'start' })\n headingButton?.focus({ preventScroll: true })\n })\n }, [canCollapse, canShowSideBySide, setCollapsed])\n\n const handleCollapse = React.useCallback(() => {\n if (!canCollapse) return\n setExpandedWhileConstrained(false)\n setCollapsed(true)\n requestAnimationFrame(() => {\n expandButtonRef.current?.focus()\n })\n }, [canCollapse, setCollapsed])\n\n return (\n <div\n ref={layoutRef}\n data-zone-layout-mode={layoutMode}\n data-persistence-hydrated={isHydrated ? 'true' : 'false'}\n aria-hidden={isHydrated ? undefined : true}\n className={cn(\n 'flex gap-4',\n !isHydrated && 'invisible',\n showStackedExpanded ? 'flex-col' : 'flex-col lg:flex-row',\n )}\n >\n {showCollapsedRail ? (\n <>\n <div className=\"hidden lg:flex shrink-0 flex-col items-center gap-3\">\n <Button\n ref={expandButtonRef}\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n onClick={handleExpand}\n className=\"h-auto rounded-[10px] px-1.5 py-2 shadow-sm\"\n aria-label={t('ui.zone.expand', 'Expand form panel')}\n >\n <ChevronsRight className=\"size-4\" />\n </Button>\n {sections?.length ? (\n <div className=\"flex flex-col items-center gap-2 rounded-[14px] border border-border/70 bg-card px-2 py-3\">\n {sections.map((section) => {\n const SectionIcon = section.icon\n const hasErrors = Boolean(section.errorCount && section.errorCount > 0)\n return (\n <IconButton\n key={section.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n onClick={() => handleSectionActivate(section)}\n className=\"relative size-9 rounded-[10px] border border-transparent bg-muted/70 text-muted-foreground hover:border-border hover:bg-accent hover:text-accent-foreground\"\n title={section.label}\n aria-label={section.ariaLabel ?? section.label}\n >\n <SectionIcon className=\"size-4\" />\n {hasErrors ? (\n <span className=\"absolute right-1.5 top-1.5 size-1.5 rounded-full bg-destructive\" />\n ) : null}\n </IconButton>\n )\n })}\n </div>\n ) : null}\n </div>\n {/* Zone 2 takes full width */}\n <div className=\"min-w-0 flex-1\">\n {zone2}\n </div>\n </>\n ) : showStackedExpanded ? (\n <>\n <div className=\"w-full space-y-2\">\n {canCollapse ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleCollapse}\n className=\"h-auto rounded-[6px] border bg-card px-1.5 py-2\"\n aria-label={t('ui.zone.collapse', 'Collapse form panel')}\n >\n <ChevronsLeft className=\"size-4\" />\n </Button>\n </div>\n ) : null}\n <div className=\"w-full\">\n {zone1}\n </div>\n </div>\n\n <div className=\"min-w-0 w-full\">\n {zone2}\n </div>\n </>\n ) : (\n <>\n {/* Zone 1 \u2014 CrudForm area */}\n <div\n className={cn('w-full lg:shrink-0', zone1DefaultWidth ? undefined : 'lg:w-[40%]')}\n style={zone1SideBySideStyle}\n >\n {zone1}\n </div>\n\n {/* Divider with collapse toggle */}\n <div className=\"hidden lg:flex relative shrink-0 w-8 items-start justify-center pt-4\">\n <div className=\"absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border\" />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleCollapse}\n className=\"relative z-10 h-auto rounded-[6px] border bg-card px-1.5 py-2\"\n aria-label={t('ui.zone.collapse', 'Collapse form panel')}\n >\n <ChevronsLeft className=\"size-4\" />\n </Button>\n </div>\n\n {/* Zone 2 \u2014 Tabs / related data area */}\n <div className=\"min-w-0 w-full lg:flex-1\">\n {zone2}\n </div>\n </>\n )}\n </div>\n )\n}\n"],
5
- "mappings": ";AA2JQ,mBAWM,KAQM,YAnBZ;AA1JR,YAAY,WAAW;AACvB,SAAS,cAAc,qBAAqB;AAC5C,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAGhC,MAAM,yBAAyB;AAuB/B,SAAS,kBAAkB,UAAsB;AAC/C,QAAM,aAAa,OAAO,WAAW,qBAAqB;AAC1D,aAAW,iBAAiB,UAAU,QAAQ;AAC9C,SAAO,MAAM,WAAW,oBAAoB,UAAU,QAAQ;AAChE;AAEA,SAAS,sBAAsB;AAC7B,SAAO,OAAO,WAAW,qBAAqB,EAAE;AAClD;AAEA,SAAS,4BAA4B;AACnC,SAAO;AACT;AAEO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,UAAU;AAAA,EACV;AACF,GAA+B;AAC7B,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,WAAW,cAAc,WAAW,IAAI,gBAAgB,QAAQ;AACxE,QAAM,cAAc,MAAM;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,YAAY,MAAM,OAAuB,IAAI;AACnD,QAAM,kBAAkB,MAAM,OAA0B,IAAI;AAC5D,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,MAAO,OAAO,WAAW,cAAc,IAAI,OAAO,UAAW;AACxH,QAAM,CAAC,0BAA0B,2BAA2B,IAAI,MAAM,SAAS,KAAK;AAEpF,QAAM,UAAU,MAAM;AACpB,UAAM,OAAO,UAAU;AACvB,QAAI,CAAC,KAAM;AAEX,UAAM,cAAc,CAAC,cAAsB;AACzC,wBAAkB,CAAC,SAAU,KAAK,IAAI,OAAO,SAAS,IAAI,IAAI,OAAO,SAAU;AAAA,IACjF;AAEA,UAAM,UAAU,MAAM;AACpB,kBAAY,KAAK,sBAAsB,EAAE,SAAS,OAAO,UAAU;AAAA,IACrE;AAEA,YAAQ;AAER,QAAI,OAAO,mBAAmB,aAAa;AACzC,aAAO,iBAAiB,UAAU,OAAO;AACzC,aAAO,MAAM,OAAO,oBAAoB,UAAU,OAAO;AAAA,IAC3D;AAEA,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAC/C,YAAM,QAAQ,QAAQ,CAAC;AACvB,UAAI,CAAC,MAAO;AACZ,kBAAY,MAAM,YAAY,KAAK;AAAA,IACrC,CAAC;AAED,aAAS,QAAQ,IAAI;AACrB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoB,kBAAkB;AAE5C,QAAM,UAAU,MAAM;AACpB,QAAI,mBAAmB;AACrB,kCAA4B,KAAK;AAAA,IACnC;AAAA,EACF,GAAG,CAAC,iBAAiB,CAAC;AAEtB,QAAM,oBAAoB,gBAAgB,aAAc,CAAC,qBAAqB,CAAC;AAC/E,QAAM,sBAAsB,CAAC,qBAAqB,CAAC;AACnD,QAAM,aAAa,oBAAoB,cAAc,sBAAsB,YAAY;AACvF,QAAM,uBAAuB,oBACzB,EAAE,OAAO,mBAAmB,WAAW,kBAAkB,IACzD;AAEJ,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,QAAI,CAAC,YAAa;AAClB,iBAAa,KAAK;AAClB,gCAA4B,CAAC,iBAAiB;AAAA,EAChD,GAAG,CAAC,aAAa,mBAAmB,YAAY,CAAC;AAEjD,QAAM,wBAAwB,MAAM,YAAY,CAAC,YAAmC;AAClF,QAAI,CAAC,YAAa;AAClB,iBAAa,KAAK;AAClB,gCAA4B,CAAC,iBAAiB;AAC9C,0BAAsB,MAAM;AAC1B,YAAM,SACJ,SAAS,eAAe,QAAQ,YAAY,6BAA6B,QAAQ,EAAE,EAAE,KAClF,SAAS,eAAe,qBAAqB,QAAQ,EAAE,EAAE;AAC9D,YAAM,gBAAgB,QAAQ,cAAiC,uBAAuB;AACtF,cAAQ,eAAe,EAAE,UAAU,UAAU,OAAO,QAAQ,CAAC;AAC7D,qBAAe,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,IAC9C,CAAC;AAAA,EACH,GAAG,CAAC,aAAa,mBAAmB,YAAY,CAAC;AAEjD,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,QAAI,CAAC,YAAa;AAClB,gCAA4B,KAAK;AACjC,iBAAa,IAAI;AACjB,0BAAsB,MAAM;AAC1B,sBAAgB,SAAS,MAAM;AAAA,IACjC,CAAC;AAAA,EACH,GAAG,CAAC,aAAa,YAAY,CAAC;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,yBAAuB;AAAA,MACvB,6BAA2B,aAAa,SAAS;AAAA,MACjD,eAAa,aAAa,SAAY;AAAA,MACtC,WAAW;AAAA,QACT;AAAA,QACA,CAAC,cAAc;AAAA,QACf,sBAAsB,aAAa;AAAA,MACrC;AAAA,MAEC,8BACC,iCACE;AAAA,6BAAC,SAAI,WAAU,uDACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAU;AAAA,cACV,cAAY,EAAE,kBAAkB,mBAAmB;AAAA,cAEnD,8BAAC,iBAAc,WAAU,UAAS;AAAA;AAAA,UACpC;AAAA,UACC,UAAU,SACT,oBAAC,SAAI,WAAU,6FACZ,mBAAS,IAAI,CAAC,YAAY;AACzB,kBAAM,cAAc,QAAQ;AAC5B,kBAAM,YAAY,QAAQ,QAAQ,cAAc,QAAQ,aAAa,CAAC;AACtE,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,SAAS,MAAM,sBAAsB,OAAO;AAAA,gBAC5C,WAAU;AAAA,gBACV,OAAO,QAAQ;AAAA,gBACf,cAAY,QAAQ,aAAa,QAAQ;AAAA,gBAEzC;AAAA,sCAAC,eAAY,WAAU,UAAS;AAAA,kBAC/B,YACC,oBAAC,UAAK,WAAU,mEAAkE,IAChF;AAAA;AAAA;AAAA,cAZC,QAAQ;AAAA,YAaf;AAAA,UAEJ,CAAC,GACH,IACE;AAAA,WACN;AAAA,QAEA,oBAAC,SAAI,WAAU,kBACZ,iBACH;AAAA,SACF,IACE,sBACF,iCACE;AAAA,6BAAC,SAAI,WAAU,oBACZ;AAAA,wBACC,oBAAC,SAAI,WAAU,oBACb;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAU;AAAA,cACV,cAAY,EAAE,oBAAoB,qBAAqB;AAAA,cAEvD,8BAAC,gBAAa,WAAU,UAAS;AAAA;AAAA,UACnC,GACF,IACE;AAAA,UACJ,oBAAC,SAAI,WAAU,UACZ,iBACH;AAAA,WACF;AAAA,QAEA,oBAAC,SAAI,WAAU,kBACZ,iBACH;AAAA,SACF,IAEA,iCAEE;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,GAAG,sBAAsB,oBAAoB,SAAY,YAAY;AAAA,YAChF,OAAO;AAAA,YAEN;AAAA;AAAA,QACH;AAAA,QAGA,qBAAC,SAAI,WAAU,wEACb;AAAA,8BAAC,SAAI,WAAU,+DAA8D;AAAA,UAC7E;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAU;AAAA,cACV,cAAY,EAAE,oBAAoB,qBAAqB;AAAA,cAEvD,8BAAC,gBAAa,WAAU,UAAS;AAAA;AAAA,UACnC;AAAA,WACF;AAAA,QAGA,oBAAC,SAAI,WAAU,4BACZ,iBACH;AAAA,SACF;AAAA;AAAA,EAEJ;AAEJ;",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { ChevronsLeft, ChevronsRight } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { useZoneCollapse } from './useZoneCollapse'\nimport type { LucideIcon } from 'lucide-react'\n\nconst SIDE_BY_SIDE_MIN_WIDTH = 1280\n\nexport interface ZoneSectionDescriptor {\n id: string\n icon: LucideIcon\n label: string\n targetId?: string\n ariaLabel?: string\n errorCount?: number\n}\n\nexport interface CollapsibleZoneLayoutProps {\n zone1: React.ReactNode\n zone2: React.ReactNode\n entityName: string\n pageType: string\n zone1DefaultWidth?: string\n errorCount?: number\n isDirty?: boolean\n /** Section descriptors for the collapsed rail icon sidebar. When omitted the rail shows the legacy minimal view. */\n sections?: ZoneSectionDescriptor[]\n}\n\nfunction subscribeViewport(callback: () => void) {\n const mediaQuery = window.matchMedia('(min-width: 1024px)')\n mediaQuery.addEventListener('change', callback)\n return () => mediaQuery.removeEventListener('change', callback)\n}\n\nfunction getViewportSnapshot() {\n return window.matchMedia('(min-width: 1024px)').matches\n}\n\nfunction getViewportServerSnapshot() {\n return false\n}\n\nexport function CollapsibleZoneLayout({\n zone1,\n zone2,\n entityName,\n pageType,\n zone1DefaultWidth,\n errorCount = 0,\n isDirty = false,\n sections,\n}: CollapsibleZoneLayoutProps) {\n const t = useT()\n const { collapsed, setCollapsed, isHydrated } = useZoneCollapse(pageType)\n const canCollapse = React.useSyncExternalStore(\n subscribeViewport,\n getViewportSnapshot,\n getViewportServerSnapshot,\n )\n const layoutRef = React.useRef<HTMLDivElement>(null)\n const expandButtonRef = React.useRef<HTMLButtonElement>(null)\n const [containerWidth, setContainerWidth] = React.useState(() => (typeof window === 'undefined' ? 0 : window.innerWidth))\n const [expandedWhileConstrained, setExpandedWhileConstrained] = React.useState(false)\n\n React.useEffect(() => {\n const node = layoutRef.current\n if (!node) return\n\n const updateWidth = (nextWidth: number) => {\n setContainerWidth((prev) => (Math.abs(prev - nextWidth) < 1 ? prev : nextWidth))\n }\n\n const measure = () => {\n updateWidth(node.getBoundingClientRect().width || window.innerWidth)\n }\n\n measure()\n\n if (typeof ResizeObserver === 'undefined') {\n window.addEventListener('resize', measure)\n return () => window.removeEventListener('resize', measure)\n }\n\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n updateWidth(entry.contentRect.width)\n })\n\n observer.observe(node)\n return () => observer.disconnect()\n }, [])\n\n const canShowSideBySide = containerWidth >= SIDE_BY_SIDE_MIN_WIDTH\n\n React.useEffect(() => {\n if (canShowSideBySide) {\n setExpandedWhileConstrained(false)\n }\n }, [canShowSideBySide])\n\n const showCollapsedRail = canCollapse && (collapsed || (!canShowSideBySide && !expandedWhileConstrained))\n const showStackedExpanded = !showCollapsedRail && !canShowSideBySide\n const layoutMode = showCollapsedRail ? 'collapsed' : showStackedExpanded ? 'stacked' : 'side-by-side'\n const zone1SideBySideStyle = zone1DefaultWidth\n ? { width: zone1DefaultWidth, flexBasis: zone1DefaultWidth }\n : undefined\n\n const handleExpand = React.useCallback(() => {\n if (!canCollapse) return\n setCollapsed(false)\n setExpandedWhileConstrained(!canShowSideBySide)\n }, [canCollapse, canShowSideBySide, setCollapsed])\n\n const handleSectionActivate = React.useCallback((section: ZoneSectionDescriptor) => {\n if (!canCollapse) return\n setCollapsed(false)\n setExpandedWhileConstrained(!canShowSideBySide)\n requestAnimationFrame(() => {\n const target =\n document.getElementById(section.targetId ?? `collapsible-group-wrapper-${section.id}`)\n ?? document.getElementById(`collapsible-group-${section.id}`)\n if (!target) return\n const headingButton = target.querySelector<HTMLButtonElement>('button[aria-controls]')\n // If the inner CollapsibleGroup is currently collapsed, expand it so its\n // contents become visible and tabbable for the user who just navigated here.\n if (headingButton?.getAttribute('aria-expanded') === 'false') {\n headingButton.click()\n }\n target.scrollIntoView({ behavior: 'smooth', block: 'start' })\n // Prefer focusing the first focusable input/textarea/select inside the\n // section so the user can start typing immediately. Skip hidden, disabled,\n // or non-interactive controls. Fall back to the section heading.\n requestAnimationFrame(() => {\n const focusables = Array.from(\n target.querySelectorAll<HTMLElement>(\n 'input:not([type=\"hidden\"]), textarea, select, [contenteditable=\"true\"]',\n ),\n )\n const firstInput = focusables.find((el) => {\n if (el.hasAttribute('disabled') || el.getAttribute('aria-hidden') === 'true') return false\n if (el instanceof HTMLInputElement && el.readOnly) return false\n return true\n })\n if (firstInput) {\n firstInput.focus({ preventScroll: true })\n return\n }\n headingButton?.focus({ preventScroll: true })\n })\n })\n }, [canCollapse, canShowSideBySide, setCollapsed])\n\n const handleCollapse = React.useCallback(() => {\n if (!canCollapse) return\n setExpandedWhileConstrained(false)\n setCollapsed(true)\n requestAnimationFrame(() => {\n expandButtonRef.current?.focus()\n })\n }, [canCollapse, setCollapsed])\n\n return (\n <div\n ref={layoutRef}\n data-zone-layout-mode={layoutMode}\n data-persistence-hydrated={isHydrated ? 'true' : 'false'}\n aria-hidden={isHydrated ? undefined : true}\n className={cn(\n 'flex gap-4',\n !isHydrated && 'invisible',\n showStackedExpanded ? 'flex-col' : 'flex-col lg:flex-row',\n )}\n >\n {showCollapsedRail ? (\n <>\n <div className=\"hidden lg:flex shrink-0 flex-col items-center gap-3\">\n <Button\n ref={expandButtonRef}\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n onClick={handleExpand}\n className=\"h-auto rounded-[10px] px-1.5 py-2 shadow-sm\"\n aria-label={t('ui.zone.expand', 'Expand form panel')}\n >\n <ChevronsRight className=\"size-4\" />\n </Button>\n {sections?.length ? (\n <div className=\"flex flex-col items-center gap-2 rounded-[14px] border border-border/70 bg-card px-2 py-3\">\n {sections.map((section) => {\n const SectionIcon = section.icon\n const hasErrors = Boolean(section.errorCount && section.errorCount > 0)\n return (\n <IconButton\n key={section.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n onClick={() => handleSectionActivate(section)}\n className=\"relative size-9 rounded-[10px] border border-transparent bg-muted/70 text-muted-foreground hover:border-border hover:bg-accent hover:text-accent-foreground\"\n title={section.label}\n aria-label={section.ariaLabel ?? section.label}\n >\n <SectionIcon className=\"size-4\" />\n {hasErrors ? (\n <span className=\"absolute right-1.5 top-1.5 size-1.5 rounded-full bg-destructive\" />\n ) : null}\n </IconButton>\n )\n })}\n </div>\n ) : null}\n </div>\n {/* Zone 2 takes full width */}\n <div className=\"min-w-0 flex-1\">\n {zone2}\n </div>\n </>\n ) : showStackedExpanded ? (\n <>\n <div className=\"w-full space-y-2\">\n {canCollapse ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleCollapse}\n className=\"h-auto rounded-[6px] border bg-card px-1.5 py-2\"\n aria-label={t('ui.zone.collapse', 'Collapse form panel')}\n >\n <ChevronsLeft className=\"size-4\" />\n </Button>\n </div>\n ) : null}\n <div className=\"w-full\">\n {zone1}\n </div>\n </div>\n\n <div className=\"min-w-0 w-full\">\n {zone2}\n </div>\n </>\n ) : (\n <>\n {/* Zone 1 \u2014 CrudForm area */}\n <div\n className={cn('w-full lg:shrink-0', zone1DefaultWidth ? undefined : 'lg:w-[40%]')}\n style={zone1SideBySideStyle}\n >\n {zone1}\n </div>\n\n {/* Divider with collapse toggle */}\n <div className=\"hidden lg:flex relative shrink-0 w-8 items-start justify-center pt-4\">\n <div className=\"absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border\" />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleCollapse}\n className=\"relative z-10 h-auto rounded-[6px] border bg-card px-1.5 py-2\"\n aria-label={t('ui.zone.collapse', 'Collapse form panel')}\n >\n <ChevronsLeft className=\"size-4\" />\n </Button>\n </div>\n\n {/* Zone 2 \u2014 Tabs / related data area */}\n <div className=\"min-w-0 w-full lg:flex-1\">\n {zone2}\n </div>\n </>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -35,15 +35,18 @@ function SectionNav({
35
35
  const renderItem = (item) => {
36
36
  const isActive = activePath === item.href || activePath.startsWith(item.href + "/");
37
37
  const label = item.labelKey ? t(item.labelKey, item.label) : item.label;
38
+ const base = collapsed ? "w-10 h-10 justify-center" : "w-full py-2 gap-2";
39
+ const spacingStyle = !collapsed ? { paddingLeft: "12px", paddingRight: "12px" } : void 0;
38
40
  return /* @__PURE__ */ jsxs(
39
41
  Link,
40
42
  {
41
43
  href: item.href,
42
- className: `relative text-sm rounded px-3 py-1.5 flex items-center gap-2 transition-colors ${isActive ? "bg-background border shadow-sm font-medium" : "hover:bg-accent hover:text-accent-foreground"}`,
44
+ className: `relative text-sm font-medium rounded-lg inline-flex items-center transition-colors ${base} ${isActive ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted"}`,
45
+ style: spacingStyle,
43
46
  title: collapsed ? label : void 0,
44
47
  children: [
45
- isActive && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" }),
46
- /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center shrink-0 text-muted-foreground", children: item.icon ?? DefaultIcon }),
48
+ isActive && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `absolute ${collapsed ? "left-[-20px]" : "left-[-12px]"} top-2 w-1 h-5 rounded-r bg-foreground` }),
49
+ /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center shrink-0", children: item.icon ?? DefaultIcon }),
47
50
  !collapsed && /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
48
51
  ]
49
52
  },
@@ -73,12 +76,12 @@ function SectionNav({
73
76
  const sortedItems = [...visibleItems].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
74
77
  const sectionLabel = section.labelKey ? t(section.labelKey, section.label) : section.label;
75
78
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
76
- !collapsed && /* @__PURE__ */ jsx("div", { className: "px-3 py-1.5 text-xs uppercase text-muted-foreground/80 font-medium", children: sectionLabel }),
77
- sortedItems.map(renderItem)
79
+ !collapsed && /* @__PURE__ */ jsx("div", { className: "w-full px-1 py-1 text-xs font-medium uppercase tracking-wider text-muted-foreground/70", children: sectionLabel }),
80
+ /* @__PURE__ */ jsx("div", { className: `flex flex-col ${collapsed ? "items-center" : ""} gap-1`, children: sortedItems.map(renderItem) })
78
81
  ] }, section.id);
79
82
  };
80
83
  const sortedSections = [...sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
81
- return /* @__PURE__ */ jsxs("nav", { className: `flex flex-col gap-4 p-3 ${collapsed ? "items-center" : ""}`, children: [
84
+ return /* @__PURE__ */ jsxs("nav", { className: `flex flex-col gap-3 ${collapsed ? "items-center" : ""}`, children: [
82
85
  /* @__PURE__ */ jsxs("div", { className: `flex items-center ${collapsed ? "justify-center" : "justify-between"} gap-2`, children: [
83
86
  !collapsed && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: resolvedTitle }),
84
87
  /* @__PURE__ */ jsx(
@@ -94,8 +97,7 @@ function SectionNav({
94
97
  }
95
98
  )
96
99
  ] }),
97
- /* @__PURE__ */ jsx("div", { className: "border-t" }),
98
- /* @__PURE__ */ jsx("div", { className: `flex flex-col gap-4 ${collapsed ? "items-center" : ""}`, children: sortedSections.map(renderSection) })
100
+ /* @__PURE__ */ jsx("div", { className: `flex flex-col gap-2 ${collapsed ? "items-center" : ""}`, children: sortedSections.map(renderSection) })
99
101
  ] });
100
102
  }
101
103
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/section-page/SectionNav.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { hasAllFeatures } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { IconButton } from '../../primitives/icon-button'\nimport type { SectionNavGroup, SectionNavItem } from './types'\nimport { mergeMenuItems } from '../injection/mergeMenuItems'\nimport { useInjectedMenuItems, type MenuSurfaceId } from '../injection/useInjectedMenuItems'\nimport { resolveInjectedIcon } from '../injection/resolveInjectedIcon'\n\nconst DefaultIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M8 6h13M8 12h13M8 18h13\" />\n <path d=\"M3 6h.01M3 12h.01M3 18h.01\" />\n </svg>\n)\n\nexport type SectionNavProps = {\n title: string\n titleKey?: string\n sections: SectionNavGroup[]\n activePath: string\n userFeatures?: Set<string>\n collapsed: boolean\n onToggleCollapse: () => void\n menuSurfaceId?: MenuSurfaceId\n}\n\nexport function SectionNav({\n title,\n titleKey,\n sections,\n activePath,\n userFeatures,\n collapsed,\n onToggleCollapse,\n menuSurfaceId,\n}: SectionNavProps) {\n const t = useT()\n const { items: injectedMenuItems } = useInjectedMenuItems(menuSurfaceId ?? 'menu:sidebar:settings')\n const grantedFeatureList = React.useMemo(() => (userFeatures ? Array.from(userFeatures) : []), [userFeatures])\n\n const hasRequiredFeatures = (item: SectionNavItem): boolean => {\n if (!item.requireFeatures || item.requireFeatures.length === 0) return true\n if (!userFeatures) return true\n return hasAllFeatures(grantedFeatureList, item.requireFeatures)\n }\n\n const resolvedTitle = titleKey ? t(titleKey, title) : title\n\n const renderItem = (item: SectionNavItem) => {\n const isActive = activePath === item.href || activePath.startsWith(item.href + '/')\n const label = item.labelKey ? t(item.labelKey, item.label) : item.label\n\n return (\n <Link\n key={item.id}\n href={item.href}\n className={`relative text-sm rounded px-3 py-1.5 flex items-center gap-2 transition-colors ${\n isActive\n ? 'bg-background border shadow-sm font-medium'\n : 'hover:bg-accent hover:text-accent-foreground'\n }`}\n title={collapsed ? label : undefined}\n >\n {isActive && (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n )}\n <span className=\"flex items-center justify-center shrink-0 text-muted-foreground\">\n {item.icon ?? DefaultIcon}\n </span>\n {!collapsed && <span className=\"truncate\">{label}</span>}\n </Link>\n )\n }\n\n const renderSection = (section: SectionNavGroup) => {\n const sectionInjected = injectedMenuItems.filter((item) => (item.groupId ?? section.id) === section.id)\n const mergedItems = mergeMenuItems(\n section.items.map((item) => ({ id: item.id, item })),\n sectionInjected,\n ).flatMap((item) => {\n if (item.source === 'built-in') {\n const original = section.items.find((entry) => entry.id === item.id)\n return original ? [original] : []\n }\n if (!item.href) return []\n return [{\n id: item.id,\n label: item.labelKey ? t(item.labelKey, item.label ?? item.id) : (item.label ?? item.id),\n href: item.href,\n icon: resolveInjectedIcon(item.icon) ?? undefined,\n }]\n })\n const visibleItems = mergedItems.filter(hasRequiredFeatures)\n if (visibleItems.length === 0) return null\n\n const sortedItems = [...visibleItems].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n const sectionLabel = section.labelKey ? t(section.labelKey, section.label) : section.label\n\n return (\n <div key={section.id} className=\"flex flex-col gap-1\">\n {!collapsed && (\n <div className=\"px-3 py-1.5 text-xs uppercase text-muted-foreground/80 font-medium\">\n {sectionLabel}\n </div>\n )}\n {sortedItems.map(renderItem)}\n </div>\n )\n }\n\n const sortedSections = [...sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n\n return (\n <nav className={`flex flex-col gap-4 p-3 ${collapsed ? 'items-center' : ''}`}>\n <div className={`flex items-center ${collapsed ? 'justify-center' : 'justify-between'} gap-2`}>\n {!collapsed && (\n <span className=\"text-sm font-medium truncate\">{resolvedTitle}</span>\n )}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={onToggleCollapse}\n title={collapsed ? t('common.expand', 'Expand') : t('common.collapse', 'Collapse')}\n aria-label={collapsed ? t('common.expand', 'Expand') : t('common.collapse', 'Collapse')}\n >\n {collapsed ? <ChevronRight className=\"size-4\" /> : <ChevronLeft className=\"size-4\" />}\n </IconButton>\n </div>\n <div className=\"border-t\" />\n <div className={`flex flex-col gap-4 ${collapsed ? 'items-center' : ''}`}>\n {sortedSections.map(renderSection)}\n </div>\n </nav>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { hasAllFeatures } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { IconButton } from '../../primitives/icon-button'\nimport type { SectionNavGroup, SectionNavItem } from './types'\nimport { mergeMenuItems } from '../injection/mergeMenuItems'\nimport { useInjectedMenuItems, type MenuSurfaceId } from '../injection/useInjectedMenuItems'\nimport { resolveInjectedIcon } from '../injection/resolveInjectedIcon'\n\nconst DefaultIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M8 6h13M8 12h13M8 18h13\" />\n <path d=\"M3 6h.01M3 12h.01M3 18h.01\" />\n </svg>\n)\n\nexport type SectionNavProps = {\n title: string\n titleKey?: string\n sections: SectionNavGroup[]\n activePath: string\n userFeatures?: Set<string>\n collapsed: boolean\n onToggleCollapse: () => void\n menuSurfaceId?: MenuSurfaceId\n}\n\nexport function SectionNav({\n title,\n titleKey,\n sections,\n activePath,\n userFeatures,\n collapsed,\n onToggleCollapse,\n menuSurfaceId,\n}: SectionNavProps) {\n const t = useT()\n const { items: injectedMenuItems } = useInjectedMenuItems(menuSurfaceId ?? 'menu:sidebar:settings')\n const grantedFeatureList = React.useMemo(() => (userFeatures ? Array.from(userFeatures) : []), [userFeatures])\n\n const hasRequiredFeatures = (item: SectionNavItem): boolean => {\n if (!item.requireFeatures || item.requireFeatures.length === 0) return true\n if (!userFeatures) return true\n return hasAllFeatures(grantedFeatureList, item.requireFeatures)\n }\n\n const resolvedTitle = titleKey ? t(titleKey, title) : title\n\n const renderItem = (item: SectionNavItem) => {\n const isActive = activePath === item.href || activePath.startsWith(item.href + '/')\n const label = item.labelKey ? t(item.labelKey, item.label) : item.label\n const base = collapsed ? 'w-10 h-10 justify-center' : 'w-full py-2 gap-2'\n const spacingStyle = !collapsed ? { paddingLeft: '12px', paddingRight: '12px' } : undefined\n\n return (\n <Link\n key={item.id}\n href={item.href}\n className={`relative text-sm font-medium rounded-lg inline-flex items-center transition-colors ${base} ${\n isActive\n ? 'bg-muted text-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n style={spacingStyle}\n title={collapsed ? label : undefined}\n >\n {isActive && (\n <span aria-hidden className={`absolute ${collapsed ? 'left-[-20px]' : 'left-[-12px]'} top-2 w-1 h-5 rounded-r bg-foreground`} />\n )}\n <span className=\"flex items-center justify-center shrink-0\">\n {item.icon ?? DefaultIcon}\n </span>\n {!collapsed && <span className=\"truncate\">{label}</span>}\n </Link>\n )\n }\n\n const renderSection = (section: SectionNavGroup) => {\n const sectionInjected = injectedMenuItems.filter((item) => (item.groupId ?? section.id) === section.id)\n const mergedItems = mergeMenuItems(\n section.items.map((item) => ({ id: item.id, item })),\n sectionInjected,\n ).flatMap((item) => {\n if (item.source === 'built-in') {\n const original = section.items.find((entry) => entry.id === item.id)\n return original ? [original] : []\n }\n if (!item.href) return []\n return [{\n id: item.id,\n label: item.labelKey ? t(item.labelKey, item.label ?? item.id) : (item.label ?? item.id),\n href: item.href,\n icon: resolveInjectedIcon(item.icon) ?? undefined,\n }]\n })\n const visibleItems = mergedItems.filter(hasRequiredFeatures)\n if (visibleItems.length === 0) return null\n\n const sortedItems = [...visibleItems].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n const sectionLabel = section.labelKey ? t(section.labelKey, section.label) : section.label\n\n return (\n <div key={section.id} className=\"flex flex-col gap-1\">\n {!collapsed && (\n <div className=\"w-full px-1 py-1 text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {sectionLabel}\n </div>\n )}\n <div className={`flex flex-col ${collapsed ? 'items-center' : ''} gap-1`}>\n {sortedItems.map(renderItem)}\n </div>\n </div>\n )\n }\n\n const sortedSections = [...sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n\n return (\n <nav className={`flex flex-col gap-3 ${collapsed ? 'items-center' : ''}`}>\n <div className={`flex items-center ${collapsed ? 'justify-center' : 'justify-between'} gap-2`}>\n {!collapsed && (\n <span className=\"text-sm font-medium truncate\">{resolvedTitle}</span>\n )}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={onToggleCollapse}\n title={collapsed ? t('common.expand', 'Expand') : t('common.collapse', 'Collapse')}\n aria-label={collapsed ? t('common.expand', 'Expand') : t('common.collapse', 'Collapse')}\n >\n {collapsed ? <ChevronRight className=\"size-4\" /> : <ChevronLeft className=\"size-4\" />}\n </IconButton>\n </div>\n <div className={`flex flex-col gap-2 ${collapsed ? 'items-center' : ''}`}>\n {sortedSections.map(renderSection)}\n </div>\n </nav>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -12,7 +12,7 @@ function SectionPage({
12
12
  }) {
13
13
  const [collapsed, setCollapsed] = React.useState(false);
14
14
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-[calc(100vh-8rem)]", children: [
15
- /* @__PURE__ */ jsx("aside", { className: `${collapsed ? "w-16" : "w-64"} border-r bg-muted/30 overflow-y-auto shrink-0 transition-all duration-200`, children: /* @__PURE__ */ jsx(
15
+ /* @__PURE__ */ jsx("aside", { className: `${collapsed ? "w-16" : "w-64"} border-r bg-background shrink-0 py-4 transition-all duration-200`, children: /* @__PURE__ */ jsx("div", { className: `h-full overflow-y-auto scrollbar-hide ${collapsed ? "pl-2 pr-1" : "pl-3 pr-1"}`, children: /* @__PURE__ */ jsx(
16
16
  SectionNav,
17
17
  {
18
18
  title,
@@ -23,7 +23,7 @@ function SectionPage({
23
23
  collapsed,
24
24
  onToggleCollapse: () => setCollapsed(!collapsed)
25
25
  }
26
- ) }),
26
+ ) }) }),
27
27
  /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-y-auto p-6", children })
28
28
  ] });
29
29
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/section-page/SectionPage.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { SectionNav } from './SectionNav'\nimport type { SectionPageProps } from './types'\n\nexport function SectionPage({\n title,\n titleKey,\n sections,\n activePath,\n userFeatures,\n children,\n}: SectionPageProps) {\n const [collapsed, setCollapsed] = React.useState(false)\n\n return (\n <div className=\"flex h-full min-h-[calc(100vh-8rem)]\">\n <aside className={`${collapsed ? 'w-16' : 'w-64'} border-r bg-muted/30 overflow-y-auto shrink-0 transition-all duration-200`}>\n <SectionNav\n title={title}\n titleKey={titleKey}\n sections={sections}\n activePath={activePath}\n userFeatures={userFeatures}\n collapsed={collapsed}\n onToggleCollapse={() => setCollapsed(!collapsed)}\n />\n </aside>\n <main className=\"flex-1 overflow-y-auto p-6\">\n {children}\n </main>\n </div>\n )\n}\n"],
5
- "mappings": ";AAgBI,SAEI,KAFJ;AAfJ,YAAY,WAAW;AACvB,SAAS,kBAAkB;AAGpB,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AAEtD,SACE,qBAAC,SAAI,WAAU,wCACb;AAAA,wBAAC,WAAM,WAAW,GAAG,YAAY,SAAS,MAAM,8EAC9C;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,kBAAkB,MAAM,aAAa,CAAC,SAAS;AAAA;AAAA,IACjD,GACF;AAAA,IACA,oBAAC,UAAK,WAAU,8BACb,UACH;AAAA,KACF;AAEJ;",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { SectionNav } from './SectionNav'\nimport type { SectionPageProps } from './types'\n\nexport function SectionPage({\n title,\n titleKey,\n sections,\n activePath,\n userFeatures,\n children,\n}: SectionPageProps) {\n const [collapsed, setCollapsed] = React.useState(false)\n\n return (\n <div className=\"flex h-full min-h-[calc(100vh-8rem)]\">\n <aside className={`${collapsed ? 'w-16' : 'w-64'} border-r bg-background shrink-0 py-4 transition-all duration-200`}>\n {/* Padding lives on the inner scroll container so the absolute active-marker\n (left: -12px from each link) renders inside the inner div's padding box \u2014\n CSS clips at padding-box edges, so a marker placed there stays visible. */}\n <div className={`h-full overflow-y-auto scrollbar-hide ${collapsed ? 'pl-2 pr-1' : 'pl-3 pr-1'}`}>\n <SectionNav\n title={title}\n titleKey={titleKey}\n sections={sections}\n activePath={activePath}\n userFeatures={userFeatures}\n collapsed={collapsed}\n onToggleCollapse={() => setCollapsed(!collapsed)}\n />\n </div>\n </aside>\n <main className=\"flex-1 overflow-y-auto p-6\">\n {children}\n </main>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAgBI,SAMM,KANN;AAfJ,YAAY,WAAW;AACvB,SAAS,kBAAkB;AAGpB,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AAEtD,SACE,qBAAC,SAAI,WAAU,wCACb;AAAA,wBAAC,WAAM,WAAW,GAAG,YAAY,SAAS,MAAM,qEAI9C,8BAAC,SAAI,WAAW,yCAAyC,YAAY,cAAc,WAAW,IAC5F;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,kBAAkB,MAAM,aAAa,CAAC,SAAS;AAAA;AAAA,IACjD,GACF,GACF;AAAA,IACA,oBAAC,UAAK,WAAU,8BACb,UACH;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }