@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.159.1 → 1.0.0-beta.161.1
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.
- package/dist/components/composed/AppShellSimple/AppShellSimple.cjs +1 -1
- package/dist/components/composed/AppShellSimple/AppShellSimple.cjs.map +1 -1
- package/dist/components/composed/AppShellSimple/AppShellSimple.d.ts +5 -1
- package/dist/components/composed/AppShellSimple/AppShellSimple.js +58 -55
- package/dist/components/composed/AppShellSimple/AppShellSimple.js.map +1 -1
- package/dist/components/composed/BuildInfoFooter/BuildInfoFooter.cjs +2 -0
- package/dist/components/composed/BuildInfoFooter/BuildInfoFooter.cjs.map +1 -0
- package/dist/components/composed/BuildInfoFooter/BuildInfoFooter.d.ts +24 -0
- package/dist/components/composed/BuildInfoFooter/BuildInfoFooter.js +34 -0
- package/dist/components/composed/BuildInfoFooter/BuildInfoFooter.js.map +1 -0
- package/dist/components/composed/DataAppShell/DataAppShell.cjs +1 -1
- package/dist/components/composed/DataAppShell/DataAppShell.cjs.map +1 -1
- package/dist/components/composed/DataAppShell/DataAppShell.d.ts +3 -1
- package/dist/components/composed/DataAppShell/DataAppShell.js +111 -109
- package/dist/components/composed/DataAppShell/DataAppShell.js.map +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/index.css +1 -1
- package/dist/index.scoped.css +1 -1
- package/dist/index.tailwind.css +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),K=require("lucide-react"),U=require("react"),$=require("../BuildInfoFooter/BuildInfoFooter.cjs"),G=require("../DataAppShell/PrimaryNav.cjs"),X=require("../TopBar/TopBar.cjs"),l=require("../../../ui/breadcrumb.cjs"),Y=require("../../../ui/button.cjs"),x=require("../../../ui/tooltip.cjs"),N=require("../../../lib/utils.cjs");function J(n){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const a in n)if(a!=="default"){const i=Object.getOwnPropertyDescriptor(n,a);Object.defineProperty(e,a,i.get?i:{enumerable:!0,get:()=>n[a]})}}return e.default=n,Object.freeze(e)}const c=J(U),f=["sidebar","rail","hidden"],u=f.length-1,d=n=>n>=u?-1:1,D=n=>Math.max(0,f.indexOf(n)),y=48,_=220,Q=(y+_)/2,Z=y/2,I={sidebar:_,rail:y,hidden:0};function ee(n){const e=n>=Q?0:n>=Z?1:u;return{index:e,dir:d(e)}}function re({index:n,dir:e}){const a=n+e;if(a<0||a>u){const i=e*-1;return{index:n+i,dir:i}}return{index:a,dir:e}}function ne(n,e){switch(f[e]){case"sidebar":return"Expand navigation to labels";case"hidden":return"Hide navigation";default:return e>n?"Collapse navigation to icons":"Expand navigation to icons"}}const te=[];function ae({items:n}){return r.jsx(l.Breadcrumb,{className:"min-w-0",children:r.jsx(l.BreadcrumbList,{className:"flex-nowrap min-w-0",children:n.map((e,a)=>{const i=a===n.length-1,h=!i&&(!!e.href||!!e.onClick);return r.jsxs(c.Fragment,{children:[a>0&&r.jsx(l.BreadcrumbSeparator,{className:"shrink-0",children:"/"}),r.jsx(l.BreadcrumbItem,{className:i?"min-w-0":"shrink-0",children:i?r.jsx(l.BreadcrumbPage,{className:"truncate font-medium",children:e.label}):h&&e.href?r.jsx(l.BreadcrumbLink,{href:e.href,className:"whitespace-nowrap text-primary",children:e.label}):h&&e.onClick?r.jsx(l.BreadcrumbLink,{asChild:!0,className:"whitespace-nowrap text-primary",children:r.jsx("button",{type:"button",className:"cursor-pointer",onClick:e.onClick,children:e.label})}):r.jsx("span",{className:"whitespace-nowrap",children:e.label})})]},`${e.label}-${a}`)})})})}function ie({label:n,onCycle:e}){return r.jsx(x.TooltipProvider,{children:r.jsxs(x.Tooltip,{children:[r.jsx(x.TooltipTrigger,{asChild:!0,children:r.jsx(Y.Button,{"data-slot":"app-shell-simple-nav-toggle",variant:"ghost",size:"icon","aria-label":n,className:"-ml-1 shrink-0 text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground active:translate-y-0",onClick:e,children:r.jsx(K.PanelLeft,{className:"size-4"})})}),r.jsx(x.TooltipContent,{side:"bottom",children:n})]})})}function se({showNav:n=!0,navGroups:e=te,activeKey:a,onSelect:i,breadcrumbs:h,headerActions:L,userMenu:q,defaultNav:O="sidebar",nav:g,onNavChange:S,version:T,commitSha:C,children:z}){const[v,H]=c.useState(()=>{const t=D(g??O);return{index:t,dir:d(t)}}),s=g===void 0?v.index:D(g),E=v.index===s?v.dir:d(s),o=f[s],p=c.useCallback(t=>{H(t),t.index!==s&&S?.(f[t.index])},[s,S]),P=re({index:s,dir:E}),M=()=>p(P),F=ne(s,P.index),k=c.useId(),A=c.useRef(null),[j,m]=c.useState(!1),V=t=>p(ee(t-(A.current?.getBoundingClientRect().left??0))),B=t=>{const b=Math.min(u,Math.max(0,s+t));b!==s&&p({index:b,dir:d(b)})},W=t=>{const R={ArrowLeft:()=>B(1),ArrowRight:()=>B(-1),Home:()=>p({index:0,dir:d(0)}),End:()=>p({index:u,dir:d(u)})}[t.key];R&&(t.preventDefault(),R())},w=o==="hidden";return r.jsxs("div",{"data-slot":"app-shell-simple","data-nav-state":o,className:N.cn("grid h-screen w-full overflow-hidden [grid-template-rows:auto_minmax(0,1fr)]",n?"[grid-template-columns:auto_minmax(0,1fr)] [grid-template-areas:'top_top'_'nav_body']":"[grid-template-columns:minmax(0,1fr)] [grid-template-areas:'top'_'body']",j&&"select-none cursor-col-resize"),children:[r.jsx("div",{className:"[grid-area:top] min-w-0",children:r.jsx(X.TopBar,{left:r.jsxs(r.Fragment,{children:[n&&r.jsx(ie,{label:F,onCycle:M}),r.jsx(ae,{items:h})]}),right:L})}),n&&r.jsxs("div",{ref:A,id:k,"data-slot":"app-shell-simple-nav","data-nav-state":o,className:N.cn("[grid-area:nav] relative min-h-0 flex flex-col shrink-0 bg-sidebar h-full transition-[width] duration-200",w?"overflow-hidden":"border-r border-sidebar-border"),style:{width:I[o]},children:[r.jsx("div",{"data-slot":"app-shell-simple-nav-content",className:"h-full w-full overflow-hidden",inert:w,children:r.jsx(G.DataAppShellPrimaryNav,{variant:o==="rail"?"rail":"sidebar","aria-label":"Application navigation",navGroups:e,activeKey:a,onSelect:i,user:q,className:"h-full w-full"})}),r.jsx("div",{role:"separator",tabIndex:0,"aria-orientation":"vertical","aria-label":"Resize navigation","aria-controls":k,"aria-valuemin":0,"aria-valuemax":_,"aria-valuenow":I[o],"aria-valuetext":o,onKeyDown:W,onPointerDown:t=>{t.preventDefault(),t.currentTarget.setPointerCapture(t.pointerId),m(!0)},onPointerMove:t=>{if(j){if(t.buttons===0){m(!1);return}V(t.clientX)}},onPointerUp:()=>m(!1),onPointerCancel:()=>m(!1),onLostPointerCapture:()=>m(!1),className:N.cn("absolute top-0 right-0 z-10 h-full translate-x-1/2 cursor-col-resize transition-colors hover:bg-primary/40 focus-visible:outline-none focus-visible:bg-primary/60 focus-visible:ring-2 focus-visible:ring-ring",w?"w-3":"w-1.5",j&&"bg-primary/60")})]}),r.jsx("main",{"data-slot":"app-shell-simple-content",className:"[grid-area:body] min-h-0 min-w-0 overflow-auto bg-background",children:r.jsx($.WithBuildInfoFooter,{show:!!(T||C),version:T,commitSha:C,children:z})})]})}exports.AppShellSimple=se;
|
|
2
2
|
//# sourceMappingURL=AppShellSimple.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShellSimple.cjs","sources":["../../../../src/components/composed/AppShellSimple/AppShellSimple.tsx"],"sourcesContent":["/**\n * AppShellSimple (SW-2410) — a Data App Shell stripped down to just two zones:\n * a full-width **top bar** and a three-state **side nav**. For apps that don't\n * need the full `DataAppShell` (no secondary nav, right panel, horizontal nav\n * variant, or mobile Sheet).\n *\n * - The side-nav control **lives in the top bar** (far left, above the nav).\n * It ping-pongs the nav through three states — labels ↔ icons ↔ hidden —\n * and dragging the nav's right border snaps between the same three.\n * - The **breadcrumb is the top bar name** — it sits right after the toggle.\n *\n * Composes existing kit primitives (`TopBar`, `DataAppShellPrimaryNav`,\n * `Breadcrumb`, `Button`, `Tooltip`).\n */\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n DataAppShellPrimaryNav,\n type NavGroup,\n type NavPage,\n} from \"@/components/composed/DataAppShell\";\nimport { TopBar } from \"@/components/composed/TopBar\";\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AppShellSimpleCrumb {\n /** Display label */\n label: string;\n /** If set, the crumb renders as a link */\n href?: string;\n /** Click handler when there's no `href` */\n onClick?: () => void;\n}\n\n/**\n * The side nav has three states. The top-bar toggle ping-pongs between them,\n * and dragging the nav's right border snaps between them:\n * - `sidebar` — full panel with icon + label rows\n * - `rail` — icon-only rail\n * - `hidden` — no side nav; only the top bar + content\n */\nexport type AppShellSimpleNavState = \"sidebar\" | \"rail\" | \"hidden\";\n\n/**\n * Where the nav sits in the ping-pong: which state (`index` into `NAV_ORDER`)\n * and which way the next toggle click moves (`dir`, reversing at either end).\n */\ntype NavCursor = { index: number; dir: 1 | -1 };\n\n// Ping-pong order (does not wrap): labels ↔ icons ↔ hidden.\nconst NAV_ORDER: AppShellSimpleNavState[] = [\"sidebar\", \"rail\", \"hidden\"];\nconst LAST_NAV = NAV_ORDER.length - 1;\n\n// Single source of truth for the direction convention: point inward from the\n// ends so the next toggle keeps the ping-pong moving (middle defaults forward).\nconst dirFor = (index: number): 1 | -1 => (index >= LAST_NAV ? -1 : 1);\n\nconst indexOfNav = (state: AppShellSimpleNavState) => Math.max(0, NAV_ORDER.indexOf(state));\n\n// Fixed widths per state — the border drag snaps between these; it never sets\n// an in-between width.\nconst RAIL_WIDTH = 48;\nconst SIDEBAR_WIDTH = 220;\n// Snap thresholds: midpoints between the fixed widths.\nconst RAIL_SNAP = (RAIL_WIDTH + SIDEBAR_WIDTH) / 2;\nconst HIDE_SNAP = RAIL_WIDTH / 2;\n\nconst NAV_WIDTH: Record<AppShellSimpleNavState, number> = {\n sidebar: SIDEBAR_WIDTH,\n rail: RAIL_WIDTH,\n hidden: 0,\n};\n\n/** Map a dragged pixel width to the nearest nav state. */\nfunction snapNav(width: number): NavCursor {\n const index = width >= RAIL_SNAP ? 0 : width >= HIDE_SNAP ? 1 : LAST_NAV;\n return { index, dir: dirFor(index) };\n}\n\n/** Next cursor in the ping-pong, reversing direction at either end. */\nfunction nextNav({ index, dir }: NavCursor): NavCursor {\n const forward = index + dir;\n if (forward < 0 || forward > LAST_NAV) {\n const reversed = (dir * -1) as 1 | -1;\n return { index: index + reversed, dir: reversed };\n }\n return { index: forward, dir };\n}\n\n/**\n * Label for the toggle, describing the transition the next click performs.\n * `rail` is reachable from both directions, so it can't be keyed on the\n * destination alone — arriving from `hidden` is an expand, not a collapse.\n */\nfunction navToggleLabel(from: number, to: number): string {\n switch (NAV_ORDER[to]) {\n case \"sidebar\":\n return \"Expand navigation to labels\";\n case \"hidden\":\n return \"Hide navigation\";\n default:\n return to > from ? \"Collapse navigation to icons\" : \"Expand navigation to icons\";\n }\n}\n\nexport interface AppShellSimpleProps {\n /**\n * Whether the app has a side nav at all (default `true`). When `false`, the\n * shell renders as a top bar + full-width body: no nav zone, and no top-bar\n * toggle. This is the app-level switch — distinct from the runtime `hidden`\n * nav state, which the end user can toggle back open. `navGroups` is ignored\n * when `showNav` is `false`.\n */\n showNav?: boolean;\n /** Nav groups rendered in the side nav (ignored when `showNav` is `false`) */\n navGroups?: NavGroup[];\n /** Controlled active page id — takes precedence over per-page `isActive` */\n activeKey?: string;\n /** Called with the selected page id after the page's own `onClick` */\n onSelect?: (key: string, page: NavPage) => void;\n /** Breadcrumb trail — the top bar name */\n breadcrumbs: AppShellSimpleCrumb[];\n /** Right-side top bar actions (e.g. notifications, user menu) */\n headerActions?: React.ReactNode;\n /** Bottom-of-nav user slot */\n userMenu?: React.ReactNode;\n /** Uncontrolled initial nav state (default `sidebar`) */\n defaultNav?: AppShellSimpleNavState;\n /** Controlled nav state — pair with `onNavChange`; overrides `defaultNav` */\n nav?: AppShellSimpleNavState;\n /** Fired whenever the nav state changes (toggle, drag, or keyboard resize) */\n onNavChange?: (nav: AppShellSimpleNavState) => void;\n /** Main content */\n children: React.ReactNode;\n}\n\nconst EMPTY_NAV_GROUPS: NavGroup[] = [];\n\n// =============================================================================\n// Breadcrumb trail — the top bar name\n// =============================================================================\n\nfunction ShellBreadcrumb({ items }: { items: AppShellSimpleCrumb[] }) {\n return (\n <Breadcrumb className=\"min-w-0\">\n <BreadcrumbList className=\"flex-nowrap min-w-0\">\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n const isClickable = !isLast && (!!item.href || !!item.onClick);\n return (\n <React.Fragment key={`${item.label}-${index}`}>\n {index > 0 && <BreadcrumbSeparator className=\"shrink-0\">/</BreadcrumbSeparator>}\n <BreadcrumbItem className={isLast ? \"min-w-0\" : \"shrink-0\"}>\n {isLast ? (\n <BreadcrumbPage className=\"truncate font-medium\">{item.label}</BreadcrumbPage>\n ) : isClickable && item.href ? (\n <BreadcrumbLink href={item.href} className=\"whitespace-nowrap text-primary\">\n {item.label}\n </BreadcrumbLink>\n ) : isClickable && item.onClick ? (\n <BreadcrumbLink asChild className=\"whitespace-nowrap text-primary\">\n <button type=\"button\" className=\"cursor-pointer\" onClick={item.onClick}>\n {item.label}\n </button>\n </BreadcrumbLink>\n ) : (\n <span className=\"whitespace-nowrap\">{item.label}</span>\n )}\n </BreadcrumbItem>\n </React.Fragment>\n );\n })}\n </BreadcrumbList>\n </Breadcrumb>\n );\n}\n\n// =============================================================================\n// Top-bar side-nav toggle — the collapse/expand control (SW-2410).\n// =============================================================================\n\nfunction SideNavToggle({ label, onCycle }: { label: string; onCycle: () => void }) {\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n data-slot=\"app-shell-simple-nav-toggle\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={label}\n // size-8 (32px) so the hover highlight box matches the nav icon box;\n // -ml-1 centers the icon on the nav's 24px gutter (TopBar has px-3);\n // hover uses sidebar-accent (not the ghost default accent) to match the\n // nav-row hover on the shared sidebar surface; no press inset.\n className=\"-ml-1 shrink-0 text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground active:translate-y-0\"\n onClick={onCycle}\n >\n <PanelLeft className=\"size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{label}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n}\n\n// =============================================================================\n// AppShellSimple — two zones: full-width top bar + collapsible side nav\n// =============================================================================\n\nexport function AppShellSimple({\n showNav = true,\n navGroups = EMPTY_NAV_GROUPS,\n activeKey,\n onSelect,\n breadcrumbs,\n headerActions,\n userMenu,\n defaultNav = \"sidebar\",\n nav: navProp,\n onNavChange,\n children,\n}: AppShellSimpleProps) {\n // The cursor is always local: `index` is only authoritative while\n // uncontrolled, but `dir` (which way the ping-pong is travelling) has no\n // prop equivalent and is tracked here in both modes.\n const [cursor, setCursor] = React.useState<NavCursor>(() => {\n const index = indexOfNav(navProp ?? defaultNav);\n return { index, dir: dirFor(index) };\n });\n\n const index = navProp === undefined ? cursor.index : indexOfNav(navProp);\n // A controlled index moved by the parent invalidates our stored direction.\n const dir = cursor.index === index ? cursor.dir : dirFor(index);\n const navState = NAV_ORDER[index];\n\n const applyNav = React.useCallback(\n (next: NavCursor) => {\n setCursor(next);\n if (next.index !== index) onNavChange?.(NAV_ORDER[next.index]);\n },\n [index, onNavChange]\n );\n\n const nextCursor = nextNav({ index, dir });\n const cycleNav = () => applyNav(nextCursor);\n // Label describes the transition the next click performs.\n const nextLabel = navToggleLabel(index, nextCursor.index);\n\n // Drag the border to snap between the three states (widest → slimmest:\n // icon + text → icon rail → fully closed). Pointer capture keeps the drag\n // scoped to the handle — no window listeners to leak.\n //\n // The nav zone stays mounted at `width: 0` in the `hidden` state rather than\n // unmounting: unmounting mid-drag destroyed the element holding pointer\n // capture, so `pointerup` never came back and `resizing` stuck on. Keeping it\n // mounted also makes the handle draggable back out of `hidden`.\n const navId = React.useId();\n const navRef = React.useRef<HTMLDivElement>(null);\n const [resizing, setResizing] = React.useState(false);\n const snapToPointer = (clientX: number) =>\n applyNav(snapNav(clientX - (navRef.current?.getBoundingClientRect().left ?? 0)));\n\n const stepNav = (delta: 1 | -1) => {\n const target = Math.min(LAST_NAV, Math.max(0, index + delta));\n if (target !== index) applyNav({ index: target, dir: dirFor(target) });\n };\n\n const onHandleKeyDown = (e: React.KeyboardEvent) => {\n // ← narrower · → wider, matching the on-screen direction of the drag\n // (the arrow moves the nav's right border the same way).\n const move: Record<string, () => void> = {\n ArrowLeft: () => stepNav(1),\n ArrowRight: () => stepNav(-1),\n Home: () => applyNav({ index: 0, dir: dirFor(0) }),\n End: () => applyNav({ index: LAST_NAV, dir: dirFor(LAST_NAV) }),\n };\n const action = move[e.key];\n if (!action) return;\n e.preventDefault();\n action();\n };\n\n const isHidden = navState === \"hidden\";\n\n return (\n <div\n data-slot=\"app-shell-simple\"\n data-nav-state={navState}\n className={cn(\n \"grid h-screen w-full overflow-hidden [grid-template-rows:auto_minmax(0,1fr)]\",\n showNav\n ? \"[grid-template-columns:auto_minmax(0,1fr)] [grid-template-areas:'top_top'_'nav_body']\"\n : \"[grid-template-columns:minmax(0,1fr)] [grid-template-areas:'top'_'body']\",\n resizing && \"select-none cursor-col-resize\"\n )}\n >\n {/* Top bar */}\n <div className=\"[grid-area:top] min-w-0\">\n <TopBar\n left={\n <>\n {showNav && <SideNavToggle label={nextLabel} onCycle={cycleNav} />}\n <ShellBreadcrumb items={breadcrumbs} />\n </>\n }\n right={headerActions}\n />\n </div>\n\n {/* Side nav — sidebar (labels), rail (icons), or collapsed to zero width\n when hidden; the toggle ping-pongs and the border drag snaps between\n states. Omitted entirely when the app opts out (`showNav={false}`). */}\n {showNav && (\n <div\n ref={navRef}\n id={navId}\n data-slot=\"app-shell-simple-nav\"\n data-nav-state={navState}\n className={cn(\n \"[grid-area:nav] relative min-h-0 flex flex-col shrink-0 bg-sidebar h-full transition-[width] duration-200\",\n isHidden ? \"overflow-hidden\" : \"border-r border-sidebar-border\"\n )}\n style={{ width: NAV_WIDTH[navState] }}\n >\n {/* `inert` keeps the collapsed nav out of the tab order and the\n accessibility tree while it stays mounted for the drag handle. */}\n <div\n data-slot=\"app-shell-simple-nav-content\"\n className=\"h-full w-full overflow-hidden\"\n inert={isHidden}\n >\n <DataAppShellPrimaryNav\n variant={navState === \"rail\" ? \"rail\" : \"sidebar\"}\n aria-label=\"Application navigation\"\n navGroups={navGroups}\n activeKey={activeKey}\n onSelect={onSelect}\n user={userMenu}\n className=\"h-full w-full\"\n />\n </div>\n {/* Drag handle on the right border — snaps between the nav states.\n Also a keyboard-operable window splitter (arrows / Home / End). */}\n <div\n role=\"separator\"\n tabIndex={0}\n aria-orientation=\"vertical\"\n aria-label=\"Resize navigation\"\n aria-controls={navId}\n aria-valuemin={0}\n aria-valuemax={SIDEBAR_WIDTH}\n aria-valuenow={NAV_WIDTH[navState]}\n aria-valuetext={navState}\n onKeyDown={onHandleKeyDown}\n onPointerDown={(e) => {\n e.preventDefault();\n e.currentTarget.setPointerCapture(e.pointerId);\n setResizing(true);\n }}\n onPointerMove={(e) => {\n if (!resizing) return;\n // Defensive: a lost capture can strand `resizing` on, which would\n // otherwise turn a plain hover into a resize.\n if (e.buttons === 0) {\n setResizing(false);\n return;\n }\n snapToPointer(e.clientX);\n }}\n onPointerUp={() => setResizing(false)}\n onPointerCancel={() => setResizing(false)}\n onLostPointerCapture={() => setResizing(false)}\n className={cn(\n \"absolute top-0 right-0 z-10 h-full translate-x-1/2 cursor-col-resize transition-colors hover:bg-primary/40 focus-visible:outline-none focus-visible:bg-primary/60 focus-visible:ring-2 focus-visible:ring-ring\",\n isHidden ? \"w-3\" : \"w-1.5\",\n resizing && \"bg-primary/60\"\n )}\n />\n </div>\n )}\n\n {/* Body */}\n <main\n data-slot=\"app-shell-simple-content\"\n className=\"[grid-area:body] min-h-0 min-w-0 overflow-auto bg-background\"\n >\n {children}\n </main>\n </div>\n );\n}\n"],"names":["NAV_ORDER","LAST_NAV","dirFor","index","indexOfNav","state","RAIL_WIDTH","SIDEBAR_WIDTH","RAIL_SNAP","HIDE_SNAP","NAV_WIDTH","snapNav","width","nextNav","dir","forward","reversed","navToggleLabel","from","to","EMPTY_NAV_GROUPS","ShellBreadcrumb","items","jsx","Breadcrumb","BreadcrumbList","item","isLast","isClickable","jsxs","React","BreadcrumbSeparator","BreadcrumbItem","BreadcrumbPage","BreadcrumbLink","SideNavToggle","label","onCycle","TooltipProvider","Tooltip","TooltipTrigger","Button","PanelLeft","TooltipContent","AppShellSimple","showNav","navGroups","activeKey","onSelect","breadcrumbs","headerActions","userMenu","defaultNav","navProp","onNavChange","children","cursor","setCursor","navState","applyNav","next","nextCursor","cycleNav","nextLabel","navId","navRef","resizing","setResizing","snapToPointer","clientX","stepNav","delta","target","onHandleKeyDown","e","action","isHidden","cn","TopBar","Fragment","DataAppShellPrimaryNav"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppShellSimple.cjs","sources":["../../../../src/components/composed/AppShellSimple/AppShellSimple.tsx"],"sourcesContent":["/**\n * AppShellSimple (SW-2410) — a Data App Shell stripped down to just two zones:\n * a full-width **top bar** and a three-state **side nav**. For apps that don't\n * need the full `DataAppShell` (no secondary nav, right panel, horizontal nav\n * variant, or mobile Sheet).\n *\n * - The side-nav control **lives in the top bar** (far left, above the nav).\n * It ping-pongs the nav through three states — labels ↔ icons ↔ hidden —\n * and dragging the nav's right border snaps between the same three.\n * - The **breadcrumb is the top bar name** — it sits right after the toggle.\n *\n * Composes existing kit primitives (`TopBar`, `DataAppShellPrimaryNav`,\n * `Breadcrumb`, `Button`, `Tooltip`).\n */\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { WithBuildInfoFooter } from \"@/components/composed/BuildInfoFooter/BuildInfoFooter\";\nimport {\n DataAppShellPrimaryNav,\n type NavGroup,\n type NavPage,\n} from \"@/components/composed/DataAppShell\";\nimport { TopBar } from \"@/components/composed/TopBar\";\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AppShellSimpleCrumb {\n /** Display label */\n label: string;\n /** If set, the crumb renders as a link */\n href?: string;\n /** Click handler when there's no `href` */\n onClick?: () => void;\n}\n\n/**\n * The side nav has three states. The top-bar toggle ping-pongs between them,\n * and dragging the nav's right border snaps between them:\n * - `sidebar` — full panel with icon + label rows\n * - `rail` — icon-only rail\n * - `hidden` — no side nav; only the top bar + content\n */\nexport type AppShellSimpleNavState = \"sidebar\" | \"rail\" | \"hidden\";\n\n/**\n * Where the nav sits in the ping-pong: which state (`index` into `NAV_ORDER`)\n * and which way the next toggle click moves (`dir`, reversing at either end).\n */\ntype NavCursor = { index: number; dir: 1 | -1 };\n\n// Ping-pong order (does not wrap): labels ↔ icons ↔ hidden.\nconst NAV_ORDER: AppShellSimpleNavState[] = [\"sidebar\", \"rail\", \"hidden\"];\nconst LAST_NAV = NAV_ORDER.length - 1;\n\n// Single source of truth for the direction convention: point inward from the\n// ends so the next toggle keeps the ping-pong moving (middle defaults forward).\nconst dirFor = (index: number): 1 | -1 => (index >= LAST_NAV ? -1 : 1);\n\nconst indexOfNav = (state: AppShellSimpleNavState) => Math.max(0, NAV_ORDER.indexOf(state));\n\n// Fixed widths per state — the border drag snaps between these; it never sets\n// an in-between width.\nconst RAIL_WIDTH = 48;\nconst SIDEBAR_WIDTH = 220;\n// Snap thresholds: midpoints between the fixed widths.\nconst RAIL_SNAP = (RAIL_WIDTH + SIDEBAR_WIDTH) / 2;\nconst HIDE_SNAP = RAIL_WIDTH / 2;\n\nconst NAV_WIDTH: Record<AppShellSimpleNavState, number> = {\n sidebar: SIDEBAR_WIDTH,\n rail: RAIL_WIDTH,\n hidden: 0,\n};\n\n/** Map a dragged pixel width to the nearest nav state. */\nfunction snapNav(width: number): NavCursor {\n const index = width >= RAIL_SNAP ? 0 : width >= HIDE_SNAP ? 1 : LAST_NAV;\n return { index, dir: dirFor(index) };\n}\n\n/** Next cursor in the ping-pong, reversing direction at either end. */\nfunction nextNav({ index, dir }: NavCursor): NavCursor {\n const forward = index + dir;\n if (forward < 0 || forward > LAST_NAV) {\n const reversed = (dir * -1) as 1 | -1;\n return { index: index + reversed, dir: reversed };\n }\n return { index: forward, dir };\n}\n\n/**\n * Label for the toggle, describing the transition the next click performs.\n * `rail` is reachable from both directions, so it can't be keyed on the\n * destination alone — arriving from `hidden` is an expand, not a collapse.\n */\nfunction navToggleLabel(from: number, to: number): string {\n switch (NAV_ORDER[to]) {\n case \"sidebar\":\n return \"Expand navigation to labels\";\n case \"hidden\":\n return \"Hide navigation\";\n default:\n return to > from ? \"Collapse navigation to icons\" : \"Expand navigation to icons\";\n }\n}\n\nexport interface AppShellSimpleProps {\n /**\n * Whether the app has a side nav at all (default `true`). When `false`, the\n * shell renders as a top bar + full-width body: no nav zone, and no top-bar\n * toggle. This is the app-level switch — distinct from the runtime `hidden`\n * nav state, which the end user can toggle back open. `navGroups` is ignored\n * when `showNav` is `false`.\n */\n showNav?: boolean;\n /** Nav groups rendered in the side nav (ignored when `showNav` is `false`) */\n navGroups?: NavGroup[];\n /** Controlled active page id — takes precedence over per-page `isActive` */\n activeKey?: string;\n /** Called with the selected page id after the page's own `onClick` */\n onSelect?: (key: string, page: NavPage) => void;\n /** Breadcrumb trail — the top bar name */\n breadcrumbs: AppShellSimpleCrumb[];\n /** Right-side top bar actions (e.g. notifications, user menu) */\n headerActions?: React.ReactNode;\n /** Bottom-of-nav user slot */\n userMenu?: React.ReactNode;\n /** Uncontrolled initial nav state (default `sidebar`) */\n defaultNav?: AppShellSimpleNavState;\n /** Controlled nav state — pair with `onNavChange`; overrides `defaultNav` */\n nav?: AppShellSimpleNavState;\n /** Fired whenever the nav state changes (toggle, drag, or keyboard resize) */\n onNavChange?: (nav: AppShellSimpleNavState) => void;\n /** App version, shown in the build-info footer (rendered when this or `commitSha` is set) */\n version?: string;\n /** Short commit SHA, shown in the build-info footer */\n commitSha?: string;\n /** Main content */\n children: React.ReactNode;\n}\n\nconst EMPTY_NAV_GROUPS: NavGroup[] = [];\n\n// =============================================================================\n// Breadcrumb trail — the top bar name\n// =============================================================================\n\nfunction ShellBreadcrumb({ items }: { items: AppShellSimpleCrumb[] }) {\n return (\n <Breadcrumb className=\"min-w-0\">\n <BreadcrumbList className=\"flex-nowrap min-w-0\">\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n const isClickable = !isLast && (!!item.href || !!item.onClick);\n return (\n <React.Fragment key={`${item.label}-${index}`}>\n {index > 0 && <BreadcrumbSeparator className=\"shrink-0\">/</BreadcrumbSeparator>}\n <BreadcrumbItem className={isLast ? \"min-w-0\" : \"shrink-0\"}>\n {isLast ? (\n <BreadcrumbPage className=\"truncate font-medium\">{item.label}</BreadcrumbPage>\n ) : isClickable && item.href ? (\n <BreadcrumbLink href={item.href} className=\"whitespace-nowrap text-primary\">\n {item.label}\n </BreadcrumbLink>\n ) : isClickable && item.onClick ? (\n <BreadcrumbLink asChild className=\"whitespace-nowrap text-primary\">\n <button type=\"button\" className=\"cursor-pointer\" onClick={item.onClick}>\n {item.label}\n </button>\n </BreadcrumbLink>\n ) : (\n <span className=\"whitespace-nowrap\">{item.label}</span>\n )}\n </BreadcrumbItem>\n </React.Fragment>\n );\n })}\n </BreadcrumbList>\n </Breadcrumb>\n );\n}\n\n// =============================================================================\n// Top-bar side-nav toggle — the collapse/expand control (SW-2410).\n// =============================================================================\n\nfunction SideNavToggle({ label, onCycle }: { label: string; onCycle: () => void }) {\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n data-slot=\"app-shell-simple-nav-toggle\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={label}\n // size-8 (32px) so the hover highlight box matches the nav icon box;\n // -ml-1 centers the icon on the nav's 24px gutter (TopBar has px-3);\n // hover uses sidebar-accent (not the ghost default accent) to match the\n // nav-row hover on the shared sidebar surface; no press inset.\n className=\"-ml-1 shrink-0 text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground active:translate-y-0\"\n onClick={onCycle}\n >\n <PanelLeft className=\"size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{label}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n}\n\n// =============================================================================\n// AppShellSimple — two zones: full-width top bar + collapsible side nav\n// =============================================================================\n\nexport function AppShellSimple({\n showNav = true,\n navGroups = EMPTY_NAV_GROUPS,\n activeKey,\n onSelect,\n breadcrumbs,\n headerActions,\n userMenu,\n defaultNav = \"sidebar\",\n nav: navProp,\n onNavChange,\n version,\n commitSha,\n children,\n}: AppShellSimpleProps) {\n // The cursor is always local: `index` is only authoritative while\n // uncontrolled, but `dir` (which way the ping-pong is travelling) has no\n // prop equivalent and is tracked here in both modes.\n const [cursor, setCursor] = React.useState<NavCursor>(() => {\n const index = indexOfNav(navProp ?? defaultNav);\n return { index, dir: dirFor(index) };\n });\n\n const index = navProp === undefined ? cursor.index : indexOfNav(navProp);\n // A controlled index moved by the parent invalidates our stored direction.\n const dir = cursor.index === index ? cursor.dir : dirFor(index);\n const navState = NAV_ORDER[index];\n\n const applyNav = React.useCallback(\n (next: NavCursor) => {\n setCursor(next);\n if (next.index !== index) onNavChange?.(NAV_ORDER[next.index]);\n },\n [index, onNavChange]\n );\n\n const nextCursor = nextNav({ index, dir });\n const cycleNav = () => applyNav(nextCursor);\n // Label describes the transition the next click performs.\n const nextLabel = navToggleLabel(index, nextCursor.index);\n\n // Drag the border to snap between the three states (widest → slimmest:\n // icon + text → icon rail → fully closed). Pointer capture keeps the drag\n // scoped to the handle — no window listeners to leak.\n //\n // The nav zone stays mounted at `width: 0` in the `hidden` state rather than\n // unmounting: unmounting mid-drag destroyed the element holding pointer\n // capture, so `pointerup` never came back and `resizing` stuck on. Keeping it\n // mounted also makes the handle draggable back out of `hidden`.\n const navId = React.useId();\n const navRef = React.useRef<HTMLDivElement>(null);\n const [resizing, setResizing] = React.useState(false);\n const snapToPointer = (clientX: number) =>\n applyNav(snapNav(clientX - (navRef.current?.getBoundingClientRect().left ?? 0)));\n\n const stepNav = (delta: 1 | -1) => {\n const target = Math.min(LAST_NAV, Math.max(0, index + delta));\n if (target !== index) applyNav({ index: target, dir: dirFor(target) });\n };\n\n const onHandleKeyDown = (e: React.KeyboardEvent) => {\n // ← narrower · → wider, matching the on-screen direction of the drag\n // (the arrow moves the nav's right border the same way).\n const move: Record<string, () => void> = {\n ArrowLeft: () => stepNav(1),\n ArrowRight: () => stepNav(-1),\n Home: () => applyNav({ index: 0, dir: dirFor(0) }),\n End: () => applyNav({ index: LAST_NAV, dir: dirFor(LAST_NAV) }),\n };\n const action = move[e.key];\n if (!action) return;\n e.preventDefault();\n action();\n };\n\n const isHidden = navState === \"hidden\";\n\n return (\n <div\n data-slot=\"app-shell-simple\"\n data-nav-state={navState}\n className={cn(\n \"grid h-screen w-full overflow-hidden [grid-template-rows:auto_minmax(0,1fr)]\",\n showNav\n ? \"[grid-template-columns:auto_minmax(0,1fr)] [grid-template-areas:'top_top'_'nav_body']\"\n : \"[grid-template-columns:minmax(0,1fr)] [grid-template-areas:'top'_'body']\",\n resizing && \"select-none cursor-col-resize\"\n )}\n >\n {/* Top bar */}\n <div className=\"[grid-area:top] min-w-0\">\n <TopBar\n left={\n <>\n {showNav && <SideNavToggle label={nextLabel} onCycle={cycleNav} />}\n <ShellBreadcrumb items={breadcrumbs} />\n </>\n }\n right={headerActions}\n />\n </div>\n\n {/* Side nav — sidebar (labels), rail (icons), or collapsed to zero width\n when hidden; the toggle ping-pongs and the border drag snaps between\n states. Omitted entirely when the app opts out (`showNav={false}`). */}\n {showNav && (\n <div\n ref={navRef}\n id={navId}\n data-slot=\"app-shell-simple-nav\"\n data-nav-state={navState}\n className={cn(\n \"[grid-area:nav] relative min-h-0 flex flex-col shrink-0 bg-sidebar h-full transition-[width] duration-200\",\n isHidden ? \"overflow-hidden\" : \"border-r border-sidebar-border\"\n )}\n style={{ width: NAV_WIDTH[navState] }}\n >\n {/* `inert` keeps the collapsed nav out of the tab order and the\n accessibility tree while it stays mounted for the drag handle. */}\n <div\n data-slot=\"app-shell-simple-nav-content\"\n className=\"h-full w-full overflow-hidden\"\n inert={isHidden}\n >\n <DataAppShellPrimaryNav\n variant={navState === \"rail\" ? \"rail\" : \"sidebar\"}\n aria-label=\"Application navigation\"\n navGroups={navGroups}\n activeKey={activeKey}\n onSelect={onSelect}\n user={userMenu}\n className=\"h-full w-full\"\n />\n </div>\n {/* Drag handle on the right border — snaps between the nav states.\n Also a keyboard-operable window splitter (arrows / Home / End). */}\n <div\n role=\"separator\"\n tabIndex={0}\n aria-orientation=\"vertical\"\n aria-label=\"Resize navigation\"\n aria-controls={navId}\n aria-valuemin={0}\n aria-valuemax={SIDEBAR_WIDTH}\n aria-valuenow={NAV_WIDTH[navState]}\n aria-valuetext={navState}\n onKeyDown={onHandleKeyDown}\n onPointerDown={(e) => {\n e.preventDefault();\n e.currentTarget.setPointerCapture(e.pointerId);\n setResizing(true);\n }}\n onPointerMove={(e) => {\n if (!resizing) return;\n // Defensive: a lost capture can strand `resizing` on, which would\n // otherwise turn a plain hover into a resize.\n if (e.buttons === 0) {\n setResizing(false);\n return;\n }\n snapToPointer(e.clientX);\n }}\n onPointerUp={() => setResizing(false)}\n onPointerCancel={() => setResizing(false)}\n onLostPointerCapture={() => setResizing(false)}\n className={cn(\n \"absolute top-0 right-0 z-10 h-full translate-x-1/2 cursor-col-resize transition-colors hover:bg-primary/40 focus-visible:outline-none focus-visible:bg-primary/60 focus-visible:ring-2 focus-visible:ring-ring\",\n isHidden ? \"w-3\" : \"w-1.5\",\n resizing && \"bg-primary/60\"\n )}\n />\n </div>\n )}\n\n {/* Body */}\n <main\n data-slot=\"app-shell-simple-content\"\n className=\"[grid-area:body] min-h-0 min-w-0 overflow-auto bg-background\"\n >\n <WithBuildInfoFooter show={Boolean(version || commitSha)} version={version} commitSha={commitSha}>\n {children}\n </WithBuildInfoFooter>\n </main>\n </div>\n );\n}\n"],"names":["NAV_ORDER","LAST_NAV","dirFor","index","indexOfNav","state","RAIL_WIDTH","SIDEBAR_WIDTH","RAIL_SNAP","HIDE_SNAP","NAV_WIDTH","snapNav","width","nextNav","dir","forward","reversed","navToggleLabel","from","to","EMPTY_NAV_GROUPS","ShellBreadcrumb","items","jsx","Breadcrumb","BreadcrumbList","item","isLast","isClickable","jsxs","React","BreadcrumbSeparator","BreadcrumbItem","BreadcrumbPage","BreadcrumbLink","SideNavToggle","label","onCycle","TooltipProvider","Tooltip","TooltipTrigger","Button","PanelLeft","TooltipContent","AppShellSimple","showNav","navGroups","activeKey","onSelect","breadcrumbs","headerActions","userMenu","defaultNav","navProp","onNavChange","version","commitSha","children","cursor","setCursor","navState","applyNav","next","nextCursor","cycleNav","nextLabel","navId","navRef","resizing","setResizing","snapToPointer","clientX","stepNav","delta","target","onHandleKeyDown","e","action","isHidden","cn","TopBar","Fragment","DataAppShellPrimaryNav","WithBuildInfoFooter"],"mappings":"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"}
|
|
@@ -43,7 +43,11 @@ export interface AppShellSimpleProps {
|
|
|
43
43
|
nav?: AppShellSimpleNavState;
|
|
44
44
|
/** Fired whenever the nav state changes (toggle, drag, or keyboard resize) */
|
|
45
45
|
onNavChange?: (nav: AppShellSimpleNavState) => void;
|
|
46
|
+
/** App version, shown in the build-info footer (rendered when this or `commitSha` is set) */
|
|
47
|
+
version?: string;
|
|
48
|
+
/** Short commit SHA, shown in the build-info footer */
|
|
49
|
+
commitSha?: string;
|
|
46
50
|
/** Main content */
|
|
47
51
|
children: React.ReactNode;
|
|
48
52
|
}
|
|
49
|
-
export declare function AppShellSimple({ showNav, navGroups, activeKey, onSelect, breadcrumbs, headerActions, userMenu, defaultNav, nav: navProp, onNavChange, children, }: AppShellSimpleProps): import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
export declare function AppShellSimple({ showNav, navGroups, activeKey, onSelect, breadcrumbs, headerActions, userMenu, defaultNav, nav: navProp, onNavChange, version, commitSha, children, }: AppShellSimpleProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { jsxs as p, jsx as r, Fragment as
|
|
2
|
-
import { PanelLeft as
|
|
1
|
+
import { jsxs as p, jsx as r, Fragment as U } from "react/jsx-runtime";
|
|
2
|
+
import { PanelLeft as $ } from "lucide-react";
|
|
3
3
|
import * as s from "react";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { WithBuildInfoFooter as G } from "../BuildInfoFooter/BuildInfoFooter.js";
|
|
5
|
+
import { DataAppShellPrimaryNav as X } from "../DataAppShell/PrimaryNav.js";
|
|
6
|
+
import { TopBar as Y } from "../TopBar/TopBar.js";
|
|
7
|
+
import { Breadcrumb as q, BreadcrumbList as J, BreadcrumbSeparator as Q, BreadcrumbItem as Z, BreadcrumbPage as ee, BreadcrumbLink as S } from "../../../ui/breadcrumb.js";
|
|
8
|
+
import { Button as re } from "../../../ui/button.js";
|
|
9
|
+
import { TooltipProvider as ae, Tooltip as ne, TooltipTrigger as te, TooltipContent as ie } from "../../../ui/tooltip.js";
|
|
9
10
|
import { cn as N } from "../../../lib/utils.js";
|
|
10
|
-
const f = ["sidebar", "rail", "hidden"], d = f.length - 1, c = (a) => a >= d ? -1 : 1, D = (a) => Math.max(0, f.indexOf(a)), y = 48, C = 220,
|
|
11
|
+
const f = ["sidebar", "rail", "hidden"], d = f.length - 1, c = (a) => a >= d ? -1 : 1, D = (a) => Math.max(0, f.indexOf(a)), y = 48, C = 220, oe = (y + C) / 2, le = y / 2, L = {
|
|
11
12
|
sidebar: C,
|
|
12
13
|
rail: y,
|
|
13
14
|
hidden: 0
|
|
14
15
|
};
|
|
15
|
-
function
|
|
16
|
-
const e = a >=
|
|
16
|
+
function se(a) {
|
|
17
|
+
const e = a >= oe ? 0 : a >= le ? 1 : d;
|
|
17
18
|
return { index: e, dir: c(e) };
|
|
18
19
|
}
|
|
19
|
-
function
|
|
20
|
+
function ce({ index: a, dir: e }) {
|
|
20
21
|
const t = a + e;
|
|
21
22
|
if (t < 0 || t > d) {
|
|
22
23
|
const o = e * -1;
|
|
@@ -24,7 +25,7 @@ function oe({ index: a, dir: e }) {
|
|
|
24
25
|
}
|
|
25
26
|
return { index: t, dir: e };
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
+
function de(a, e) {
|
|
28
29
|
switch (f[e]) {
|
|
29
30
|
case "sidebar":
|
|
30
31
|
return "Expand navigation to labels";
|
|
@@ -34,20 +35,20 @@ function le(a, e) {
|
|
|
34
35
|
return e > a ? "Collapse navigation to icons" : "Expand navigation to icons";
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
const
|
|
38
|
-
function
|
|
39
|
-
return /* @__PURE__ */ r(
|
|
38
|
+
const ue = [];
|
|
39
|
+
function me({ items: a }) {
|
|
40
|
+
return /* @__PURE__ */ r(q, { className: "min-w-0", children: /* @__PURE__ */ r(J, { className: "flex-nowrap min-w-0", children: a.map((e, t) => {
|
|
40
41
|
const o = t === a.length - 1, h = !o && (!!e.href || !!e.onClick);
|
|
41
42
|
return /* @__PURE__ */ p(s.Fragment, { children: [
|
|
42
|
-
t > 0 && /* @__PURE__ */ r(
|
|
43
|
-
/* @__PURE__ */ r(
|
|
43
|
+
t > 0 && /* @__PURE__ */ r(Q, { className: "shrink-0", children: "/" }),
|
|
44
|
+
/* @__PURE__ */ r(Z, { className: o ? "min-w-0" : "shrink-0", children: o ? /* @__PURE__ */ r(ee, { className: "truncate font-medium", children: e.label }) : h && e.href ? /* @__PURE__ */ r(S, { href: e.href, className: "whitespace-nowrap text-primary", children: e.label }) : h && e.onClick ? /* @__PURE__ */ r(S, { asChild: !0, className: "whitespace-nowrap text-primary", children: /* @__PURE__ */ r("button", { type: "button", className: "cursor-pointer", onClick: e.onClick, children: e.label }) }) : /* @__PURE__ */ r("span", { className: "whitespace-nowrap", children: e.label }) })
|
|
44
45
|
] }, `${e.label}-${t}`);
|
|
45
46
|
}) }) });
|
|
46
47
|
}
|
|
47
|
-
function
|
|
48
|
-
return /* @__PURE__ */ r(
|
|
49
|
-
/* @__PURE__ */ r(
|
|
50
|
-
|
|
48
|
+
function pe({ label: a, onCycle: e }) {
|
|
49
|
+
return /* @__PURE__ */ r(ae, { children: /* @__PURE__ */ p(ne, { children: [
|
|
50
|
+
/* @__PURE__ */ r(te, { asChild: !0, children: /* @__PURE__ */ r(
|
|
51
|
+
re,
|
|
51
52
|
{
|
|
52
53
|
"data-slot": "app-shell-simple-nav-toggle",
|
|
53
54
|
variant: "ghost",
|
|
@@ -55,44 +56,46 @@ function de({ label: a, onCycle: e }) {
|
|
|
55
56
|
"aria-label": a,
|
|
56
57
|
className: "-ml-1 shrink-0 text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground active:translate-y-0",
|
|
57
58
|
onClick: e,
|
|
58
|
-
children: /* @__PURE__ */ r(
|
|
59
|
+
children: /* @__PURE__ */ r($, { className: "size-4" })
|
|
59
60
|
}
|
|
60
61
|
) }),
|
|
61
|
-
/* @__PURE__ */ r(
|
|
62
|
+
/* @__PURE__ */ r(ie, { side: "bottom", children: a })
|
|
62
63
|
] }) });
|
|
63
64
|
}
|
|
64
|
-
function
|
|
65
|
+
function Ce({
|
|
65
66
|
showNav: a = !0,
|
|
66
|
-
navGroups: e =
|
|
67
|
+
navGroups: e = ue,
|
|
67
68
|
activeKey: t,
|
|
68
69
|
onSelect: o,
|
|
69
70
|
breadcrumbs: h,
|
|
70
|
-
headerActions:
|
|
71
|
-
userMenu:
|
|
72
|
-
defaultNav:
|
|
71
|
+
headerActions: z,
|
|
72
|
+
userMenu: H,
|
|
73
|
+
defaultNav: E = "sidebar",
|
|
73
74
|
nav: b,
|
|
74
75
|
onNavChange: A,
|
|
75
|
-
|
|
76
|
+
version: T,
|
|
77
|
+
commitSha: _,
|
|
78
|
+
children: M
|
|
76
79
|
}) {
|
|
77
|
-
const [g,
|
|
78
|
-
const n = D(b ??
|
|
80
|
+
const [g, F] = s.useState(() => {
|
|
81
|
+
const n = D(b ?? E);
|
|
79
82
|
return { index: n, dir: c(n) };
|
|
80
|
-
}), i = b === void 0 ? g.index : D(b),
|
|
83
|
+
}), i = b === void 0 ? g.index : D(b), O = g.index === i ? g.dir : c(i), l = f[i], u = s.useCallback(
|
|
81
84
|
(n) => {
|
|
82
|
-
|
|
85
|
+
F(n), n.index !== i && A?.(f[n.index]);
|
|
83
86
|
},
|
|
84
87
|
[i, A]
|
|
85
|
-
),
|
|
88
|
+
), k = ce({ index: i, dir: O }), V = () => u(k), W = de(i, k.index), B = s.useId(), I = s.useRef(null), [x, m] = s.useState(!1), j = (n) => u(se(n - (I.current?.getBoundingClientRect().left ?? 0))), P = (n) => {
|
|
86
89
|
const v = Math.min(d, Math.max(0, i + n));
|
|
87
90
|
v !== i && u({ index: v, dir: c(v) });
|
|
88
|
-
},
|
|
89
|
-
const
|
|
90
|
-
ArrowLeft: () =>
|
|
91
|
-
ArrowRight: () =>
|
|
91
|
+
}, K = (n) => {
|
|
92
|
+
const R = {
|
|
93
|
+
ArrowLeft: () => P(1),
|
|
94
|
+
ArrowRight: () => P(-1),
|
|
92
95
|
Home: () => u({ index: 0, dir: c(0) }),
|
|
93
96
|
End: () => u({ index: d, dir: c(d) })
|
|
94
97
|
}[n.key];
|
|
95
|
-
|
|
98
|
+
R && (n.preventDefault(), R());
|
|
96
99
|
}, w = l === "hidden";
|
|
97
100
|
return /* @__PURE__ */ p(
|
|
98
101
|
"div",
|
|
@@ -106,27 +109,27 @@ function xe({
|
|
|
106
109
|
),
|
|
107
110
|
children: [
|
|
108
111
|
/* @__PURE__ */ r("div", { className: "[grid-area:top] min-w-0", children: /* @__PURE__ */ r(
|
|
109
|
-
|
|
112
|
+
Y,
|
|
110
113
|
{
|
|
111
|
-
left: /* @__PURE__ */ p(
|
|
112
|
-
a && /* @__PURE__ */ r(
|
|
113
|
-
/* @__PURE__ */ r(
|
|
114
|
+
left: /* @__PURE__ */ p(U, { children: [
|
|
115
|
+
a && /* @__PURE__ */ r(pe, { label: W, onCycle: V }),
|
|
116
|
+
/* @__PURE__ */ r(me, { items: h })
|
|
114
117
|
] }),
|
|
115
|
-
right:
|
|
118
|
+
right: z
|
|
116
119
|
}
|
|
117
120
|
) }),
|
|
118
121
|
a && /* @__PURE__ */ p(
|
|
119
122
|
"div",
|
|
120
123
|
{
|
|
121
|
-
ref:
|
|
122
|
-
id:
|
|
124
|
+
ref: I,
|
|
125
|
+
id: B,
|
|
123
126
|
"data-slot": "app-shell-simple-nav",
|
|
124
127
|
"data-nav-state": l,
|
|
125
128
|
className: N(
|
|
126
129
|
"[grid-area:nav] relative min-h-0 flex flex-col shrink-0 bg-sidebar h-full transition-[width] duration-200",
|
|
127
130
|
w ? "overflow-hidden" : "border-r border-sidebar-border"
|
|
128
131
|
),
|
|
129
|
-
style: { width:
|
|
132
|
+
style: { width: L[l] },
|
|
130
133
|
children: [
|
|
131
134
|
/* @__PURE__ */ r(
|
|
132
135
|
"div",
|
|
@@ -135,14 +138,14 @@ function xe({
|
|
|
135
138
|
className: "h-full w-full overflow-hidden",
|
|
136
139
|
inert: w,
|
|
137
140
|
children: /* @__PURE__ */ r(
|
|
138
|
-
|
|
141
|
+
X,
|
|
139
142
|
{
|
|
140
143
|
variant: l === "rail" ? "rail" : "sidebar",
|
|
141
144
|
"aria-label": "Application navigation",
|
|
142
145
|
navGroups: e,
|
|
143
146
|
activeKey: t,
|
|
144
147
|
onSelect: o,
|
|
145
|
-
user:
|
|
148
|
+
user: H,
|
|
146
149
|
className: "h-full w-full"
|
|
147
150
|
}
|
|
148
151
|
)
|
|
@@ -155,12 +158,12 @@ function xe({
|
|
|
155
158
|
tabIndex: 0,
|
|
156
159
|
"aria-orientation": "vertical",
|
|
157
160
|
"aria-label": "Resize navigation",
|
|
158
|
-
"aria-controls":
|
|
161
|
+
"aria-controls": B,
|
|
159
162
|
"aria-valuemin": 0,
|
|
160
163
|
"aria-valuemax": C,
|
|
161
|
-
"aria-valuenow":
|
|
164
|
+
"aria-valuenow": L[l],
|
|
162
165
|
"aria-valuetext": l,
|
|
163
|
-
onKeyDown:
|
|
166
|
+
onKeyDown: K,
|
|
164
167
|
onPointerDown: (n) => {
|
|
165
168
|
n.preventDefault(), n.currentTarget.setPointerCapture(n.pointerId), m(!0);
|
|
166
169
|
},
|
|
@@ -170,7 +173,7 @@ function xe({
|
|
|
170
173
|
m(!1);
|
|
171
174
|
return;
|
|
172
175
|
}
|
|
173
|
-
|
|
176
|
+
j(n.clientX);
|
|
174
177
|
}
|
|
175
178
|
},
|
|
176
179
|
onPointerUp: () => m(!1),
|
|
@@ -191,7 +194,7 @@ function xe({
|
|
|
191
194
|
{
|
|
192
195
|
"data-slot": "app-shell-simple-content",
|
|
193
196
|
className: "[grid-area:body] min-h-0 min-w-0 overflow-auto bg-background",
|
|
194
|
-
children:
|
|
197
|
+
children: /* @__PURE__ */ r(G, { show: !!(T || _), version: T, commitSha: _, children: M })
|
|
195
198
|
}
|
|
196
199
|
)
|
|
197
200
|
]
|
|
@@ -199,6 +202,6 @@ function xe({
|
|
|
199
202
|
);
|
|
200
203
|
}
|
|
201
204
|
export {
|
|
202
|
-
|
|
205
|
+
Ce as AppShellSimple
|
|
203
206
|
};
|
|
204
207
|
//# sourceMappingURL=AppShellSimple.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShellSimple.js","sources":["../../../../src/components/composed/AppShellSimple/AppShellSimple.tsx"],"sourcesContent":["/**\n * AppShellSimple (SW-2410) — a Data App Shell stripped down to just two zones:\n * a full-width **top bar** and a three-state **side nav**. For apps that don't\n * need the full `DataAppShell` (no secondary nav, right panel, horizontal nav\n * variant, or mobile Sheet).\n *\n * - The side-nav control **lives in the top bar** (far left, above the nav).\n * It ping-pongs the nav through three states — labels ↔ icons ↔ hidden —\n * and dragging the nav's right border snaps between the same three.\n * - The **breadcrumb is the top bar name** — it sits right after the toggle.\n *\n * Composes existing kit primitives (`TopBar`, `DataAppShellPrimaryNav`,\n * `Breadcrumb`, `Button`, `Tooltip`).\n */\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n DataAppShellPrimaryNav,\n type NavGroup,\n type NavPage,\n} from \"@/components/composed/DataAppShell\";\nimport { TopBar } from \"@/components/composed/TopBar\";\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AppShellSimpleCrumb {\n /** Display label */\n label: string;\n /** If set, the crumb renders as a link */\n href?: string;\n /** Click handler when there's no `href` */\n onClick?: () => void;\n}\n\n/**\n * The side nav has three states. The top-bar toggle ping-pongs between them,\n * and dragging the nav's right border snaps between them:\n * - `sidebar` — full panel with icon + label rows\n * - `rail` — icon-only rail\n * - `hidden` — no side nav; only the top bar + content\n */\nexport type AppShellSimpleNavState = \"sidebar\" | \"rail\" | \"hidden\";\n\n/**\n * Where the nav sits in the ping-pong: which state (`index` into `NAV_ORDER`)\n * and which way the next toggle click moves (`dir`, reversing at either end).\n */\ntype NavCursor = { index: number; dir: 1 | -1 };\n\n// Ping-pong order (does not wrap): labels ↔ icons ↔ hidden.\nconst NAV_ORDER: AppShellSimpleNavState[] = [\"sidebar\", \"rail\", \"hidden\"];\nconst LAST_NAV = NAV_ORDER.length - 1;\n\n// Single source of truth for the direction convention: point inward from the\n// ends so the next toggle keeps the ping-pong moving (middle defaults forward).\nconst dirFor = (index: number): 1 | -1 => (index >= LAST_NAV ? -1 : 1);\n\nconst indexOfNav = (state: AppShellSimpleNavState) => Math.max(0, NAV_ORDER.indexOf(state));\n\n// Fixed widths per state — the border drag snaps between these; it never sets\n// an in-between width.\nconst RAIL_WIDTH = 48;\nconst SIDEBAR_WIDTH = 220;\n// Snap thresholds: midpoints between the fixed widths.\nconst RAIL_SNAP = (RAIL_WIDTH + SIDEBAR_WIDTH) / 2;\nconst HIDE_SNAP = RAIL_WIDTH / 2;\n\nconst NAV_WIDTH: Record<AppShellSimpleNavState, number> = {\n sidebar: SIDEBAR_WIDTH,\n rail: RAIL_WIDTH,\n hidden: 0,\n};\n\n/** Map a dragged pixel width to the nearest nav state. */\nfunction snapNav(width: number): NavCursor {\n const index = width >= RAIL_SNAP ? 0 : width >= HIDE_SNAP ? 1 : LAST_NAV;\n return { index, dir: dirFor(index) };\n}\n\n/** Next cursor in the ping-pong, reversing direction at either end. */\nfunction nextNav({ index, dir }: NavCursor): NavCursor {\n const forward = index + dir;\n if (forward < 0 || forward > LAST_NAV) {\n const reversed = (dir * -1) as 1 | -1;\n return { index: index + reversed, dir: reversed };\n }\n return { index: forward, dir };\n}\n\n/**\n * Label for the toggle, describing the transition the next click performs.\n * `rail` is reachable from both directions, so it can't be keyed on the\n * destination alone — arriving from `hidden` is an expand, not a collapse.\n */\nfunction navToggleLabel(from: number, to: number): string {\n switch (NAV_ORDER[to]) {\n case \"sidebar\":\n return \"Expand navigation to labels\";\n case \"hidden\":\n return \"Hide navigation\";\n default:\n return to > from ? \"Collapse navigation to icons\" : \"Expand navigation to icons\";\n }\n}\n\nexport interface AppShellSimpleProps {\n /**\n * Whether the app has a side nav at all (default `true`). When `false`, the\n * shell renders as a top bar + full-width body: no nav zone, and no top-bar\n * toggle. This is the app-level switch — distinct from the runtime `hidden`\n * nav state, which the end user can toggle back open. `navGroups` is ignored\n * when `showNav` is `false`.\n */\n showNav?: boolean;\n /** Nav groups rendered in the side nav (ignored when `showNav` is `false`) */\n navGroups?: NavGroup[];\n /** Controlled active page id — takes precedence over per-page `isActive` */\n activeKey?: string;\n /** Called with the selected page id after the page's own `onClick` */\n onSelect?: (key: string, page: NavPage) => void;\n /** Breadcrumb trail — the top bar name */\n breadcrumbs: AppShellSimpleCrumb[];\n /** Right-side top bar actions (e.g. notifications, user menu) */\n headerActions?: React.ReactNode;\n /** Bottom-of-nav user slot */\n userMenu?: React.ReactNode;\n /** Uncontrolled initial nav state (default `sidebar`) */\n defaultNav?: AppShellSimpleNavState;\n /** Controlled nav state — pair with `onNavChange`; overrides `defaultNav` */\n nav?: AppShellSimpleNavState;\n /** Fired whenever the nav state changes (toggle, drag, or keyboard resize) */\n onNavChange?: (nav: AppShellSimpleNavState) => void;\n /** Main content */\n children: React.ReactNode;\n}\n\nconst EMPTY_NAV_GROUPS: NavGroup[] = [];\n\n// =============================================================================\n// Breadcrumb trail — the top bar name\n// =============================================================================\n\nfunction ShellBreadcrumb({ items }: { items: AppShellSimpleCrumb[] }) {\n return (\n <Breadcrumb className=\"min-w-0\">\n <BreadcrumbList className=\"flex-nowrap min-w-0\">\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n const isClickable = !isLast && (!!item.href || !!item.onClick);\n return (\n <React.Fragment key={`${item.label}-${index}`}>\n {index > 0 && <BreadcrumbSeparator className=\"shrink-0\">/</BreadcrumbSeparator>}\n <BreadcrumbItem className={isLast ? \"min-w-0\" : \"shrink-0\"}>\n {isLast ? (\n <BreadcrumbPage className=\"truncate font-medium\">{item.label}</BreadcrumbPage>\n ) : isClickable && item.href ? (\n <BreadcrumbLink href={item.href} className=\"whitespace-nowrap text-primary\">\n {item.label}\n </BreadcrumbLink>\n ) : isClickable && item.onClick ? (\n <BreadcrumbLink asChild className=\"whitespace-nowrap text-primary\">\n <button type=\"button\" className=\"cursor-pointer\" onClick={item.onClick}>\n {item.label}\n </button>\n </BreadcrumbLink>\n ) : (\n <span className=\"whitespace-nowrap\">{item.label}</span>\n )}\n </BreadcrumbItem>\n </React.Fragment>\n );\n })}\n </BreadcrumbList>\n </Breadcrumb>\n );\n}\n\n// =============================================================================\n// Top-bar side-nav toggle — the collapse/expand control (SW-2410).\n// =============================================================================\n\nfunction SideNavToggle({ label, onCycle }: { label: string; onCycle: () => void }) {\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n data-slot=\"app-shell-simple-nav-toggle\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={label}\n // size-8 (32px) so the hover highlight box matches the nav icon box;\n // -ml-1 centers the icon on the nav's 24px gutter (TopBar has px-3);\n // hover uses sidebar-accent (not the ghost default accent) to match the\n // nav-row hover on the shared sidebar surface; no press inset.\n className=\"-ml-1 shrink-0 text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground active:translate-y-0\"\n onClick={onCycle}\n >\n <PanelLeft className=\"size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{label}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n}\n\n// =============================================================================\n// AppShellSimple — two zones: full-width top bar + collapsible side nav\n// =============================================================================\n\nexport function AppShellSimple({\n showNav = true,\n navGroups = EMPTY_NAV_GROUPS,\n activeKey,\n onSelect,\n breadcrumbs,\n headerActions,\n userMenu,\n defaultNav = \"sidebar\",\n nav: navProp,\n onNavChange,\n children,\n}: AppShellSimpleProps) {\n // The cursor is always local: `index` is only authoritative while\n // uncontrolled, but `dir` (which way the ping-pong is travelling) has no\n // prop equivalent and is tracked here in both modes.\n const [cursor, setCursor] = React.useState<NavCursor>(() => {\n const index = indexOfNav(navProp ?? defaultNav);\n return { index, dir: dirFor(index) };\n });\n\n const index = navProp === undefined ? cursor.index : indexOfNav(navProp);\n // A controlled index moved by the parent invalidates our stored direction.\n const dir = cursor.index === index ? cursor.dir : dirFor(index);\n const navState = NAV_ORDER[index];\n\n const applyNav = React.useCallback(\n (next: NavCursor) => {\n setCursor(next);\n if (next.index !== index) onNavChange?.(NAV_ORDER[next.index]);\n },\n [index, onNavChange]\n );\n\n const nextCursor = nextNav({ index, dir });\n const cycleNav = () => applyNav(nextCursor);\n // Label describes the transition the next click performs.\n const nextLabel = navToggleLabel(index, nextCursor.index);\n\n // Drag the border to snap between the three states (widest → slimmest:\n // icon + text → icon rail → fully closed). Pointer capture keeps the drag\n // scoped to the handle — no window listeners to leak.\n //\n // The nav zone stays mounted at `width: 0` in the `hidden` state rather than\n // unmounting: unmounting mid-drag destroyed the element holding pointer\n // capture, so `pointerup` never came back and `resizing` stuck on. Keeping it\n // mounted also makes the handle draggable back out of `hidden`.\n const navId = React.useId();\n const navRef = React.useRef<HTMLDivElement>(null);\n const [resizing, setResizing] = React.useState(false);\n const snapToPointer = (clientX: number) =>\n applyNav(snapNav(clientX - (navRef.current?.getBoundingClientRect().left ?? 0)));\n\n const stepNav = (delta: 1 | -1) => {\n const target = Math.min(LAST_NAV, Math.max(0, index + delta));\n if (target !== index) applyNav({ index: target, dir: dirFor(target) });\n };\n\n const onHandleKeyDown = (e: React.KeyboardEvent) => {\n // ← narrower · → wider, matching the on-screen direction of the drag\n // (the arrow moves the nav's right border the same way).\n const move: Record<string, () => void> = {\n ArrowLeft: () => stepNav(1),\n ArrowRight: () => stepNav(-1),\n Home: () => applyNav({ index: 0, dir: dirFor(0) }),\n End: () => applyNav({ index: LAST_NAV, dir: dirFor(LAST_NAV) }),\n };\n const action = move[e.key];\n if (!action) return;\n e.preventDefault();\n action();\n };\n\n const isHidden = navState === \"hidden\";\n\n return (\n <div\n data-slot=\"app-shell-simple\"\n data-nav-state={navState}\n className={cn(\n \"grid h-screen w-full overflow-hidden [grid-template-rows:auto_minmax(0,1fr)]\",\n showNav\n ? \"[grid-template-columns:auto_minmax(0,1fr)] [grid-template-areas:'top_top'_'nav_body']\"\n : \"[grid-template-columns:minmax(0,1fr)] [grid-template-areas:'top'_'body']\",\n resizing && \"select-none cursor-col-resize\"\n )}\n >\n {/* Top bar */}\n <div className=\"[grid-area:top] min-w-0\">\n <TopBar\n left={\n <>\n {showNav && <SideNavToggle label={nextLabel} onCycle={cycleNav} />}\n <ShellBreadcrumb items={breadcrumbs} />\n </>\n }\n right={headerActions}\n />\n </div>\n\n {/* Side nav — sidebar (labels), rail (icons), or collapsed to zero width\n when hidden; the toggle ping-pongs and the border drag snaps between\n states. Omitted entirely when the app opts out (`showNav={false}`). */}\n {showNav && (\n <div\n ref={navRef}\n id={navId}\n data-slot=\"app-shell-simple-nav\"\n data-nav-state={navState}\n className={cn(\n \"[grid-area:nav] relative min-h-0 flex flex-col shrink-0 bg-sidebar h-full transition-[width] duration-200\",\n isHidden ? \"overflow-hidden\" : \"border-r border-sidebar-border\"\n )}\n style={{ width: NAV_WIDTH[navState] }}\n >\n {/* `inert` keeps the collapsed nav out of the tab order and the\n accessibility tree while it stays mounted for the drag handle. */}\n <div\n data-slot=\"app-shell-simple-nav-content\"\n className=\"h-full w-full overflow-hidden\"\n inert={isHidden}\n >\n <DataAppShellPrimaryNav\n variant={navState === \"rail\" ? \"rail\" : \"sidebar\"}\n aria-label=\"Application navigation\"\n navGroups={navGroups}\n activeKey={activeKey}\n onSelect={onSelect}\n user={userMenu}\n className=\"h-full w-full\"\n />\n </div>\n {/* Drag handle on the right border — snaps between the nav states.\n Also a keyboard-operable window splitter (arrows / Home / End). */}\n <div\n role=\"separator\"\n tabIndex={0}\n aria-orientation=\"vertical\"\n aria-label=\"Resize navigation\"\n aria-controls={navId}\n aria-valuemin={0}\n aria-valuemax={SIDEBAR_WIDTH}\n aria-valuenow={NAV_WIDTH[navState]}\n aria-valuetext={navState}\n onKeyDown={onHandleKeyDown}\n onPointerDown={(e) => {\n e.preventDefault();\n e.currentTarget.setPointerCapture(e.pointerId);\n setResizing(true);\n }}\n onPointerMove={(e) => {\n if (!resizing) return;\n // Defensive: a lost capture can strand `resizing` on, which would\n // otherwise turn a plain hover into a resize.\n if (e.buttons === 0) {\n setResizing(false);\n return;\n }\n snapToPointer(e.clientX);\n }}\n onPointerUp={() => setResizing(false)}\n onPointerCancel={() => setResizing(false)}\n onLostPointerCapture={() => setResizing(false)}\n className={cn(\n \"absolute top-0 right-0 z-10 h-full translate-x-1/2 cursor-col-resize transition-colors hover:bg-primary/40 focus-visible:outline-none focus-visible:bg-primary/60 focus-visible:ring-2 focus-visible:ring-ring\",\n isHidden ? \"w-3\" : \"w-1.5\",\n resizing && \"bg-primary/60\"\n )}\n />\n </div>\n )}\n\n {/* Body */}\n <main\n data-slot=\"app-shell-simple-content\"\n className=\"[grid-area:body] min-h-0 min-w-0 overflow-auto bg-background\"\n >\n {children}\n </main>\n </div>\n );\n}\n"],"names":["NAV_ORDER","LAST_NAV","dirFor","index","indexOfNav","state","RAIL_WIDTH","SIDEBAR_WIDTH","RAIL_SNAP","HIDE_SNAP","NAV_WIDTH","snapNav","width","nextNav","dir","forward","reversed","navToggleLabel","from","to","EMPTY_NAV_GROUPS","ShellBreadcrumb","items","jsx","Breadcrumb","BreadcrumbList","item","isLast","isClickable","jsxs","React","BreadcrumbSeparator","BreadcrumbItem","BreadcrumbPage","BreadcrumbLink","SideNavToggle","label","onCycle","TooltipProvider","Tooltip","TooltipTrigger","Button","PanelLeft","TooltipContent","AppShellSimple","showNav","navGroups","activeKey","onSelect","breadcrumbs","headerActions","userMenu","defaultNav","navProp","onNavChange","children","cursor","setCursor","navState","applyNav","next","nextCursor","cycleNav","nextLabel","navId","navRef","resizing","setResizing","snapToPointer","clientX","stepNav","delta","target","onHandleKeyDown","e","action","isHidden","cn","TopBar","Fragment","DataAppShellPrimaryNav"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppShellSimple.js","sources":["../../../../src/components/composed/AppShellSimple/AppShellSimple.tsx"],"sourcesContent":["/**\n * AppShellSimple (SW-2410) — a Data App Shell stripped down to just two zones:\n * a full-width **top bar** and a three-state **side nav**. For apps that don't\n * need the full `DataAppShell` (no secondary nav, right panel, horizontal nav\n * variant, or mobile Sheet).\n *\n * - The side-nav control **lives in the top bar** (far left, above the nav).\n * It ping-pongs the nav through three states — labels ↔ icons ↔ hidden —\n * and dragging the nav's right border snaps between the same three.\n * - The **breadcrumb is the top bar name** — it sits right after the toggle.\n *\n * Composes existing kit primitives (`TopBar`, `DataAppShellPrimaryNav`,\n * `Breadcrumb`, `Button`, `Tooltip`).\n */\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { WithBuildInfoFooter } from \"@/components/composed/BuildInfoFooter/BuildInfoFooter\";\nimport {\n DataAppShellPrimaryNav,\n type NavGroup,\n type NavPage,\n} from \"@/components/composed/DataAppShell\";\nimport { TopBar } from \"@/components/composed/TopBar\";\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AppShellSimpleCrumb {\n /** Display label */\n label: string;\n /** If set, the crumb renders as a link */\n href?: string;\n /** Click handler when there's no `href` */\n onClick?: () => void;\n}\n\n/**\n * The side nav has three states. The top-bar toggle ping-pongs between them,\n * and dragging the nav's right border snaps between them:\n * - `sidebar` — full panel with icon + label rows\n * - `rail` — icon-only rail\n * - `hidden` — no side nav; only the top bar + content\n */\nexport type AppShellSimpleNavState = \"sidebar\" | \"rail\" | \"hidden\";\n\n/**\n * Where the nav sits in the ping-pong: which state (`index` into `NAV_ORDER`)\n * and which way the next toggle click moves (`dir`, reversing at either end).\n */\ntype NavCursor = { index: number; dir: 1 | -1 };\n\n// Ping-pong order (does not wrap): labels ↔ icons ↔ hidden.\nconst NAV_ORDER: AppShellSimpleNavState[] = [\"sidebar\", \"rail\", \"hidden\"];\nconst LAST_NAV = NAV_ORDER.length - 1;\n\n// Single source of truth for the direction convention: point inward from the\n// ends so the next toggle keeps the ping-pong moving (middle defaults forward).\nconst dirFor = (index: number): 1 | -1 => (index >= LAST_NAV ? -1 : 1);\n\nconst indexOfNav = (state: AppShellSimpleNavState) => Math.max(0, NAV_ORDER.indexOf(state));\n\n// Fixed widths per state — the border drag snaps between these; it never sets\n// an in-between width.\nconst RAIL_WIDTH = 48;\nconst SIDEBAR_WIDTH = 220;\n// Snap thresholds: midpoints between the fixed widths.\nconst RAIL_SNAP = (RAIL_WIDTH + SIDEBAR_WIDTH) / 2;\nconst HIDE_SNAP = RAIL_WIDTH / 2;\n\nconst NAV_WIDTH: Record<AppShellSimpleNavState, number> = {\n sidebar: SIDEBAR_WIDTH,\n rail: RAIL_WIDTH,\n hidden: 0,\n};\n\n/** Map a dragged pixel width to the nearest nav state. */\nfunction snapNav(width: number): NavCursor {\n const index = width >= RAIL_SNAP ? 0 : width >= HIDE_SNAP ? 1 : LAST_NAV;\n return { index, dir: dirFor(index) };\n}\n\n/** Next cursor in the ping-pong, reversing direction at either end. */\nfunction nextNav({ index, dir }: NavCursor): NavCursor {\n const forward = index + dir;\n if (forward < 0 || forward > LAST_NAV) {\n const reversed = (dir * -1) as 1 | -1;\n return { index: index + reversed, dir: reversed };\n }\n return { index: forward, dir };\n}\n\n/**\n * Label for the toggle, describing the transition the next click performs.\n * `rail` is reachable from both directions, so it can't be keyed on the\n * destination alone — arriving from `hidden` is an expand, not a collapse.\n */\nfunction navToggleLabel(from: number, to: number): string {\n switch (NAV_ORDER[to]) {\n case \"sidebar\":\n return \"Expand navigation to labels\";\n case \"hidden\":\n return \"Hide navigation\";\n default:\n return to > from ? \"Collapse navigation to icons\" : \"Expand navigation to icons\";\n }\n}\n\nexport interface AppShellSimpleProps {\n /**\n * Whether the app has a side nav at all (default `true`). When `false`, the\n * shell renders as a top bar + full-width body: no nav zone, and no top-bar\n * toggle. This is the app-level switch — distinct from the runtime `hidden`\n * nav state, which the end user can toggle back open. `navGroups` is ignored\n * when `showNav` is `false`.\n */\n showNav?: boolean;\n /** Nav groups rendered in the side nav (ignored when `showNav` is `false`) */\n navGroups?: NavGroup[];\n /** Controlled active page id — takes precedence over per-page `isActive` */\n activeKey?: string;\n /** Called with the selected page id after the page's own `onClick` */\n onSelect?: (key: string, page: NavPage) => void;\n /** Breadcrumb trail — the top bar name */\n breadcrumbs: AppShellSimpleCrumb[];\n /** Right-side top bar actions (e.g. notifications, user menu) */\n headerActions?: React.ReactNode;\n /** Bottom-of-nav user slot */\n userMenu?: React.ReactNode;\n /** Uncontrolled initial nav state (default `sidebar`) */\n defaultNav?: AppShellSimpleNavState;\n /** Controlled nav state — pair with `onNavChange`; overrides `defaultNav` */\n nav?: AppShellSimpleNavState;\n /** Fired whenever the nav state changes (toggle, drag, or keyboard resize) */\n onNavChange?: (nav: AppShellSimpleNavState) => void;\n /** App version, shown in the build-info footer (rendered when this or `commitSha` is set) */\n version?: string;\n /** Short commit SHA, shown in the build-info footer */\n commitSha?: string;\n /** Main content */\n children: React.ReactNode;\n}\n\nconst EMPTY_NAV_GROUPS: NavGroup[] = [];\n\n// =============================================================================\n// Breadcrumb trail — the top bar name\n// =============================================================================\n\nfunction ShellBreadcrumb({ items }: { items: AppShellSimpleCrumb[] }) {\n return (\n <Breadcrumb className=\"min-w-0\">\n <BreadcrumbList className=\"flex-nowrap min-w-0\">\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n const isClickable = !isLast && (!!item.href || !!item.onClick);\n return (\n <React.Fragment key={`${item.label}-${index}`}>\n {index > 0 && <BreadcrumbSeparator className=\"shrink-0\">/</BreadcrumbSeparator>}\n <BreadcrumbItem className={isLast ? \"min-w-0\" : \"shrink-0\"}>\n {isLast ? (\n <BreadcrumbPage className=\"truncate font-medium\">{item.label}</BreadcrumbPage>\n ) : isClickable && item.href ? (\n <BreadcrumbLink href={item.href} className=\"whitespace-nowrap text-primary\">\n {item.label}\n </BreadcrumbLink>\n ) : isClickable && item.onClick ? (\n <BreadcrumbLink asChild className=\"whitespace-nowrap text-primary\">\n <button type=\"button\" className=\"cursor-pointer\" onClick={item.onClick}>\n {item.label}\n </button>\n </BreadcrumbLink>\n ) : (\n <span className=\"whitespace-nowrap\">{item.label}</span>\n )}\n </BreadcrumbItem>\n </React.Fragment>\n );\n })}\n </BreadcrumbList>\n </Breadcrumb>\n );\n}\n\n// =============================================================================\n// Top-bar side-nav toggle — the collapse/expand control (SW-2410).\n// =============================================================================\n\nfunction SideNavToggle({ label, onCycle }: { label: string; onCycle: () => void }) {\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n data-slot=\"app-shell-simple-nav-toggle\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={label}\n // size-8 (32px) so the hover highlight box matches the nav icon box;\n // -ml-1 centers the icon on the nav's 24px gutter (TopBar has px-3);\n // hover uses sidebar-accent (not the ghost default accent) to match the\n // nav-row hover on the shared sidebar surface; no press inset.\n className=\"-ml-1 shrink-0 text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground active:translate-y-0\"\n onClick={onCycle}\n >\n <PanelLeft className=\"size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{label}</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n}\n\n// =============================================================================\n// AppShellSimple — two zones: full-width top bar + collapsible side nav\n// =============================================================================\n\nexport function AppShellSimple({\n showNav = true,\n navGroups = EMPTY_NAV_GROUPS,\n activeKey,\n onSelect,\n breadcrumbs,\n headerActions,\n userMenu,\n defaultNav = \"sidebar\",\n nav: navProp,\n onNavChange,\n version,\n commitSha,\n children,\n}: AppShellSimpleProps) {\n // The cursor is always local: `index` is only authoritative while\n // uncontrolled, but `dir` (which way the ping-pong is travelling) has no\n // prop equivalent and is tracked here in both modes.\n const [cursor, setCursor] = React.useState<NavCursor>(() => {\n const index = indexOfNav(navProp ?? defaultNav);\n return { index, dir: dirFor(index) };\n });\n\n const index = navProp === undefined ? cursor.index : indexOfNav(navProp);\n // A controlled index moved by the parent invalidates our stored direction.\n const dir = cursor.index === index ? cursor.dir : dirFor(index);\n const navState = NAV_ORDER[index];\n\n const applyNav = React.useCallback(\n (next: NavCursor) => {\n setCursor(next);\n if (next.index !== index) onNavChange?.(NAV_ORDER[next.index]);\n },\n [index, onNavChange]\n );\n\n const nextCursor = nextNav({ index, dir });\n const cycleNav = () => applyNav(nextCursor);\n // Label describes the transition the next click performs.\n const nextLabel = navToggleLabel(index, nextCursor.index);\n\n // Drag the border to snap between the three states (widest → slimmest:\n // icon + text → icon rail → fully closed). Pointer capture keeps the drag\n // scoped to the handle — no window listeners to leak.\n //\n // The nav zone stays mounted at `width: 0` in the `hidden` state rather than\n // unmounting: unmounting mid-drag destroyed the element holding pointer\n // capture, so `pointerup` never came back and `resizing` stuck on. Keeping it\n // mounted also makes the handle draggable back out of `hidden`.\n const navId = React.useId();\n const navRef = React.useRef<HTMLDivElement>(null);\n const [resizing, setResizing] = React.useState(false);\n const snapToPointer = (clientX: number) =>\n applyNav(snapNav(clientX - (navRef.current?.getBoundingClientRect().left ?? 0)));\n\n const stepNav = (delta: 1 | -1) => {\n const target = Math.min(LAST_NAV, Math.max(0, index + delta));\n if (target !== index) applyNav({ index: target, dir: dirFor(target) });\n };\n\n const onHandleKeyDown = (e: React.KeyboardEvent) => {\n // ← narrower · → wider, matching the on-screen direction of the drag\n // (the arrow moves the nav's right border the same way).\n const move: Record<string, () => void> = {\n ArrowLeft: () => stepNav(1),\n ArrowRight: () => stepNav(-1),\n Home: () => applyNav({ index: 0, dir: dirFor(0) }),\n End: () => applyNav({ index: LAST_NAV, dir: dirFor(LAST_NAV) }),\n };\n const action = move[e.key];\n if (!action) return;\n e.preventDefault();\n action();\n };\n\n const isHidden = navState === \"hidden\";\n\n return (\n <div\n data-slot=\"app-shell-simple\"\n data-nav-state={navState}\n className={cn(\n \"grid h-screen w-full overflow-hidden [grid-template-rows:auto_minmax(0,1fr)]\",\n showNav\n ? \"[grid-template-columns:auto_minmax(0,1fr)] [grid-template-areas:'top_top'_'nav_body']\"\n : \"[grid-template-columns:minmax(0,1fr)] [grid-template-areas:'top'_'body']\",\n resizing && \"select-none cursor-col-resize\"\n )}\n >\n {/* Top bar */}\n <div className=\"[grid-area:top] min-w-0\">\n <TopBar\n left={\n <>\n {showNav && <SideNavToggle label={nextLabel} onCycle={cycleNav} />}\n <ShellBreadcrumb items={breadcrumbs} />\n </>\n }\n right={headerActions}\n />\n </div>\n\n {/* Side nav — sidebar (labels), rail (icons), or collapsed to zero width\n when hidden; the toggle ping-pongs and the border drag snaps between\n states. Omitted entirely when the app opts out (`showNav={false}`). */}\n {showNav && (\n <div\n ref={navRef}\n id={navId}\n data-slot=\"app-shell-simple-nav\"\n data-nav-state={navState}\n className={cn(\n \"[grid-area:nav] relative min-h-0 flex flex-col shrink-0 bg-sidebar h-full transition-[width] duration-200\",\n isHidden ? \"overflow-hidden\" : \"border-r border-sidebar-border\"\n )}\n style={{ width: NAV_WIDTH[navState] }}\n >\n {/* `inert` keeps the collapsed nav out of the tab order and the\n accessibility tree while it stays mounted for the drag handle. */}\n <div\n data-slot=\"app-shell-simple-nav-content\"\n className=\"h-full w-full overflow-hidden\"\n inert={isHidden}\n >\n <DataAppShellPrimaryNav\n variant={navState === \"rail\" ? \"rail\" : \"sidebar\"}\n aria-label=\"Application navigation\"\n navGroups={navGroups}\n activeKey={activeKey}\n onSelect={onSelect}\n user={userMenu}\n className=\"h-full w-full\"\n />\n </div>\n {/* Drag handle on the right border — snaps between the nav states.\n Also a keyboard-operable window splitter (arrows / Home / End). */}\n <div\n role=\"separator\"\n tabIndex={0}\n aria-orientation=\"vertical\"\n aria-label=\"Resize navigation\"\n aria-controls={navId}\n aria-valuemin={0}\n aria-valuemax={SIDEBAR_WIDTH}\n aria-valuenow={NAV_WIDTH[navState]}\n aria-valuetext={navState}\n onKeyDown={onHandleKeyDown}\n onPointerDown={(e) => {\n e.preventDefault();\n e.currentTarget.setPointerCapture(e.pointerId);\n setResizing(true);\n }}\n onPointerMove={(e) => {\n if (!resizing) return;\n // Defensive: a lost capture can strand `resizing` on, which would\n // otherwise turn a plain hover into a resize.\n if (e.buttons === 0) {\n setResizing(false);\n return;\n }\n snapToPointer(e.clientX);\n }}\n onPointerUp={() => setResizing(false)}\n onPointerCancel={() => setResizing(false)}\n onLostPointerCapture={() => setResizing(false)}\n className={cn(\n \"absolute top-0 right-0 z-10 h-full translate-x-1/2 cursor-col-resize transition-colors hover:bg-primary/40 focus-visible:outline-none focus-visible:bg-primary/60 focus-visible:ring-2 focus-visible:ring-ring\",\n isHidden ? \"w-3\" : \"w-1.5\",\n resizing && \"bg-primary/60\"\n )}\n />\n </div>\n )}\n\n {/* Body */}\n <main\n data-slot=\"app-shell-simple-content\"\n className=\"[grid-area:body] min-h-0 min-w-0 overflow-auto bg-background\"\n >\n <WithBuildInfoFooter show={Boolean(version || commitSha)} version={version} commitSha={commitSha}>\n {children}\n </WithBuildInfoFooter>\n </main>\n </div>\n );\n}\n"],"names":["NAV_ORDER","LAST_NAV","dirFor","index","indexOfNav","state","RAIL_WIDTH","SIDEBAR_WIDTH","RAIL_SNAP","HIDE_SNAP","NAV_WIDTH","snapNav","width","nextNav","dir","forward","reversed","navToggleLabel","from","to","EMPTY_NAV_GROUPS","ShellBreadcrumb","items","jsx","Breadcrumb","BreadcrumbList","item","isLast","isClickable","jsxs","React","BreadcrumbSeparator","BreadcrumbItem","BreadcrumbPage","BreadcrumbLink","SideNavToggle","label","onCycle","TooltipProvider","Tooltip","TooltipTrigger","Button","PanelLeft","TooltipContent","AppShellSimple","showNav","navGroups","activeKey","onSelect","breadcrumbs","headerActions","userMenu","defaultNav","navProp","onNavChange","version","commitSha","children","cursor","setCursor","navState","applyNav","next","nextCursor","cycleNav","nextLabel","navId","navRef","resizing","setResizing","snapToPointer","clientX","stepNav","delta","target","onHandleKeyDown","e","action","isHidden","cn","TopBar","Fragment","DataAppShellPrimaryNav","WithBuildInfoFooter"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),u=require("react"),l=require("../../../lib/utils.cjs");function c(e){const r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const o in e)if(o!=="default"){const s=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(r,o,s.get?s:{enumerable:!0,get:()=>e[o]})}}return r.default=e,Object.freeze(r)}const d=c(u);function i({version:e,commitSha:r}){const s=[e&&(/^v/i.test(e)?e:`v${e}`),r].filter(n=>!!n);return t.jsx("footer",{"data-slot":"build-info-footer",className:"flex w-full flex-wrap items-center justify-end gap-2 border-t border-border bg-muted/30 px-4 py-2 text-xs text-muted-foreground tabular-nums",children:s.map((n,a)=>t.jsxs(d.Fragment,{children:[a>0&&t.jsx("span",{"aria-hidden":!0,className:"text-muted-foreground/40",children:"·"}),t.jsx("span",{children:n})]},n))})}function f({show:e,version:r,commitSha:o,className:s,children:n}){return e?t.jsxs("div",{"data-slot":"build-info-layout",className:l.cn("grid min-h-full grid-rows-[1fr_auto]",s),children:[t.jsx("div",{className:"min-w-0",children:n}),t.jsx(i,{version:r,commitSha:o})]}):t.jsx(t.Fragment,{children:n})}exports.BuildInfoFooter=i;exports.WithBuildInfoFooter=f;
|
|
2
|
+
//# sourceMappingURL=BuildInfoFooter.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BuildInfoFooter.cjs","sources":["../../../../src/components/composed/BuildInfoFooter/BuildInfoFooter.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Internal to the app shells (SW-2383) — not exported from the package. Apps\n * get the footer by passing `version` / `commitSha` to their shell, so they\n * never have to decide where it goes.\n */\n\nexport interface BuildInfo {\n /** Application version; rendered with a `v` prefix unless it already has one */\n version?: string;\n /** Short commit SHA */\n commitSha?: string;\n}\n\n/** A subtle full-width footer bar showing the app version and commit SHA. */\nexport function BuildInfoFooter({ version, commitSha }: BuildInfo) {\n const shownVersion = version && (/^v/i.test(version) ? version : `v${version}`);\n const meta = [shownVersion, commitSha].filter((x): x is string => Boolean(x));\n\n return (\n <footer\n data-slot=\"build-info-footer\"\n className=\"flex w-full flex-wrap items-center justify-end gap-2 border-t border-border bg-muted/30 px-4 py-2 text-xs text-muted-foreground tabular-nums\"\n >\n {meta.map((item, i) => (\n <React.Fragment key={item}>\n {i > 0 && (\n <span aria-hidden className=\"text-muted-foreground/40\">\n ·\n </span>\n )}\n <span>{item}</span>\n </React.Fragment>\n ))}\n </footer>\n );\n}\n\n/**\n * Lays out a shell's scrolling content with the footer after it: the footer\n * follows the content, and is pinned to the bottom when the content is shorter\n * than the viewport. Renders `children` untouched when `show` is false.\n */\nexport function WithBuildInfoFooter({\n show,\n version,\n commitSha,\n className,\n children,\n}: BuildInfo & { show: boolean; className?: string; children: React.ReactNode }) {\n if (!show) return <>{children}</>;\n return (\n <div data-slot=\"build-info-layout\" className={cn(\"grid min-h-full grid-rows-[1fr_auto]\", className)}>\n <div className=\"min-w-0\">{children}</div>\n <BuildInfoFooter version={version} commitSha={commitSha} />\n </div>\n );\n}\n"],"names":["BuildInfoFooter","version","commitSha","meta","x","jsx","item","i","jsxs","React","WithBuildInfoFooter","show","className","children","cn","Fragment"],"mappings":"scAkBO,SAASA,EAAgB,CAAE,QAAAC,EAAS,UAAAC,GAAwB,CAEjE,MAAMC,EAAO,CADQF,IAAY,MAAM,KAAKA,CAAO,EAAIA,EAAU,IAAIA,CAAO,IAChDC,CAAS,EAAE,OAAQE,GAAmB,EAAQA,CAAE,EAE5E,OACEC,EAAAA,IAAC,SAAA,CACC,YAAU,oBACV,UAAU,+IAET,SAAAF,EAAK,IAAI,CAACG,EAAMC,IACfC,EAAAA,KAACC,EAAM,SAAN,CACE,SAAA,CAAAF,EAAI,GACHF,MAAC,OAAA,CAAK,cAAW,GAAC,UAAU,2BAA2B,SAAA,GAAA,CAEvD,EAEFA,EAAAA,IAAC,QAAM,SAAAC,CAAA,CAAK,CAAA,CAAA,EANOA,CAOrB,CACD,CAAA,CAAA,CAGP,CAOO,SAASI,EAAoB,CAClC,KAAAC,EACA,QAAAV,EACA,UAAAC,EACA,UAAAU,EACA,SAAAC,CACF,EAAiF,CAC/E,OAAKF,EAEHH,OAAC,OAAI,YAAU,oBAAoB,UAAWM,EAAAA,GAAG,uCAAwCF,CAAS,EAChG,SAAA,CAAAP,EAAAA,IAAC,MAAA,CAAI,UAAU,UAAW,SAAAQ,CAAA,CAAS,EACnCR,EAAAA,IAACL,EAAA,CAAgB,QAAAC,EAAkB,UAAAC,CAAA,CAAsB,CAAA,EAC3D,EALgBG,EAAAA,IAAAU,EAAAA,SAAA,CAAG,SAAAF,CAAA,CAAS,CAOhC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Internal to the app shells (SW-2383) — not exported from the package. Apps
|
|
4
|
+
* get the footer by passing `version` / `commitSha` to their shell, so they
|
|
5
|
+
* never have to decide where it goes.
|
|
6
|
+
*/
|
|
7
|
+
export interface BuildInfo {
|
|
8
|
+
/** Application version; rendered with a `v` prefix unless it already has one */
|
|
9
|
+
version?: string;
|
|
10
|
+
/** Short commit SHA */
|
|
11
|
+
commitSha?: string;
|
|
12
|
+
}
|
|
13
|
+
/** A subtle full-width footer bar showing the app version and commit SHA. */
|
|
14
|
+
export declare function BuildInfoFooter({ version, commitSha }: BuildInfo): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
/**
|
|
16
|
+
* Lays out a shell's scrolling content with the footer after it: the footer
|
|
17
|
+
* follows the content, and is pinned to the bottom when the content is shorter
|
|
18
|
+
* than the viewport. Renders `children` untouched when `show` is false.
|
|
19
|
+
*/
|
|
20
|
+
export declare function WithBuildInfoFooter({ show, version, commitSha, className, children, }: BuildInfo & {
|
|
21
|
+
show: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as e, Fragment as s, jsxs as i } from "react/jsx-runtime";
|
|
2
|
+
import * as l from "react";
|
|
3
|
+
import { cn as u } from "../../../lib/utils.js";
|
|
4
|
+
function f({ version: r, commitSha: o }) {
|
|
5
|
+
const n = [r && (/^v/i.test(r) ? r : `v${r}`), o].filter((t) => !!t);
|
|
6
|
+
return /* @__PURE__ */ e(
|
|
7
|
+
"footer",
|
|
8
|
+
{
|
|
9
|
+
"data-slot": "build-info-footer",
|
|
10
|
+
className: "flex w-full flex-wrap items-center justify-end gap-2 border-t border-border bg-muted/30 px-4 py-2 text-xs text-muted-foreground tabular-nums",
|
|
11
|
+
children: n.map((t, d) => /* @__PURE__ */ i(l.Fragment, { children: [
|
|
12
|
+
d > 0 && /* @__PURE__ */ e("span", { "aria-hidden": !0, className: "text-muted-foreground/40", children: "·" }),
|
|
13
|
+
/* @__PURE__ */ e("span", { children: t })
|
|
14
|
+
] }, t))
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function p({
|
|
19
|
+
show: r,
|
|
20
|
+
version: o,
|
|
21
|
+
commitSha: a,
|
|
22
|
+
className: n,
|
|
23
|
+
children: t
|
|
24
|
+
}) {
|
|
25
|
+
return r ? /* @__PURE__ */ i("div", { "data-slot": "build-info-layout", className: u("grid min-h-full grid-rows-[1fr_auto]", n), children: [
|
|
26
|
+
/* @__PURE__ */ e("div", { className: "min-w-0", children: t }),
|
|
27
|
+
/* @__PURE__ */ e(f, { version: o, commitSha: a })
|
|
28
|
+
] }) : /* @__PURE__ */ e(s, { children: t });
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
f as BuildInfoFooter,
|
|
32
|
+
p as WithBuildInfoFooter
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=BuildInfoFooter.js.map
|