gridsmith-ui 0.6.2 → 0.6.4

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.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { Skeleton } from './chunk-QGBG72OB.js';
2
2
  export { Skeleton } from './chunk-QGBG72OB.js';
3
- import { Badge } from './chunk-TYU2LQGK.js';
4
- export { Badge } from './chunk-TYU2LQGK.js';
5
- import { useBreakpoint } from './chunk-DPF6776I.js';
6
- export { BreakpointWidthProvider, useBreakpoint } from './chunk-DPF6776I.js';
7
- import { Tooltip, useTextOverflow, usePosition, Slot } from './chunk-R5QGJURZ.js';
8
- export { Tooltip, useTextOverflow } from './chunk-R5QGJURZ.js';
3
+ import { Badge } from './chunk-KXNJJKIV.js';
4
+ export { Badge } from './chunk-KXNJJKIV.js';
5
+ import { useBreakpoint } from './chunk-EKQHHP72.js';
6
+ export { BreakpointWidthProvider, useBreakpoint } from './chunk-EKQHHP72.js';
7
+ import { Tooltip, useTextOverflow, usePosition, Slot, useLatest } from './chunk-DDKCQVKO.js';
8
+ export { Tooltip, useTextOverflow } from './chunk-DDKCQVKO.js';
9
9
  import { Icon } from './chunk-GX6O72IV.js';
10
- import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY, HAS_FOCUS_RING, FOCUS_RING, FOCUS_WITHIN_RING } from './chunk-TTZSZNJ6.js';
11
- import { createContext, forwardRef, useContext, useState, useRef, useEffect, useCallback, useId, isValidElement, cloneElement, useMemo, Children, Fragment as Fragment$1, useLayoutEffect } from 'react';
10
+ import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, FOCUS_RING, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY, HAS_FOCUS_RING, FOCUS_WITHIN_RING } from './chunk-TTZSZNJ6.js';
11
+ import { createContext, forwardRef, useContext, useState, useRef, useEffect, useCallback, useId, isValidElement, cloneElement, Fragment as Fragment$1, useMemo, Children, useLayoutEffect } from 'react';
12
12
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
13
  import { createPortal } from 'react-dom';
14
14
 
@@ -149,11 +149,13 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
149
149
  if (!isDivider(item) && !item.disabled) acc.push(i);
150
150
  return acc;
151
151
  }, []);
