@classic-homes/theme-react 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -122,6 +122,7 @@ var Label = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
122
122
  Label.displayName = LabelPrimitive.Root.displayName;
123
123
 
124
124
  // src/components/Badge.tsx
125
+ import * as React5 from "react";
125
126
  import { cva as cva3 } from "class-variance-authority";
126
127
  import { jsx as jsx5 } from "react/jsx-runtime";
127
128
  var badgeVariants = cva3(
@@ -140,15 +141,26 @@ var badgeVariants = cva3(
140
141
  }
141
142
  }
142
143
  );
143
- function Badge({ className, variant, ...props }) {
144
- return /* @__PURE__ */ jsx5("div", { className: cn(badgeVariants({ variant }), className), ...props });
145
- }
144
+ var Badge = React5.forwardRef(
145
+ ({ className, variant, ...props }, ref) => {
146
+ return /* @__PURE__ */ jsx5(
147
+ "div",
148
+ {
149
+ ref,
150
+ role: "status",
151
+ className: cn(badgeVariants({ variant }), className),
152
+ ...props
153
+ }
154
+ );
155
+ }
156
+ );
157
+ Badge.displayName = "Badge";
146
158
 
147
159
  // src/components/Separator.tsx
148
- import * as React5 from "react";
160
+ import * as React6 from "react";
149
161
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
150
162
  import { jsx as jsx6 } from "react/jsx-runtime";
