gridsmith-ui 0.6.3 → 0.6.5

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,13 +1,13 @@
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, FOCUS_RING, HAS_FOCUS_RING, FOCUS_WITHIN_RING } from './chunk-TTZSZNJ6.js';
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
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';
@@ -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
  }, []);
@@ -5818,9 +5825,12 @@ function MonthDropdown({
5818
5825
  offset: 8,
5819
5826
  flip: true
5820
5827
  });
5821
- useEffect(() => {
5828
+ const browseSyncKey = open ? currentDate.getTime() : null;
5829
+ const [prevBrowseSyncKey, setPrevBrowseSyncKey] = useState(browseSyncKey);
5830
+ if (browseSyncKey !== prevBrowseSyncKey) {
5831
+ setPrevBrowseSyncKey(browseSyncKey);
5822
5832
  if (open) setBrowseYear(currentDate.getFullYear());
5823
- }, [open, currentDate]);
5833
+ }
5824
5834
  useEffect(() => {
5825
5835
  if (!open) return;
5826
5836
  const onKey = (e) => {
@@ -5938,9 +5948,12 @@ function YearDropdown({
5938
5948
  });
5939
5949
  const currentYear = currentDate.getFullYear();
5940
5950
  const [rangeStart, setRangeStart] = useState(() => currentYear - 4);
5941
- useEffect(() => {
5951
+ const rangeSyncKey = open ? currentYear : null;
5952
+ const [prevRangeSyncKey, setPrevRangeSyncKey] = useState(rangeSyncKey);
5953
+ if (rangeSyncKey !== prevRangeSyncKey) {
5954
+ setPrevRangeSyncKey(rangeSyncKey);
5942
5955
  if (open) setRangeStart(currentYear - 4);
5943
- }, [open, currentYear]);
5956
+ }
5944
5957
  useEffect(() => {
5945
5958
  if (!open) return;
5946
5959
  const onKey = (e) => {
@@ -6662,6 +6675,13 @@ function TruncLabel2({ children }) {
6662
6675
  if (!isTruncated) return label;
6663
6676
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: label });
6664
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
+ }
6665
6685
  function TreeView({
6666
6686
  nodes,
6667
6687
  selectable,
@@ -6704,13 +6724,6 @@ function TreeView({
6704
6724
  },
6705
6725
  [selectable, multiSelect, onSelect]
6706
6726
  );
6707
- const allDescendantIds = useCallback((node) => {
6708
- const ids = [node.id];
6709
- if (node.children) {
6710
- for (const child of node.children) ids.push(...allDescendantIds(child));
6711
- }
6712
- return ids;
6713
- }, []);
6714
6727
  const handleCheck = useCallback(
6715
6728
  (node) => {
6716
6729
  if (!checkable) return;
@@ -6726,7 +6739,7 @@ function TreeView({
6726
6739
  return next;
6727
6740
  });
6728
6741
  },
6729
- [checkable, allDescendantIds, onCheck]
6742
+ [checkable, onCheck]
6730
6743
  );
6731
6744
  const getCheckState = useCallback(
6732
6745
  (node) => {
@@ -6737,7 +6750,7 @@ function TreeView({
6737
6750
  if (checkedCount > 0) return "indeterminate";
6738
6751
  return "unchecked";
6739
6752
  },
6740
- [checked, allDescendantIds]
6753
+ [checked]
6741
6754
  );
6742
6755
  const flatNodes = useCallback(() => {
6743
6756
  const result = [];
@@ -6833,7 +6846,9 @@ function TreeNodeRow({
6833
6846
  className: cn(
6834
6847
  "flex items-center gap-ds-2 py-ds-1.5 px-ds-2 rounded-[var(--ds-radius-md)] cursor-pointer transition-colors",
6835
6848
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
6836
- isSelected ? "bg-[var(--ds-tab-active-bg)] text-[var(--ds-accent-primary)] font-medium border-l-2 border-[var(--ds-accent-primary)]" : "text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)] hover:text-[var(--ds-text-primary)]",
6849
+ // Active style comes from the theme's --ds-tree-* tokens (rail / tint;
6850
+ // pill degrades to tint). The rail is drawn on the selected row only.
6851
+ isSelected ? "bg-[var(--ds-tree-active-bg)] text-[var(--ds-tree-active-text)] font-medium border-l-[length:var(--ds-tree-active-rail)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)] hover:text-[var(--ds-text-primary)]",
6837
6852
  node.disabled && "opacity-disabled pointer-events-none"
6838
6853
  ),
6839
6854
  style: { paddingLeft: `calc(var(--ds-space-5) * ${depth} + var(--ds-space-2))` },
@@ -6860,7 +6875,7 @@ function TreeNodeRow({
6860
6875
  {
6861
6876
  name: "chevron-right",
6862
6877
  size: 14,
6863
- className: cn("text-[var(--ds-text-muted)] transition-transform", isExpanded && "rotate-90")
6878
+ className: cn(isSelected ? "text-[var(--ds-tree-active-muted)]" : "text-[var(--ds-text-muted)]", "transition-transform", isExpanded && "rotate-90")
6864
6879
  }
6865
6880
  )
6866
6881
  }
@@ -6875,7 +6890,7 @@ function TreeNodeRow({
6875
6890
  }
6876
6891
  }
6877
6892
  ),
6878
- node.icon && /* @__PURE__ */ jsx(Icon, { name: node.icon, size: 16, className: cn("shrink-0", isSelected ? "text-[var(--ds-accent-primary)]" : "text-[var(--ds-text-muted)]") }),
6893
+ node.icon && /* @__PURE__ */ jsx(Icon, { name: node.icon, size: 16, className: cn("shrink-0", isSelected ? "text-[var(--ds-tree-active-text)]" : "text-[var(--ds-text-muted)]") }),
6879
6894
  /* @__PURE__ */ jsx(TruncLabel2, { children: node.label })
6880
6895
  ]
6881
6896
  }
@@ -7363,6 +7378,19 @@ function useTabsCtx() {
7363
7378
  if (!ctx) throw new Error("Tabs.List / Tabs.Trigger / Tabs.Content must be used inside <Tabs>");
7364
7379
  return ctx;
7365
7380
  }
7381
+ function trackClasses(orientation) {
7382
+ if (orientation === "vertical") {
7383
+ return cn(
7384
+ "flex flex-col gap-ds-0.5 min-w-[var(--ds-tab-rail-width)]",
7385
+ "border-r-[length:var(--ds-nav-v-track-border)] border-[var(--ds-border-default)]",
7386
+ "pr-[var(--ds-nav-v-track-padding)]"
7387
+ );
7388
+ }
7389
+ return cn(
7390
+ "flex gap-[var(--ds-nav-h-track-gap)]",
7391
+ "border-b-[length:var(--ds-nav-h-track-border)] border-[var(--ds-border-default)]"
7392
+ );
7393
+ }
7366
7394
  function getTabsFromDOM(el) {
7367
7395
  const tablist = el.closest('[role="tablist"]');
7368
7396
  if (!tablist) return [];
@@ -7410,7 +7438,6 @@ function TabButton({
7410
7438
  icon,
7411
7439
  disabled,
7412
7440
  orientation,
7413
- variant,
7414
7441
  activeTab,
7415
7442
  onTabChange,
7416
7443
  fullWidth,
@@ -7423,10 +7450,11 @@ function TabButton({
7423
7450
  const isActive = activeTab === id;
7424
7451
  const tabId = `ds-tab-${id}`;
7425
7452
  const panelId = hasPanel === false ? void 0 : `ds-tabpanel-${id}`;
7453
+ const badgeBg = orientation === "vertical" ? "bg-[var(--ds-nav-v-badge-bg)]" : "bg-[var(--ds-nav-h-badge-bg)]";
7426
7454
  const countBadge = count !== void 0 ? /* @__PURE__ */ jsx("span", { className: cn(
7427
7455
  orientation === "vertical" ? "ml-ds-2" : "",
7428
7456
  "px-ds-1.5 py-ds-0.5 text-xs rounded-[var(--ds-radius-sm)]",
7429
- variant === "pill" && isActive ? "bg-[var(--ds-bg-canvas)]/20 text-[var(--ds-text-on-primary)]" : isActive ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "bg-[var(--ds-bg-emphasis)] text-[var(--ds-text-muted)]"
7457
+ isActive ? cn(badgeBg, "text-[var(--ds-text-on-primary)]") : "bg-[var(--ds-bg-emphasis)] text-[var(--ds-text-muted)]"
7430
7458
  ), children: count }) : null;
7431
7459
  if (orientation === "vertical") {
7432
7460
  const btn2 = /* @__PURE__ */ jsxs(
@@ -7443,10 +7471,12 @@ function TabButton({
7443
7471
  className: cn(
7444
7472
  "flex items-center justify-between px-ds-3 py-ds-2 text-sm font-medium text-left",
7445
7473
  disabled ? "cursor-not-allowed opacity-disabled" : "cursor-pointer",
7446
- "rounded-[var(--ds-radius-sm)]",
7474
+ "rounded-[var(--ds-nav-v-item-radius)]",
7447
7475
  "transition-colors duration-[var(--ds-transition-speed)]",
7448
7476
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
7449
- variant === "pill" ? isActive ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]" : isActive ? "text-[var(--ds-accent-primary)] bg-[var(--ds-tab-active-bg)] border-l-[length:var(--ds-border-width-thicker)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]",
7477
+ // The rail is drawn on the active item only (today's behaviour; a
7478
+ // transparent placeholder on every item is a future layout tweak).
7479
+ isActive ? "bg-[var(--ds-nav-v-active-bg)] text-[var(--ds-nav-v-active-text)] border-l-[length:var(--ds-nav-v-active-rail)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]",
7450
7480
  className
7451
7481
  ),
7452
7482
  children: [
@@ -7479,13 +7509,11 @@ function TabButton({
7479
7509
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
7480
7510
  fullWidth && "flex-1",
7481
7511
  truncate && "flex-1 min-w-0 flex items-center",
7482
- variant === "pill" ? cn(
7483
- "rounded-[var(--ds-radius-md)]",
7484
- isActive ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]"
7485
- ) : isActive ? "text-[var(--ds-text-primary)] border-b-[length:var(--ds-border-width)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]",
7512
+ "rounded-[var(--ds-nav-h-item-radius)]",
7513
+ isActive ? "bg-[var(--ds-nav-h-active-bg)] text-[var(--ds-nav-h-active-text)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-nav-h-inactive-text)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-nav-h-hover-bg)]",
7486
7514
  className
7487
7515
  ),
7488
- style: variant === "underline" && orientation === "horizontal" && isActive ? { borderBottomWidth: "calc(var(--ds-border-width) * 3)", marginBottom: "calc(var(--ds-border-width) * -1)" } : void 0,
7516
+ style: isActive ? { borderBottomWidth: "var(--ds-nav-h-indicator)", marginBottom: "calc(var(--ds-nav-h-track-border) * -1)" } : void 0,
7489
7517
  children: truncate ? /* @__PURE__ */ jsxs(Fragment, { children: [
7490
7518
  icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16, className: "shrink-0" }),
7491
7519
  /* @__PURE__ */ jsx("span", { ref: truncRef, className: "truncate min-w-0 flex-1", children: label }),
@@ -7501,18 +7529,14 @@ function TabButton({
7501
7529
  return btn;
7502
7530
  }
7503
7531
  function TabsList({ children, className }) {
7504
- const { orientation, variant, fullWidth, overflow } = useTabsCtx();
7532
+ const { orientation, fullWidth, overflow } = useTabsCtx();
7505
7533
  if (orientation === "vertical") {
7506
7534
  return /* @__PURE__ */ jsx(
7507
7535
  "div",
7508
7536
  {
7509
7537
  role: "tablist",
7510
7538
  "aria-orientation": "vertical",
7511
- className: cn(
7512
- "flex flex-col gap-ds-0.5 min-w-[var(--ds-tab-rail-width)]",
7513
- variant === "underline" && "border-r-[length:var(--ds-border-width)] border-[var(--ds-border-default)] pr-ds-1",
7514
- className
7515
- ),
7539
+ className: cn(trackClasses("vertical"), className),
7516
7540
  children
7517
7541
  }
7518
7542
  );
@@ -7522,10 +7546,9 @@ function TabsList({ children, className }) {
7522
7546
  {
7523
7547
  role: "tablist",
7524
7548
  className: cn(
7525
- "flex gap-0 max-w-full",
7549
+ trackClasses("horizontal"),
7550
+ "max-w-full",
7526
7551
  overflow === "hidden" ? "overflow-hidden" : overflow === "truncate" ? "overflow-hidden" : "overflow-x-auto overflow-y-hidden",
7527
- variant === "underline" && "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
7528
- variant === "pill" && "gap-ds-1 p-ds-0.5 bg-[var(--ds-bg-muted)] rounded-[var(--ds-radius-lg)]",
7529
7552
  fullWidth && "w-full",
7530
7553
  className
7531
7554
  ),
@@ -7534,7 +7557,7 @@ function TabsList({ children, className }) {
7534
7557
  );
7535
7558
  }
7536
7559
  function TabsTrigger({ value, children, count, icon, disabled, truncate, className }) {
7537
- const { activeTab, onTabChange, orientation, variant, fullWidth, overflow } = useTabsCtx();
7560
+ const { activeTab, onTabChange, orientation, fullWidth, overflow } = useTabsCtx();
7538
7561
  return /* @__PURE__ */ jsx(
7539
7562
  TabButton,
7540
7563
  {
@@ -7545,7 +7568,6 @@ function TabsTrigger({ value, children, count, icon, disabled, truncate, classNa
7545
7568
  disabled,
7546
7569
  truncate: overflow === "truncate" || truncate,
7547
7570
  orientation,
7548
- variant,
7549
7571
  activeTab,
7550
7572
  onTabChange,
7551
7573
  fullWidth,
@@ -7574,7 +7596,6 @@ function TabsRoot(props) {
7574
7596
  activeTab,
7575
7597
  onTabChange,
7576
7598
  orientation = "horizontal",
7577
- variant = "underline",
7578
7599
  fullWidth = false,
7579
7600
  overflow = "scroll",
7580
7601
  className
@@ -7601,11 +7622,7 @@ function TabsRoot(props) {
7601
7622
  {
7602
7623
  role: "tablist",
7603
7624
  "aria-orientation": "vertical",
7604
- className: cn(
7605
- "flex flex-col gap-ds-0.5 min-w-[var(--ds-tab-rail-width)]",
7606
- variant === "underline" && "border-r-[length:var(--ds-border-width)] border-[var(--ds-border-default)] pr-ds-1",
7607
- className
7608
- ),
7625
+ className: cn(trackClasses("vertical"), className),
7609
7626
  children: tabs.map((tab, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
7610
7627
  tab.group && tab.group !== tabs[i - 1]?.group && /* @__PURE__ */ jsx(
7611
7628
  "span",
@@ -7618,7 +7635,7 @@ function TabsRoot(props) {
7618
7635
  children: tab.group
7619
7636
  }
7620
7637
  ),
7621
- /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation: effectiveOrientation, variant, activeTab, onTabChange, fullWidth, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, effectiveOrientation, onTabChange) })
7638
+ /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation: effectiveOrientation, activeTab, onTabChange, fullWidth, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, effectiveOrientation, onTabChange) })
7622
7639
  ] }, tab.id))
7623
7640
  }
7624
7641
  );
@@ -7629,18 +7646,16 @@ function TabsRoot(props) {
7629
7646
  {
7630
7647
  role: "tablist",
7631
7648
  className: cn(
7632
- "flex gap-0",
7649
+ trackClasses("horizontal"),
7633
7650
  effectiveOverflow === "scroll" ? "overflow-x-auto overflow-y-hidden" : "overflow-hidden",
7634
- variant === "underline" && "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
7635
- variant === "pill" && "gap-ds-1 p-ds-0.5 bg-[var(--ds-bg-muted)] rounded-[var(--ds-radius-lg)]",
7636
7651
  fullWidth && "w-full",
7637
7652
  className
7638
7653
  ),
7639
- 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))
7654
+ children: tabs.map((tab) => /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation: effectiveOrientation, activeTab, onTabChange, fullWidth, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, effectiveOrientation, onTabChange) }, tab.id))
7640
7655
  }
7641
7656
  );
7642
7657
  }
7643
- return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { activeTab, onTabChange, orientation: effectiveOrientation, variant, fullWidth, overflow }, children: /* @__PURE__ */ jsx("div", { className, children: props.children }) });
7658
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { activeTab, onTabChange, orientation: effectiveOrientation, fullWidth, overflow }, children: /* @__PURE__ */ jsx("div", { className, children: props.children }) });
7644
7659
  }
7645
7660
  var Tabs = Object.assign(TabsRoot, {
7646
7661
  List: TabsList,
@@ -7845,6 +7860,11 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
7845
7860
  function Popover({ open, onOpenChange, trigger, children, side = "bottom", align = "center", width, className, container }) {
7846
7861
  const triggerRef = useRef(null);
7847
7862
  const floatingRef = useRef(null);
7863
+ const [triggerWidth, setTriggerWidth] = useState(void 0);
7864
+ const setTriggerNode = useCallback((node) => {
7865
+ triggerRef.current = node;
7866
+ setTriggerWidth(node?.offsetWidth);
7867
+ }, []);
7848
7868
  const pos = usePosition(triggerRef, floatingRef, open, { side, align, offset: 8, flip: true });
7849
7869
  useEffect(() => {
7850
7870
  if (!open) return;
@@ -7870,8 +7890,11 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
7870
7890
  /* @__PURE__ */ jsx(
7871
7891
  Slot,
7872
7892
  {
7873
- ref: triggerRef,
7874
- onClick: () => onOpenChange(!open),
7893
+ ref: setTriggerNode,
7894
+ onClick: () => {
7895
+ setTriggerWidth(triggerRef.current?.offsetWidth);
7896
+ onOpenChange(!open);
7897
+ },
7875
7898
  children: trigger
7876
7899
  }
7877
7900
  ),
@@ -7891,7 +7914,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
7891
7914
  style: {
7892
7915
  top: pos.top,
7893
7916
  left: pos.left,
7894
- width: width === "trigger" ? triggerRef.current?.offsetWidth : width === "auto" ? void 0 : width
7917
+ width: width === "trigger" ? triggerWidth : width === "auto" ? void 0 : width
7895
7918
  },
7896
7919
  children
7897
7920
  }
@@ -8507,10 +8530,7 @@ function CompactFlyout({
8507
8530
  onMouseEnter,
8508
8531
  onMouseLeave
8509
8532
  }) {
8510
- const [pos, setPos] = useState(() => computeCompactFlyoutPos(anchorEl, items.length));
8511
- useLayoutEffect(() => {
8512
- setPos(computeCompactFlyoutPos(anchorEl, items.length));
8513
- }, [anchorEl, items.length]);
8533
+ const pos = useMemo(() => computeCompactFlyoutPos(anchorEl, items.length), [anchorEl, items.length]);
8514
8534
  useEffect(() => {
8515
8535
  function handler(e) {
8516
8536
  if (e.key === "Escape") onClose();
@@ -8549,7 +8569,7 @@ function CompactFlyout({
8549
8569
  },
8550
8570
  className: cn(
8551
8571
  "flex items-center gap-ds-2 px-ds-2 py-ds-1.5 text-xs rounded-[var(--ds-radius-sm)] transition-colors cursor-pointer",
8552
- child.active ? "bg-sidebar-accent-subtle text-[var(--ds-sidebar-accent)] font-medium" : "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8572
+ child.active ? "bg-[var(--ds-sidebar-active-bg)] text-[var(--ds-sidebar-active-accent)] font-medium" : "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8553
8573
  ),
8554
8574
  children: [
8555
8575
  child.icon && /* @__PURE__ */ jsx(Icon, { name: child.icon, size: 14 }),
@@ -8655,7 +8675,10 @@ function SidebarNav({
8655
8675
  "flex items-center gap-ds-2 transition-colors cursor-pointer",
8656
8676
  isRoot ? cn(rootPy, rootText, isMinimal ? "rounded-[var(--ds-radius-full)]" : "rounded-[var(--ds-radius-md)]") : cn(childPy, "text-xs rounded-[var(--ds-radius-sm)]"),
8657
8677
  isRoot ? collapsed ? "justify-center px-0" : "px-ds-3" : "px-ds-2",
8658
- item.active ? isRoot ? isMinimal ? "bg-sidebar-accent-subtle text-[var(--ds-sidebar-text)] font-medium" : "bg-sidebar-accent-subtle text-[var(--ds-sidebar-text)] font-medium border-l-2 border-[var(--ds-sidebar-accent)]" : "bg-sidebar-accent-subtle text-[var(--ds-sidebar-accent)] font-medium" : isRoot ? isMinimal ? "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]" : "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)] border-l-2 border-transparent" : "text-[var(--ds-sidebar-text-muted)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8678
+ // Active style comes from the theme's --ds-sidebar-active-* tokens
8679
+ // (rail / tint / pill). The rail token is 0px for minimal sidebars,
8680
+ // so the transparent placeholder on inactive roots is harmless there.
8681
+ item.active ? isRoot ? "bg-[var(--ds-sidebar-active-bg)] text-[var(--ds-sidebar-active-text)] font-medium border-l-[length:var(--ds-sidebar-active-rail)] border-[var(--ds-sidebar-active-accent)]" : "bg-[var(--ds-sidebar-active-bg)] text-[var(--ds-sidebar-active-accent)] font-medium" : isRoot ? "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)] border-l-[length:var(--ds-sidebar-active-rail)] border-transparent" : "text-[var(--ds-sidebar-text-muted)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8659
8682
  ),
8660
8683
  children: [
8661
8684
  item.icon && /* @__PURE__ */ jsx(
@@ -8663,7 +8686,7 @@ function SidebarNav({
8663
8686
  {
8664
8687
  name: item.icon,
8665
8688
  size: isRoot ? rootIconSize : childIconSize,
8666
- className: cn("shrink-0", item.active && "text-[var(--ds-sidebar-accent)]")
8689
+ className: cn("shrink-0", item.active && "text-[var(--ds-sidebar-active-accent)]")
8667
8690
  }
8668
8691
  ),
8669
8692
  !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -8735,7 +8758,10 @@ function SidebarNav({
8735
8758
  "flex items-center gap-ds-2 w-full text-left transition-colors cursor-pointer",
8736
8759
  isRoot ? cn(rootPy, rootText, isMinimal ? "rounded-[var(--ds-radius-full)]" : "rounded-[var(--ds-radius-md)]") : cn(childPy, "text-xs rounded-[var(--ds-radius-sm)]"),
8737
8760
  isRoot ? collapsed ? "justify-center px-0" : "px-ds-3" : "px-ds-2",
8738
- item.active || childActive ? isRoot ? isMinimal ? "text-[var(--ds-sidebar-text)] font-medium" : "text-[var(--ds-sidebar-text)] font-medium border-l-2 border-transparent" : "text-[var(--ds-sidebar-text)] font-medium" : isRoot ? isMinimal ? "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]" : "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)] border-l-2 border-transparent" : "text-[var(--ds-sidebar-text-muted)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8761
+ // This row has no fill, so its label, icon and chevron keep the plain
8762
+ // sidebar tokens: --ds-sidebar-active-accent is on-primary white under
8763
+ // `pill` and would vanish here. Only filled elements read the active set.
8764
+ item.active || childActive ? isRoot ? "text-[var(--ds-sidebar-text)] font-medium border-l-[length:var(--ds-sidebar-active-rail)] border-transparent" : "text-[var(--ds-sidebar-text)] font-medium" : isRoot ? "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)] border-l-[length:var(--ds-sidebar-active-rail)] border-transparent" : "text-[var(--ds-sidebar-text-muted)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8739
8765
  ),
8740
8766
  children: [
8741
8767
  item.icon && /* @__PURE__ */ jsx(
@@ -8900,7 +8926,7 @@ function CompactNavItem({
8900
8926
  expanded,
8901
8927
  onToggleExpand
8902
8928
  }) {
8903
- const triggerRef = useRef(null);
8929
+ const [triggerEl, setTriggerEl] = useState(null);
8904
8930
  const handleMouseEnter = useCallback(() => {
8905
8931
  onCancelClose();
8906
8932
  if (hasChildren && !isOpen) onOpen(flyoutKey);
@@ -8919,14 +8945,16 @@ function CompactNavItem({
8919
8945
  /* @__PURE__ */ jsxs(
8920
8946
  "button",
8921
8947
  {
8922
- ref: triggerRef,
8948
+ ref: setTriggerEl,
8923
8949
  onMouseEnter: !mobile && hasChildren ? handleMouseEnter : void 0,
8924
8950
  onMouseLeave: !mobile && hasChildren && isOpen ? handleMouseLeave : void 0,
8925
8951
  onClick: handleClick,
8926
8952
  "aria-expanded": hasChildren ? mobile ? expanded : isOpen : void 0,
8927
8953
  className: cn(
8928
8954
  "relative flex flex-col items-center justify-center gap-[3px] w-full py-ds-3 px-ds-1 rounded-[var(--ds-radius-md)] transition-colors cursor-pointer",
8929
- item.active || childActive ? "bg-sidebar-accent-subtle text-[var(--ds-sidebar-accent)]" : "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8955
+ // Compact tiles have no edge to stroke: they read the fill and text
8956
+ // tokens only, never --ds-sidebar-active-rail.
8957
+ item.active || childActive ? "bg-[var(--ds-sidebar-active-bg)] text-[var(--ds-sidebar-active-accent)]" : "text-[var(--ds-sidebar-text-secondary)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8930
8958
  ),
8931
8959
  children: [
8932
8960
  item.icon && /* @__PURE__ */ jsx(
@@ -8934,7 +8962,7 @@ function CompactNavItem({
8934
8962
  {
8935
8963
  name: item.icon,
8936
8964
  size: 20,
8937
- className: cn((item.active || childActive) && "text-[var(--ds-sidebar-accent)]")
8965
+ className: cn((item.active || childActive) && "text-[var(--ds-sidebar-active-accent)]")
8938
8966
  }
8939
8967
  ),
8940
8968
  /* @__PURE__ */ jsx(TruncLabel3, { className: "text-[clamp(9px,var(--ds-font-size-2xs),12px)] leading-tight max-w-full", side: "right", container: portalContainer ?? void 0, children: item.label }),
@@ -8945,7 +8973,7 @@ function CompactNavItem({
8945
8973
  size: 10,
8946
8974
  className: cn(
8947
8975
  "absolute right-1 top-1/2 -translate-y-1/2 transition-transform",
8948
- item.active || childActive ? "text-[var(--ds-sidebar-accent)]" : "text-[var(--ds-sidebar-text-muted)]",
8976
+ item.active || childActive ? "text-[var(--ds-sidebar-active-accent)]" : "text-[var(--ds-sidebar-text-muted)]",
8949
8977
  expanded && "rotate-180"
8950
8978
  )
8951
8979
  }
@@ -8953,12 +8981,12 @@ function CompactNavItem({
8953
8981
  ]
8954
8982
  }
8955
8983
  ),
8956
- !mobile && isOpen && hasChildren && item.children && triggerRef.current && /* @__PURE__ */ jsx(
8984
+ !mobile && isOpen && hasChildren && item.children && triggerEl && /* @__PURE__ */ jsx(
8957
8985
  CompactFlyout,
8958
8986
  {
8959
8987
  parentLabel: item.label,
8960
8988
  items: item.children,
8961
- anchorEl: triggerRef.current,
8989
+ anchorEl: triggerEl,
8962
8990
  portalContainer,
8963
8991
  onItemClick,
8964
8992
  onClose,
@@ -8972,7 +9000,7 @@ function CompactNavItem({
8972
9000
  onClick: () => onItemClick?.(child, ci),
8973
9001
  className: cn(
8974
9002
  "flex items-center gap-ds-2 px-ds-3 py-ds-1.5 text-[length:var(--ds-font-size-xs)] rounded-[var(--ds-radius-sm)] transition-colors cursor-pointer w-full",
8975
- child.active ? "bg-sidebar-accent-subtle text-[var(--ds-sidebar-accent)] font-medium" : "text-[var(--ds-sidebar-text-muted)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
9003
+ child.active ? "bg-[var(--ds-sidebar-active-bg)] text-[var(--ds-sidebar-active-accent)] font-medium" : "text-[var(--ds-sidebar-text-muted)] hover:bg-[var(--ds-sidebar-item-hover)] hover:text-[var(--ds-sidebar-text)]"
8976
9004
  ),
8977
9005
  children: [
8978
9006
  child.icon && /* @__PURE__ */ jsx(Icon, { name: child.icon, size: 14, className: "shrink-0" }),
@@ -9948,6 +9976,187 @@ function Dock({
9948
9976
  }
9949
9977
  );
9950
9978
  }
9979
+ function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92], container, flush }) {
9980
+ const titleId = useId();
9981
+ const panelRef = useRef(null);
9982
+ const [mounted, setMounted] = useState(open);
9983
+ const [snapIdx, setSnapIdx] = useState(0);
9984
+ const [dragOffset, setDragOffset] = useState(0);
9985
+ const [dragging, setDragging] = useState(false);
9986
+ const dragStartY = useRef(0);
9987
+ const startHeight = useRef(0);
9988
+ const onCloseRef = useLatest(onClose);
9989
+ const [prevOpen, setPrevOpen] = useState(open);
9990
+ if (open !== prevOpen) {
9991
+ setPrevOpen(open);
9992
+ if (open) {
9993
+ setMounted(true);
9994
+ setSnapIdx(0);
9995
+ setDragOffset(0);
9996
+ }
9997
+ }
9998
+ const viewportHeight = useCallback(
9999
+ () => container instanceof HTMLElement ? container.clientHeight : window.innerHeight,
10000
+ [container]
10001
+ );
10002
+ function handleTransitionEnd() {
10003
+ if (!open) setMounted(false);
10004
+ }
10005
+ useReturnFocus(open);
10006
+ useEffect(() => {
10007
+ if (!open || !mounted) return;
10008
+ const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
10009
+ return () => cancelAnimationFrame(raf);
10010
+ }, [open, mounted]);
10011
+ useEffect(() => {
10012
+ if (!open) return;
10013
+ const onKeyDown = (e) => {
10014
+ if (e.key === "Escape") onCloseRef.current();
10015
+ };
10016
+ document.addEventListener("keydown", onKeyDown);
10017
+ if (!container) lockBodyScroll();
10018
+ return () => {
10019
+ document.removeEventListener("keydown", onKeyDown);
10020
+ if (!container) unlockBodyScroll();
10021
+ };
10022
+ }, [open, container, onCloseRef]);
10023
+ const handlePointerDown = useCallback((e) => {
10024
+ setDragging(true);
10025
+ dragStartY.current = e.clientY;
10026
+ startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * viewportHeight();
10027
+ setDragOffset(0);
10028
+ e.target.setPointerCapture(e.pointerId);
10029
+ }, [snapIdx, snapPoints, viewportHeight]);
10030
+ const handlePointerMove = useCallback((e) => {
10031
+ if (!dragging) return;
10032
+ const delta = dragStartY.current - e.clientY;
10033
+ setDragOffset(delta);
10034
+ }, [dragging]);
10035
+ const handlePointerUp = useCallback(() => {
10036
+ if (!dragging) return;
10037
+ setDragging(false);
10038
+ const currentHeightPx = startHeight.current + dragOffset;
10039
+ const currentFraction = currentHeightPx / viewportHeight();
10040
+ if (currentFraction < snapPoints[0] * 0.6) {
10041
+ onClose();
10042
+ setDragOffset(0);
10043
+ return;
10044
+ }
10045
+ let bestIdx = 0;
10046
+ let bestDist = Math.abs(currentFraction - snapPoints[0]);
10047
+ for (let i = 1; i < snapPoints.length; i++) {
10048
+ const dist = Math.abs(currentFraction - snapPoints[i]);
10049
+ if (dist < bestDist) {
10050
+ bestDist = dist;
10051
+ bestIdx = i;
10052
+ }
10053
+ }
10054
+ setSnapIdx(bestIdx);
10055
+ setDragOffset(0);
10056
+ }, [dragging, dragOffset, snapPoints, onClose, viewportHeight]);
10057
+ if (!mounted) return null;
10058
+ const baseHeight = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
10059
+ const heightUnit = container ? "%" : "vh";
10060
+ const dragPx = dragging ? dragOffset : 0;
10061
+ const positionClass = container ? "absolute" : "fixed";
10062
+ return createPortal(
10063
+ /* @__PURE__ */ jsxs(
10064
+ "div",
10065
+ {
10066
+ className: cn(
10067
+ `${positionClass} inset-0 z-[var(--ds-z-modal)] transition-opacity duration-[var(--ds-transition-speed)]`,
10068
+ open ? "pointer-events-auto" : "pointer-events-none"
10069
+ ),
10070
+ onTransitionEnd: handleTransitionEnd,
10071
+ children: [
10072
+ /* @__PURE__ */ jsx(
10073
+ "div",
10074
+ {
10075
+ className: cn(
10076
+ "absolute inset-0 bg-black/50 transition-opacity duration-[var(--ds-transition-speed)]",
10077
+ open ? "opacity-100" : "opacity-0"
10078
+ ),
10079
+ onClick: onClose
10080
+ }
10081
+ ),
10082
+ /* @__PURE__ */ jsxs(
10083
+ "div",
10084
+ {
10085
+ ref: panelRef,
10086
+ role: "dialog",
10087
+ "aria-modal": "true",
10088
+ "aria-labelledby": title ? titleId : void 0,
10089
+ "data-ds-sheet": "",
10090
+ className: cn(
10091
+ `${positionClass} bottom-0 left-0 right-0 z-10 flex flex-col`,
10092
+ "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
10093
+ GLASS_OVERLAY,
10094
+ "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
10095
+ "rounded-t-[var(--ds-radius-xl)]",
10096
+ !dragging && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
10097
+ open ? "translate-y-0" : "translate-y-full"
10098
+ ),
10099
+ style: {
10100
+ containerType: "inline-size",
10101
+ height: dragPx ? `calc(${baseHeight}${heightUnit} + ${dragPx}px)` : `${baseHeight}${heightUnit}`
10102
+ },
10103
+ children: [
10104
+ /* @__PURE__ */ jsx(
10105
+ "div",
10106
+ {
10107
+ className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
10108
+ onPointerDown: handlePointerDown,
10109
+ onPointerMove: handlePointerMove,
10110
+ onPointerUp: handlePointerUp,
10111
+ onPointerCancel: handlePointerUp,
10112
+ children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
10113
+ }
10114
+ ),
10115
+ title && /* @__PURE__ */ jsxs(
10116
+ "div",
10117
+ {
10118
+ className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
10119
+ style: { padding: "var(--ds-space-2) var(--ds-space-5)" },
10120
+ children: [
10121
+ /* @__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 }),
10122
+ /* @__PURE__ */ jsx(
10123
+ "button",
10124
+ {
10125
+ onClick: onClose,
10126
+ ...{ [OVERLAY_CLOSE_ATTR]: "" },
10127
+ className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
10128
+ "aria-label": "Close",
10129
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
10130
+ }
10131
+ )
10132
+ ]
10133
+ }
10134
+ ),
10135
+ /* @__PURE__ */ jsx(
10136
+ "div",
10137
+ {
10138
+ className: "flex-1 overflow-y-auto",
10139
+ style: flush ? void 0 : { padding: "var(--ds-space-5)" },
10140
+ children
10141
+ }
10142
+ ),
10143
+ footer && /* @__PURE__ */ jsx(
10144
+ "div",
10145
+ {
10146
+ className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
10147
+ style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
10148
+ children: footer
10149
+ }
10150
+ )
10151
+ ]
10152
+ }
10153
+ )
10154
+ ]
10155
+ }
10156
+ ),
10157
+ container ?? document.body
10158
+ );
10159
+ }
9951
10160
  var slideClasses = {
9952
10161
  right: {
9953
10162
  panel: "top-0 right-0 h-full",
@@ -9970,19 +10179,23 @@ var borderByPosition = {
9970
10179
  left: "border-r-[length:var(--ds-border-width)] border-r-[var(--ds-border-default)]",
9971
10180
  bottom: "border-t-[length:var(--ds-border-width)] border-t-[var(--ds-border-default)]"
9972
10181
  };
9973
- function Drawer({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset }) {
10182
+ function Drawer(props) {
10183
+ const { position = "right", mobile, snapPoints, open, onClose, title, children, footer, container, flush } = props;
10184
+ const isPhone = useBreakpoint("sm");
10185
+ const mobileMode = mobile ?? (position === "left" ? "panel" : "sheet");
10186
+ if (isPhone && mobileMode === "sheet") {
10187
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title, footer, container, flush, snapPoints, children });
10188
+ }
10189
+ return /* @__PURE__ */ jsx(DrawerPanel, { ...props });
10190
+ }
10191
+ function DrawerPanel({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset }) {
9974
10192
  const titleId = useId();
9975
10193
  const { ref: titleOverflowRef, isTruncated: titleTruncated } = useTextOverflow({ lines: 1 });
9976
10194
  const isMobile = useBreakpoint("sm");
9977
10195
  const panelRef = useRef(null);
9978
- const [mounted, setMounted] = useState(false);
9979
- const onCloseRef = useRef(onClose);
9980
- onCloseRef.current = onClose;
9981
- useEffect(() => {
9982
- if (open) {
9983
- setMounted(true);
9984
- }
9985
- }, [open]);
10196
+ const [mounted, setMounted] = useState(open);
10197
+ const onCloseRef = useLatest(onClose);
10198
+ if (open && !mounted) setMounted(true);
9986
10199
  function handleTransitionEnd() {
9987
10200
  if (!open) setMounted(false);
9988
10201
  }
@@ -9997,7 +10210,7 @@ function Drawer({ open, onClose, position = "right", width = "400px", title, chi
9997
10210
  document.removeEventListener("keydown", onKeyDown);
9998
10211
  if (!container) unlockBodyScroll();
9999
10212
  };
10000
- }, [open, container]);
10213
+ }, [open, container, onCloseRef]);
10001
10214
  useReturnFocus(open);
10002
10215
  useEffect(() => {
10003
10216
  if (!open || !mounted) return;
@@ -10132,13 +10345,13 @@ var AppShell = forwardRef(function AppShell2({
10132
10345
  const hasMobileMenu = mobileMenu != null;
10133
10346
  const hasMobileNav = mobileNav != null;
10134
10347
  const showMobileDrawer = hasMobileMenu && !(isSmallMobile && hasMobileNav);
10135
- const shellRef = useRef(null);
10348
+ const [shellEl, setShellEl] = useState(null);
10136
10349
  return /* @__PURE__ */ jsx(
10137
10350
  AppShellContext,
10138
10351
  {
10139
10352
  value: { hasSidebar, hasMobileMenu, hasMobileNav, sidebarOpen, setSidebarOpen, isMobile, isTablet },
10140
10353
  children: /* @__PURE__ */ jsxs("div", { ref: (el) => {
10141
- shellRef.current = el;
10354
+ setShellEl(el);
10142
10355
  if (typeof ref === "function") ref(el);
10143
10356
  else if (ref) ref.current = el;
10144
10357
  }, className: cn("relative h-screen flex flex-col bg-[var(--ds-bg-canvas)] overflow-hidden", className), children: [
@@ -10170,7 +10383,7 @@ var AppShell = forwardRef(function AppShell2({
10170
10383
  onClose: () => setSidebarOpen(false),
10171
10384
  position: "left",
10172
10385
  width: mobileMenuWidth ?? "var(--ds-drawer-width)",
10173
- container: shellRef.current ?? void 0,
10386
+ container: shellEl ?? void 0,
10174
10387
  flush: mobileMenuFlush,
10175
10388
  lightBackdrop: mobileMenuLightBackdrop,
10176
10389
  topOffset: layout !== "stacked" && navbar != null ? "var(--ds-navbar-height)" : void 0,
@@ -10182,10 +10395,13 @@ var AppShell = forwardRef(function AppShell2({
10182
10395
  }
10183
10396
  );
10184
10397
  });
10185
- var NavbarTypeContext = createContext("default");
10398
+ function resolveNavItemType(type) {
10399
+ return type === "default" ? "tint" : type;
10400
+ }
10401
+ var NavbarTypeContext = createContext("tint");
10186
10402
  var NavItem = forwardRef(function NavItem2({ label, icon, active = false, href, onClick, type: typeProp, className, children }, ref) {
10187
10403
  const contextType = useContext(NavbarTypeContext);
10188
- const type = typeProp ?? contextType;
10404
+ const type = resolveNavItemType(typeProp ?? contextType);
10189
10405
  const baseClasses = cn(
10190
10406
  "inline-flex items-center gap-ds-2 cursor-pointer select-none",
10191
10407
  "transition-colors duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
@@ -10217,6 +10433,11 @@ var NavItem = forwardRef(function NavItem2({ label, icon, active = false, href,
10217
10433
  });
10218
10434
  function getTypeClasses(type, active) {
10219
10435
  switch (type) {
10436
+ case "pill":
10437
+ return cn(
10438
+ "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
10439
+ active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
10440
+ );
10220
10441
  case "underline":
10221
10442
  return cn(
10222
10443
  "px-ds-2 py-ds-2 relative",
@@ -10227,7 +10448,7 @@ function getTypeClasses(type, active) {
10227
10448
  "px-ds-2 py-ds-1",
10228
10449
  active ? "text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]"
10229
10450
  );
10230
- default:
10451
+ case "tint":
10231
10452
  return cn(
10232
10453
  "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
10233
10454
  active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
@@ -10261,7 +10482,7 @@ var Navbar = forwardRef(function Navbar2({
10261
10482
  actions,
10262
10483
  userMenu,
10263
10484
  variant = "default",
10264
- type = "default",
10485
+ type: typeProp = "tint",
10265
10486
  align = "left",
10266
10487
  size,
10267
10488
  position = "flush",
@@ -10275,6 +10496,7 @@ var Navbar = forwardRef(function Navbar2({
10275
10496
  const showHamburger = appShell?.isMobile && appShell.hasMobileMenu && !(isSmallPhone && appShell.hasMobileNav) || !!onMenuClick;
10276
10497
  const handleHamburger = () => onMenuClick ? onMenuClick() : appShell?.setSidebarOpen(!appShell.sidebarOpen);
10277
10498
  const hideNavLinks = appShell?.isMobile && (appShell.hasMobileMenu || appShell.hasMobileNav);
10499
+ const type = resolveNavItemType(typeProp);
10278
10500
  const isMinimal = type === "minimal";
10279
10501
  const isFloating = position === "floating";
10280
10502
  const resolvedSize = size ?? (variant === "compact" ? "sm" : "md");
@@ -10381,7 +10603,7 @@ function NavMenu({
10381
10603
  label,
10382
10604
  items,
10383
10605
  active = false,
10384
- type = "default",
10606
+ type = "tint",
10385
10607
  onItemClick,
10386
10608
  className
10387
10609
  }) {
@@ -10421,6 +10643,7 @@ function NavMenu({
10421
10643
  );
10422
10644
  const isUnderline = type === "underline";
10423
10645
  const isMinimal = type === "minimal";
10646
+ const isPill = type === "pill";
10424
10647
  const triggerClasses = cn(
10425
10648
  "inline-flex items-center gap-ds-1 cursor-pointer transition-colors whitespace-nowrap",
10426
10649
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -10430,9 +10653,14 @@ function NavMenu({
10430
10653
  ) : isMinimal ? cn(
10431
10654
  "px-ds-2 py-ds-1 text-sm",
10432
10655
  hasActiveChild ? "text-[var(--ds-text-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)]"
10656
+ ) : isPill ? cn(
10657
+ "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)]",
10658
+ hasActiveChild ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
10433
10659
  ) : cn(
10660
+ // tint (and the deprecated `default` alias) — the same soft accent fill
10661
+ // NavItem uses, so a menu trigger and a link read as one row (decision 30).
10434
10662
  "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)]",
10435
- hasActiveChild ? "bg-[var(--ds-bg-muted)] text-[var(--ds-text-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
10663
+ hasActiveChild ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
10436
10664
  ),
10437
10665
  className
10438
10666
  );
@@ -10519,7 +10747,9 @@ function NavMenuItemRow({
10519
10747
  },
10520
10748
  className: cn(
10521
10749
  "flex items-center gap-ds-2 mx-ds-1 px-ds-2 py-ds-1.5 rounded-[var(--ds-radius-sm)] transition-colors cursor-pointer",
10522
- item.active ? "bg-[var(--ds-bg-muted)] text-[var(--ds-text-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
10750
+ // The active row takes the accent tint like an active NavItem; it marks
10751
+ // the current page, not a hover.
10752
+ item.active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
10523
10753
  ),
10524
10754
  children: [
10525
10755
  item.icon && /* @__PURE__ */ jsx(
@@ -10562,7 +10792,7 @@ function NavMenuGroup({
10562
10792
  ))
10563
10793
  ] });
10564
10794
  }
10565
- function MobileHeaderMenu({ items, type = "default", onItemClick, className }) {
10795
+ function MobileHeaderMenu({ items, type = "tint", onItemClick, className }) {
10566
10796
  return /* @__PURE__ */ jsx("nav", { className: cn("flex flex-col", className), style: { padding: "var(--ds-space-4)" }, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col", style: { gap: type === "underline" ? 0 : "var(--ds-space-1)" }, children: items.map((item, i) => /* @__PURE__ */ jsx(
10567
10797
  MobileMenuItem,
10568
10798
  {
@@ -10619,6 +10849,23 @@ function MobileMenuItem({
10619
10849
  }
10620
10850
  );
10621
10851
  }
10852
+ if (type === "pill") {
10853
+ return /* @__PURE__ */ jsxs(
10854
+ "button",
10855
+ {
10856
+ onClick,
10857
+ className: cn(
10858
+ "flex items-center gap-ds-3 px-ds-3 py-ds-2.5 text-sm rounded-[var(--ds-radius-md)] cursor-pointer w-full text-left",
10859
+ "transition-colors duration-[var(--ds-transition-speed)]",
10860
+ item.active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
10861
+ ),
10862
+ children: [
10863
+ item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: 18, className: "shrink-0" }),
10864
+ /* @__PURE__ */ jsx("span", { children: item.label })
10865
+ ]
10866
+ }
10867
+ );
10868
+ }
10622
10869
  return /* @__PURE__ */ jsxs(
10623
10870
  "button",
10624
10871
  {
@@ -10649,8 +10896,7 @@ function Dialog({ open, onClose, title, description, children, footer, variant =
10649
10896
  const overlayRef = useRef(null);
10650
10897
  const dialogRef = useRef(null);
10651
10898
  const triggerRef = useRef(null);
10652
- const onCloseRef = useRef(onClose);
10653
- onCloseRef.current = onClose;
10899
+ const onCloseRef = useLatest(onClose);
10654
10900
  const trapFocus = useCallback((e) => {
10655
10901
  if (e.key !== "Tab" || !dialogRef.current) return;
10656
10902
  const focusable = dialogRef.current.querySelectorAll(FOCUSABLE);
@@ -10686,7 +10932,7 @@ function Dialog({ open, onClose, title, description, children, footer, variant =
10686
10932
  return () => {
10687
10933
  document.removeEventListener("keydown", handler);
10688
10934
  };
10689
- }, [open, trapFocus, alert]);
10935
+ }, [open, trapFocus, alert, onCloseRef]);
10690
10936
  useEffect(() => {
10691
10937
  if (!open) return;
10692
10938
  lockBodyScroll();
@@ -10798,16 +11044,22 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
10798
11044
  groups.get(g).push(cmd);
10799
11045
  }
10800
11046
  const flatFiltered = filtered;
10801
- useEffect(() => {
11047
+ const [prevOpen, setPrevOpen] = useState(open);
11048
+ if (open !== prevOpen) {
11049
+ setPrevOpen(open);
10802
11050
  if (open) {
10803
11051
  setQuery("");
10804
11052
  setSelectedIdx(0);
10805
- requestAnimationFrame(() => inputRef.current?.focus());
10806
11053
  }
10807
- }, [open]);
10808
- useEffect(() => {
11054
+ }
11055
+ const [prevQuery, setPrevQuery] = useState(query);
11056
+ if (query !== prevQuery) {
11057
+ setPrevQuery(query);
10809
11058
  setSelectedIdx(0);
10810
- }, [query]);
11059
+ }
11060
+ useEffect(() => {
11061
+ if (open) requestAnimationFrame(() => inputRef.current?.focus());
11062
+ }, [open]);
10811
11063
  useEffect(() => {
10812
11064
  if (!open) return;
10813
11065
  const handler = (e) => {
@@ -10823,23 +11075,20 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
10823
11075
  },
10824
11076
  [onOpenChange]
10825
11077
  );
10826
- const handleKeyDown = useCallback(
10827
- (e) => {
10828
- if (e.key === "ArrowDown") {
10829
- e.preventDefault();
10830
- setSelectedIdx((prev) => Math.min(prev + 1, flatFiltered.length - 1));
10831
- listRef.current?.children[Math.min(selectedIdx + 1, flatFiltered.length - 1)]?.scrollIntoView({ block: "nearest" });
10832
- } else if (e.key === "ArrowUp") {
10833
- e.preventDefault();
10834
- setSelectedIdx((prev) => Math.max(prev - 1, 0));
10835
- listRef.current?.children[Math.max(selectedIdx - 1, 0)]?.scrollIntoView({ block: "nearest" });
10836
- } else if (e.key === "Enter" && flatFiltered[selectedIdx]) {
10837
- e.preventDefault();
10838
- handleSelect(flatFiltered[selectedIdx]);
10839
- }
10840
- },
10841
- [flatFiltered, selectedIdx, handleSelect]
10842
- );
11078
+ const handleKeyDown = (e) => {
11079
+ if (e.key === "ArrowDown") {
11080
+ e.preventDefault();
11081
+ setSelectedIdx((prev) => Math.min(prev + 1, flatFiltered.length - 1));
11082
+ listRef.current?.children[Math.min(selectedIdx + 1, flatFiltered.length - 1)]?.scrollIntoView({ block: "nearest" });
11083
+ } else if (e.key === "ArrowUp") {
11084
+ e.preventDefault();
11085
+ setSelectedIdx((prev) => Math.max(prev - 1, 0));
11086
+ listRef.current?.children[Math.max(selectedIdx - 1, 0)]?.scrollIntoView({ block: "nearest" });
11087
+ } else if (e.key === "Enter" && flatFiltered[selectedIdx]) {
11088
+ e.preventDefault();
11089
+ handleSelect(flatFiltered[selectedIdx]);
11090
+ }
11091
+ };
10843
11092
  if (!open) return null;
10844
11093
  let flatIdx = 0;
10845
11094
  return createPortal(
@@ -11132,179 +11381,6 @@ function ContextMenu({ items, children, className, ref, container }) {
11132
11381
  }
11133
11382
  );
11134
11383
  }
11135
- function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92] }) {
11136
- const titleId = useId();
11137
- const panelRef = useRef(null);
11138
- const [mounted, setMounted] = useState(false);
11139
- const [snapIdx, setSnapIdx] = useState(0);
11140
- const [dragOffset, setDragOffset] = useState(0);
11141
- const isDragging = useRef(false);
11142
- const dragStartY = useRef(0);
11143
- const startHeight = useRef(0);
11144
- const onCloseRef = useRef(onClose);
11145
- onCloseRef.current = onClose;
11146
- useEffect(() => {
11147
- if (open) {
11148
- setMounted(true);
11149
- setSnapIdx(0);
11150
- setDragOffset(0);
11151
- }
11152
- }, [open]);
11153
- function handleTransitionEnd() {
11154
- if (!open) setMounted(false);
11155
- }
11156
- useReturnFocus(open);
11157
- useEffect(() => {
11158
- if (!open || !mounted) return;
11159
- const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
11160
- return () => cancelAnimationFrame(raf);
11161
- }, [open, mounted]);
11162
- useEffect(() => {
11163
- if (!open) return;
11164
- const onKeyDown = (e) => {
11165
- if (e.key === "Escape") onCloseRef.current();
11166
- };
11167
- document.addEventListener("keydown", onKeyDown);
11168
- lockBodyScroll();
11169
- return () => {
11170
- document.removeEventListener("keydown", onKeyDown);
11171
- unlockBodyScroll();
11172
- };
11173
- }, [open]);
11174
- const handlePointerDown = useCallback((e) => {
11175
- isDragging.current = true;
11176
- dragStartY.current = e.clientY;
11177
- startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * window.innerHeight;
11178
- setDragOffset(0);
11179
- e.target.setPointerCapture(e.pointerId);
11180
- }, [snapIdx, snapPoints]);
11181
- const handlePointerMove = useCallback((e) => {
11182
- if (!isDragging.current) return;
11183
- const delta = dragStartY.current - e.clientY;
11184
- setDragOffset(delta);
11185
- }, []);
11186
- const handlePointerUp = useCallback(() => {
11187
- if (!isDragging.current) return;
11188
- isDragging.current = false;
11189
- const currentHeightPx = startHeight.current + dragOffset;
11190
- const currentFraction = currentHeightPx / window.innerHeight;
11191
- if (currentFraction < snapPoints[0] * 0.6) {
11192
- onClose();
11193
- setDragOffset(0);
11194
- return;
11195
- }
11196
- let bestIdx = 0;
11197
- let bestDist = Math.abs(currentFraction - snapPoints[0]);
11198
- for (let i = 1; i < snapPoints.length; i++) {
11199
- const dist = Math.abs(currentFraction - snapPoints[i]);
11200
- if (dist < bestDist) {
11201
- bestDist = dist;
11202
- bestIdx = i;
11203
- }
11204
- }
11205
- setSnapIdx(bestIdx);
11206
- setDragOffset(0);
11207
- }, [dragOffset, snapPoints, onClose]);
11208
- if (!mounted) return null;
11209
- const baseHeightVh = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
11210
- const dragPx = isDragging.current ? dragOffset : 0;
11211
- return createPortal(
11212
- /* @__PURE__ */ jsxs(
11213
- "div",
11214
- {
11215
- className: cn(
11216
- "fixed inset-0 z-[var(--ds-z-modal)] transition-opacity duration-[var(--ds-transition-speed)]",
11217
- open ? "pointer-events-auto" : "pointer-events-none"
11218
- ),
11219
- onTransitionEnd: handleTransitionEnd,
11220
- children: [
11221
- /* @__PURE__ */ jsx(
11222
- "div",
11223
- {
11224
- className: cn(
11225
- "absolute inset-0 bg-black/50 transition-opacity duration-[var(--ds-transition-speed)]",
11226
- open ? "opacity-100" : "opacity-0"
11227
- ),
11228
- onClick: onClose
11229
- }
11230
- ),
11231
- /* @__PURE__ */ jsxs(
11232
- "div",
11233
- {
11234
- ref: panelRef,
11235
- role: "dialog",
11236
- "aria-modal": "true",
11237
- "aria-labelledby": title ? titleId : void 0,
11238
- className: cn(
11239
- "fixed bottom-0 left-0 right-0 z-10 flex flex-col",
11240
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
11241
- GLASS_OVERLAY,
11242
- "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
11243
- "rounded-t-[var(--ds-radius-xl)]",
11244
- !isDragging.current && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
11245
- open ? "translate-y-0" : "translate-y-full"
11246
- ),
11247
- style: {
11248
- containerType: "inline-size",
11249
- height: dragPx ? `calc(${baseHeightVh}vh + ${dragPx}px)` : `${baseHeightVh}vh`
11250
- },
11251
- children: [
11252
- /* @__PURE__ */ jsx(
11253
- "div",
11254
- {
11255
- className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
11256
- onPointerDown: handlePointerDown,
11257
- onPointerMove: handlePointerMove,
11258
- onPointerUp: handlePointerUp,
11259
- onPointerCancel: handlePointerUp,
11260
- children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
11261
- }
11262
- ),
11263
- title && /* @__PURE__ */ jsxs(
11264
- "div",
11265
- {
11266
- className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11267
- style: { padding: "var(--ds-space-2) var(--ds-space-5)" },
11268
- children: [
11269
- /* @__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 }),
11270
- /* @__PURE__ */ jsx(
11271
- "button",
11272
- {
11273
- onClick: onClose,
11274
- ...{ [OVERLAY_CLOSE_ATTR]: "" },
11275
- className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
11276
- "aria-label": "Close",
11277
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
11278
- }
11279
- )
11280
- ]
11281
- }
11282
- ),
11283
- /* @__PURE__ */ jsx(
11284
- "div",
11285
- {
11286
- className: "flex-1 overflow-y-auto",
11287
- style: { padding: "var(--ds-space-5)" },
11288
- children
11289
- }
11290
- ),
11291
- footer && /* @__PURE__ */ jsx(
11292
- "div",
11293
- {
11294
- className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11295
- style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
11296
- children: footer
11297
- }
11298
- )
11299
- ]
11300
- }
11301
- )
11302
- ]
11303
- }
11304
- ),
11305
- document.body
11306
- );
11307
- }
11308
11384
  function TruncTitle({ children }) {
11309
11385
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
11310
11386
  const el = /* @__PURE__ */ jsx("span", { ref, className: "truncate", children });
@@ -11335,7 +11411,10 @@ function NotificationCenter({
11335
11411
  const [internalFilter, setInternalFilter] = useState("all");
11336
11412
  const isControlled = controlledOpen !== void 0;
11337
11413
  const open = isControlled ? controlledOpen : internalOpen;
11338
- const setOpen = isControlled ? (v) => onOpenChange?.(v) : setInternalOpen;
11414
+ const setOpen = useCallback((v) => {
11415
+ if (isControlled) onOpenChange?.(v);
11416
+ else setInternalOpen(v);
11417
+ }, [isControlled, onOpenChange]);
11339
11418
  const triggerRef = useRef(null);
11340
11419
  const panelRef = useRef(null);
11341
11420
  const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "end", offset: 8, flip: true });
@@ -11359,7 +11438,7 @@ function NotificationCenter({
11359
11438
  document.removeEventListener("keydown", onKey);
11360
11439
  document.removeEventListener("mousedown", onClick);
11361
11440
  };
11362
- }, [open]);
11441
+ }, [open, setOpen]);
11363
11442
  const grouped = groupByDate(filteredNotifications);
11364
11443
  if (variant === "page") {
11365
11444
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), children: [
@@ -11606,11 +11685,13 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11606
11685
  if (el) setRect(el.getBoundingClientRect());
11607
11686
  else setRect(null);
11608
11687
  }, [step]);
11688
+ const [prevOpen, setPrevOpen] = useState(open);
11689
+ if (open !== prevOpen) {
11690
+ setPrevOpen(open);
11691
+ if (!open) setCurrent(0);
11692
+ }
11609
11693
  useEffect(() => {
11610
- if (!open) {
11611
- setCurrent(0);
11612
- return;
11613
- }
11694
+ if (!open) return;
11614
11695
  measureTarget();
11615
11696
  window.addEventListener("resize", measureTarget);
11616
11697
  window.addEventListener("scroll", measureTarget, true);
@@ -11619,16 +11700,6 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11619
11700
  window.removeEventListener("scroll", measureTarget, true);
11620
11701
  };
11621
11702
  }, [open, current, measureTarget]);
11622
- useEffect(() => {
11623
- if (!open) return;
11624
- const onKey = (e) => {
11625
- if (e.key === "Escape") onClose();
11626
- else if (e.key === "ArrowRight") handleNext();
11627
- else if (e.key === "ArrowLeft") handlePrev();
11628
- };
11629
- document.addEventListener("keydown", onKey);
11630
- return () => document.removeEventListener("keydown", onKey);
11631
- });
11632
11703
  const handleNext = useCallback(() => {
11633
11704
  if (current < steps.length - 1) setCurrent((c) => c + 1);
11634
11705
  else {
@@ -11639,6 +11710,16 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11639
11710
  const handlePrev = useCallback(() => {
11640
11711
  if (current > 0) setCurrent((c) => c - 1);
11641
11712
  }, [current]);
11713
+ useEffect(() => {
11714
+ if (!open) return;
11715
+ const onKey = (e) => {
11716
+ if (e.key === "Escape") onClose();
11717
+ else if (e.key === "ArrowRight") handleNext();
11718
+ else if (e.key === "ArrowLeft") handlePrev();
11719
+ };
11720
+ document.addEventListener("keydown", onKey);
11721
+ return () => document.removeEventListener("keydown", onKey);
11722
+ }, [open, onClose, handleNext, handlePrev]);
11642
11723
  if (!open || !step) return null;
11643
11724
  const padding = 8;
11644
11725
  const spotlightRadius = 8;
@@ -11942,15 +12023,14 @@ function Toast({ variant = "default", message, visible, onDismiss, duration = 3e
11942
12023
  const timerRef = useRef(void 0);
11943
12024
  const startedAtRef = useRef(0);
11944
12025
  const remainingRef = useRef(duration);
11945
- const onDismissRef = useRef(onDismiss);
11946
- onDismissRef.current = onDismiss;
12026
+ const onDismissRef = useLatest(onDismiss);
11947
12027
  const startTimer = useCallback((ms) => {
11948
12028
  const delay = ms ?? remainingRef.current;
11949
12029
  remainingRef.current = delay;
11950
12030
  startedAtRef.current = Date.now();
11951
12031
  if (!Number.isFinite(delay) || delay <= 0) return;
11952
12032
  timerRef.current = setTimeout(() => onDismissRef.current(), delay);
11953
- }, []);
12033
+ }, [onDismissRef]);
11954
12034
  const clearTimer = useCallback(() => {
11955
12035
  if (timerRef.current) {
11956
12036
  clearTimeout(timerRef.current);