152
+ const [prevOpen, setPrevOpen] = useState(open);
153
+ if (open !== prevOpen) {
154
+ setPrevOpen(open);
155
+ if (!open) setFocusedIdx(-1);
156
+ }
152
157
  useEffect(() => {
153
- if (!open) {
154
- setFocusedIdx(-1);
155
- return;
156
- }
158
+ if (!open) return;
157
159
  const onKeyDown = (e) => {
158
160
  if (e.key === "Escape") setOpen(false);
159
161
  };
@@ -534,13 +536,15 @@ function Dropdown({
534
536
  }
535
537
  setPos({ top, left, width });
536
538
  }, []);
539
+ const [prevOpen, setPrevOpen] = useState(open);
540
+ if (open !== prevOpen) {
541
+ setPrevOpen(open);
542
+ if (open) setSearch("");
543
+ else setPositioned(false);
544
+ }
537
545
  useEffect(() => {
538
- if (!open) {
539
- setPositioned(false);
540
- return;
541
- }
546
+ if (!open) return;
542
547
  updatePos();
543
- setSearch("");
544
548
  requestAnimationFrame(() => {
545
549
  if (searchRef.current) {
546
550
  searchRef.current.focus();
@@ -570,14 +574,14 @@ function Dropdown({
570
574
  return () => document.removeEventListener("mousedown", handler);
571
575
  }, [open]);
572
576
  const typeahead = useRef({ buffer: "", at: 0 });
573
- const optionButtons = () => Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
574
- const focusOption = (idx) => {
575
- const els = optionButtons();
576
- if (els.length === 0) return;
577
- els[(idx % els.length + els.length) % els.length].focus();
578
- };
579
577
  useEffect(() => {
580
578
  if (!open) return;
579
+ const optionButtons = () => Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
580
+ const focusOption = (idx) => {
581
+ const els = optionButtons();
582
+ if (els.length === 0) return;
583
+ els[(idx % els.length + els.length) % els.length].focus();
584
+ };
581
585
  const onKey = (e) => {
582
586
  const inMenu = menuRef.current?.contains(document.activeElement) || triggerRef.current === document.activeElement;
583
587
  if (!inMenu) return;
@@ -812,6 +816,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
812
816
  const [highlightIdx, setHighlightIdx] = useState(0);
813
817
  const inputRef = useRef(null);
814
818
  const triggerRef = useRef(null);
819
+ const [triggerWidth, setTriggerWidth] = useState(void 0);
815
820
  const listRef = useRef(null);
816
821
  const listboxId = useId();
817
822
  const pos = usePosition(triggerRef, listRef, open, { side: "bottom", align: "start", offset: 4, flip: true });
@@ -820,9 +825,11 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
820
825
  );
821
826
  const selectedLabel = options.find((o) => o.value === value)?.label ?? "";
822
827
  const { ref: overflowRef, isTruncated: labelTruncated } = useTextOverflow({ lines: 1, content: selectedLabel, disabled: open });
823
- useEffect(() => {
828
+ const [prevQuery, setPrevQuery] = useState(query);
829
+ if (query !== prevQuery) {
830
+ setPrevQuery(query);
824
831
  setHighlightIdx(0);
825
- }, [query]);
832
+ }
826
833
  useEffect(() => {
827
834
  if (!open) return;
828
835
  const handler = (e) => {
@@ -870,6 +877,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
870
877
  }, [overflowRef]);
871
878
  const triggerContent = /* @__PURE__ */ jsxs("div", { ref: (node) => {
872
879
  triggerRef.current = node;
880
+ setTriggerWidth(node?.offsetWidth);
873
881
  if (typeof ref === "function") ref(node);
874
882
  else if (ref) ref.current = node;
875
883
  }, className: cn("relative", className), children: [
@@ -950,7 +958,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
950
958
  "rounded-[var(--ds-radius-md)]",
951
959
  "shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]"
952
960
  ),
953
- style: { top: pos.top, left: pos.left, width: triggerRef.current?.offsetWidth },
961
+ style: { top: pos.top, left: pos.left, width: triggerWidth },
954
962
  children: /* @__PURE__ */ jsx("div", { style: { padding: "var(--ds-space-1)" }, children: loading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-ds-4", children: /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "Loading options" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-ds-2 py-ds-3 text-sm text-[var(--ds-text-muted)] text-center", children: "No results" }) : filtered.map((opt, i) => /* @__PURE__ */ jsxs(
955
963
  "button",
956
964
  {
@@ -1491,7 +1499,7 @@ function getMonthGrid(year, month, weekStartsOn = 0) {
1491
1499
  const firstOfMonth = new Date(year, month, 1);
1492
1500
  const gridStart = startOfWeek(firstOfMonth, weekStartsOn);
1493
1501
  const weeks = [];
1494
- let current = new Date(gridStart);
1502
+ const current = new Date(gridStart);
1495
1503
  for (let w = 0; w < 6; w++) {
1496
1504
  const week = [];
1497
1505
  for (let d = 0; d < 7; d++) {
@@ -2292,11 +2300,11 @@ function NumberInput({
2292
2300
  const [focused, setFocused] = useState(false);
2293
2301
  const [draft, setDraft] = useState(String(value));
2294
2302
  const decimals = allowDecimals ?? step % 1 !== 0;
2295
- useEffect(() => {
2296
- if (!focused) {
2297
- setDraft(String(value));
2298
- }
2299
- }, [value, focused]);
2303
+ const [prevSync, setPrevSync] = useState({ value, focused });
2304
+ if (value !== prevSync.value || focused !== prevSync.focused) {
2305
+ setPrevSync({ value, focused });
2306
+ if (!focused) setDraft(String(value));
2307
+ }
2300
2308
  const clamp = useCallback(
2301
2309
  (v) => Math.min(max, Math.max(min, v)),
2302
2310
  [min, max]
@@ -2397,7 +2405,7 @@ function NumberInput({
2397
2405
  onMouseDown: (e) => {
2398
2406
  e.preventDefault();
2399
2407
  inputRef.current?.focus();
2400
- !disabled && !atMin && startRepeat(decrement);
2408
+ if (!disabled && !atMin) startRepeat(decrement);
2401
2409
  },
2402
2410
  onMouseUp: stopRepeat,
2403
2411
  onMouseLeave: stopRepeat,
@@ -2453,7 +2461,7 @@ function NumberInput({
2453
2461
  onMouseDown: (e) => {
2454
2462
  e.preventDefault();
2455
2463
  inputRef.current?.focus();
2456
- !disabled && !atMax && startRepeat(increment);
2464
+ if (!disabled && !atMax) startRepeat(increment);
2457
2465
  },
2458
2466
  onMouseUp: stopRepeat,
2459
2467
  onMouseLeave: stopRepeat,
@@ -3012,11 +3020,12 @@ function ColorPicker({
3012
3020
  const [hexInput, setHexInput] = useState(value);
3013
3021
  const satLightRef = useRef(null);
3014
3022
  const draggingRef = useRef(false);
3015
- useEffect(() => {
3016
- const parsed = hexToHsl(value);
3017
- setHsl(parsed);
3023
+ const [prevValue, setPrevValue] = useState(value);
3024
+ if (value !== prevValue) {
3025
+ setPrevValue(value);
3026
+ setHsl(hexToHsl(value));
3018
3027
  setHexInput(value);
3019
- }, [value]);
3028
+ }
3020
3029
  const emitChange = useCallback(
3021
3030
  (newHsl) => {
3022
3031
  setHsl(newHsl);
@@ -3250,17 +3259,15 @@ function useFormContext() {
3250
3259
  function FormWrapper({ onSubmit, validationMode = "onSubmit", children, className, ref }) {
3251
3260
  const [errors, setErrors] = useState({});
3252
3261
  const [submitted, setSubmitted] = useState(false);
3253
- const errorsRef = useRef(errors);
3254
- errorsRef.current = errors;
3262
+ const errorsRef = useLatest(errors);
3255
3263
  const pendingSubmit = useRef(false);
3256
- const onSubmitRef = useRef(onSubmit);
3257
- onSubmitRef.current = onSubmit;
3264
+ const onSubmitRef = useLatest(onSubmit);
3258
3265
  useEffect(() => {
3259
3266
  if (pendingSubmit.current && submitted) {
3260
3267
  pendingSubmit.current = false;
3261
3268
  onSubmitRef.current(errorsRef.current);
3262
3269
  }
3263
- }, [submitted, errors]);
3270
+ }, [submitted, errors, errorsRef, onSubmitRef]);
3264
3271
  const setError = useCallback((field, message) => {
3265
3272
  setErrors((prev) => ({ ...prev, [field]: message }));
3266
3273
  }, []);
@@ -3580,13 +3587,30 @@ var sizeStyles3 = {
3580
3587
  md: "h-10 w-10 text-sm",
3581
3588
  lg: "h-12 w-12 text-base"
3582
3589
  };
3590
+ var paletteStyles = {
3591
+ 1: "bg-[var(--ds-palette-1-bg,var(--ds-bg-muted))] text-[var(--ds-palette-1-text,var(--ds-text-secondary))]",
3592
+ 2: "bg-[var(--ds-palette-2-bg,var(--ds-bg-muted))] text-[var(--ds-palette-2-text,var(--ds-text-secondary))]",
3593
+ 3: "bg-[var(--ds-palette-3-bg,var(--ds-bg-muted))] text-[var(--ds-palette-3-text,var(--ds-text-secondary))]",
3594
+ 4: "bg-[var(--ds-palette-4-bg,var(--ds-bg-muted))] text-[var(--ds-palette-4-text,var(--ds-text-secondary))]",
3595
+ 5: "bg-[var(--ds-palette-5-bg,var(--ds-bg-muted))] text-[var(--ds-palette-5-text,var(--ds-text-secondary))]"
3596
+ };
3597
+ function paletteFor(seed) {
3598
+ let h = 0;
3599
+ for (const ch of seed) h = h * 31 + ch.charCodeAt(0) >>> 0;
3600
+ return h % 5 + 1;
3601
+ }
3602
+ function colorStyles(color, seed) {
3603
+ if (color === "accent") return "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]";
3604
+ if (color === "neutral") return "bg-[var(--ds-bg-muted)] text-[var(--ds-text-secondary)]";
3605
+ return paletteStyles[color === "auto" ? paletteFor(seed) : color];
3606
+ }
3583
3607
  var statusVariantMap = {
3584
3608
  online: "success",
3585
3609
  away: "warning",
3586
3610
  busy: "error",
3587
3611
  offline: "offline"
3588
3612
  };
3589
- function Avatar({ initials, src, alt, size = "md", status, className }) {
3613
+ function Avatar({ initials, src, alt, size = "md", status, color = "auto", className }) {
3590
3614
  const base = cn(
3591
3615
  "inline-flex items-center justify-center font-medium shrink-0",
3592
3616
  "rounded-[var(--ds-radius-full)]",
@@ -3605,7 +3629,7 @@ function Avatar({ initials, src, alt, size = "md", status, className }) {
3605
3629
  {
3606
3630
  className: cn(
3607
3631
  base,
3608
- "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]",
3632
+ colorStyles(color, alt ?? initials ?? ""),
3609
3633
  "border-[length:var(--ds-border-width)] border-transparent"
3610
3634
  ),
3611
3635
  children: initials
@@ -5801,9 +5825,12 @@ function MonthDropdown({
5801
5825
  offset: 8,
5802
5826
  flip: true
5803
5827
  });
5804
- useEffect(() => {
5828
+ const browseSyncKey = open ? currentDate.getTime() : null;
5829
+ const [prevBrowseSyncKey, setPrevBrowseSyncKey] = useState(browseSyncKey);
5830
+ if (browseSyncKey !== prevBrowseSyncKey) {
5831
+ setPrevBrowseSyncKey(browseSyncKey);
5805
5832
  if (open) setBrowseYear(currentDate.getFullYear());
5806
- }, [open, currentDate]);
5833
+ }
5807
5834
  useEffect(() => {
5808
5835
  if (!open) return;
5809
5836
  const onKey = (e) => {
@@ -5921,9 +5948,12 @@ function YearDropdown({
5921
5948
  });
5922
5949
  const currentYear = currentDate.getFullYear();
5923
5950
  const [rangeStart, setRangeStart] = useState(() => currentYear - 4);
5924
- useEffect(() => {
5951
+ const rangeSyncKey = open ? currentYear : null;
5952
+ const [prevRangeSyncKey, setPrevRangeSyncKey] = useState(rangeSyncKey);
5953
+ if (rangeSyncKey !== prevRangeSyncKey) {
5954
+ setPrevRangeSyncKey(rangeSyncKey);
5925
5955
  if (open) setRangeStart(currentYear - 4);
5926
- }, [open, currentYear]);
5956
+ }
5927
5957
  useEffect(() => {
5928
5958
  if (!open) return;
5929
5959
  const onKey = (e) => {
@@ -6344,6 +6374,7 @@ function AvatarGroup({ items, max = 4, size = "sm", ring = "canvas", onOverflowC
6344
6374
  initials,
6345
6375
  size,
6346
6376
  status: item.status,
6377
+ color: item.color,
6347
6378
  className: ringClass
6348
6379
  },
6349
6380
  i
@@ -6644,6 +6675,13 @@ function TruncLabel2({ children }) {
6644
6675
  if (!isTruncated) return label;
6645
6676
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: label });
6646
6677
  }
6678
+ function allDescendantIds(node) {
6679
+ const ids = [node.id];
6680
+ if (node.children) {
6681
+ for (const child of node.children) ids.push(...allDescendantIds(child));
6682
+ }
6683
+ return ids;
6684
+ }
6647
6685
  function TreeView({
6648
6686
  nodes,
6649
6687
  selectable,
@@ -6686,13 +6724,6 @@ function TreeView({
6686
6724
  },
6687
6725
  [selectable, multiSelect, onSelect]
6688
6726
  );
6689
- const allDescendantIds = useCallback((node) => {
6690
- const ids = [node.id];
6691
- if (node.children) {
6692
- for (const child of node.children) ids.push(...allDescendantIds(child));
6693
- }
6694
- return ids;
6695
- }, []);
6696
6727
  const handleCheck = useCallback(
6697
6728
  (node) => {
6698
6729
  if (!checkable) return;
@@ -6708,7 +6739,7 @@ function TreeView({
6708
6739
  return next;
6709
6740
  });
6710
6741
  },
6711
- [checkable, allDescendantIds, onCheck]
6742
+ [checkable, onCheck]
6712
6743
  );
6713
6744
  const getCheckState = useCallback(
6714
6745
  (node) => {
@@ -6719,7 +6750,7 @@ function TreeView({
6719
6750
  if (checkedCount > 0) return "indeterminate";
6720
6751
  return "unchecked";
6721
6752
  },
6722
- [checked, allDescendantIds]
6753
+ [checked]
6723
6754
  );
6724
6755
  const flatNodes = useCallback(() => {
6725
6756
  const result = [];
@@ -7561,9 +7592,23 @@ function TabsRoot(props) {
7561
7592
  overflow = "scroll",
7562
7593
  className
7563
7594
  } = props;
7595
+ const isMobile = useBreakpoint("md");
7596
+ const effectiveOrientation = orientation === "responsive" ? "vertical" : orientation;
7564
7597
  if ("tabs" in props && props.tabs) {
7565
- const { tabs } = props;
7566
- if (orientation === "vertical") {
7598
+ const { tabs, mobile = "scroll" } = props;
7599
+ const collapseToDropdown = isMobile && (orientation === "responsive" || mobile === "dropdown");
7600
+ if (collapseToDropdown) {
7601
+ return /* @__PURE__ */ jsx(
7602
+ Dropdown,
7603
+ {
7604
+ options: tabs.filter((t) => !t.disabled).map((t) => ({ id: t.id, label: t.label, group: t.group })),
7605
+ value: activeTab,
7606
+ onChange: (v) => onTabChange(v),
7607
+ className
7608
+ }
7609
+ );
7610
+ }
7611
+ if (effectiveOrientation === "vertical") {
7567
7612
  return /* @__PURE__ */ jsx(
7568
7613
  "div",
7569
7614
  {
@@ -7574,7 +7619,20 @@ function TabsRoot(props) {
7574
7619
  variant === "underline" && "border-r-[length:var(--ds-border-width)] border-[var(--ds-border-default)] pr-ds-1",
7575
7620
  className
7576
7621
  ),
7577
- children: tabs.map((tab) => /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation, variant, activeTab, onTabChange, fullWidth, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, orientation, onTabChange) }, tab.id))
7622
+ children: tabs.map((tab, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
7623
+ tab.group && tab.group !== tabs[i - 1]?.group && /* @__PURE__ */ jsx(
7624
+ "span",
7625
+ {
7626
+ "aria-hidden": "true",
7627
+ className: cn(
7628
+ "px-ds-3 pb-ds-1 text-xs font-semibold uppercase tracking-wider text-[var(--ds-text-muted)]",
7629
+ i > 0 && "mt-ds-3"
7630
+ ),
7631
+ children: tab.group
7632
+ }
7633
+ ),
7634
+ /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation: effectiveOrientation, variant, activeTab, onTabChange, fullWidth, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, effectiveOrientation, onTabChange) })
7635
+ ] }, tab.id))
7578
7636
  }
7579
7637
  );
7580
7638
  }
@@ -7591,11 +7649,11 @@ function TabsRoot(props) {
7591
7649
  fullWidth && "w-full",
7592
7650
  className
7593
7651
  ),
7594
- children: tabs.map((tab) => /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation, variant, activeTab, onTabChange, fullWidth, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, orientation, onTabChange) }, tab.id))
7652
+ children: tabs.map((tab) => /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation: effectiveOrientation, variant, activeTab, onTabChange, fullWidth, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, effectiveOrientation, onTabChange) }, tab.id))
7595
7653
  }
7596
7654
  );
7597
7655
  }
7598
- return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { activeTab, onTabChange, orientation, variant, fullWidth, overflow }, children: /* @__PURE__ */ jsx("div", { className, children: props.children }) });
7656
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { activeTab, onTabChange, orientation: effectiveOrientation, variant, fullWidth, overflow }, children: /* @__PURE__ */ jsx("div", { className, children: props.children }) });
7599
7657
  }
7600
7658
  var Tabs = Object.assign(TabsRoot, {
7601
7659
  List: TabsList,
@@ -7800,6 +7858,11 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
7800
7858
  function Popover({ open, onOpenChange, trigger, children, side = "bottom", align = "center", width, className, container }) {
7801
7859
  const triggerRef = useRef(null);
7802
7860
  const floatingRef = useRef(null);
7861
+ const [triggerWidth, setTriggerWidth] = useState(void 0);
7862
+ const setTriggerNode = useCallback((node) => {
7863
+ triggerRef.current = node;
7864
+ setTriggerWidth(node?.offsetWidth);
7865
+ }, []);
7803
7866
  const pos = usePosition(triggerRef, floatingRef, open, { side, align, offset: 8, flip: true });
7804
7867
  useEffect(() => {
7805
7868
  if (!open) return;
@@ -7825,8 +7888,11 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
7825
7888
  /* @__PURE__ */ jsx(
7826
7889
  Slot,
7827
7890
  {
7828
- ref: triggerRef,
7829
- onClick: () => onOpenChange(!open),
7891
+ ref: setTriggerNode,
7892
+ onClick: () => {
7893
+ setTriggerWidth(triggerRef.current?.offsetWidth);
7894
+ onOpenChange(!open);
7895
+ },
7830
7896
  children: trigger
7831
7897
  }
7832
7898
  ),
@@ -7846,7 +7912,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
7846
7912
  style: {
7847
7913
  top: pos.top,
7848
7914
  left: pos.left,
7849
- width: width === "trigger" ? triggerRef.current?.offsetWidth : width === "auto" ? void 0 : width
7915
+ width: width === "trigger" ? triggerWidth : width === "auto" ? void 0 : width
7850
7916
  },
7851
7917
  children
7852
7918
  }
@@ -8462,10 +8528,7 @@ function CompactFlyout({
8462
8528
  onMouseEnter,
8463
8529
  onMouseLeave
8464
8530
  }) {
8465
- const [pos, setPos] = useState(() => computeCompactFlyoutPos(anchorEl, items.length));
8466
- useLayoutEffect(() => {
8467
- setPos(computeCompactFlyoutPos(anchorEl, items.length));
8468
- }, [anchorEl, items.length]);
8531
+ const pos = useMemo(() => computeCompactFlyoutPos(anchorEl, items.length), [anchorEl, items.length]);
8469
8532
  useEffect(() => {
8470
8533
  function handler(e) {
8471
8534
  if (e.key === "Escape") onClose();
@@ -8855,7 +8918,7 @@ function CompactNavItem({
8855
8918
  expanded,
8856
8919
  onToggleExpand
8857
8920
  }) {
8858
- const triggerRef = useRef(null);
8921
+ const [triggerEl, setTriggerEl] = useState(null);
8859
8922
  const handleMouseEnter = useCallback(() => {
8860
8923
  onCancelClose();
8861
8924
  if (hasChildren && !isOpen) onOpen(flyoutKey);
@@ -8874,7 +8937,7 @@ function CompactNavItem({
8874
8937
  /* @__PURE__ */ jsxs(
8875
8938
  "button",
8876
8939
  {
8877
- ref: triggerRef,
8940
+ ref: setTriggerEl,
8878
8941
  onMouseEnter: !mobile && hasChildren ? handleMouseEnter : void 0,
8879
8942
  onMouseLeave: !mobile && hasChildren && isOpen ? handleMouseLeave : void 0,
8880
8943
  onClick: handleClick,
@@ -8908,12 +8971,12 @@ function CompactNavItem({
8908
8971
  ]
8909
8972
  }
8910
8973
  ),
8911
- !mobile && isOpen && hasChildren && item.children && triggerRef.current && /* @__PURE__ */ jsx(
8974
+ !mobile && isOpen && hasChildren && item.children && triggerEl && /* @__PURE__ */ jsx(
8912
8975
  CompactFlyout,
8913
8976
  {
8914
8977
  parentLabel: item.label,
8915
8978
  items: item.children,
8916
- anchorEl: triggerRef.current,
8979
+ anchorEl: triggerEl,
8917
8980
  portalContainer,
8918
8981
  onItemClick,
8919
8982
  onClose,
@@ -9903,6 +9966,187 @@ function Dock({
9903
9966
  }
9904
9967
  );
9905
9968
  }
9969
+ function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92], container, flush }) {
9970
+ const titleId = useId();
9971
+ const panelRef = useRef(null);
9972
+ const [mounted, setMounted] = useState(open);
9973
+ const [snapIdx, setSnapIdx] = useState(0);
9974
+ const [dragOffset, setDragOffset] = useState(0);
9975
+ const [dragging, setDragging] = useState(false);
9976
+ const dragStartY = useRef(0);
9977
+ const startHeight = useRef(0);
9978
+ const onCloseRef = useLatest(onClose);
9979
+ const [prevOpen, setPrevOpen] = useState(open);
9980
+ if (open !== prevOpen) {
9981
+ setPrevOpen(open);
9982
+ if (open) {
9983
+ setMounted(true);
9984
+ setSnapIdx(0);
9985
+ setDragOffset(0);
9986
+ }
9987
+ }
9988
+ const viewportHeight = useCallback(
9989
+ () => container instanceof HTMLElement ? container.clientHeight : window.innerHeight,
9990
+ [container]
9991
+ );
9992
+ function handleTransitionEnd() {
9993
+ if (!open) setMounted(false);
9994
+ }
9995
+ useReturnFocus(open);
9996
+ useEffect(() => {
9997
+ if (!open || !mounted) return;
9998
+ const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
9999
+ return () => cancelAnimationFrame(raf);
10000
+ }, [open, mounted]);
10001
+ useEffect(() => {
10002
+ if (!open) return;
10003
+ const onKeyDown = (e) => {
10004
+ if (e.key === "Escape") onCloseRef.current();
10005
+ };
10006
+ document.addEventListener("keydown", onKeyDown);
10007
+ if (!container) lockBodyScroll();
10008
+ return () => {
10009
+ document.removeEventListener("keydown", onKeyDown);
10010
+ if (!container) unlockBodyScroll();
10011
+ };
10012
+ }, [open, container, onCloseRef]);
10013
+ const handlePointerDown = useCallback((e) => {
10014
+ setDragging(true);
10015
+ dragStartY.current = e.clientY;
10016
+ startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * viewportHeight();
10017
+ setDragOffset(0);
10018
+ e.target.setPointerCapture(e.pointerId);
10019
+ }, [snapIdx, snapPoints, viewportHeight]);
10020
+ const handlePointerMove = useCallback((e) => {
10021
+ if (!dragging) return;
10022
+ const delta = dragStartY.current - e.clientY;
10023
+ setDragOffset(delta);
10024
+ }, [dragging]);
10025
+ const handlePointerUp = useCallback(() => {
10026
+ if (!dragging) return;
10027
+ setDragging(false);
10028
+ const currentHeightPx = startHeight.current + dragOffset;
10029
+ const currentFraction = currentHeightPx / viewportHeight();
10030
+ if (currentFraction < snapPoints[0] * 0.6) {
10031
+ onClose();
10032
+ setDragOffset(0);
10033
+ return;
10034
+ }
10035
+ let bestIdx = 0;
10036
+ let bestDist = Math.abs(currentFraction - snapPoints[0]);
10037
+ for (let i = 1; i < snapPoints.length; i++) {
10038
+ const dist = Math.abs(currentFraction - snapPoints[i]);
10039
+ if (dist < bestDist) {
10040
+ bestDist = dist;
10041
+ bestIdx = i;
10042
+ }
10043
+ }
10044
+ setSnapIdx(bestIdx);
10045
+ setDragOffset(0);
10046
+ }, [dragging, dragOffset, snapPoints, onClose, viewportHeight]);
10047
+ if (!mounted) return null;
10048
+ const baseHeight = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
10049
+ const heightUnit = container ? "%" : "vh";
10050
+ const dragPx = dragging ? dragOffset : 0;
10051
+ const positionClass = container ? "absolute" : "fixed";
10052
+ return createPortal(
10053
+ /* @__PURE__ */ jsxs(
10054
+ "div",
10055
+ {
10056
+ className: cn(
10057
+ `${positionClass} inset-0 z-[var(--ds-z-modal)] transition-opacity duration-[var(--ds-transition-speed)]`,
10058
+ open ? "pointer-events-auto" : "pointer-events-none"
10059
+ ),
10060
+ onTransitionEnd: handleTransitionEnd,
10061
+ children: [
10062
+ /* @__PURE__ */ jsx(
10063
+ "div",
10064
+ {
10065
+ className: cn(
10066
+ "absolute inset-0 bg-black/50 transition-opacity duration-[var(--ds-transition-speed)]",
10067
+ open ? "opacity-100" : "opacity-0"
10068
+ ),
10069
+ onClick: onClose
10070
+ }
10071
+ ),
10072
+ /* @__PURE__ */ jsxs(
10073
+ "div",
10074
+ {
10075
+ ref: panelRef,
10076
+ role: "dialog",
10077
+ "aria-modal": "true",
10078
+ "aria-labelledby": title ? titleId : void 0,
10079
+ "data-ds-sheet": "",
10080
+ className: cn(
10081
+ `${positionClass} bottom-0 left-0 right-0 z-10 flex flex-col`,
10082
+ "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
10083
+ GLASS_OVERLAY,
10084
+ "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
10085
+ "rounded-t-[var(--ds-radius-xl)]",
10086
+ !dragging && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
10087
+ open ? "translate-y-0" : "translate-y-full"
10088
+ ),
10089
+ style: {
10090
+ containerType: "inline-size",
10091
+ height: dragPx ? `calc(${baseHeight}${heightUnit} + ${dragPx}px)` : `${baseHeight}${heightUnit}`
10092
+ },
10093
+ children: [
10094
+ /* @__PURE__ */ jsx(
10095
+ "div",
10096
+ {
10097
+ className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
10098
+ onPointerDown: handlePointerDown,
10099
+ onPointerMove: handlePointerMove,
10100
+ onPointerUp: handlePointerUp,
10101
+ onPointerCancel: handlePointerUp,
10102
+ children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
10103
+ }
10104
+ ),
10105
+ title && /* @__PURE__ */ jsxs(
10106
+ "div",
10107
+ {
10108
+ className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
10109
+ style: { padding: "var(--ds-space-2) var(--ds-space-5)" },
10110
+ children: [
10111
+ /* @__PURE__ */ jsx("h2", { id: titleId, className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", children: title }),
10112
+ /* @__PURE__ */ jsx(
10113
+ "button",
10114
+ {
10115
+ onClick: onClose,
10116
+ ...{ [OVERLAY_CLOSE_ATTR]: "" },
10117
+ className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
10118
+ "aria-label": "Close",
10119
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
10120
+ }
10121
+ )
10122
+ ]
10123
+ }
10124
+ ),
10125
+ /* @__PURE__ */ jsx(
10126
+ "div",
10127
+ {
10128
+ className: "flex-1 overflow-y-auto",
10129
+ style: flush ? void 0 : { padding: "var(--ds-space-5)" },
10130
+ children
10131
+ }
10132
+ ),
10133
+ footer && /* @__PURE__ */ jsx(
10134
+ "div",
10135
+ {
10136
+ className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
10137
+ style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
10138
+ children: footer
10139
+ }
10140
+ )
10141
+ ]
10142
+ }
10143
+ )
10144
+ ]
10145
+ }
10146
+ ),
10147
+ container ?? document.body
10148
+ );
10149
+ }
9906
10150
  var slideClasses = {
9907
10151
  right: {
9908
10152
  panel: "top-0 right-0 h-full",
@@ -9925,19 +10169,23 @@ var borderByPosition = {
9925
10169
  left: "border-r-[length:var(--ds-border-width)] border-r-[var(--ds-border-default)]",
9926
10170
  bottom: "border-t-[length:var(--ds-border-width)] border-t-[var(--ds-border-default)]"
9927
10171
  };
9928
- function Drawer({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset }) {
10172
+ function Drawer(props) {
10173
+ const { position = "right", mobile, snapPoints, open, onClose, title, children, footer, container, flush } = props;
10174
+ const isPhone = useBreakpoint("sm");
10175
+ const mobileMode = mobile ?? (position === "left" ? "panel" : "sheet");
10176
+ if (isPhone && mobileMode === "sheet") {
10177
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title, footer, container, flush, snapPoints, children });
10178
+ }
10179
+ return /* @__PURE__ */ jsx(DrawerPanel, { ...props });
10180
+ }
10181
+ function DrawerPanel({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset }) {
9929
10182
  const titleId = useId();
9930
10183
  const { ref: titleOverflowRef, isTruncated: titleTruncated } = useTextOverflow({ lines: 1 });
9931
10184
  const isMobile = useBreakpoint("sm");
9932
10185
  const panelRef = useRef(null);
9933
- const [mounted, setMounted] = useState(false);
9934
- const onCloseRef = useRef(onClose);
9935
- onCloseRef.current = onClose;
9936
- useEffect(() => {
9937
- if (open) {
9938
- setMounted(true);
9939
- }
9940
- }, [open]);
10186
+ const [mounted, setMounted] = useState(open);
10187
+ const onCloseRef = useLatest(onClose);
10188
+ if (open && !mounted) setMounted(true);
9941
10189
  function handleTransitionEnd() {
9942
10190
  if (!open) setMounted(false);
9943
10191
  }
@@ -9952,7 +10200,7 @@ function Drawer({ open, onClose, position = "right", width = "400px", title, chi
9952
10200
  document.removeEventListener("keydown", onKeyDown);
9953
10201
  if (!container) unlockBodyScroll();
9954
10202
  };
9955
- }, [open, container]);
10203
+ }, [open, container, onCloseRef]);
9956
10204
  useReturnFocus(open);
9957
10205
  useEffect(() => {
9958
10206
  if (!open || !mounted) return;
@@ -10087,13 +10335,13 @@ var AppShell = forwardRef(function AppShell2({
10087
10335
  const hasMobileMenu = mobileMenu != null;
10088
10336
  const hasMobileNav = mobileNav != null;
10089
10337
  const showMobileDrawer = hasMobileMenu && !(isSmallMobile && hasMobileNav);
10090
- const shellRef = useRef(null);
10338
+ const [shellEl, setShellEl] = useState(null);
10091
10339
  return /* @__PURE__ */ jsx(
10092
10340
  AppShellContext,
10093
10341
  {
10094
10342
  value: { hasSidebar, hasMobileMenu, hasMobileNav, sidebarOpen, setSidebarOpen, isMobile, isTablet },
10095
10343
  children: /* @__PURE__ */ jsxs("div", { ref: (el) => {
10096
- shellRef.current = el;
10344
+ setShellEl(el);
10097
10345
  if (typeof ref === "function") ref(el);
10098
10346
  else if (ref) ref.current = el;
10099
10347
  }, className: cn("relative h-screen flex flex-col bg-[var(--ds-bg-canvas)] overflow-hidden", className), children: [
@@ -10125,7 +10373,7 @@ var AppShell = forwardRef(function AppShell2({
10125
10373
  onClose: () => setSidebarOpen(false),
10126
10374
  position: "left",
10127
10375
  width: mobileMenuWidth ?? "var(--ds-drawer-width)",
10128
- container: shellRef.current ?? void 0,
10376
+ container: shellEl ?? void 0,
10129
10377
  flush: mobileMenuFlush,
10130
10378
  lightBackdrop: mobileMenuLightBackdrop,
10131
10379
  topOffset: layout !== "stacked" && navbar != null ? "var(--ds-navbar-height)" : void 0,
@@ -10604,8 +10852,7 @@ function Dialog({ open, onClose, title, description, children, footer, variant =
10604
10852
  const overlayRef = useRef(null);
10605
10853
  const dialogRef = useRef(null);
10606
10854
  const triggerRef = useRef(null);
10607
- const onCloseRef = useRef(onClose);
10608
- onCloseRef.current = onClose;
10855
+ const onCloseRef = useLatest(onClose);
10609
10856
  const trapFocus = useCallback((e) => {
10610
10857
  if (e.key !== "Tab" || !dialogRef.current) return;
10611
10858
  const focusable = dialogRef.current.querySelectorAll(FOCUSABLE);
@@ -10641,7 +10888,7 @@ function Dialog({ open, onClose, title, description, children, footer, variant =
10641
10888
  return () => {
10642
10889
  document.removeEventListener("keydown", handler);
10643
10890
  };
10644
- }, [open, trapFocus, alert]);
10891
+ }, [open, trapFocus, alert, onCloseRef]);
10645
10892
  useEffect(() => {
10646
10893
  if (!open) return;
10647
10894
  lockBodyScroll();
@@ -10753,16 +11000,22 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
10753
11000
  groups.get(g).push(cmd);
10754
11001
  }
10755
11002
  const flatFiltered = filtered;
10756
- useEffect(() => {
11003
+ const [prevOpen, setPrevOpen] = useState(open);
11004
+ if (open !== prevOpen) {
11005
+ setPrevOpen(open);
10757
11006
  if (open) {
10758
11007
  setQuery("");
10759
11008
  setSelectedIdx(0);
10760
- requestAnimationFrame(() => inputRef.current?.focus());
10761
11009
  }
10762
- }, [open]);
10763
- useEffect(() => {
11010
+ }
11011
+ const [prevQuery, setPrevQuery] = useState(query);
11012
+ if (query !== prevQuery) {
11013
+ setPrevQuery(query);
10764
11014
  setSelectedIdx(0);
10765
- }, [query]);
11015
+ }
11016
+ useEffect(() => {
11017
+ if (open) requestAnimationFrame(() => inputRef.current?.focus());
11018
+ }, [open]);
10766
11019
  useEffect(() => {
10767
11020
  if (!open) return;
10768
11021
  const handler = (e) => {
@@ -10778,23 +11031,20 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
10778
11031
  },
10779
11032
  [onOpenChange]
10780
11033
  );
10781
- const handleKeyDown = useCallback(
10782
- (e) => {
10783
- if (e.key === "ArrowDown") {
10784
- e.preventDefault();
10785
- setSelectedIdx((prev) => Math.min(prev + 1, flatFiltered.length - 1));
10786
- listRef.current?.children[Math.min(selectedIdx + 1, flatFiltered.length - 1)]?.scrollIntoView({ block: "nearest" });
10787
- } else if (e.key === "ArrowUp") {
10788
- e.preventDefault();
10789
- setSelectedIdx((prev) => Math.max(prev - 1, 0));
10790
- listRef.current?.children[Math.max(selectedIdx - 1, 0)]?.scrollIntoView({ block: "nearest" });
10791
- } else if (e.key === "Enter" && flatFiltered[selectedIdx]) {
10792
- e.preventDefault();
10793
- handleSelect(flatFiltered[selectedIdx]);
10794
- }
10795
- },
10796
- [flatFiltered, selectedIdx, handleSelect]
10797
- );
11034
+ const handleKeyDown = (e) => {
11035
+ if (e.key === "ArrowDown") {
11036
+ e.preventDefault();
11037
+ setSelectedIdx((prev) => Math.min(prev + 1, flatFiltered.length - 1));
11038
+ listRef.current?.children[Math.min(selectedIdx + 1, flatFiltered.length - 1)]?.scrollIntoView({ block: "nearest" });
11039
+ } else if (e.key === "ArrowUp") {
11040
+ e.preventDefault();
11041
+ setSelectedIdx((prev) => Math.max(prev - 1, 0));
11042
+ listRef.current?.children[Math.max(selectedIdx - 1, 0)]?.scrollIntoView({ block: "nearest" });
11043
+ } else if (e.key === "Enter" && flatFiltered[selectedIdx]) {
11044
+ e.preventDefault();
11045
+ handleSelect(flatFiltered[selectedIdx]);
11046
+ }
11047
+ };
10798
11048
  if (!open) return null;
10799
11049
  let flatIdx = 0;
10800
11050
  return createPortal(
@@ -11087,179 +11337,6 @@ function ContextMenu({ items, children, className, ref, container }) {
11087
11337
  }
11088
11338
  );
11089
11339
  }
11090
- function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92] }) {
11091
- const titleId = useId();
11092
- const panelRef = useRef(null);
11093
- const [mounted, setMounted] = useState(false);
11094
- const [snapIdx, setSnapIdx] = useState(0);
11095
- const [dragOffset, setDragOffset] = useState(0);
11096
- const isDragging = useRef(false);
11097
- const dragStartY = useRef(0);
11098
- const startHeight = useRef(0);
11099
- const onCloseRef = useRef(onClose);
11100
- onCloseRef.current = onClose;
11101
- useEffect(() => {
11102
- if (open) {
11103
- setMounted(true);
11104
- setSnapIdx(0);
11105
- setDragOffset(0);
11106
- }
11107
- }, [open]);
11108
- function handleTransitionEnd() {
11109
- if (!open) setMounted(false);
11110
- }
11111
- useReturnFocus(open);
11112
- useEffect(() => {
11113
- if (!open || !mounted) return;
11114
- const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
11115
- return () => cancelAnimationFrame(raf);
11116
- }, [open, mounted]);
11117
- useEffect(() => {
11118
- if (!open) return;
11119
- const onKeyDown = (e) => {
11120
- if (e.key === "Escape") onCloseRef.current();
11121
- };
11122
- document.addEventListener("keydown", onKeyDown);
11123
- lockBodyScroll();
11124
- return () => {
11125
- document.removeEventListener("keydown", onKeyDown);
11126
- unlockBodyScroll();
11127
- };
11128
- }, [open]);
11129
- const handlePointerDown = useCallback((e) => {
11130
- isDragging.current = true;
11131
- dragStartY.current = e.clientY;
11132
- startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * window.innerHeight;
11133
- setDragOffset(0);
11134
- e.target.setPointerCapture(e.pointerId);
11135
- }, [snapIdx, snapPoints]);
11136
- const handlePointerMove = useCallback((e) => {
11137
- if (!isDragging.current) return;
11138
- const delta = dragStartY.current - e.clientY;
11139
- setDragOffset(delta);
11140
- }, []);
11141
- const handlePointerUp = useCallback(() => {
11142
- if (!isDragging.current) return;
11143
- isDragging.current = false;
11144
- const currentHeightPx = startHeight.current + dragOffset;
11145
- const currentFraction = currentHeightPx / window.innerHeight;
11146
- if (currentFraction < snapPoints[0] * 0.6) {
11147
- onClose();
11148
- setDragOffset(0);
11149
- return;
11150
- }
11151
- let bestIdx = 0;
11152
- let bestDist = Math.abs(currentFraction - snapPoints[0]);
11153
- for (let i = 1; i < snapPoints.length; i++) {
11154
- const dist = Math.abs(currentFraction - snapPoints[i]);
11155
- if (dist < bestDist) {
11156
- bestDist = dist;
11157
- bestIdx = i;
11158
- }
11159
- }
11160
- setSnapIdx(bestIdx);
11161
- setDragOffset(0);
11162
- }, [dragOffset, snapPoints, onClose]);
11163
- if (!mounted) return null;
11164
- const baseHeightVh = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
11165
- const dragPx = isDragging.current ? dragOffset : 0;
11166
- return createPortal(
11167
- /* @__PURE__ */ jsxs(
11168
- "div",
11169
- {
11170
- className: cn(
11171
- "fixed inset-0 z-[var(--ds-z-modal)] transition-opacity duration-[var(--ds-transition-speed)]",
11172
- open ? "pointer-events-auto" : "pointer-events-none"
11173
- ),
11174
- onTransitionEnd: handleTransitionEnd,
11175
- children: [
11176
- /* @__PURE__ */ jsx(
11177
- "div",
11178
- {
11179
- className: cn(
11180
- "absolute inset-0 bg-black/50 transition-opacity duration-[var(--ds-transition-speed)]",
11181
- open ? "opacity-100" : "opacity-0"
11182
- ),
11183
- onClick: onClose
11184
- }
11185
- ),
11186
- /* @__PURE__ */ jsxs(
11187
- "div",
11188
- {
11189
- ref: panelRef,
11190
- role: "dialog",
11191
- "aria-modal": "true",
11192
- "aria-labelledby": title ? titleId : void 0,
11193
- className: cn(
11194
- "fixed bottom-0 left-0 right-0 z-10 flex flex-col",
11195
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
11196
- GLASS_OVERLAY,
11197
- "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
11198
- "rounded-t-[var(--ds-radius-xl)]",
11199
- !isDragging.current && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
11200
- open ? "translate-y-0" : "translate-y-full"
11201
- ),
11202
- style: {
11203
- containerType: "inline-size",
11204
- height: dragPx ? `calc(${baseHeightVh}vh + ${dragPx}px)` : `${baseHeightVh}vh`
11205
- },
11206
- children: [
11207
- /* @__PURE__ */ jsx(
11208
- "div",
11209
- {
11210
- className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
11211
- onPointerDown: handlePointerDown,
11212
- onPointerMove: handlePointerMove,
11213
- onPointerUp: handlePointerUp,
11214
- onPointerCancel: handlePointerUp,
11215
- children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
11216
- }
11217
- ),
11218
- title && /* @__PURE__ */ jsxs(
11219
- "div",
11220
- {
11221
- className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11222
- style: { padding: "var(--ds-space-2) var(--ds-space-5)" },
11223
- children: [
11224
- /* @__PURE__ */ jsx("h2", { id: titleId, className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", children: title }),
11225
- /* @__PURE__ */ jsx(
11226
- "button",
11227
- {
11228
- onClick: onClose,
11229
- ...{ [OVERLAY_CLOSE_ATTR]: "" },
11230
- className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
11231
- "aria-label": "Close",
11232
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
11233
- }
11234
- )
11235
- ]
11236
- }
11237
- ),
11238
- /* @__PURE__ */ jsx(
11239
- "div",
11240
- {
11241
- className: "flex-1 overflow-y-auto",
11242
- style: { padding: "var(--ds-space-5)" },
11243
- children
11244
- }
11245
- ),
11246
- footer && /* @__PURE__ */ jsx(
11247
- "div",
11248
- {
11249
- className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11250
- style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
11251
- children: footer
11252
- }
11253
- )
11254
- ]
11255
- }
11256
- )
11257
- ]
11258
- }
11259
- ),
11260
- document.body
11261
- );
11262
- }
11263
11340
  function TruncTitle({ children }) {
11264
11341
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
11265
11342
  const el = /* @__PURE__ */ jsx("span", { ref, className: "truncate", children });
@@ -11290,7 +11367,10 @@ function NotificationCenter({
11290
11367
  const [internalFilter, setInternalFilter] = useState("all");
11291
11368
  const isControlled = controlledOpen !== void 0;
11292
11369
  const open = isControlled ? controlledOpen : internalOpen;
11293
- const setOpen = isControlled ? (v) => onOpenChange?.(v) : setInternalOpen;
11370
+ const setOpen = useCallback((v) => {
11371
+ if (isControlled) onOpenChange?.(v);
11372
+ else setInternalOpen(v);
11373
+ }, [isControlled, onOpenChange]);
11294
11374
  const triggerRef = useRef(null);
11295
11375
  const panelRef = useRef(null);
11296
11376
  const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "end", offset: 8, flip: true });
@@ -11314,7 +11394,7 @@ function NotificationCenter({
11314
11394
  document.removeEventListener("keydown", onKey);
11315
11395
  document.removeEventListener("mousedown", onClick);
11316
11396
  };
11317
- }, [open]);
11397
+ }, [open, setOpen]);
11318
11398
  const grouped = groupByDate(filteredNotifications);
11319
11399
  if (variant === "page") {
11320
11400
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), children: [
@@ -11561,11 +11641,13 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11561
11641
  if (el) setRect(el.getBoundingClientRect());
11562
11642
  else setRect(null);
11563
11643
  }, [step]);
11644
+ const [prevOpen, setPrevOpen] = useState(open);
11645
+ if (open !== prevOpen) {
11646
+ setPrevOpen(open);
11647
+ if (!open) setCurrent(0);
11648
+ }
11564
11649
  useEffect(() => {
11565
- if (!open) {
11566
- setCurrent(0);
11567
- return;
11568
- }
11650
+ if (!open) return;
11569
11651
  measureTarget();
11570
11652
  window.addEventListener("resize", measureTarget);
11571
11653
  window.addEventListener("scroll", measureTarget, true);
@@ -11574,16 +11656,6 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11574
11656
  window.removeEventListener("scroll", measureTarget, true);
11575
11657
  };
11576
11658
  }, [open, current, measureTarget]);
11577
- useEffect(() => {
11578
- if (!open) return;
11579
- const onKey = (e) => {
11580
- if (e.key === "Escape") onClose();
11581
- else if (e.key === "ArrowRight") handleNext();
11582
- else if (e.key === "ArrowLeft") handlePrev();
11583
- };
11584
- document.addEventListener("keydown", onKey);
11585
- return () => document.removeEventListener("keydown", onKey);
11586
- });
11587
11659
  const handleNext = useCallback(() => {
11588
11660
  if (current < steps.length - 1) setCurrent((c) => c + 1);
11589
11661
  else {
@@ -11594,6 +11666,16 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11594
11666
  const handlePrev = useCallback(() => {
11595
11667
  if (current > 0) setCurrent((c) => c - 1);
11596
11668
  }, [current]);
11669
+ useEffect(() => {
11670
+ if (!open) return;
11671
+ const onKey = (e) => {
11672
+ if (e.key === "Escape") onClose();
11673
+ else if (e.key === "ArrowRight") handleNext();
11674
+ else if (e.key === "ArrowLeft") handlePrev();
11675
+ };
11676
+ document.addEventListener("keydown", onKey);
11677
+ return () => document.removeEventListener("keydown", onKey);
11678
+ }, [open, onClose, handleNext, handlePrev]);
11597
11679
  if (!open || !step) return null;
11598
11680
  const padding = 8;
11599
11681
  const spotlightRadius = 8;
@@ -11897,15 +11979,14 @@ function Toast({ variant = "default", message, visible, onDismiss, duration = 3e
11897
11979
  const timerRef = useRef(void 0);
11898
11980
  const startedAtRef = useRef(0);
11899
11981
  const remainingRef = useRef(duration);
11900
- const onDismissRef = useRef(onDismiss);
11901
- onDismissRef.current = onDismiss;
11982
+ const onDismissRef = useLatest(onDismiss);
11902
11983
  const startTimer = useCallback((ms) => {
11903
11984
  const delay = ms ?? remainingRef.current;
11904
11985
  remainingRef.current = delay;
11905
11986
  startedAtRef.current = Date.now();
11906
11987
  if (!Number.isFinite(delay) || delay <= 0) return;
11907
11988
  timerRef.current = setTimeout(() => onDismissRef.current(), delay);
11908
- }, []);
11989
+ }, [onDismissRef]);
11909
11990
  const clearTimer = useCallback(() => {
11910
11991
  if (timerRef.current) {
11911
11992
  clearTimeout(timerRef.current);