151
- var Separator = React5.forwardRef(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => /* @__PURE__ */ jsx6(
163
+ var Separator = React6.forwardRef(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => /* @__PURE__ */ jsx6(
152
164
  SeparatorPrimitive.Root,
153
165
  {
154
166
  ref,
@@ -165,10 +177,10 @@ var Separator = React5.forwardRef(({ className, orientation = "horizontal", deco
165
177
  Separator.displayName = SeparatorPrimitive.Root.displayName;
166
178
 
167
179
  // src/components/Switch.tsx
168
- import * as React6 from "react";
180
+ import * as React7 from "react";
169
181
  import * as SwitchPrimitives from "@radix-ui/react-switch";
170
182
  import { jsx as jsx7 } from "react/jsx-runtime";
171
- var Switch = React6.forwardRef(
183
+ var Switch = React7.forwardRef(
172
184
  ({ className, size = "default", ...props }, ref) => /* @__PURE__ */ jsx7(
173
185
  SwitchPrimitives.Root,
174
186
  {
@@ -196,10 +208,10 @@ var Switch = React6.forwardRef(
196
208
  Switch.displayName = SwitchPrimitives.Root.displayName;
197
209
 
198
210
  // src/components/Avatar.tsx
199
- import * as React7 from "react";
211
+ import * as React8 from "react";
200
212
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
201
213
  import { jsx as jsx8 } from "react/jsx-runtime";
202
- var Avatar = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
214
+ var Avatar = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
203
215
  AvatarPrimitive.Root,
204
216
  {
205
217
  ref,
@@ -208,16 +220,16 @@ var Avatar = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
208
220
  }
209
221
  ));
210
222
  Avatar.displayName = AvatarPrimitive.Root.displayName;
211
- var AvatarImage = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
223
+ var AvatarImage = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
212
224
  AvatarPrimitive.Image,
213
225
  {
214
226
  ref,
215
- className: cn("aspect-square h-full w-full", className),
227
+ className: cn("aspect-square h-full w-full object-cover", className),
216
228
  ...props
217
229
  }
218
230
  ));
219
231
  AvatarImage.displayName = AvatarPrimitive.Image.displayName;
220
- var AvatarFallback = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
232
+ var AvatarFallback = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
221
233
  AvatarPrimitive.Fallback,
222
234
  {
223
235
  ref,
@@ -231,14 +243,14 @@ var AvatarFallback = React7.forwardRef(({ className, ...props }, ref) => /* @__P
231
243
  AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
232
244
 
233
245
  // src/components/Dialog.tsx
234
- import * as React8 from "react";
246
+ import * as React9 from "react";
235
247
  import * as DialogPrimitive from "@radix-ui/react-dialog";
236
248
  import { jsx as jsx9, jsxs } from "react/jsx-runtime";
237
249
  var Dialog = DialogPrimitive.Root;
238
250
  var DialogTrigger = DialogPrimitive.Trigger;
239
251
  var DialogPortal = DialogPrimitive.Portal;
240
252
  var DialogClose = DialogPrimitive.Close;
241
- var DialogOverlay = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
253
+ var DialogOverlay = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
242
254
  DialogPrimitive.Overlay,
243
255
  {
244
256
  ref,
@@ -250,7 +262,7 @@ var DialogOverlay = React8.forwardRef(({ className, ...props }, ref) => /* @__PU
250
262
  }
251
263
  ));
252
264
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
253
- var DialogContent = React8.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(DialogPortal, { children: [
265
+ var DialogContent = React9.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(DialogPortal, { children: [
254
266
  /* @__PURE__ */ jsx9(DialogOverlay, {}),
255
267
  /* @__PURE__ */ jsx9(
256
268
  DialogPrimitive.Content,
@@ -276,7 +288,7 @@ var DialogFooter = ({ className, ...props }) => /* @__PURE__ */ jsx9(
276
288
  }
277
289
  );
278
290
  DialogFooter.displayName = "DialogFooter";
279
- var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
291
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
280
292
  DialogPrimitive.Title,
281
293
  {
282
294
  ref,
@@ -285,7 +297,7 @@ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE
285
297
  }
286
298
  ));
287
299
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
288
- var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
300
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
289
301
  DialogPrimitive.Description,
290
302
  {
291
303
  ref,
@@ -296,7 +308,7 @@ var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @
296
308
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
297
309
 
298
310
  // src/components/PageHeader.tsx
299
- import * as React9 from "react";
311
+ import * as React10 from "react";
300
312
  import { cva as cva4 } from "class-variance-authority";
301
313
  import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
302
314
  var pageHeaderContainerVariants = cva4("", {
@@ -347,7 +359,7 @@ var pageHeaderActionsVariants = cva4("flex gap-4", {
347
359
  variant: "default"
348
360
  }
349
361
  });
350
- var PageHeader = React9.forwardRef(
362
+ var PageHeader = React10.forwardRef(
351
363
  ({ className, variant, title, subtitle, actions, ...props }, ref) => /* @__PURE__ */ jsxs2(
352
364
  "header",
353
365
  {
@@ -363,6 +375,895 @@ var PageHeader = React9.forwardRef(
363
375
  )
364
376
  );
365
377
  PageHeader.displayName = "PageHeader";
378
+
379
+ // src/components/DataPanel.tsx
380
+ import * as React11 from "react";
381
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
382
+ import {
383
+ DEFAULT_PANEL_STATE,
384
+ DEFAULT_CONSTRAINTS,
385
+ loadPanelState,
386
+ savePanelState,
387
+ getPointerPosition,
388
+ calculateDragPosition,
389
+ calculateResize,
390
+ getResizeCursor,
391
+ getPinnedResizeHandle,
392
+ constrainSize,
393
+ constrainPosition,
394
+ constrainPinnedWidth,
395
+ constrainPinnedHeight,
396
+ detectEdgeSnap,
397
+ getPinnedPositionStyles,
398
+ getDetachedPositionStyles,
399
+ getInitialDetachedPosition,
400
+ isHorizontalEdge,
401
+ calculateDetachDistance,
402
+ calculatePullOffset,
403
+ calculateDetachedPositionFromPinned,
404
+ getPinnedPullTransform
405
+ } from "@classic-homes/data-panel-core";
406
+ import { Fragment, jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
407
+ function useDataPanel(options = {}) {
408
+ const {
409
+ persistKey,
410
+ initialMode = DEFAULT_PANEL_STATE.mode,
411
+ initialEdge = DEFAULT_PANEL_STATE.edge,
412
+ initialExpanded = DEFAULT_PANEL_STATE.isExpanded,
413
+ constraints = {}
414
+ } = options;
415
+ const resolvedConstraints = { ...DEFAULT_CONSTRAINTS, ...constraints };
416
+ const [persistedState] = React11.useState(() => {
417
+ if (persistKey && typeof window !== "undefined") {
418
+ return loadPanelState(persistKey);
419
+ }
420
+ return null;
421
+ });
422
+ const [mode, setMode] = React11.useState(persistedState?.mode ?? initialMode);
423
+ const [edge, setEdge] = React11.useState(persistedState?.edge ?? initialEdge);
424
+ const [isExpanded, setIsExpanded] = React11.useState(
425
+ persistedState?.isExpanded ?? initialExpanded
426
+ );
427
+ const [detachedPosition, setDetachedPosition] = React11.useState(
428
+ persistedState?.detachedPosition ?? DEFAULT_PANEL_STATE.detachedPosition
429
+ );
430
+ const [detachedSize, setDetachedSize] = React11.useState(
431
+ persistedState?.detachedSize ?? DEFAULT_PANEL_STATE.detachedSize
432
+ );
433
+ const [pinnedSize, setPinnedSize] = React11.useState(
434
+ persistedState?.pinnedSize ?? DEFAULT_PANEL_STATE.pinnedSize
435
+ );
436
+ const saveTimeoutRef = React11.useRef(null);
437
+ const debouncedSave = React11.useCallback(() => {
438
+ if (!persistKey) return;
439
+ if (saveTimeoutRef.current) {
440
+ clearTimeout(saveTimeoutRef.current);
441
+ }
442
+ saveTimeoutRef.current = setTimeout(() => {
443
+ savePanelState(persistKey, {
444
+ mode,
445
+ variant: "full",
446
+ edge,
447
+ isExpanded,
448
+ detachedPosition,
449
+ detachedSize,
450
+ pinnedSize,
451
+ cardSnapIndex: 0
452
+ });
453
+ }, 300);
454
+ }, [persistKey, mode, edge, isExpanded, detachedPosition, detachedSize, pinnedSize]);
455
+ const handleSetMode = React11.useCallback(
456
+ (newMode) => {
457
+ setMode(newMode);
458
+ if (newMode === "detached" && typeof window !== "undefined" && detachedPosition.x === DEFAULT_PANEL_STATE.detachedPosition.x && detachedPosition.y === DEFAULT_PANEL_STATE.detachedPosition.y) {
459
+ setDetachedPosition(
460
+ getInitialDetachedPosition(detachedSize, window.innerWidth, window.innerHeight)
461
+ );
462
+ }
463
+ },
464
+ [detachedPosition, detachedSize]
465
+ );
466
+ const handleSetDetachedPosition = React11.useCallback(
467
+ (position) => {
468
+ if (typeof window === "undefined") {
469
+ setDetachedPosition(position);
470
+ return;
471
+ }
472
+ setDetachedPosition(
473
+ constrainPosition(position, detachedSize, window.innerWidth, window.innerHeight)
474
+ );
475
+ },
476
+ [detachedSize]
477
+ );
478
+ const handleSetDetachedSize = React11.useCallback(
479
+ (size) => {
480
+ setDetachedSize(constrainSize(size, resolvedConstraints));
481
+ },
482
+ [resolvedConstraints]
483
+ );
484
+ const handleSetPinnedSize = React11.useCallback(
485
+ (size) => {
486
+ if (typeof window === "undefined") {
487
+ setPinnedSize(size);
488
+ return;
489
+ }
490
+ if (isHorizontalEdge(edge)) {
491
+ setPinnedSize(constrainPinnedWidth(size, resolvedConstraints, window.innerWidth));
492
+ } else {
493
+ setPinnedSize(constrainPinnedHeight(size, resolvedConstraints, window.innerHeight));
494
+ }
495
+ },
496
+ [edge, resolvedConstraints]
497
+ );
498
+ React11.useEffect(() => {
499
+ debouncedSave();
500
+ }, [debouncedSave]);
501
+ return {
502
+ mode,
503
+ setMode: handleSetMode,
504
+ edge,
505
+ setEdge,
506
+ isExpanded,
507
+ setIsExpanded,
508
+ detachedPosition,
509
+ setDetachedPosition: handleSetDetachedPosition,
510
+ detachedSize,
511
+ setDetachedSize: handleSetDetachedSize,
512
+ pinnedSize,
513
+ setPinnedSize: handleSetPinnedSize
514
+ };
515
+ }
516
+ var ChevronUpIcon = () => /* @__PURE__ */ jsx11(
517
+ "svg",
518
+ {
519
+ xmlns: "http://www.w3.org/2000/svg",
520
+ width: "16",
521
+ height: "16",
522
+ viewBox: "0 0 24 24",
523
+ fill: "none",
524
+ stroke: "currentColor",
525
+ strokeWidth: "2",
526
+ strokeLinecap: "round",
527
+ strokeLinejoin: "round",
528
+ children: /* @__PURE__ */ jsx11("path", { d: "m18 15-6-6-6 6" })
529
+ }
530
+ );
531
+ var MoreVerticalIcon = () => /* @__PURE__ */ jsxs3(
532
+ "svg",
533
+ {
534
+ xmlns: "http://www.w3.org/2000/svg",
535
+ width: "16",
536
+ height: "16",
537
+ viewBox: "0 0 24 24",
538
+ fill: "none",
539
+ stroke: "currentColor",
540
+ strokeWidth: "2",
541
+ strokeLinecap: "round",
542
+ strokeLinejoin: "round",
543
+ children: [
544
+ /* @__PURE__ */ jsx11("circle", { cx: "12", cy: "12", r: "1" }),
545
+ /* @__PURE__ */ jsx11("circle", { cx: "12", cy: "5", r: "1" }),
546
+ /* @__PURE__ */ jsx11("circle", { cx: "12", cy: "19", r: "1" })
547
+ ]
548
+ }
549
+ );
550
+ var CloseIcon = () => /* @__PURE__ */ jsxs3(
551
+ "svg",
552
+ {
553
+ xmlns: "http://www.w3.org/2000/svg",
554
+ width: "16",
555
+ height: "16",
556
+ viewBox: "0 0 24 24",
557
+ fill: "none",
558
+ stroke: "currentColor",
559
+ strokeWidth: "2",
560
+ strokeLinecap: "round",
561
+ strokeLinejoin: "round",
562
+ children: [
563
+ /* @__PURE__ */ jsx11("path", { d: "M18 6 6 18" }),
564
+ /* @__PURE__ */ jsx11("path", { d: "m6 6 12 12" })
565
+ ]
566
+ }
567
+ );
568
+ var edgeLabels = {
569
+ left: "Pin to left",
570
+ right: "Pin to right",
571
+ top: "Pin to top",
572
+ bottom: "Pin to bottom"
573
+ };
574
+ var DataPanelHeader = React11.forwardRef(
575
+ ({
576
+ title,
577
+ subtitle,
578
+ mode,
579
+ edge,
580
+ isExpanded,
581
+ onModeChange,
582
+ onEdgeChange,
583
+ onExpandedChange,
584
+ onClose,
585
+ disableClose = false,
586
+ disableModeSwitch = false,
587
+ headerContent,
588
+ headerActions,
589
+ className,
590
+ draggable = false
591
+ }, ref) => {
592
+ return /* @__PURE__ */ jsxs3(
593
+ "div",
594
+ {
595
+ ref,
596
+ className: cn(
597
+ "flex items-center justify-between gap-2 border-b border-border bg-muted/30 px-4 py-3",
598
+ draggable && "cursor-grab active:cursor-grabbing",
599
+ className
600
+ ),
601
+ "data-panel-header": true,
602
+ children: [
603
+ /* @__PURE__ */ jsx11("div", { className: "flex flex-col min-w-0 flex-1", children: headerContent ? headerContent : /* @__PURE__ */ jsxs3(Fragment, { children: [
604
+ title && /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold leading-none truncate", children: title }),
605
+ subtitle && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-1 truncate", children: subtitle })
606
+ ] }) }),
607
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 shrink-0", children: [
608
+ headerActions,
609
+ /* @__PURE__ */ jsx11(
610
+ "button",
611
+ {
612
+ type: "button",
613
+ className: "flex h-8 w-8 items-center justify-center rounded-md bg-transparent text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
614
+ onClick: () => onExpandedChange?.(!isExpanded),
615
+ "aria-label": isExpanded ? "Collapse panel" : "Expand panel",
616
+ "aria-expanded": isExpanded,
617
+ children: /* @__PURE__ */ jsx11("span", { className: cn("transition-transform", !isExpanded && "rotate-180"), children: /* @__PURE__ */ jsx11(ChevronUpIcon, {}) })
618
+ }
619
+ ),
620
+ !disableModeSwitch && /* @__PURE__ */ jsxs3(DropdownMenuPrimitive.Root, { children: [
621
+ /* @__PURE__ */ jsx11(DropdownMenuPrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx11(
622
+ "button",
623
+ {
624
+ type: "button",
625
+ className: "flex h-8 w-8 items-center justify-center rounded-md bg-transparent text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
626
+ "aria-label": "Panel options",
627
+ children: /* @__PURE__ */ jsx11(MoreVerticalIcon, {})
628
+ }
629
+ ) }),
630
+ /* @__PURE__ */ jsx11(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsxs3(
631
+ DropdownMenuPrimitive.Content,
632
+ {
633
+ className: "z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95",
634
+ sideOffset: 4,
635
+ align: "end",
636
+ children: [
637
+ /* @__PURE__ */ jsx11(DropdownMenuPrimitive.Label, { className: "px-2 py-1.5 text-sm font-semibold", children: "Panel Position" }),
638
+ ["left", "right", "top", "bottom"].map((e) => /* @__PURE__ */ jsxs3(
639
+ DropdownMenuPrimitive.Item,
640
+ {
641
+ className: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
642
+ onSelect: () => {
643
+ onModeChange?.("pinned");
644
+ onEdgeChange?.(e);
645
+ },
646
+ children: [
647
+ edgeLabels[e],
648
+ mode === "pinned" && edge === e && /* @__PURE__ */ jsx11("span", { className: "ml-auto text-xs", children: "\u2713" })
649
+ ]
650
+ },
651
+ e
652
+ )),
653
+ /* @__PURE__ */ jsx11(DropdownMenuPrimitive.Separator, { className: "-mx-1 my-1 h-px bg-muted" }),
654
+ /* @__PURE__ */ jsxs3(
655
+ DropdownMenuPrimitive.Item,
656
+ {
657
+ className: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground",
658
+ onSelect: () => onModeChange?.("detached"),
659
+ children: [
660
+ "Detach",
661
+ mode === "detached" && /* @__PURE__ */ jsx11("span", { className: "ml-auto text-xs", children: "\u2713" })
662
+ ]
663
+ }
664
+ )
665
+ ]
666
+ }
667
+ ) })
668
+ ] }),
669
+ !disableClose && /* @__PURE__ */ jsx11(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ className: "flex h-8 w-8 items-center justify-center rounded-md bg-transparent text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
674
+ onClick: onClose,
675
+ "aria-label": "Close panel",
676
+ children: /* @__PURE__ */ jsx11(CloseIcon, {})
677
+ }
678
+ )
679
+ ] })
680
+ ]
681
+ }
682
+ );
683
+ }
684
+ );
685
+ DataPanelHeader.displayName = "DataPanelHeader";
686
+ var DataPanelContent = React11.forwardRef(
687
+ ({ children, className }, ref) => {
688
+ return /* @__PURE__ */ jsx11("div", { ref, className: cn("flex-1 overflow-auto p-4", className), children });
689
+ }
690
+ );
691
+ DataPanelContent.displayName = "DataPanelContent";
692
+ var DataPanelFooter = React11.forwardRef(
693
+ ({ actions = [], footerContent, footerActions, className }, ref) => {
694
+ if (!footerContent && actions.length === 0 && !footerActions) {
695
+ return null;
696
+ }
697
+ const mapVariant = (variant) => {
698
+ if (variant === "primary") return "default";
699
+ return variant ?? "default";
700
+ };
701
+ return /* @__PURE__ */ jsx11(
702
+ "div",
703
+ {
704
+ ref,
705
+ className: cn(
706
+ "flex items-center justify-end gap-2 border-t border-border bg-muted/30 px-4 py-3",
707
+ className
708
+ ),
709
+ children: footerContent ? footerContent : /* @__PURE__ */ jsxs3(Fragment, { children: [
710
+ footerActions,
711
+ actions.map((action, index) => /* @__PURE__ */ jsx11(
712
+ Button,
713
+ {
714
+ variant: mapVariant(action.variant),
715
+ size: "sm",
716
+ disabled: action.disabled,
717
+ onClick: action.onClick,
718
+ children: action.label
719
+ },
720
+ index
721
+ ))
722
+ ] })
723
+ }
724
+ );
725
+ }
726
+ );
727
+ DataPanelFooter.displayName = "DataPanelFooter";
728
+ var DataPanelTab = React11.forwardRef(
729
+ ({ title = "Panel", edge, onClick, className }, ref) => {
730
+ const positionClasses = {
731
+ left: "left-0 top-1/2 -translate-y-1/2 rounded-r-md border-l-0",
732
+ right: "right-0 top-1/2 -translate-y-1/2 rounded-l-md border-r-0",
733
+ top: "top-0 left-1/2 -translate-x-1/2 rounded-b-md border-t-0",
734
+ bottom: "bottom-0 left-1/2 -translate-x-1/2 rounded-t-md border-b-0"
735
+ }[edge];
736
+ const isVertical = edge === "left" || edge === "right";
737
+ const iconRotation = {
738
+ left: "rotate-90",
739
+ right: "-rotate-90",
740
+ top: "rotate-180",
741
+ bottom: ""
742
+ }[edge];
743
+ return /* @__PURE__ */ jsxs3(
744
+ "button",
745
+ {
746
+ ref,
747
+ type: "button",
748
+ className: cn(
749
+ "fixed z-40 flex items-center justify-center gap-2 border border-border bg-background px-3 py-2 text-sm font-medium shadow-md transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
750
+ positionClasses,
751
+ isVertical && "[writing-mode:vertical-rl]",
752
+ className
753
+ ),
754
+ onClick,
755
+ "aria-label": `Expand ${title} panel`,
756
+ children: [
757
+ /* @__PURE__ */ jsx11("span", { className: cn("shrink-0", iconRotation), children: /* @__PURE__ */ jsx11(ChevronUpIcon, {}) }),
758
+ /* @__PURE__ */ jsx11("span", { className: cn(isVertical && "rotate-180"), children: title })
759
+ ]
760
+ }
761
+ );
762
+ }
763
+ );
764
+ DataPanelTab.displayName = "DataPanelTab";
765
+ var DataPanel = React11.forwardRef(
766
+ ({
767
+ open: controlledOpen,
768
+ onOpenChange,
769
+ mode: controlledMode,
770
+ onModeChange,
771
+ edge: controlledEdge,
772
+ onEdgeChange,
773
+ expanded: controlledExpanded,
774
+ onExpandedChange,
775
+ title,
776
+ subtitle,
777
+ children,
778
+ actions = [],
779
+ constraints = {},
780
+ disableClose = false,
781
+ disableResize = false,
782
+ disableDrag = false,
783
+ disableModeSwitch = false,
784
+ persistKey,
785
+ snapThreshold = 20,
786
+ detachThreshold = 40,
787
+ headerContent,
788
+ headerActions,
789
+ footerContent,
790
+ footerActions,
791
+ className
792
+ }, ref) => {
793
+ const resolvedConstraints = { ...DEFAULT_CONSTRAINTS, ...constraints };
794
+ const [internalOpen, setInternalOpen] = React11.useState(true);
795
+ const [internalMode, setInternalMode] = React11.useState(DEFAULT_PANEL_STATE.mode);
796
+ const [internalEdge, setInternalEdge] = React11.useState(DEFAULT_PANEL_STATE.edge);
797
+ const [internalExpanded, setInternalExpanded] = React11.useState(
798
+ DEFAULT_PANEL_STATE.isExpanded
799
+ );
800
+ const [detachedPosition, setDetachedPosition] = React11.useState(
801
+ DEFAULT_PANEL_STATE.detachedPosition
802
+ );
803
+ const [detachedSize, setDetachedSize] = React11.useState(DEFAULT_PANEL_STATE.detachedSize);
804
+ const [pinnedSize, setPinnedSize] = React11.useState(DEFAULT_PANEL_STATE.pinnedSize);
805
+ const [isDragging, setIsDragging] = React11.useState(false);
806
+ const [isResizing, setIsResizing] = React11.useState(false);
807
+ const [snapPreview, setSnapPreview] = React11.useState(null);
808
+ const [isPinnedDragging, setIsPinnedDragging] = React11.useState(false);
809
+ const [pullOffset, setPullOffset] = React11.useState(0);
810
+ const dragStateRef = React11.useRef({
811
+ startPosition: { x: 0, y: 0 },
812
+ startPanelPosition: { x: 0, y: 0 }
813
+ });
814
+ const pinnedDragStateRef = React11.useRef({
815
+ startPosition: { x: 0, y: 0 },
816
+ hasDetached: false
817
+ });
818
+ const resizeStateRef = React11.useRef({
819
+ handle: null,
820
+ startPosition: { x: 0, y: 0 },
821
+ startSize: { width: 0, height: 0 },
822
+ startPanelPosition: { x: 0, y: 0 }
823
+ });
824
+ const open = controlledOpen ?? internalOpen;
825
+ const mode = controlledMode ?? internalMode;
826
+ const edge = controlledEdge ?? internalEdge;
827
+ const isExpanded = controlledExpanded ?? internalExpanded;
828
+ const saveTimeoutRef = React11.useRef(null);
829
+ const debouncedSave = React11.useCallback(() => {
830
+ if (!persistKey) return;
831
+ if (saveTimeoutRef.current) {
832
+ clearTimeout(saveTimeoutRef.current);
833
+ }
834
+ saveTimeoutRef.current = setTimeout(() => {
835
+ savePanelState(persistKey, {
836
+ mode: internalMode,
837
+ variant: "full",
838
+ edge: internalEdge,
839
+ isExpanded: internalExpanded,
840
+ detachedPosition,
841
+ detachedSize,
842
+ pinnedSize,
843
+ cardSnapIndex: 0
844
+ });
845
+ }, 300);
846
+ }, [
847
+ persistKey,
848
+ internalMode,
849
+ internalEdge,
850
+ internalExpanded,
851
+ detachedPosition,
852
+ detachedSize,
853
+ pinnedSize
854
+ ]);
855
+ React11.useEffect(() => {
856
+ if (!persistKey) return;
857
+ const persisted = loadPanelState(persistKey);
858
+ if (persisted) {
859
+ if (controlledMode === void 0 && persisted.mode) setInternalMode(persisted.mode);
860
+ if (controlledEdge === void 0 && persisted.edge) setInternalEdge(persisted.edge);
861
+ if (controlledExpanded === void 0 && persisted.isExpanded !== void 0)
862
+ setInternalExpanded(persisted.isExpanded);
863
+ if (persisted.detachedPosition) setDetachedPosition(persisted.detachedPosition);
864
+ if (persisted.detachedSize) setDetachedSize(persisted.detachedSize);
865
+ if (persisted.pinnedSize) setPinnedSize(persisted.pinnedSize);
866
+ }
867
+ }, [persistKey, controlledMode, controlledEdge, controlledExpanded]);
868
+ React11.useEffect(() => {
869
+ if (mode === "detached" && detachedPosition.x === DEFAULT_PANEL_STATE.detachedPosition.x && detachedPosition.y === DEFAULT_PANEL_STATE.detachedPosition.y) {
870
+ setDetachedPosition(
871
+ getInitialDetachedPosition(detachedSize, window.innerWidth, window.innerHeight)
872
+ );
873
+ }
874
+ }, [mode, detachedPosition, detachedSize]);
875
+ const handleOpenChange = (newOpen) => {
876
+ if (controlledOpen === void 0) {
877
+ setInternalOpen(newOpen);
878
+ }
879
+ onOpenChange?.(newOpen);
880
+ };
881
+ const handleModeChange = (newMode) => {
882
+ if (controlledMode === void 0) {
883
+ setInternalMode(newMode);
884
+ }
885
+ onModeChange?.(newMode);
886
+ debouncedSave();
887
+ };
888
+ const handleEdgeChange = (newEdge) => {
889
+ if (controlledEdge === void 0) {
890
+ setInternalEdge(newEdge);
891
+ }
892
+ onEdgeChange?.(newEdge);
893
+ debouncedSave();
894
+ };
895
+ const handleExpandedChange = (newExpanded) => {
896
+ if (controlledExpanded === void 0) {
897
+ setInternalExpanded(newExpanded);
898
+ }
899
+ onExpandedChange?.(newExpanded);
900
+ debouncedSave();
901
+ };
902
+ const handleDragStart = (e) => {
903
+ if (disableDrag) return;
904
+ const target = e.target;
905
+ if (!target.closest("[data-panel-header]")) return;
906
+ const pos = getPointerPosition(e.nativeEvent);
907
+ if (mode === "detached") {
908
+ setIsDragging(true);
909
+ dragStateRef.current = {
910
+ startPosition: pos,
911
+ startPanelPosition: { ...detachedPosition }
912
+ };
913
+ document.body.style.cursor = "grabbing";
914
+ document.body.style.userSelect = "none";
915
+ } else if (mode === "pinned" && detachThreshold > 0) {
916
+ setIsPinnedDragging(true);
917
+ pinnedDragStateRef.current = {
918
+ startPosition: pos,
919
+ hasDetached: false
920
+ };
921
+ setPullOffset(0);
922
+ document.body.style.cursor = "grab";
923
+ document.body.style.userSelect = "none";
924
+ }
925
+ };
926
+ const handleDragMove = React11.useCallback(
927
+ (e) => {
928
+ if (!isDragging) return;
929
+ const currentPos = getPointerPosition(e);
930
+ const newPosition = calculateDragPosition(
931
+ {
932
+ isDragging: true,
933
+ startPosition: dragStateRef.current.startPosition,
934
+ startPanelPosition: dragStateRef.current.startPanelPosition,
935
+ currentPosition: currentPos
936
+ },
937
+ currentPos
938
+ );
939
+ const snap = detectEdgeSnap(
940
+ newPosition,
941
+ detachedSize,
942
+ window.innerWidth,
943
+ window.innerHeight,
944
+ snapThreshold
945
+ );
946
+ setSnapPreview(snap.shouldSnap ? snap.edge : null);
947
+ setDetachedPosition(
948
+ constrainPosition(newPosition, detachedSize, window.innerWidth, window.innerHeight)
949
+ );
950
+ },
951
+ [isDragging, detachedSize, snapThreshold]
952
+ );
953
+ const handleDragEnd = React11.useCallback(() => {
954
+ if (!isDragging) return;
955
+ setIsDragging(false);
956
+ document.body.style.cursor = "";
957
+ document.body.style.userSelect = "";
958
+ if (snapPreview) {
959
+ handleModeChange("pinned");
960
+ handleEdgeChange(snapPreview);
961
+ setSnapPreview(null);
962
+ } else {
963
+ debouncedSave();
964
+ }
965
+ }, [isDragging, snapPreview, handleModeChange, handleEdgeChange, debouncedSave]);
966
+ const handlePinnedDragMove = React11.useCallback(
967
+ (e) => {
968
+ if (!isPinnedDragging) return;
969
+ const currentPos = getPointerPosition(e);
970
+ const distance = calculateDetachDistance(
971
+ pinnedDragStateRef.current.startPosition,
972
+ currentPos,
973
+ edge
974
+ );
975
+ if (distance >= detachThreshold && !pinnedDragStateRef.current.hasDetached) {
976
+ pinnedDragStateRef.current.hasDetached = true;
977
+ setIsPinnedDragging(false);
978
+ setPullOffset(0);
979
+ const newPosition = calculateDetachedPositionFromPinned(
980
+ currentPos,
981
+ pinnedSize,
982
+ detachedSize,
983
+ edge,
984
+ window.innerWidth,
985
+ window.innerHeight
986
+ );
987
+ setDetachedPosition(newPosition);
988
+ handleModeChange("detached");
989
+ setIsDragging(true);
990
+ dragStateRef.current = {
991
+ startPosition: currentPos,
992
+ startPanelPosition: newPosition
993
+ };
994
+ document.body.style.cursor = "grabbing";
995
+ } else if (!pinnedDragStateRef.current.hasDetached) {
996
+ const pull = calculatePullOffset(distance, detachThreshold);
997
+ setPullOffset(pull);
998
+ }
999
+ },
1000
+ [isPinnedDragging, edge, detachThreshold, pinnedSize, detachedSize, handleModeChange]
1001
+ );
1002
+ const handlePinnedDragEnd = React11.useCallback(() => {
1003
+ if (!isPinnedDragging) return;
1004
+ setIsPinnedDragging(false);
1005
+ setPullOffset(0);
1006
+ document.body.style.cursor = "";
1007
+ document.body.style.userSelect = "";
1008
+ }, [isPinnedDragging]);
1009
+ const handleResizeStart = (e, handle) => {
1010
+ if (disableResize) return;
1011
+ e.preventDefault();
1012
+ e.stopPropagation();
1013
+ setIsResizing(true);
1014
+ const pos = getPointerPosition(e.nativeEvent);
1015
+ resizeStateRef.current = {
1016
+ handle,
1017
+ startPosition: pos,
1018
+ startSize: mode === "detached" ? { ...detachedSize } : { width: pinnedSize, height: pinnedSize },
1019
+ startPanelPosition: { ...detachedPosition }
1020
+ };
1021
+ document.body.style.cursor = getResizeCursor(handle);
1022
+ document.body.style.userSelect = "none";
1023
+ };
1024
+ const handleResizeMove = React11.useCallback(
1025
+ (e) => {
1026
+ if (!isResizing || !resizeStateRef.current.handle) return;
1027
+ const currentPos = getPointerPosition(e);
1028
+ const { handle, startPosition, startSize, startPanelPosition } = resizeStateRef.current;
1029
+ if (mode === "detached") {
1030
+ const result = calculateResize(
1031
+ {
1032
+ isResizing: true,
1033
+ handle,
1034
+ startPosition,
1035
+ startSize,
1036
+ startPanelPosition
1037
+ },
1038
+ currentPos
1039
+ );
1040
+ const constrainedSize = constrainSize(result.size, resolvedConstraints);
1041
+ const constrainedPosition = constrainPosition(
1042
+ result.position,
1043
+ constrainedSize,
1044
+ window.innerWidth,
1045
+ window.innerHeight
1046
+ );
1047
+ setDetachedSize(constrainedSize);
1048
+ setDetachedPosition(constrainedPosition);
1049
+ } else {
1050
+ const delta = isHorizontalEdge(edge) ? currentPos.x - startPosition.x : currentPos.y - startPosition.y;
1051
+ const direction = edge === "left" || edge === "top" ? 1 : -1;
1052
+ const newSize = startSize.width + delta * direction;
1053
+ if (isHorizontalEdge(edge)) {
1054
+ setPinnedSize(constrainPinnedWidth(newSize, resolvedConstraints, window.innerWidth));
1055
+ } else {
1056
+ setPinnedSize(constrainPinnedHeight(newSize, resolvedConstraints, window.innerHeight));
1057
+ }
1058
+ }
1059
+ },
1060
+ [isResizing, mode, edge, resolvedConstraints]
1061
+ );
1062
+ const handleResizeEnd = React11.useCallback(() => {
1063
+ if (!isResizing) return;
1064
+ setIsResizing(false);
1065
+ resizeStateRef.current.handle = null;
1066
+ document.body.style.cursor = "";
1067
+ document.body.style.userSelect = "";
1068
+ debouncedSave();
1069
+ }, [isResizing, debouncedSave]);
1070
+ React11.useEffect(() => {
1071
+ if (isDragging) {
1072
+ window.addEventListener("pointermove", handleDragMove);
1073
+ window.addEventListener("pointerup", handleDragEnd);
1074
+ return () => {
1075
+ window.removeEventListener("pointermove", handleDragMove);
1076
+ window.removeEventListener("pointerup", handleDragEnd);
1077
+ };
1078
+ }
1079
+ }, [isDragging, handleDragMove, handleDragEnd]);
1080
+ React11.useEffect(() => {
1081
+ if (isPinnedDragging) {
1082
+ window.addEventListener("pointermove", handlePinnedDragMove);
1083
+ window.addEventListener("pointerup", handlePinnedDragEnd);
1084
+ return () => {
1085
+ window.removeEventListener("pointermove", handlePinnedDragMove);
1086
+ window.removeEventListener("pointerup", handlePinnedDragEnd);
1087
+ };
1088
+ }
1089
+ }, [isPinnedDragging, handlePinnedDragMove, handlePinnedDragEnd]);
1090
+ React11.useEffect(() => {
1091
+ if (isResizing) {
1092
+ window.addEventListener("pointermove", handleResizeMove);
1093
+ window.addEventListener("pointerup", handleResizeEnd);
1094
+ return () => {
1095
+ window.removeEventListener("pointermove", handleResizeMove);
1096
+ window.removeEventListener("pointerup", handleResizeEnd);
1097
+ };
1098
+ }
1099
+ }, [isResizing, handleResizeMove, handleResizeEnd]);
1100
+ const panelStyles = React11.useMemo(() => {
1101
+ if (mode === "pinned") {
1102
+ const baseStyles = getPinnedPositionStyles(edge, pinnedSize);
1103
+ if (pullOffset > 0) {
1104
+ const transform = getPinnedPullTransform(pullOffset, edge);
1105
+ return { ...baseStyles, transform };
1106
+ }
1107
+ return baseStyles;
1108
+ }
1109
+ return getDetachedPositionStyles(detachedPosition, detachedSize);
1110
+ }, [mode, edge, pinnedSize, detachedPosition, detachedSize, pullOffset]);
1111
+ const pinnedClasses = {
1112
+ left: "left-0 top-0 bottom-0 border-r",
1113
+ right: "right-0 top-0 bottom-0 border-l",
1114
+ top: "top-0 left-0 right-0 border-b",
1115
+ bottom: "bottom-0 left-0 right-0 border-t"
1116
+ }[edge];
1117
+ const animationClass = mode === "pinned" ? {
1118
+ left: "animate-slide-right",
1119
+ right: "animate-slide-left",
1120
+ top: "animate-slide-down",
1121
+ bottom: "animate-slide-up"
1122
+ }[edge] : "animate-scale-in";
1123
+ if (!open) return null;
1124
+ if (!isExpanded && mode === "pinned") {
1125
+ return /* @__PURE__ */ jsx11(DataPanelTab, { title, edge, onClick: () => handleExpandedChange(true) });
1126
+ }
1127
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
1128
+ snapPreview && /* @__PURE__ */ jsx11(
1129
+ "div",
1130
+ {
1131
+ className: cn(
1132
+ "fixed z-40 bg-primary/20 border-2 border-primary border-dashed transition-all",
1133
+ snapPreview === "left" && "left-0 top-0 bottom-0 w-80",
1134
+ snapPreview === "right" && "right-0 top-0 bottom-0 w-80",
1135
+ snapPreview === "top" && "top-0 left-0 right-0 h-64",
1136
+ snapPreview === "bottom" && "bottom-0 left-0 right-0 h-64"
1137
+ )
1138
+ }
1139
+ ),
1140
+ /* @__PURE__ */ jsxs3(
1141
+ "div",
1142
+ {
1143
+ ref,
1144
+ className: cn(
1145
+ "fixed z-50 flex flex-col bg-background border border-border shadow-lg overflow-hidden",
1146
+ mode === "pinned" && pinnedClasses,
1147
+ mode === "detached" && "rounded-lg",
1148
+ isPinnedDragging && pullOffset > 0 && "shadow-2xl",
1149
+ animationClass,
1150
+ className
1151
+ ),
1152
+ style: panelStyles,
1153
+ role: mode === "detached" ? "dialog" : void 0,
1154
+ "aria-modal": mode === "detached" ? "true" : void 0,
1155
+ "aria-labelledby": title ? "data-panel-title" : void 0,
1156
+ onPointerDown: handleDragStart,
1157
+ children: [
1158
+ /* @__PURE__ */ jsx11(
1159
+ DataPanelHeader,
1160
+ {
1161
+ title,
1162
+ subtitle,
1163
+ mode,
1164
+ edge,
1165
+ isExpanded,
1166
+ onModeChange: handleModeChange,
1167
+ onEdgeChange: handleEdgeChange,
1168
+ onExpandedChange: handleExpandedChange,
1169
+ onClose: () => handleOpenChange(false),
1170
+ disableClose,
1171
+ disableModeSwitch,
1172
+ headerContent,
1173
+ headerActions,
1174
+ draggable: !disableDrag && (mode === "detached" || mode === "pinned" && detachThreshold > 0)
1175
+ }
1176
+ ),
1177
+ /* @__PURE__ */ jsx11(DataPanelContent, { children }),
1178
+ /* @__PURE__ */ jsx11(
1179
+ DataPanelFooter,
1180
+ {
1181
+ actions,
1182
+ footerContent,
1183
+ footerActions
1184
+ }
1185
+ ),
1186
+ !disableResize && /* @__PURE__ */ jsx11(Fragment, { children: mode === "detached" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1187
+ /* @__PURE__ */ jsx11(
1188
+ "div",
1189
+ {
1190
+ className: "resize-handle resize-handle--horizontal resize-handle--top",
1191
+ onPointerDown: (e) => handleResizeStart(e, "top")
1192
+ }
1193
+ ),
1194
+ /* @__PURE__ */ jsx11(
1195
+ "div",
1196
+ {
1197
+ className: "resize-handle resize-handle--horizontal resize-handle--bottom",
1198
+ onPointerDown: (e) => handleResizeStart(e, "bottom")
1199
+ }
1200
+ ),
1201
+ /* @__PURE__ */ jsx11(
1202
+ "div",
1203
+ {
1204
+ className: "resize-handle resize-handle--vertical resize-handle--left",
1205
+ onPointerDown: (e) => handleResizeStart(e, "left")
1206
+ }
1207
+ ),
1208
+ /* @__PURE__ */ jsx11(
1209
+ "div",
1210
+ {
1211
+ className: "resize-handle resize-handle--vertical resize-handle--right",
1212
+ onPointerDown: (e) => handleResizeStart(e, "right")
1213
+ }
1214
+ ),
1215
+ /* @__PURE__ */ jsx11(
1216
+ "div",
1217
+ {
1218
+ className: "resize-handle resize-handle--corner resize-handle--top-left",
1219
+ style: { "--handle-radius": "6px" },
1220
+ onPointerDown: (e) => handleResizeStart(e, "top-left")
1221
+ }
1222
+ ),
1223
+ /* @__PURE__ */ jsx11(
1224
+ "div",
1225
+ {
1226
+ className: "resize-handle resize-handle--corner resize-handle--top-right",
1227
+ style: { "--handle-radius": "6px" },
1228
+ onPointerDown: (e) => handleResizeStart(e, "top-right")
1229
+ }
1230
+ ),
1231
+ /* @__PURE__ */ jsx11(
1232
+ "div",
1233
+ {
1234
+ className: "resize-handle resize-handle--corner resize-handle--bottom-left",
1235
+ style: { "--handle-radius": "6px" },
1236
+ onPointerDown: (e) => handleResizeStart(e, "bottom-left")
1237
+ }
1238
+ ),
1239
+ /* @__PURE__ */ jsx11(
1240
+ "div",
1241
+ {
1242
+ className: "resize-handle resize-handle--corner resize-handle--bottom-right",
1243
+ style: { "--handle-radius": "6px" },
1244
+ onPointerDown: (e) => handleResizeStart(e, "bottom-right")
1245
+ }
1246
+ )
1247
+ ] }) : /* @__PURE__ */ jsx11(
1248
+ "div",
1249
+ {
1250
+ className: cn(
1251
+ "resize-handle",
1252
+ edge === "left" && "resize-handle--vertical resize-handle--right",
1253
+ edge === "right" && "resize-handle--vertical resize-handle--left",
1254
+ edge === "top" && "resize-handle--horizontal resize-handle--bottom",
1255
+ edge === "bottom" && "resize-handle--horizontal resize-handle--top"
1256
+ ),
1257
+ onPointerDown: (e) => handleResizeStart(e, getPinnedResizeHandle(edge))
1258
+ }
1259
+ ) })
1260
+ ]
1261
+ }
1262
+ )
1263
+ ] });
1264
+ }
1265
+ );
1266
+ DataPanel.displayName = "DataPanel";
366
1267
  export {
367
1268
  Avatar,
368
1269
  AvatarFallback,
@@ -375,6 +1276,11 @@ export {
375
1276
  CardFooter,
376
1277
  CardHeader,
377
1278
  CardTitle,
1279
+ DataPanel,
1280
+ DataPanelContent,
1281
+ DataPanelFooter,
1282
+ DataPanelHeader,
1283
+ DataPanelTab,
378
1284
  Dialog,
379
1285
  DialogClose,
380
1286
  DialogContent,
@@ -396,5 +1302,6 @@ export {
396
1302
  pageHeaderActionsVariants,
397
1303
  pageHeaderContainerVariants,
398
1304
  pageHeaderSubtitleVariants,
399
- pageHeaderTitleVariants
1305
+ pageHeaderTitleVariants,
1306
+ useDataPanel
400
1307
  };