@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.155.1 → 1.0.0-beta.157.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.
@@ -1 +1 @@
1
- {"version":3,"file":"RightPanel.js","sources":["../../../../src/components/composed/DataAppShell/RightPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelRightOpen, X } from \"lucide-react\";\nimport { Slot } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Sheet, SheetContent, SheetDescription, SheetTitle } from \"@/components/ui/sheet\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Width persistence — one localStorage entry per panel `id`\n// =============================================================================\n\nconst STORAGE_PREFIX = \"ts-ui.right-panel\";\n\nfunction storageKey(id: string): string {\n return `${STORAGE_PREFIX}.${id}.width`;\n}\n\nfunction clampWidth(width: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, width));\n}\n\nfunction readWidth(id: string, fallback: number, min: number, max: number): number {\n if (typeof window === \"undefined\") return fallback;\n let v: number;\n try {\n v = Number(window.localStorage.getItem(storageKey(id)));\n } catch {\n // Storage can be blocked (privacy mode, sandboxed iframe) — fall back.\n return fallback;\n }\n if (!Number.isFinite(v) || v <= 0) return fallback;\n return clampWidth(v, min, max);\n}\n\nfunction persistWidth(id: string, width: number) {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey(id), String(Math.round(width)));\n } catch {\n // Storage blocked or full — resizing still works, the width just isn't persisted.\n }\n}\n\n// =============================================================================\n// Trigger — FAB over the content area, or an icon button for the top bar\n// =============================================================================\n\nconst dataAppShellRightPanelTriggerVariants = cva(\n \"inline-flex items-center justify-center border-none cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n {\n variants: {\n variant: {\n /** Floating action button, bottom-right of the nearest `relative` container */\n fab: \"absolute bottom-4 right-4 z-40 rounded-full bg-primary text-primary-foreground shadow-elevation-4 hover:bg-primary/90 focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95\",\n /** Ghost icon button matching top-bar actions — place in `headerActions` */\n icon: \"rounded-lg bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-foreground\",\n },\n size: {\n default: \"\",\n lg: \"\",\n },\n },\n compoundVariants: [\n { variant: \"fab\", size: \"default\", class: \"size-10 [&_svg:not([class*='size-'])]:size-4\" },\n { variant: \"fab\", size: \"lg\", class: \"size-12 [&_svg:not([class*='size-'])]:size-5\" },\n { variant: \"icon\", size: \"default\", class: \"size-7 [&_svg:not([class*='size-'])]:size-4\" },\n { variant: \"icon\", size: \"lg\", class: \"size-8 [&_svg:not([class*='size-'])]:size-4\" },\n ],\n defaultVariants: { variant: \"fab\", size: \"default\" },\n },\n);\n\nexport interface DataAppShellRightPanelTriggerProps\n extends React.ComponentProps<\"button\">,\n VariantProps<typeof dataAppShellRightPanelTriggerVariants> {\n /** Merge the trigger styling/props onto the child element instead of\n * rendering a `<button>` — makes anything clickable a panel trigger. */\n asChild?: boolean;\n}\n\n/**\n * Trigger for a `DataAppShellRightPanel`, in two variants:\n *\n * - `fab` (default) — floating action button; what the panel auto-renders\n * while closed. Positioned `absolute` — place inside a `relative` container\n * (normally the shell's content area).\n * - `icon` — ghost icon button sized like the shell's top-bar actions. Place\n * it in the shell's `headerActions`, set `showTrigger={false}` on the panel,\n * and pass the same ref to the panel's `triggerRef` so focus returns here\n * when the panel closes.\n *\n * With `asChild` the trigger renders no element of its own and instead merges\n * onto its child — any clickable element (a link, menu item, custom button)\n * can act as the trigger.\n */\nfunction DataAppShellRightPanelTrigger({\n className,\n variant,\n size,\n asChild = false,\n children,\n ...props\n}: DataAppShellRightPanelTriggerProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n return (\n <Comp\n // `type` only when we own the element — a slotted child may not be a button\n {...(asChild ? {} : { type: \"button\" as const })}\n data-slot=\"data-app-shell-right-panel-trigger\"\n data-variant={variant ?? \"fab\"}\n className={cn(dataAppShellRightPanelTriggerVariants({ variant, size }), className)}\n {...props}\n >\n {asChild ? children : (children ?? <PanelRightOpen />)}\n </Comp>\n );\n}\n\n// =============================================================================\n// DragHandle — pointer + keyboard resize on the panel's left edge\n// =============================================================================\n\n/** Pixels per arrow-key press when resizing with the keyboard. */\nconst KEYBOARD_RESIZE_STEP = 16;\n\ninterface DragHandleProps {\n width: number;\n minWidth: number;\n maxWidth: number;\n /** Live width updates while dragging (not yet persisted). */\n onResize: (width: number) => void;\n /** Final width once the interaction ends — persist here. */\n onCommit: (width: number) => void;\n onDraggingChange: (dragging: boolean) => void;\n}\n\n/**\n * Vertical resize handle docked on the panel's left edge. Pointer-driven\n * (px-based, unlike the percentage-based `Resizable` group primitive) so the\n * panel keeps an absolute width that can be persisted per `id`. Also a\n * keyboard-operable ARIA separator: arrows resize, Home/End jump to min/max.\n */\nfunction DragHandle({ width, minWidth, maxWidth, onResize, onCommit, onDraggingChange }: DragHandleProps) {\n const dragState = React.useRef<{ startX: number; startWidth: number; liveWidth: number } | null>(null);\n const widthRef = React.useRef(width);\n widthRef.current = width;\n\n const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n // Primary button / touch only\n if (e.button !== 0) return;\n dragState.current = { startX: e.clientX, startWidth: widthRef.current, liveWidth: widthRef.current };\n try {\n e.currentTarget.setPointerCapture(e.pointerId);\n } catch {\n // Synthetic pointer events (play tests) have no active pointer to\n // capture; move/up still arrive via the handle's own listeners.\n }\n onDraggingChange(true);\n };\n\n const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragState.current) return;\n // Handle sits on the left edge — dragging left grows the panel.\n const delta = dragState.current.startX - e.clientX;\n const next = clampWidth(dragState.current.startWidth + delta, minWidth, maxWidth);\n dragState.current.liveWidth = next;\n onResize(next);\n };\n\n const endDrag = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragState.current) return;\n // Commit the drag state's own width — pointermove renders lazily, so the\n // width prop can be one frame stale when pointerup lands.\n const finalWidth = dragState.current.liveWidth;\n dragState.current = null;\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n onDraggingChange(false);\n onCommit(finalWidth);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n let next: number | null = null;\n switch (e.key) {\n case \"ArrowLeft\": // handle moves left → panel grows\n next = clampWidth(widthRef.current + KEYBOARD_RESIZE_STEP, minWidth, maxWidth);\n break;\n case \"ArrowRight\":\n next = clampWidth(widthRef.current - KEYBOARD_RESIZE_STEP, minWidth, maxWidth);\n break;\n case \"Home\":\n next = minWidth;\n break;\n case \"End\":\n next = maxWidth;\n break;\n }\n if (next == null) return;\n e.preventDefault();\n onResize(next);\n onCommit(next);\n };\n\n return (\n <div\n data-slot=\"data-app-shell-right-panel-drag-handle\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize panel\"\n aria-valuemin={minWidth}\n aria-valuemax={maxWidth}\n aria-valuenow={Math.round(width)}\n tabIndex={0}\n className={cn(\n // 1px seam over the panel's left border; wide invisible grab area via ::after\n \"absolute inset-y-0 left-0 z-10 w-px cursor-col-resize touch-none select-none bg-transparent transition-colors\",\n \"after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2\",\n // reveal on hover / drag (border color) and keyboard focus (ring color)\n \"hover:bg-border active:bg-border focus-visible:outline-none focus-visible:bg-ring\",\n // centered grip dots, revealed with the seam\n \"flex items-center justify-center\",\n \"[&>div]:opacity-0 hover:[&>div]:opacity-100 active:[&>div]:opacity-100 focus-visible:[&>div]:opacity-100\",\n )}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={handleKeyDown}\n >\n <div className=\"z-10 h-6 w-1 shrink-0 rounded-lg bg-muted-foreground/40 transition-opacity\" />\n </div>\n );\n}\n\n// =============================================================================\n// DataAppShellRightPanel\n// =============================================================================\n\nexport type DataAppShellRightPanelVariant = \"docked\" | \"overlay\";\n\nexport interface DataAppShellRightPanelProps extends Omit<React.ComponentProps<\"aside\">, \"id\" | \"title\"> {\n /** Unique panel id — keys the persisted width in localStorage. */\n id: string;\n /** Whether the panel is shown or collapsed to the FAB trigger. */\n open: boolean;\n /** Called with the next open state (close button, Esc, scrim click, FAB). */\n onOpenChange?: (open: boolean) => void;\n /**\n * `docked` (default) — in-flow flex sibling: opening/resizing pushes main narrower.\n * `overlay` — reuses the design-system `Sheet`: slides in over content with a scrim, no reflow.\n */\n variant?: DataAppShellRightPanelVariant;\n /** Show the drag handle and allow resizing. Defaults to `true`. Docked only — the overlay has a fixed width. */\n resizable?: boolean;\n /** Width in px when nothing is persisted yet. */\n defaultWidth?: number;\n /** Minimum width in px the panel can be resized to. */\n minWidth?: number;\n /** Maximum width in px the panel can be resized to. */\n maxWidth?: number;\n /** Persist the width to localStorage. Defaults to `true`. */\n persist?: boolean;\n /** Header title. The header row also carries the close button. */\n title?: React.ReactNode;\n /** Optional element rendered before the title (e.g. an app icon). */\n icon?: React.ReactNode;\n /** Extra elements rendered in the header, before the close button. */\n headerActions?: React.ReactNode;\n /** Render the floating FAB trigger while closed. Defaults to `true`. Set\n * `false` when the trigger lives elsewhere (e.g. an `icon`-variant\n * `DataAppShellRightPanelTrigger` in the shell's `headerActions`). */\n showTrigger?: boolean;\n /** Icon inside the FAB trigger. Defaults to a panel-open icon. */\n triggerIcon?: React.ReactNode;\n /** Accessible label for the FAB trigger. */\n triggerLabel?: string;\n /** Ref to an external trigger (e.g. a top-bar icon trigger) that receives\n * focus when the panel closes and `showTrigger` is `false`. */\n triggerRef?: React.RefObject<HTMLButtonElement | null>;\n /** Panel content — any React node (a chat, history list, inspector, …). */\n children?: React.ReactNode;\n}\n\n/**\n * Right-hand panel for the Data App Shell (SW-2117), in two variants:\n *\n * - `docked` (default) — rendered in normal flow as a flex sibling of the main\n * content, so opening/resizing pushes main narrower. Width is drag-resizable\n * and persisted per `id`.\n * - `overlay` — reuses the design-system `Sheet` (`side=\"right\"`): slides in\n * over the content with a scrim and does not reflow main.\n *\n * In both variants a floating FAB trigger re-opens the panel while it is\n * closed, and the body is a plain slot — a chat, history list, inspector, ….\n * Pass it to the shell's `rightPanel` slot, or place it after the main content\n * inside a `relative` flex row:\n *\n * ```tsx\n * <div className=\"relative flex flex-1 min-h-0\">\n * <main className=\"flex-1 min-w-0\">…</main>\n * <DataAppShellRightPanel id=\"chat\" open={open} onOpenChange={setOpen} title=\"Chat\">\n * …\n * </DataAppShellRightPanel>\n * </div>\n * ```\n */\nfunction DataAppShellRightPanel({\n id,\n open,\n onOpenChange,\n variant = \"docked\",\n resizable = true,\n defaultWidth = 320,\n minWidth = 240,\n maxWidth = 560,\n persist = true,\n title,\n icon,\n headerActions,\n showTrigger = true,\n triggerIcon,\n triggerLabel = \"Open panel\",\n triggerRef,\n children,\n className,\n ref,\n ...props\n}: DataAppShellRightPanelProps) {\n const [width, setWidth] = React.useState<number>(() =>\n persist ? readWidth(id, defaultWidth, minWidth, maxWidth) : clampWidth(defaultWidth, minWidth, maxWidth),\n );\n const [dragging, setDragging] = React.useState(false);\n\n const closeButtonRef = React.useRef<HTMLButtonElement>(null);\n const fabRef = React.useRef<HTMLButtonElement>(null);\n // Focus lands on the built-in FAB when it renders, else on the external trigger.\n const closeFocusRef = showTrigger ? fabRef : triggerRef;\n\n const commitWidth = React.useCallback(\n (next: number) => {\n if (persist) persistWidth(id, next);\n },\n [id, persist],\n );\n\n // Focus follows the open state: into the panel on open, back to the FAB on\n // close — but only on actual transitions, not on mount.\n const mountedRef = React.useRef(false);\n React.useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n // preventScroll — plain .focus() scrolls the target into view, which\n // jolts any scrollable ancestor (e.g. a Storybook docs page) on toggle.\n if (open) closeButtonRef.current?.focus({ preventScroll: true });\n else closeFocusRef?.current?.focus({ preventScroll: true });\n }, [open, closeFocusRef]);\n\n const accessibleName = typeof title === \"string\" ? title : \"Side panel\";\n\n const fab = showTrigger ? (\n <DataAppShellRightPanelTrigger\n ref={fabRef}\n aria-label={triggerLabel}\n aria-expanded={false}\n onClick={() => onOpenChange?.(true)}\n >\n {triggerIcon}\n </DataAppShellRightPanelTrigger>\n ) : null;\n\n const header = (\n <div\n data-slot=\"data-app-shell-right-panel-header\"\n className=\"flex shrink-0 items-center gap-2 border-b border-border px-3 py-2\"\n >\n {icon != null && <span className=\"flex shrink-0 items-center justify-center\">{icon}</span>}\n {title != null && <span className=\"min-w-0 flex-1 truncate text-sm font-semibold text-foreground\">{title}</span>}\n {title == null && <span className=\"flex-1\" />}\n {headerActions}\n <Button\n ref={closeButtonRef}\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"text-muted-foreground\"\n aria-label=\"Close panel\"\n onClick={() => onOpenChange?.(false)}\n >\n <X />\n </Button>\n </div>\n );\n\n const content = (\n <div data-slot=\"data-app-shell-right-panel-content\" className=\"flex min-h-0 flex-1 flex-col overflow-auto\">\n {children}\n </div>\n );\n\n // Overlay — reuse the design-system Sheet: scrim, slide-in, Esc/click-out\n // close and focus trapping all come from the primitive. No reflow of main.\n if (variant === \"overlay\") {\n return (\n <>\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetContent\n side=\"right\"\n showCloseButton={false}\n data-slot=\"data-app-shell-right-panel\"\n data-variant=\"overlay\"\n // The FAB remounts on close, so radix's default \"restore previous\n // focus\" targets a dead node — send focus to the new FAB instead.\n onCloseAutoFocus={(e) => {\n e.preventDefault();\n closeFocusRef?.current?.focus({ preventScroll: true });\n }}\n style={{ width }}\n className={cn(\"gap-0 data-[side=right]:sm:max-w-none\", className)}\n {...props}\n >\n <SheetTitle className=\"sr-only\">{accessibleName}</SheetTitle>\n <SheetDescription className=\"sr-only\">Side panel overlay</SheetDescription>\n {header}\n {content}\n </SheetContent>\n </Sheet>\n {!open && fab}\n </>\n );\n }\n\n if (!open) return fab;\n\n return (\n <aside\n ref={ref}\n data-slot=\"data-app-shell-right-panel\"\n data-variant=\"docked\"\n aria-label={accessibleName}\n style={{ width }}\n className={cn(\n \"relative flex h-full shrink-0 flex-col overflow-hidden border-l border-border bg-background\",\n \"motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-right-4\",\n !dragging && \"motion-safe:transition-[width] motion-safe:duration-200\",\n className,\n )}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") onOpenChange?.(false);\n }}\n {...props}\n >\n {resizable && (\n <DragHandle\n width={width}\n minWidth={minWidth}\n maxWidth={maxWidth}\n onResize={setWidth}\n onCommit={commitWidth}\n onDraggingChange={setDragging}\n />\n )}\n\n {header}\n {content}\n </aside>\n );\n}\n\nexport { DataAppShellRightPanel, DataAppShellRightPanelTrigger, dataAppShellRightPanelTriggerVariants };\n"],"names":["STORAGE_PREFIX","storageKey","id","clampWidth","width","min","max","readWidth","fallback","v","persistWidth","dataAppShellRightPanelTriggerVariants","cva","DataAppShellRightPanelTrigger","className","variant","size","asChild","children","props","Comp","Slot","jsx","cn","PanelRightOpen","KEYBOARD_RESIZE_STEP","DragHandle","minWidth","maxWidth","onResize","onCommit","onDraggingChange","dragState","React","widthRef","handlePointerDown","e","handlePointerMove","delta","next","endDrag","finalWidth","handleKeyDown","DataAppShellRightPanel","open","onOpenChange","resizable","defaultWidth","persist","title","icon","headerActions","showTrigger","triggerIcon","triggerLabel","triggerRef","ref","setWidth","dragging","setDragging","closeButtonRef","fabRef","closeFocusRef","commitWidth","mountedRef","accessibleName","fab","header","jsxs","Button","X","content","Fragment","Sheet","SheetContent","SheetTitle","SheetDescription"],"mappings":"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"}
1
+ {"version":3,"file":"RightPanel.js","sources":["../../../../src/components/composed/DataAppShell/RightPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelRightOpen, X } from \"lucide-react\";\nimport { Slot } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Sheet, SheetContent, SheetDescription, SheetTitle } from \"@/components/ui/sheet\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Width persistence — one localStorage entry per panel `id`\n// =============================================================================\n\nconst STORAGE_PREFIX = \"ts-ui.right-panel\";\n\nfunction storageKey(id: string): string {\n return `${STORAGE_PREFIX}.${id}.width`;\n}\n\nfunction clampWidth(width: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, width));\n}\n\nfunction readWidth(id: string, fallback: number, min: number, max: number): number {\n if (typeof window === \"undefined\") return fallback;\n let v: number;\n try {\n v = Number(window.localStorage.getItem(storageKey(id)));\n } catch {\n // Storage can be blocked (privacy mode, sandboxed iframe) — fall back.\n return fallback;\n }\n if (!Number.isFinite(v) || v <= 0) return fallback;\n return clampWidth(v, min, max);\n}\n\nfunction persistWidth(id: string, width: number) {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey(id), String(Math.round(width)));\n } catch {\n // Storage blocked or full — resizing still works, the width just isn't persisted.\n }\n}\n\n// =============================================================================\n// Trigger — FAB over the content area, or an icon button for the top bar\n// =============================================================================\n\nconst dataAppShellRightPanelTriggerVariants = cva(\n \"inline-flex items-center justify-center border-none cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n {\n variants: {\n variant: {\n /** Floating action button, bottom-right of the nearest `relative` container */\n fab: \"absolute bottom-4 right-4 z-40 rounded-full bg-primary text-primary-foreground shadow-elevation-4 hover:bg-primary/90 focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95\",\n /** Ghost icon button matching top-bar actions — place in `headerActions` */\n icon: \"rounded-lg bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-foreground\",\n },\n size: {\n default: \"\",\n lg: \"\",\n },\n },\n compoundVariants: [\n { variant: \"fab\", size: \"default\", class: \"size-10 [&_svg:not([class*='size-'])]:size-4\" },\n { variant: \"fab\", size: \"lg\", class: \"size-12 [&_svg:not([class*='size-'])]:size-5\" },\n { variant: \"icon\", size: \"default\", class: \"size-7 [&_svg:not([class*='size-'])]:size-4\" },\n { variant: \"icon\", size: \"lg\", class: \"size-8 [&_svg:not([class*='size-'])]:size-4\" },\n ],\n defaultVariants: { variant: \"fab\", size: \"default\" },\n },\n);\n\nexport interface DataAppShellRightPanelTriggerProps\n extends React.ComponentProps<\"button\">,\n VariantProps<typeof dataAppShellRightPanelTriggerVariants> {\n /** Merge the trigger styling/props onto the child element instead of\n * rendering a `<button>` — makes anything clickable a panel trigger. */\n asChild?: boolean;\n}\n\n/**\n * Trigger for a `DataAppShellRightPanel`, in two variants:\n *\n * - `fab` (default) — floating action button; what the panel auto-renders\n * while closed. Positioned `absolute` — place inside a `relative` container\n * (normally the shell's content area).\n * - `icon` — ghost icon button sized like the shell's top-bar actions. Place\n * it in the shell's `headerActions`, set `showTrigger={false}` on the panel,\n * and pass the same ref to the panel's `triggerRef` so focus returns here\n * when the panel closes.\n *\n * With `asChild` the trigger renders no element of its own and instead merges\n * onto its child — any clickable element (a link, menu item, custom button)\n * can act as the trigger.\n */\nfunction DataAppShellRightPanelTrigger({\n className,\n variant,\n size,\n asChild = false,\n children,\n ...props\n}: DataAppShellRightPanelTriggerProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n return (\n <Comp\n // `type` only when we own the element — a slotted child may not be a button\n {...(asChild ? {} : { type: \"button\" as const })}\n data-slot=\"data-app-shell-right-panel-trigger\"\n data-variant={variant ?? \"fab\"}\n className={cn(dataAppShellRightPanelTriggerVariants({ variant, size }), className)}\n {...props}\n >\n {asChild ? children : (children ?? <PanelRightOpen />)}\n </Comp>\n );\n}\n\n// =============================================================================\n// DragHandle — pointer + keyboard resize on the panel's left edge\n// =============================================================================\n\n/** Pixels per arrow-key press when resizing with the keyboard. */\nconst KEYBOARD_RESIZE_STEP = 16;\n\ninterface DragHandleProps {\n width: number;\n minWidth: number;\n maxWidth: number;\n /** Live width updates while dragging (not yet persisted). */\n onResize: (width: number) => void;\n /** Final width once the interaction ends — persist here. */\n onCommit: (width: number) => void;\n onDraggingChange: (dragging: boolean) => void;\n}\n\n/**\n * Vertical resize handle docked on the panel's left edge. Pointer-driven\n * (px-based, unlike the percentage-based `Resizable` group primitive) so the\n * panel keeps an absolute width that can be persisted per `id`. Also a\n * keyboard-operable ARIA separator: arrows resize, Home/End jump to min/max.\n */\nfunction DragHandle({ width, minWidth, maxWidth, onResize, onCommit, onDraggingChange }: DragHandleProps) {\n const dragState = React.useRef<{ startX: number; startWidth: number; liveWidth: number } | null>(null);\n const widthRef = React.useRef(width);\n widthRef.current = width;\n\n const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n // Primary button / touch only\n if (e.button !== 0) return;\n dragState.current = { startX: e.clientX, startWidth: widthRef.current, liveWidth: widthRef.current };\n try {\n e.currentTarget.setPointerCapture(e.pointerId);\n } catch {\n // Synthetic pointer events (play tests) have no active pointer to\n // capture; move/up still arrive via the handle's own listeners.\n }\n onDraggingChange(true);\n };\n\n const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragState.current) return;\n // Handle sits on the left edge — dragging left grows the panel.\n const delta = dragState.current.startX - e.clientX;\n const next = clampWidth(dragState.current.startWidth + delta, minWidth, maxWidth);\n dragState.current.liveWidth = next;\n onResize(next);\n };\n\n const endDrag = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragState.current) return;\n // Commit the drag state's own width — pointermove renders lazily, so the\n // width prop can be one frame stale when pointerup lands.\n const finalWidth = dragState.current.liveWidth;\n dragState.current = null;\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n onDraggingChange(false);\n onCommit(finalWidth);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n let next: number | null = null;\n switch (e.key) {\n case \"ArrowLeft\": // handle moves left → panel grows\n next = clampWidth(widthRef.current + KEYBOARD_RESIZE_STEP, minWidth, maxWidth);\n break;\n case \"ArrowRight\":\n next = clampWidth(widthRef.current - KEYBOARD_RESIZE_STEP, minWidth, maxWidth);\n break;\n case \"Home\":\n next = minWidth;\n break;\n case \"End\":\n next = maxWidth;\n break;\n }\n if (next == null) return;\n e.preventDefault();\n onResize(next);\n onCommit(next);\n };\n\n return (\n <div\n data-slot=\"data-app-shell-right-panel-drag-handle\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize panel\"\n aria-valuemin={minWidth}\n aria-valuemax={maxWidth}\n aria-valuenow={Math.round(width)}\n tabIndex={0}\n className={cn(\n // 1px seam over the panel's left border; wide invisible grab area via ::after\n \"absolute inset-y-0 left-0 z-10 w-px cursor-col-resize touch-none select-none bg-transparent transition-colors\",\n \"after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2\",\n // reveal on hover / drag (border color) and keyboard focus (ring color)\n \"hover:bg-border active:bg-border focus-visible:outline-none focus-visible:bg-ring\",\n // centered grip dots, revealed with the seam\n \"flex items-center justify-center\",\n \"[&>div]:opacity-0 hover:[&>div]:opacity-100 active:[&>div]:opacity-100 focus-visible:[&>div]:opacity-100\",\n )}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={handleKeyDown}\n >\n <div className=\"z-10 h-6 w-1 shrink-0 rounded-lg bg-muted-foreground/40 transition-opacity\" />\n </div>\n );\n}\n\n// =============================================================================\n// DataAppShellRightPanel\n// =============================================================================\n\nexport type DataAppShellRightPanelVariant = \"docked\" | \"overlay\";\n\nexport interface DataAppShellRightPanelProps extends Omit<React.ComponentProps<\"aside\">, \"id\" | \"title\"> {\n /** Unique panel id — keys the persisted width in localStorage. */\n id: string;\n /** Whether the panel is shown or collapsed to the FAB trigger. */\n open: boolean;\n /** Called with the next open state (close button, Esc, scrim click, FAB). */\n onOpenChange?: (open: boolean) => void;\n /**\n * `docked` (default) — in-flow flex sibling: opening/resizing pushes main narrower.\n * `overlay` — reuses the design-system `Sheet`: slides in over content with a scrim, no reflow.\n */\n variant?: DataAppShellRightPanelVariant;\n /**\n * Panel surface (SW-2592):\n * - `raised` (default) — a `bg-card` surface distinct from the page ground, with\n * a tinted (`bg-accent`) header cap. Reads as a surface *on* the page, not a\n * column *of* it. `bg-card` is the only surface token that stays raised in both\n * light and dark (`bg-sidebar` collapses to the body colour in dark).\n * - `flat` — the legacy look: `bg-background` body and a plain bordered header,\n * for a panel that should read as part of the page ground.\n */\n surface?: \"raised\" | \"flat\";\n /** Show the drag handle and allow resizing. Defaults to `true`. Docked only — the overlay has a fixed width. */\n resizable?: boolean;\n /** Width in px when nothing is persisted yet. */\n defaultWidth?: number;\n /** Minimum width in px the panel can be resized to. */\n minWidth?: number;\n /** Maximum width in px the panel can be resized to. */\n maxWidth?: number;\n /** Persist the width to localStorage. Defaults to `true`. */\n persist?: boolean;\n /** Header title. The header row also carries the close button. */\n title?: React.ReactNode;\n /** Optional element rendered before the title (e.g. an app icon). */\n icon?: React.ReactNode;\n /** Extra elements rendered in the header, before the close button. */\n headerActions?: React.ReactNode;\n /** Render the floating FAB trigger while closed. Defaults to `true`. Set\n * `false` when the trigger lives elsewhere (e.g. an `icon`-variant\n * `DataAppShellRightPanelTrigger` in the shell's `headerActions`). */\n showTrigger?: boolean;\n /** Icon inside the FAB trigger. Defaults to a panel-open icon. */\n triggerIcon?: React.ReactNode;\n /** Accessible label for the FAB trigger. */\n triggerLabel?: string;\n /** Ref to an external trigger (e.g. a top-bar icon trigger) that receives\n * focus when the panel closes and `showTrigger` is `false`. */\n triggerRef?: React.RefObject<HTMLButtonElement | null>;\n /** Panel content — any React node (a chat, history list, inspector, …). */\n children?: React.ReactNode;\n}\n\n/**\n * Right-hand panel for the Data App Shell (SW-2117), in two variants:\n *\n * - `docked` (default) — rendered in normal flow as a flex sibling of the main\n * content, so opening/resizing pushes main narrower. Width is drag-resizable\n * and persisted per `id`.\n * - `overlay` — reuses the design-system `Sheet` (`side=\"right\"`): slides in\n * over the content with a scrim and does not reflow main.\n *\n * In both variants a floating FAB trigger re-opens the panel while it is\n * closed, and the body is a plain slot — a chat, history list, inspector, ….\n * Pass it to the shell's `rightPanel` slot, or place it after the main content\n * inside a `relative` flex row:\n *\n * ```tsx\n * <div className=\"relative flex flex-1 min-h-0\">\n * <main className=\"flex-1 min-w-0\">…</main>\n * <DataAppShellRightPanel id=\"chat\" open={open} onOpenChange={setOpen} title=\"Chat\">\n * …\n * </DataAppShellRightPanel>\n * </div>\n * ```\n */\nfunction DataAppShellRightPanel({\n id,\n open,\n onOpenChange,\n variant = \"docked\",\n surface = \"raised\",\n resizable = true,\n defaultWidth = 320,\n minWidth = 240,\n maxWidth = 560,\n persist = true,\n title,\n icon,\n headerActions,\n showTrigger = true,\n triggerIcon,\n triggerLabel = \"Open panel\",\n triggerRef,\n children,\n className,\n ref,\n ...props\n}: DataAppShellRightPanelProps) {\n const [width, setWidth] = React.useState<number>(() =>\n persist ? readWidth(id, defaultWidth, minWidth, maxWidth) : clampWidth(defaultWidth, minWidth, maxWidth),\n );\n const [dragging, setDragging] = React.useState(false);\n\n const closeButtonRef = React.useRef<HTMLButtonElement>(null);\n const fabRef = React.useRef<HTMLButtonElement>(null);\n // Focus lands on the built-in FAB when it renders, else on the external trigger.\n const closeFocusRef = showTrigger ? fabRef : triggerRef;\n\n const commitWidth = React.useCallback(\n (next: number) => {\n if (persist) persistWidth(id, next);\n },\n [id, persist],\n );\n\n // Focus follows the open state: into the panel on open, back to the FAB on\n // close — but only on actual transitions, not on mount.\n const mountedRef = React.useRef(false);\n React.useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n // preventScroll — plain .focus() scrolls the target into view, which\n // jolts any scrollable ancestor (e.g. a Storybook docs page) on toggle.\n if (open) closeButtonRef.current?.focus({ preventScroll: true });\n else closeFocusRef?.current?.focus({ preventScroll: true });\n }, [open, closeFocusRef]);\n\n const accessibleName = typeof title === \"string\" ? title : \"Side panel\";\n\n const raised = surface === \"raised\";\n // `bg-card` is raised in both themes (`--surface-bright`); `bg-background` is\n // the page ground (the legacy, indistinct look).\n const surfaceClass = raised ? \"bg-card\" : \"bg-background\";\n // Raised panels get a tinted header cap so the top of the panel brands it;\n // flat panels keep the plain bordered header.\n const headerClass = raised ? \"border-b border-primary/15 bg-accent\" : \"border-b border-border\";\n\n const fab = showTrigger ? (\n <DataAppShellRightPanelTrigger\n ref={fabRef}\n aria-label={triggerLabel}\n aria-expanded={false}\n onClick={() => onOpenChange?.(true)}\n >\n {triggerIcon}\n </DataAppShellRightPanelTrigger>\n ) : null;\n\n const header = (\n <div\n data-slot=\"data-app-shell-right-panel-header\"\n className={cn(\"flex shrink-0 items-center gap-2 px-3 py-2\", headerClass)}\n >\n {icon != null && <span className=\"flex shrink-0 items-center justify-center\">{icon}</span>}\n {title != null && <span className=\"min-w-0 flex-1 truncate text-sm font-semibold text-foreground\">{title}</span>}\n {title == null && <span className=\"flex-1\" />}\n {headerActions}\n <Button\n ref={closeButtonRef}\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"text-muted-foreground\"\n aria-label=\"Close panel\"\n onClick={() => onOpenChange?.(false)}\n >\n <X />\n </Button>\n </div>\n );\n\n const content = (\n <div data-slot=\"data-app-shell-right-panel-content\" className=\"flex min-h-0 flex-1 flex-col overflow-auto\">\n {children}\n </div>\n );\n\n // Overlay — reuse the design-system Sheet: scrim, slide-in, Esc/click-out\n // close and focus trapping all come from the primitive. No reflow of main.\n if (variant === \"overlay\") {\n return (\n <>\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetContent\n side=\"right\"\n showCloseButton={false}\n data-slot=\"data-app-shell-right-panel\"\n data-variant=\"overlay\"\n // The FAB remounts on close, so radix's default \"restore previous\n // focus\" targets a dead node — send focus to the new FAB instead.\n onCloseAutoFocus={(e) => {\n e.preventDefault();\n closeFocusRef?.current?.focus({ preventScroll: true });\n }}\n style={{ width }}\n // Sheet defaults to bg-background — override so the overlay variant\n // matches the docked variant's surface (SW-2592).\n className={cn(\"gap-0 data-[side=right]:sm:max-w-none\", surfaceClass, className)}\n {...props}\n >\n <SheetTitle className=\"sr-only\">{accessibleName}</SheetTitle>\n <SheetDescription className=\"sr-only\">Side panel overlay</SheetDescription>\n {header}\n {content}\n </SheetContent>\n </Sheet>\n {!open && fab}\n </>\n );\n }\n\n if (!open) return fab;\n\n return (\n <aside\n ref={ref}\n data-slot=\"data-app-shell-right-panel\"\n data-variant=\"docked\"\n aria-label={accessibleName}\n style={{ width }}\n className={cn(\n \"relative flex h-full shrink-0 flex-col overflow-hidden border-l border-border\",\n surfaceClass,\n \"motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-right-4\",\n !dragging && \"motion-safe:transition-[width] motion-safe:duration-200\",\n className,\n )}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") onOpenChange?.(false);\n }}\n {...props}\n >\n {resizable && (\n <DragHandle\n width={width}\n minWidth={minWidth}\n maxWidth={maxWidth}\n onResize={setWidth}\n onCommit={commitWidth}\n onDraggingChange={setDragging}\n />\n )}\n\n {header}\n {content}\n </aside>\n );\n}\n\nexport { DataAppShellRightPanel, DataAppShellRightPanelTrigger, dataAppShellRightPanelTriggerVariants };\n"],"names":["STORAGE_PREFIX","storageKey","id","clampWidth","width","min","max","readWidth","fallback","v","persistWidth","dataAppShellRightPanelTriggerVariants","cva","DataAppShellRightPanelTrigger","className","variant","size","asChild","children","props","Comp","Slot","jsx","cn","PanelRightOpen","KEYBOARD_RESIZE_STEP","DragHandle","minWidth","maxWidth","onResize","onCommit","onDraggingChange","dragState","React","widthRef","handlePointerDown","e","handlePointerMove","delta","next","endDrag","finalWidth","handleKeyDown","DataAppShellRightPanel","open","onOpenChange","surface","resizable","defaultWidth","persist","title","icon","headerActions","showTrigger","triggerIcon","triggerLabel","triggerRef","ref","setWidth","dragging","setDragging","closeButtonRef","fabRef","closeFocusRef","commitWidth","mountedRef","accessibleName","raised","surfaceClass","headerClass","fab","header","jsxs","Button","X","content","Fragment","Sheet","SheetContent","SheetTitle","SheetDescription"],"mappings":"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"}