@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.156.1 → 1.0.0-beta.158.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.
Files changed (73) hide show
  1. package/README.md +113 -0
  2. package/dist/components/composed/DataAppShell/RightPanel.cjs +1 -1
  3. package/dist/components/composed/DataAppShell/RightPanel.cjs.map +1 -1
  4. package/dist/components/composed/DataAppShell/RightPanel.d.ts +11 -1
  5. package/dist/components/composed/DataAppShell/RightPanel.js +128 -126
  6. package/dist/components/composed/DataAppShell/RightPanel.js.map +1 -1
  7. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs +1 -1
  8. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs.map +1 -1
  9. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.d.ts +5 -1
  10. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js +32 -30
  11. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js.map +1 -1
  12. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs +1 -1
  13. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs.map +1 -1
  14. package/dist/components/composed/PlateMapEditor/PlateMapEditor.d.ts +269 -13
  15. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js +392 -310
  16. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js.map +1 -1
  17. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs +1 -1
  18. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs.map +1 -1
  19. package/dist/components/composed/PlateMapEditor/PlateMapForm.d.ts +5 -1
  20. package/dist/components/composed/PlateMapEditor/PlateMapForm.js +19 -15
  21. package/dist/components/composed/PlateMapEditor/PlateMapForm.js.map +1 -1
  22. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs +1 -1
  23. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs.map +1 -1
  24. package/dist/components/composed/PlateMapEditor/PlateMapGrid.d.ts +12 -1
  25. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js +69 -63
  26. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js.map +1 -1
  27. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs +1 -1
  28. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs.map +1 -1
  29. package/dist/components/composed/PlateMapEditor/PlateMapManifest.d.ts +13 -4
  30. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js +38 -29
  31. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js.map +1 -1
  32. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs +1 -1
  33. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs.map +1 -1
  34. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.d.ts +6 -1
  35. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js +490 -275
  36. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js.map +1 -1
  37. package/dist/components/composed/PlateMapEditor/WellLegend.cjs +1 -1
  38. package/dist/components/composed/PlateMapEditor/WellLegend.cjs.map +1 -1
  39. package/dist/components/composed/PlateMapEditor/WellLegend.d.ts +8 -2
  40. package/dist/components/composed/PlateMapEditor/WellLegend.js +8 -7
  41. package/dist/components/composed/PlateMapEditor/WellLegend.js.map +1 -1
  42. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs +1 -1
  43. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs.map +1 -1
  44. package/dist/components/composed/PlateMapEditor/WellManifestTable.d.ts +6 -2
  45. package/dist/components/composed/PlateMapEditor/WellManifestTable.js +288 -256
  46. package/dist/components/composed/PlateMapEditor/WellManifestTable.js.map +1 -1
  47. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs +1 -1
  48. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs.map +1 -1
  49. package/dist/components/composed/PlateMapEditor/WellMetadataForm.d.ts +5 -1
  50. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js +61 -66
  51. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js.map +1 -1
  52. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs +2 -0
  53. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs.map +1 -0
  54. package/dist/components/composed/PlateMapEditor/WellQuickPaint.d.ts +23 -0
  55. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js +75 -0
  56. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js.map +1 -0
  57. package/dist/components/composed/PlateMapEditor/index.d.ts +6 -2
  58. package/dist/components/composed/PlateMapEditor/types.d.ts +65 -0
  59. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs +2 -0
  60. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs.map +1 -0
  61. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.d.ts +109 -0
  62. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js +246 -0
  63. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js.map +1 -0
  64. package/dist/composed/PlateMapEditor.cjs +1 -1
  65. package/dist/composed/PlateMapEditor.js +48 -42
  66. package/dist/composed/PlateMapEditor.js.map +1 -1
  67. package/dist/index.cjs +1 -1
  68. package/dist/index.css +1 -1
  69. package/dist/index.js +701 -695
  70. package/dist/index.js.map +1 -1
  71. package/dist/index.scoped.css +1 -1
  72. package/dist/index.tailwind.css +1 -1
  73. package/package.json +1 -1
package/README.md CHANGED
@@ -306,6 +306,119 @@ For AI-assisted consuming apps, add a short instruction like this to the app's `
306
306
  Use `ProcessFlow` from `@tetrascience-npm/tetrascience-react-ui` for multi-step workflow visualization. Do not build a custom stepper for upload, validation, review, approval, processing, or setup flows. Parent components own the workflow state and pass `steps: ProcessFlowStep[]`; each step status must be one of `PROCESS_FLOW_STEP_STATUSES`. Use `selectedStepId` only for the viewed/selected step. Keep completion/error side effects in the parent workflow code, not inside `ProcessFlow`.
307
307
  ```
308
308
 
309
+ #### PlateMapEditor
310
+
311
+ `PlateMapEditor` is the standard plate-map editing surface — a metadata form, an
312
+ interactive plate grid, and a sample manifest, wired together by a staged-edit
313
+ controller. Use it as-is for the default layout; tune it with props; and drop to
314
+ `usePlateMapEditorState` only when you need a layout the props can't express.
315
+
316
+ ```tsx
317
+ import { PlateMapEditor } from "@tetrascience-npm/tetrascience-react-ui";
318
+
319
+ function PlateScreen() {
320
+ const [values, setValues] = React.useState(new Map());
321
+ const [selection, setSelection] = React.useState(new Set());
322
+
323
+ return (
324
+ <PlateMapEditor
325
+ format="96"
326
+ values={values}
327
+ onChange={setValues}
328
+ selection={selection}
329
+ onSelectionChange={setSelection}
330
+ fields={FIELDS}
331
+ tableColumns={COLUMNS}
332
+ />
333
+ );
334
+ }
335
+ ```
336
+
337
+ ##### Choosing a level
338
+
339
+ | You need | Use |
340
+ | --- | --- |
341
+ | The standard surface, tuned by props | `PlateMapEditor` |
342
+ | The form somewhere the editor can't reach | `PlateMapEditor` + `hideForm` + the imperative handle |
343
+ | A layout no prop combination expresses | `usePlateMapEditorState` + `PlateMapForm` / `PlateMapGrid` / `PlateMapManifest` |
344
+
345
+ Dropping to the hook keeps the apply/clear semantics, plate scoping, and barcode
346
+ stamping — you only take over layout. Do not re-implement staged edits by hand.
347
+
348
+ ##### Customization
349
+
350
+ - **Layout** — `formPlacement` (`start` / `end` / `top` / `bottom`), `stackAt`,
351
+ `formWidth`, `hideForm`, `hideManifest`.
352
+ - **Slots** — `banner` (whole editor), `plateBanner` (plate card only),
353
+ `plateToolbar` (above grid), `plateFooter` (the plate card's footer),
354
+ `footer` (editor-wide action row), `legend` + `legendPlacement`,
355
+ `formExtras`, `formSlot`, `manifestSlot`.
356
+ - **Edit state** — `staged` / `onStagedChange` for a controlled staged record,
357
+ `mergeOnApply` for custom merge semantics, `applyScope="all-plates"` to write
358
+ across every plate at once.
359
+ - **Manifest** — one `manifest` object: `{ filterable, filterColumns, groupable,
360
+ defaultGroupBy, pageSize, pageSizeOptions, enableFillDown }`. Structural bits
361
+ stay top-level: `hideManifest`, `manifestTitle`, `manifestSlot`.
362
+ - **Labels** — one `labels` object covers every string the editor and its
363
+ manifest render, typed as the exported `PlateMapEditorLabels`, so an app's
364
+ translation table is a type error away from going stale:
365
+ `const fr: PlateMapEditorLabels = { … }`. `plateTitle` / `manifestTitle` and
366
+ the import/export menu labels are separate props (they take `ReactNode`, not
367
+ plain text).
368
+ - **Styling** — `className` / `style` on the root, plus one `classNames` map for
369
+ the regions (`PlateMapEditorClassNames`): `{ layout, formCard, plateCard,
370
+ manifestCard, form, grid, manifest }`. Each card also carries
371
+ `data-plate-map-region="form|plate|manifest"`, so plain CSS can target the
372
+ same regions without threading props.
373
+
374
+ ##### Rendering the form outside the editor
375
+
376
+ Hide the built-in form and drive the staged state through the imperative handle.
377
+ The editor keeps owning selection and apply; your form just feeds it.
378
+
379
+ ```tsx
380
+ const editor = React.useRef<PlateMapEditorHandle<MyWell>>(null);
381
+
382
+ <>
383
+ <MySidebarForm
384
+ onChange={(next) => editor.current?.setStaged(next)}
385
+ onApply={() => editor.current?.apply()}
386
+ onClear={() => editor.current?.clear()}
387
+ />
388
+ <PlateMapEditor ref={editor} hideForm {...rest} />
389
+ </>;
390
+ ```
391
+
392
+ ##### Responsiveness
393
+
394
+ The editor responds to **its container's** width, not the viewport's, so it lays
395
+ out correctly inside a narrow panel, split pane, or drawer on a wide screen.
396
+ `stackAt` names a container width — `sm` 640 / `md` 768 (default) / `lg` 1024 /
397
+ `xl` 1280 / `never` — below which the form and grid stack full-width. A plate too
398
+ dense to fit scrolls inside its own container rather than widening the page.
399
+
400
+ ##### Migration notes
401
+
402
+ Everything below is additive; existing code keeps working unchanged.
403
+
404
+ - **Layout now tracks container width, not viewport width.** This is the one
405
+ behavioural change. If your editor is full-width the result is effectively the
406
+ same; if it sits in a narrow panel on a wide screen it will now correctly
407
+ stack instead of rendering a cramped two-column layout. Tune with `stackAt`.
408
+ - `colorForWell` and `emptyEntry` are now **optional** — delete them for
409
+ read-only or single-category views and sensible defaults apply.
410
+ - `className` on the root always worked despite reports otherwise; `style` is
411
+ new.
412
+ - If you previously forked or re-assembled the primitives to change layout,
413
+ replace that with `formPlacement` / `stackAt` / `formWidth` and the slots, or
414
+ with `usePlateMapEditorState` if you still need custom structure. Hand-rolled
415
+ staged/apply logic should be deleted in favour of the hook.
416
+ - If you worked around the hardcoded 360px form column with a width utility,
417
+ switch to `formWidth` — it is the supported path and applies only at and above
418
+ `stackAt`.
419
+ - `manifestFilterable` and `manifestGroupable` still work but are deprecated in
420
+ favour of `manifest={{ filterable, groupable }}`.
421
+
309
422
  ### Charts (`charts/`)
310
423
 
311
424
  Plotly.js-based data visualisations:
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),H=require("class-variance-authority"),E=require("lucide-react"),U=require("radix-ui"),$=require("react"),G=require("../../../ui/button.cjs"),m=require("../../../ui/sheet.cjs"),x=require("../../../lib/utils.cjs");function L(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const r in t)if(r!=="default"){const i=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,i.get?i:{enumerable:!0,get:()=>t[r]})}}return e.default=t,Object.freeze(e)}const u=L($),Y="ts-ui.right-panel";function q(t){return`${Y}.${t}.width`}function b(t,e,r){return Math.min(r,Math.max(e,t))}function Z(t,e,r,i){if(typeof window>"u")return e;let l;try{l=Number(window.localStorage.getItem(q(t)))}catch{return e}return!Number.isFinite(l)||l<=0?e:b(l,r,i)}function J(t,e){if(!(typeof window>"u"))try{window.localStorage.setItem(q(t),String(Math.round(e)))}catch{}}const I=H.cva("inline-flex items-center justify-center border-none cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",{variants:{variant:{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",icon:"rounded-lg bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-foreground"},size:{default:"",lg:""}},compoundVariants:[{variant:"fab",size:"default",class:"size-10 [&_svg:not([class*='size-'])]:size-4"},{variant:"fab",size:"lg",class:"size-12 [&_svg:not([class*='size-'])]:size-5"},{variant:"icon",size:"default",class:"size-7 [&_svg:not([class*='size-'])]:size-4"},{variant:"icon",size:"lg",class:"size-8 [&_svg:not([class*='size-'])]:size-4"}],defaultVariants:{variant:"fab",size:"default"}});function M({className:t,variant:e,size:r,asChild:i=!1,children:l,...f}){const s=i?U.Slot.Root:"button";return n.jsx(s,{...i?{}:{type:"button"},"data-slot":"data-app-shell-right-panel-trigger","data-variant":e??"fab",className:x.cn(I({variant:e,size:r}),t),...f,children:i?l:l??n.jsx(E.PanelRightOpen,{})})}const T=16;function Q({width:t,minWidth:e,maxWidth:r,onResize:i,onCommit:l,onDraggingChange:f}){const s=u.useRef(null),c=u.useRef(t);c.current=t;const g=a=>{if(a.button===0){s.current={startX:a.clientX,startWidth:c.current,liveWidth:c.current};try{a.currentTarget.setPointerCapture(a.pointerId)}catch{}f(!0)}},d=a=>{if(!s.current)return;const o=s.current.startX-a.clientX,v=b(s.current.startWidth+o,e,r);s.current.liveWidth=v,i(v)},p=a=>{if(!s.current)return;const o=s.current.liveWidth;s.current=null,a.currentTarget.hasPointerCapture(a.pointerId)&&a.currentTarget.releasePointerCapture(a.pointerId),f(!1),l(o)},y=a=>{let o=null;switch(a.key){case"ArrowLeft":o=b(c.current+T,e,r);break;case"ArrowRight":o=b(c.current-T,e,r);break;case"Home":o=e;break;case"End":o=r;break}o!=null&&(a.preventDefault(),i(o),l(o))};return n.jsx("div",{"data-slot":"data-app-shell-right-panel-drag-handle",role:"separator","aria-orientation":"vertical","aria-label":"Resize panel","aria-valuemin":e,"aria-valuemax":r,"aria-valuenow":Math.round(t),tabIndex:0,className:x.cn("absolute inset-y-0 left-0 z-10 w-px cursor-col-resize touch-none select-none bg-transparent transition-colors","after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2","hover:bg-border active:bg-border focus-visible:outline-none focus-visible:bg-ring","flex items-center justify-center","[&>div]:opacity-0 hover:[&>div]:opacity-100 active:[&>div]:opacity-100 focus-visible:[&>div]:opacity-100"),onPointerDown:g,onPointerMove:d,onPointerUp:p,onPointerCancel:p,onKeyDown:y,children:n.jsx("div",{className:"z-10 h-6 w-1 shrink-0 rounded-lg bg-muted-foreground/40 transition-opacity"})})}function W({id:t,open:e,onOpenChange:r,variant:i="docked",resizable:l=!0,defaultWidth:f=320,minWidth:s=240,maxWidth:c=560,persist:g=!0,title:d,icon:p,headerActions:y,showTrigger:a=!0,triggerIcon:o,triggerLabel:v="Open panel",triggerRef:C,children:O,className:w,ref:X,...R}){const[S,F]=u.useState(()=>g?Z(t,f,s,c):b(f,s,c)),[K,V]=u.useState(!1),z=u.useRef(null),P=u.useRef(null),j=a?P:C,B=u.useCallback(h=>{g&&J(t,h)},[t,g]),D=u.useRef(!1);u.useEffect(()=>{if(!D.current){D.current=!0;return}e?z.current?.focus({preventScroll:!0}):j?.current?.focus({preventScroll:!0})},[e,j]);const k=typeof d=="string"?d:"Side panel",N=a?n.jsx(M,{ref:P,"aria-label":v,"aria-expanded":!1,onClick:()=>r?.(!0),children:o}):null,A=n.jsxs("div",{"data-slot":"data-app-shell-right-panel-header",className:"flex shrink-0 items-center gap-2 border-b border-border px-3 py-2",children:[p!=null&&n.jsx("span",{className:"flex shrink-0 items-center justify-center",children:p}),d!=null&&n.jsx("span",{className:"min-w-0 flex-1 truncate text-sm font-semibold text-foreground",children:d}),d==null&&n.jsx("span",{className:"flex-1"}),y,n.jsx(G.Button,{ref:z,variant:"ghost",size:"icon-sm",className:"text-muted-foreground","aria-label":"Close panel",onClick:()=>r?.(!1),children:n.jsx(E.X,{})})]}),_=n.jsx("div",{"data-slot":"data-app-shell-right-panel-content",className:"flex min-h-0 flex-1 flex-col overflow-auto",children:O});return i==="overlay"?n.jsxs(n.Fragment,{children:[n.jsx(m.Sheet,{open:e,onOpenChange:r,children:n.jsxs(m.SheetContent,{side:"right",showCloseButton:!1,"data-slot":"data-app-shell-right-panel","data-variant":"overlay",onCloseAutoFocus:h=>{h.preventDefault(),j?.current?.focus({preventScroll:!0})},style:{width:S},className:x.cn("gap-0 data-[side=right]:sm:max-w-none",w),...R,children:[n.jsx(m.SheetTitle,{className:"sr-only",children:k}),n.jsx(m.SheetDescription,{className:"sr-only",children:"Side panel overlay"}),A,_]})}),!e&&N]}):e?n.jsxs("aside",{ref:X,"data-slot":"data-app-shell-right-panel","data-variant":"docked","aria-label":k,style:{width:S},className:x.cn("relative flex h-full shrink-0 flex-col overflow-hidden border-l border-border bg-background","motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-right-4",!K&&"motion-safe:transition-[width] motion-safe:duration-200",w),onKeyDown:h=>{h.key==="Escape"&&r?.(!1)},...R,children:[l&&n.jsx(Q,{width:S,minWidth:s,maxWidth:c,onResize:F,onCommit:B,onDraggingChange:V}),A,_]}):N}exports.DataAppShellRightPanel=W;exports.DataAppShellRightPanelTrigger=M;exports.dataAppShellRightPanelTriggerVariants=I;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),L=require("class-variance-authority"),q=require("lucide-react"),Y=require("radix-ui"),Z=require("react"),J=require("../../../ui/button.cjs"),y=require("../../../ui/sheet.cjs"),b=require("../../../lib/utils.cjs");function Q(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const r in t)if(r!=="default"){const o=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,o.get?o:{enumerable:!0,get:()=>t[r]})}}return e.default=t,Object.freeze(e)}const u=Q(Z),W="ts-ui.right-panel";function I(t){return`${W}.${t}.width`}function v(t,e,r){return Math.min(r,Math.max(e,t))}function ee(t,e,r,o){if(typeof window>"u")return e;let l;try{l=Number(window.localStorage.getItem(I(t)))}catch{return e}return!Number.isFinite(l)||l<=0?e:v(l,r,o)}function te(t,e){if(!(typeof window>"u"))try{window.localStorage.setItem(I(t),String(Math.round(e)))}catch{}}const M=L.cva("inline-flex items-center justify-center border-none cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",{variants:{variant:{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",icon:"rounded-lg bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-foreground"},size:{default:"",lg:""}},compoundVariants:[{variant:"fab",size:"default",class:"size-10 [&_svg:not([class*='size-'])]:size-4"},{variant:"fab",size:"lg",class:"size-12 [&_svg:not([class*='size-'])]:size-5"},{variant:"icon",size:"default",class:"size-7 [&_svg:not([class*='size-'])]:size-4"},{variant:"icon",size:"lg",class:"size-8 [&_svg:not([class*='size-'])]:size-4"}],defaultVariants:{variant:"fab",size:"default"}});function O({className:t,variant:e,size:r,asChild:o=!1,children:l,...f}){const i=o?Y.Slot.Root:"button";return n.jsx(i,{...o?{}:{type:"button"},"data-slot":"data-app-shell-right-panel-trigger","data-variant":e??"fab",className:b.cn(M({variant:e,size:r}),t),...f,children:o?l:l??n.jsx(q.PanelRightOpen,{})})}const E=16;function re({width:t,minWidth:e,maxWidth:r,onResize:o,onCommit:l,onDraggingChange:f}){const i=u.useRef(null),c=u.useRef(t);c.current=t;const g=a=>{if(a.button===0){i.current={startX:a.clientX,startWidth:c.current,liveWidth:c.current};try{a.currentTarget.setPointerCapture(a.pointerId)}catch{}f(!0)}},p=a=>{if(!i.current)return;const s=i.current.startX-a.clientX,x=v(i.current.startWidth+s,e,r);i.current.liveWidth=x,o(x)},d=a=>{if(!i.current)return;const s=i.current.liveWidth;i.current=null,a.currentTarget.hasPointerCapture(a.pointerId)&&a.currentTarget.releasePointerCapture(a.pointerId),f(!1),l(s)},m=a=>{let s=null;switch(a.key){case"ArrowLeft":s=v(c.current+E,e,r);break;case"ArrowRight":s=v(c.current-E,e,r);break;case"Home":s=e;break;case"End":s=r;break}s!=null&&(a.preventDefault(),o(s),l(s))};return n.jsx("div",{"data-slot":"data-app-shell-right-panel-drag-handle",role:"separator","aria-orientation":"vertical","aria-label":"Resize panel","aria-valuemin":e,"aria-valuemax":r,"aria-valuenow":Math.round(t),tabIndex:0,className:b.cn("absolute inset-y-0 left-0 z-10 w-px cursor-col-resize touch-none select-none bg-transparent transition-colors","after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2","hover:bg-border active:bg-border focus-visible:outline-none focus-visible:bg-ring","flex items-center justify-center","[&>div]:opacity-0 hover:[&>div]:opacity-100 active:[&>div]:opacity-100 focus-visible:[&>div]:opacity-100"),onPointerDown:g,onPointerMove:p,onPointerUp:d,onPointerCancel:d,onKeyDown:m,children:n.jsx("div",{className:"z-10 h-6 w-1 shrink-0 rounded-lg bg-muted-foreground/40 transition-opacity"})})}function ne({id:t,open:e,onOpenChange:r,variant:o="docked",surface:l="raised",resizable:f=!0,defaultWidth:i=320,minWidth:c=240,maxWidth:g=560,persist:p=!0,title:d,icon:m,headerActions:a,showTrigger:s=!0,triggerIcon:x,triggerLabel:X="Open panel",triggerRef:F,children:K,className:w,ref:V,...R}){const[S,B]=u.useState(()=>p?ee(t,i,c,g):v(i,c,g)),[H,U]=u.useState(!1),z=u.useRef(null),P=u.useRef(null),j=s?P:F,$=u.useCallback(h=>{p&&te(t,h)},[t,p]),D=u.useRef(!1);u.useEffect(()=>{if(!D.current){D.current=!0;return}e?z.current?.focus({preventScroll:!0}):j?.current?.focus({preventScroll:!0})},[e,j]);const k=typeof d=="string"?d:"Side panel",N=l==="raised",A=N?"bg-card":"bg-background",G=N?"border-b border-primary/15 bg-accent":"border-b border-border",_=s?n.jsx(O,{ref:P,"aria-label":X,"aria-expanded":!1,onClick:()=>r?.(!0),children:x}):null,T=n.jsxs("div",{"data-slot":"data-app-shell-right-panel-header",className:b.cn("flex shrink-0 items-center gap-2 px-3 py-2",G),children:[m!=null&&n.jsx("span",{className:"flex shrink-0 items-center justify-center",children:m}),d!=null&&n.jsx("span",{className:"min-w-0 flex-1 truncate text-sm font-semibold text-foreground",children:d}),d==null&&n.jsx("span",{className:"flex-1"}),a,n.jsx(J.Button,{ref:z,variant:"ghost",size:"icon-sm",className:"text-muted-foreground","aria-label":"Close panel",onClick:()=>r?.(!1),children:n.jsx(q.X,{})})]}),C=n.jsx("div",{"data-slot":"data-app-shell-right-panel-content",className:"flex min-h-0 flex-1 flex-col overflow-auto",children:K});return o==="overlay"?n.jsxs(n.Fragment,{children:[n.jsx(y.Sheet,{open:e,onOpenChange:r,children:n.jsxs(y.SheetContent,{side:"right",showCloseButton:!1,"data-slot":"data-app-shell-right-panel","data-variant":"overlay",onCloseAutoFocus:h=>{h.preventDefault(),j?.current?.focus({preventScroll:!0})},style:{width:S},className:b.cn("gap-0 data-[side=right]:sm:max-w-none",A,w),...R,children:[n.jsx(y.SheetTitle,{className:"sr-only",children:k}),n.jsx(y.SheetDescription,{className:"sr-only",children:"Side panel overlay"}),T,C]})}),!e&&_]}):e?n.jsxs("aside",{ref:V,"data-slot":"data-app-shell-right-panel","data-variant":"docked","aria-label":k,style:{width:S},className:b.cn("relative flex h-full shrink-0 flex-col overflow-hidden border-l border-border",A,"motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-right-4",!H&&"motion-safe:transition-[width] motion-safe:duration-200",w),onKeyDown:h=>{h.key==="Escape"&&r?.(!1)},...R,children:[f&&n.jsx(re,{width:S,minWidth:c,maxWidth:g,onResize:B,onCommit:$,onDraggingChange:U}),T,C]}):_}exports.DataAppShellRightPanel=ne;exports.DataAppShellRightPanelTrigger=O;exports.dataAppShellRightPanelTriggerVariants=M;
2
2
  //# sourceMappingURL=RightPanel.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RightPanel.cjs","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.cjs","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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"}
@@ -38,6 +38,16 @@ export interface DataAppShellRightPanelProps extends Omit<React.ComponentProps<"
38
38
  * `overlay` — reuses the design-system `Sheet`: slides in over content with a scrim, no reflow.
39
39
  */
40
40
  variant?: DataAppShellRightPanelVariant;
41
+ /**
42
+ * Panel surface (SW-2592):
43
+ * - `raised` (default) — a `bg-card` surface distinct from the page ground, with
44
+ * a tinted (`bg-accent`) header cap. Reads as a surface *on* the page, not a
45
+ * column *of* it. `bg-card` is the only surface token that stays raised in both
46
+ * light and dark (`bg-sidebar` collapses to the body colour in dark).
47
+ * - `flat` — the legacy look: `bg-background` body and a plain bordered header,
48
+ * for a panel that should read as part of the page ground.
49
+ */
50
+ surface?: "raised" | "flat";
41
51
  /** Show the drag handle and allow resizing. Defaults to `true`. Docked only — the overlay has a fixed width. */
42
52
  resizable?: boolean;
43
53
  /** Width in px when nothing is persisted yet. */
@@ -91,5 +101,5 @@ export interface DataAppShellRightPanelProps extends Omit<React.ComponentProps<"
91
101
  * </div>
92
102
  * ```
93
103
  */
94
- declare function DataAppShellRightPanel({ id, open, onOpenChange, variant, resizable, defaultWidth, minWidth, maxWidth, persist, title, icon, headerActions, showTrigger, triggerIcon, triggerLabel, triggerRef, children, className, ref, ...props }: DataAppShellRightPanelProps): import("react/jsx-runtime").JSX.Element | null;
104
+ declare function DataAppShellRightPanel({ id, open, onOpenChange, variant, surface, resizable, defaultWidth, minWidth, maxWidth, persist, title, icon, headerActions, showTrigger, triggerIcon, triggerLabel, triggerRef, children, className, ref, ...props }: DataAppShellRightPanelProps): import("react/jsx-runtime").JSX.Element | null;
95
105
  export { DataAppShellRightPanel, DataAppShellRightPanelTrigger, dataAppShellRightPanelTriggerVariants };