gridsmith-ui 0.7.0 → 0.8.0

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,12 +1,13 @@
1
+ import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from './chunk-5B2ZFL2H.js';
1
2
  import { Skeleton } from './chunk-M3AARGLI.js';
2
3
  export { Skeleton } from './chunk-M3AARGLI.js';
3
4
  import { useOverlayLayer, useCloseOnFocusOut, useReturnFocus, focusInitial, useFocusTrap, OVERLAY_CLOSE_ATTR, useNonModalDialog, rovingTabStop } from './chunk-RLUK575N.js';
4
- import { Link } from './chunk-G6KC5YNY.js';
5
- export { CodeBlock, Divider, Link } from './chunk-G6KC5YNY.js';
5
+ import { Link } from './chunk-VXKSQ56H.js';
6
+ export { CodeBlock, Divider, Link } from './chunk-VXKSQ56H.js';
6
7
  import { Badge } from './chunk-QVNGQ64Q.js';
7
8
  export { Badge } from './chunk-QVNGQ64Q.js';
8
- import { useBreakpoint } from './chunk-EKQHHP72.js';
9
- export { BreakpointWidthProvider, useBreakpoint } from './chunk-EKQHHP72.js';
9
+ import { useBreakpoint, readBreakpointPx } from './chunk-CRQ6KCRD.js';
10
+ export { BreakpointWidthProvider, useBreakpoint } from './chunk-CRQ6KCRD.js';
10
11
  import { Tooltip, useTextOverflow, usePosition, Slot } from './chunk-RLNIRPOH.js';
11
12
  export { Tooltip, useTextOverflow } from './chunk-RLNIRPOH.js';
12
13
  import { useLatest } from './chunk-WH2FUFAC.js';
@@ -3345,40 +3346,253 @@ function Field({ label, children, hint, error, required, labelAction, id: idProp
3345
3346
  hasError && /* @__PURE__ */ jsx(InlineFieldError, { id: errorId, message: error, className: "mt-0" })
3346
3347
  ] });
3347
3348
  }
3349
+ var variantStyles3 = {
3350
+ info: "border-[var(--ds-accent-primary)] bg-[var(--ds-accent-subtle)]",
3351
+ success: "border-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
3352
+ warning: "border-[var(--ds-status-warning)] bg-[var(--ds-status-warning-bg)]",
3353
+ error: "border-[var(--ds-status-error)] bg-[var(--ds-status-error-bg)]"
3354
+ };
3355
+ var indicators = {
3356
+ info: "bg-[var(--ds-accent-primary)]",
3357
+ success: "bg-[var(--ds-status-success)]",
3358
+ warning: "bg-[var(--ds-status-warning)]",
3359
+ error: "bg-[var(--ds-status-error)]"
3360
+ };
3361
+ var actionColorsByVariant = {
3362
+ info: "text-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-muted)]",
3363
+ success: "text-[var(--ds-status-success)] hover:bg-[color-mix(in_srgb,var(--ds-status-success)_12%,transparent)]",
3364
+ warning: "text-[var(--ds-status-warning)] hover:bg-[color-mix(in_srgb,var(--ds-status-warning)_15%,transparent)]",
3365
+ error: "text-[var(--ds-status-error)] hover:bg-[color-mix(in_srgb,var(--ds-status-error)_12%,transparent)]"
3366
+ };
3367
+ var Alert = forwardRef(function Alert2({
3368
+ variant,
3369
+ title,
3370
+ children,
3371
+ dismissible,
3372
+ onDismiss,
3373
+ action,
3374
+ actions,
3375
+ placement = "inline",
3376
+ expandableContent,
3377
+ className
3378
+ }, ref) {
3379
+ const [detailsExpanded, setDetailsExpanded] = useState(false);
3380
+ const isMobile = useBreakpoint("sm");
3381
+ const resolvedActions = actions ?? (action ? [action] : []);
3382
+ const shouldCollapseActions = isMobile && resolvedActions.length > 1;
3383
+ const placementClasses = placement === "page-top" ? "sticky top-0 z-[var(--ds-z-sticky)] rounded-none" : placement === "section-top" ? "rounded-[var(--ds-radius-md)] my-ds-4" : "rounded-[var(--ds-radius-md)]";
3384
+ return /* @__PURE__ */ jsxs(
3385
+ "div",
3386
+ {
3387
+ ref,
3388
+ role: "alert",
3389
+ className: cn(
3390
+ "flex items-start gap-ds-3 p-ds-4",
3391
+ "border-[length:var(--ds-border-width)]",
3392
+ "shadow-[var(--ds-shadow-sm)]",
3393
+ "text-sm text-[var(--ds-text-primary)]",
3394
+ variantStyles3[variant],
3395
+ placementClasses,
3396
+ className
3397
+ ),
3398
+ children: [
3399
+ /* @__PURE__ */ jsx("div", { className: cn("mt-[0.3125em] w-3 h-3 shrink-0 rounded-[var(--ds-radius-sm)]", indicators[variant]) }),
3400
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
3401
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-3", children: [
3402
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: title }),
3403
+ resolvedActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: shouldCollapseActions ? /* @__PURE__ */ jsx(
3404
+ DropdownMenu,
3405
+ {
3406
+ trigger: /* @__PURE__ */ jsx(
3407
+ "button",
3408
+ {
3409
+ className: cn(
3410
+ "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
3411
+ "transition-colors cursor-pointer",
3412
+ "border-[length:var(--ds-border-width)] border-current",
3413
+ actionColorsByVariant[variant]
3414
+ ),
3415
+ children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-1", children: [
3416
+ /* @__PURE__ */ jsx(Icon, { name: "more-vertical", size: 12 }),
3417
+ "Actions"
3418
+ ] })
3419
+ }
3420
+ ),
3421
+ items: resolvedActions.map((a) => ({
3422
+ label: a.label,
3423
+ onClick: a.onClick
3424
+ })),
3425
+ align: "end"
3426
+ }
3427
+ ) : resolvedActions.map((a, i) => {
3428
+ const effectiveVariant = a.variant ?? (i === 0 ? "secondary" : "outline");
3429
+ const isAccent = effectiveVariant === "secondary";
3430
+ return /* @__PURE__ */ jsxs(
3431
+ "button",
3432
+ {
3433
+ onClick: a.onClick,
3434
+ className: cn(
3435
+ "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
3436
+ "transition-colors cursor-pointer",
3437
+ isAccent ? cn("border-[length:var(--ds-border-width)] border-current", actionColorsByVariant[variant]) : cn("border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]", "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]")
3438
+ ),
3439
+ children: [
3440
+ a.iconLeft && /* @__PURE__ */ jsx("span", { className: "inline-flex mr-ds-1", children: a.iconLeft }),
3441
+ a.label
3442
+ ]
3443
+ },
3444
+ i
3445
+ );
3446
+ }) })
3447
+ ] }),
3448
+ children && /* @__PURE__ */ jsx("div", { className: "mt-ds-1 text-xs text-[var(--ds-text-secondary)]", children }),
3449
+ expandableContent && /* @__PURE__ */ jsxs("div", { className: "mt-ds-2", children: [
3450
+ /* @__PURE__ */ jsxs(
3451
+ "button",
3452
+ {
3453
+ onClick: () => setDetailsExpanded(!detailsExpanded),
3454
+ "aria-expanded": detailsExpanded,
3455
+ className: cn(
3456
+ "inline-flex items-center gap-ds-1 text-xs font-medium cursor-pointer",
3457
+ "transition-colors duration-[var(--ds-transition-speed)]",
3458
+ actionColorsByVariant[variant],
3459
+ "rounded-[var(--ds-radius-sm)] px-ds-1.5 py-ds-0.5"
3460
+ ),
3461
+ children: [
3462
+ /* @__PURE__ */ jsx(
3463
+ Icon,
3464
+ {
3465
+ name: "chevron-right",
3466
+ size: 12,
3467
+ className: cn("transition-transform duration-[var(--ds-transition-speed)]", detailsExpanded && "rotate-90")
3468
+ }
3469
+ ),
3470
+ detailsExpanded ? "Hide details" : "Show details"
3471
+ ]
3472
+ }
3473
+ ),
3474
+ detailsExpanded && /* @__PURE__ */ jsx("div", { className: "mt-ds-2 text-xs text-[var(--ds-text-secondary)] border-l-2 border-current/20 pl-ds-3", children: expandableContent })
3475
+ ] })
3476
+ ] }),
3477
+ dismissible && /* @__PURE__ */ jsx(
3478
+ "button",
3479
+ {
3480
+ onClick: onDismiss,
3481
+ className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
3482
+ "aria-label": "Dismiss",
3483
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 16 })
3484
+ }
3485
+ )
3486
+ ]
3487
+ }
3488
+ );
3489
+ });
3348
3490
  var FormCtx = createContext(null);
3349
3491
  function useFormContext() {
3350
3492
  const ctx = useContext(FormCtx);
3351
3493
  if (!ctx) throw new Error("useFormContext must be used within FormWrapper");
3352
3494
  return ctx;
3353
3495
  }
3354
- function FormWrapper({ onSubmit, validationMode = "onSubmit", children, className, ref }) {
3496
+ function collectInvalidFields(form) {
3497
+ const out = [];
3498
+ for (const el of Array.from(form.querySelectorAll("[aria-invalid='true']"))) {
3499
+ const id = el.id || "";
3500
+ const label = id && form.querySelector(`label[for="${CSS.escape(id)}"]`)?.textContent?.replace(/\*\s*$/, "").trim() || el.getAttribute("aria-label") || el.closest("[data-field]")?.querySelector("label")?.textContent?.trim() || id || "A field";
3501
+ const described = (el.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean);
3502
+ const message = described.map((d) => document.getElementById(d)?.textContent?.trim()).find((t) => t && /error|invalid|required|must|enter|choose|select|please/i.test(t)) ?? described.map((d) => document.getElementById(d)?.textContent?.trim()).filter(Boolean).pop() ?? "Needs attention";
3503
+ out.push({ id, label, message });
3504
+ }
3505
+ return out;
3506
+ }
3507
+ function FormWrapper({ onSubmit, validationMode = "onSubmit", children, actions, actionsAlign = "end", focusFirstError = true, errorSummary = true, className, ref }) {
3355
3508
  const [errors, setErrors] = useState({});
3356
3509
  const [submitted, setSubmitted] = useState(false);
3510
+ const [submitCount, setSubmitCount] = useState(0);
3511
+ const [summary, setSummary] = useState([]);
3357
3512
  const errorsRef = useLatest(errors);
3513
+ const formRef = useRef(null);
3514
+ const setRefs = useCallback((node) => {
3515
+ formRef.current = node;
3516
+ if (typeof ref === "function") ref(node);
3517
+ else if (ref) ref.current = node;
3518
+ }, [ref]);
3358
3519
  const pendingSubmit = useRef(false);
3520
+ const pendingFocus = useRef(false);
3359
3521
  const onSubmitRef = useLatest(onSubmit);
3360
3522
  useEffect(() => {
3361
3523
  if (pendingSubmit.current && submitted) {
3362
3524
  pendingSubmit.current = false;
3363
3525
  onSubmitRef.current(errorsRef.current);
3364
3526
  }
3365
- }, [submitted, errors, errorsRef, onSubmitRef]);
3527
+ }, [submitted, submitCount, errors, errorsRef, onSubmitRef]);
3528
+ const settle = useCallback(() => {
3529
+ const form = formRef.current;
3530
+ if (!form) return;
3531
+ const invalid = collectInvalidFields(form);
3532
+ const next = invalid.length >= 2 ? invalid : [];
3533
+ setSummary((prev) => prev.length === next.length && prev.every((f, i) => f.id === next[i].id && f.message === next[i].message && f.label === next[i].label) ? prev : next);
3534
+ if (pendingFocus.current && invalid.length > 0) {
3535
+ pendingFocus.current = false;
3536
+ const first = form.querySelector("[aria-invalid='true']");
3537
+ if (first) {
3538
+ if (typeof first.scrollIntoView === "function") first.scrollIntoView({ block: "center", behavior: "smooth" });
3539
+ first.focus({ preventScroll: true });
3540
+ }
3541
+ }
3542
+ }, []);
3543
+ useLayoutEffect(() => {
3544
+ if (!submitted) return;
3545
+ settle();
3546
+ }, [submitted, submitCount, errors, settle]);
3547
+ useEffect(() => {
3548
+ const form = formRef.current;
3549
+ if (!form || typeof MutationObserver === "undefined") return;
3550
+ const obs = new MutationObserver(() => settle());
3551
+ obs.observe(form, { subtree: true, childList: true, attributes: true, attributeFilter: ["aria-invalid"] });
3552
+ return () => obs.disconnect();
3553
+ }, [settle]);
3554
+ const userActed = useCallback((e) => {
3555
+ if (!pendingFocus.current) return;
3556
+ const target = e.target;
3557
+ if (target && target.closest("button[type='submit']")) return;
3558
+ pendingFocus.current = false;
3559
+ }, []);
3366
3560
  const setError = useCallback((field, message) => {
3367
3561
  setErrors((prev) => ({ ...prev, [field]: message }));
3368
3562
  }, []);
3369
3563
  const clearErrors = useCallback(() => {
3370
3564
  setErrors({});
3371
3565
  setSubmitted(false);
3566
+ setSummary([]);
3372
3567
  }, []);
3373
3568
  const handleSubmit = useCallback(
3374
3569
  (e) => {
3375
3570
  e.preventDefault();
3376
3571
  pendingSubmit.current = true;
3572
+ pendingFocus.current = focusFirstError;
3377
3573
  setSubmitted(true);
3574
+ setSubmitCount((c) => c + 1);
3378
3575
  },
3379
- []
3576
+ [focusFirstError]
3380
3577
  );
3381
- return /* @__PURE__ */ jsx(FormCtx.Provider, { value: { errors, setError, clearErrors, validationMode, submitted }, children: /* @__PURE__ */ jsx("form", { ref, onSubmit: handleSubmit, className, noValidate: true, children }) });
3578
+ const focusField = (id) => (e) => {
3579
+ e.preventDefault();
3580
+ const el = id ? document.getElementById(id) : null;
3581
+ if (el) {
3582
+ el.scrollIntoView?.({ block: "center" });
3583
+ el.focus();
3584
+ }
3585
+ };
3586
+ const summaryNode = errorSummary && summary.length >= 2 ? typeof errorSummary === "function" ? errorSummary(summary) : /* @__PURE__ */ jsx(Alert, { variant: "error", title: `${summary.length} fields need attention`, className: "mb-ds-4", children: /* @__PURE__ */ jsx("ul", { className: "list-disc pl-ds-5 flex flex-col gap-ds-1", children: summary.map((f) => /* @__PURE__ */ jsxs("li", { children: [
3587
+ /* @__PURE__ */ jsx("a", { href: f.id ? `#${f.id}` : void 0, onClick: focusField(f.id), className: "underline underline-offset-2 text-[var(--ds-text-primary)]", children: f.label }),
3588
+ ": ",
3589
+ f.message
3590
+ ] }, f.id || f.label)) }) }) : null;
3591
+ return /* @__PURE__ */ jsx(FormCtx.Provider, { value: { errors, setError, clearErrors, validationMode, submitted }, children: /* @__PURE__ */ jsxs("form", { ref: setRefs, onSubmit: handleSubmit, onFocusCapture: userActed, onInputCapture: userActed, className, noValidate: true, children: [
3592
+ summaryNode && /* @__PURE__ */ jsx("div", { "data-form-error-summary": "", children: summaryNode }),
3593
+ children,
3594
+ actions && /* @__PURE__ */ jsx("div", { "data-form-actions": "", className: cn("flex flex-wrap items-center gap-ds-2 pt-ds-4 mt-ds-2", actionsAlign === "end" ? "justify-end" : "justify-start"), children: actions })
3595
+ ] }) });
3382
3596
  }
3383
3597
  function TransferList({
3384
3598
  available,
@@ -3958,7 +4172,7 @@ function MentionInput({
3958
4172
  ] });
3959
4173
  }
3960
4174
  var CardCtx = createContext(false);
3961
- var variantStyles3 = {
4175
+ var variantStyles4 = {
3962
4176
  default: `bg-[var(--ds-bg-surface)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] ${GLASS_SURFACE}`,
3963
4177
  featured: [
3964
4178
  `bg-[var(--ds-bg-surface)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] ${GLASS_SURFACE}`,
@@ -4107,7 +4321,7 @@ var Card = forwardRef(function Card2({
4107
4321
  style: variant === "interactive" ? { ...INTERACTIVE_SCALE_CAP, ...style } : style,
4108
4322
  className: cn(
4109
4323
  "rounded-[var(--ds-radius-md)] flex flex-col relative",
4110
- variantStyles3[variant],
4324
+ variantStyles4[variant],
4111
4325
  isSelectable && !disabled && "cursor-pointer select-none transition-all duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
4112
4326
  isSelectable && !disabled && !selected && "hover:border-[var(--ds-accent-primary)]",
4113
4327
  isSelectable && "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -4191,32 +4405,33 @@ var CardHeader = forwardRef(function CardHeader2({ className, children, actions,
4191
4405
  children
4192
4406
  ] });
4193
4407
  });
4194
- var CardTitle = forwardRef(function CardTitle2({ className, children, ...props }, forwardedRef) {
4195
- const { ref: overflowRef, isTruncated } = useTextOverflow({ lines: 1 });
4408
+ var cardLineClampClasses = {
4409
+ 1: "truncate",
4410
+ 2: "line-clamp-2",
4411
+ 3: "line-clamp-3",
4412
+ 4: "line-clamp-4",
4413
+ 5: "line-clamp-5",
4414
+ 6: "line-clamp-6"
4415
+ };
4416
+ var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2, ...props }, forwardedRef) {
4417
+ const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
4418
+ const lineClampClass = lines > 0 ? cardLineClampClasses[lines] || `line-clamp-[${lines}]` : "";
4196
4419
  const heading = /* @__PURE__ */ jsx(
4197
4420
  "h3",
4198
4421
  {
4199
4422
  ref: (node) => {
4200
- overflowRef(node);
4423
+ overflowRef(lines > 0 ? node : null);
4201
4424
  if (typeof forwardedRef === "function") forwardedRef(node);
4202
4425
  else if (forwardedRef) forwardedRef.current = node;
4203
4426
  },
4204
- className: cn("font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", className),
4427
+ className: cn("font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", lines > 0 && lineClampClass, className),
4205
4428
  ...props,
4206
4429
  children
4207
4430
  }
4208
4431
  );
4209
- if (!isTruncated) return heading;
4432
+ if (!isTruncated || lines === 0) return heading;
4210
4433
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: heading });
4211
4434
  });
4212
- var cardLineClampClasses = {
4213
- 1: "truncate",
4214
- 2: "line-clamp-2",
4215
- 3: "line-clamp-3",
4216
- 4: "line-clamp-4",
4217
- 5: "line-clamp-5",
4218
- 6: "line-clamp-6"
4219
- };
4220
4435
  var CardDescription = forwardRef(function CardDescription2({ className, children, lines = 2, ...props }, forwardedRef) {
4221
4436
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
4222
4437
  const lineClampClass = lines > 0 ? cardLineClampClasses[lines] || `line-clamp-[${lines}]` : "";
@@ -4305,10 +4520,12 @@ function StatsCard({ value, label, trend, icon, layout = "card", interactive, on
4305
4520
  }
4306
4521
  function TrendBadge({ trend }) {
4307
4522
  const colors = {
4308
- up: "text-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
4309
- down: "text-[var(--ds-status-error)] bg-[var(--ds-status-error-bg)]",
4310
- flat: "text-[var(--ds-text-muted)] bg-[var(--ds-bg-muted)]"
4523
+ positive: "text-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
4524
+ negative: "text-[var(--ds-status-error)] bg-[var(--ds-status-error-bg)]",
4525
+ neutral: "text-[var(--ds-text-muted)] bg-[var(--ds-bg-muted)]"
4311
4526
  };
4527
+ const defaultSentiment = { up: "positive", down: "negative", flat: "neutral" };
4528
+ const sentiment = trend.sentiment ?? defaultSentiment[trend.direction];
4312
4529
  const arrows = { up: "\u2191", down: "\u2193", flat: "\u2192" };
4313
4530
  const { ref, isTruncated } = useTextOverflow({ lines: 1, content: trend.value });
4314
4531
  const badge = /* @__PURE__ */ jsxs(
@@ -4316,7 +4533,7 @@ function TrendBadge({ trend }) {
4316
4533
  {
4317
4534
  className: cn(
4318
4535
  "inline-flex items-center gap-ds-0.5 text-xs font-medium rounded-[var(--ds-radius-sm)] px-ds-1.5 py-ds-0.5 min-w-0 max-w-full",
4319
- colors[trend.direction]
4536
+ colors[sentiment]
4320
4537
  ),
4321
4538
  children: [
4322
4539
  /* @__PURE__ */ jsx("span", { children: arrows[trend.direction] }),
@@ -4381,10 +4598,11 @@ var DescriptionList = forwardRef(function DescriptionList2({
4381
4598
  columns = 1,
4382
4599
  size = "md",
4383
4600
  dividers = false,
4384
- stackBelow,
4601
+ stackBelow: stackBelowProp,
4385
4602
  className
4386
4603
  }, ref) {
4387
4604
  const s = sizeStyles4[size];
4605
+ const stackBelow = stackBelowProp ?? (columns > 1 ? "sm" : void 0);
4388
4606
  if (orientation === "vertical") {
4389
4607
  return /* @__PURE__ */ jsx(
4390
4608
  "dl",
@@ -4394,7 +4612,7 @@ var DescriptionList = forwardRef(function DescriptionList2({
4394
4612
  "grid",
4395
4613
  columnClasses[columns],
4396
4614
  "gap-x-ds-6 gap-y-ds-1",
4397
- stackBelow && stackBreakpoints[stackBelow],
4615
+ stackBelow && stackBelow !== "none" && stackBreakpoints[stackBelow],
4398
4616
  dividers && "divide-y-[length:var(--ds-border-width)] divide-[var(--ds-border-subtle)]",
4399
4617
  className
4400
4618
  ),
@@ -4436,7 +4654,7 @@ var DescriptionList = forwardRef(function DescriptionList2({
4436
4654
  "grid",
4437
4655
  columnClasses[columns],
4438
4656
  "gap-x-ds-6",
4439
- stackBelow && stackBreakpoints[stackBelow],
4657
+ stackBelow && stackBelow !== "none" && stackBreakpoints[stackBelow],
4440
4658
  dividers && "divide-y-[length:var(--ds-border-width)] divide-[var(--ds-border-subtle)]",
4441
4659
  className
4442
4660
  ),
@@ -4857,8 +5075,10 @@ function SegmentedControl({
4857
5075
  fullWidth = false,
4858
5076
  onHoverChange,
4859
5077
  className,
4860
- "aria-label": ariaLabel
5078
+ "aria-label": ariaLabel,
5079
+ maxOptions = 6
4861
5080
  }) {
5081
+ const collapsed = options.length > maxOptions;
4862
5082
  const containerRef = useRef(null);
4863
5083
  const [indicator, setIndicator] = useState({ left: 0, width: 0 });
4864
5084
  const updateIndicator = useCallback(() => {
@@ -4876,12 +5096,12 @@ function SegmentedControl({
4876
5096
  updateIndicator();
4877
5097
  }, [value, optionsKey, updateIndicator]);
4878
5098
  useLayoutEffect(() => {
4879
- if (!containerRef.current || typeof ResizeObserver === "undefined") return;
5099
+ if (collapsed || !containerRef.current || typeof ResizeObserver === "undefined") return;
4880
5100
  const observer = new ResizeObserver(updateIndicator);
4881
5101
  observer.observe(containerRef.current);
4882
5102
  containerRef.current.querySelectorAll("button").forEach((b) => observer.observe(b));
4883
5103
  return () => observer.disconnect();
4884
- }, [updateIndicator, optionsKey]);
5104
+ }, [updateIndicator, optionsKey, collapsed]);
4885
5105
  const containerHeight = {
4886
5106
  xs: "h-ds-6",
4887
5107
  sm: "h-[var(--ds-control-height-sm)]",
@@ -4906,6 +5126,19 @@ function SegmentedControl({
4906
5126
  if (b.dataset.value === enabled[next].value) b.focus();
4907
5127
  });
4908
5128
  };
5129
+ if (collapsed) {
5130
+ return /* @__PURE__ */ jsx(
5131
+ Dropdown,
5132
+ {
5133
+ "aria-label": ariaLabel,
5134
+ size: size === "md" || size === "lg" ? "md" : "sm",
5135
+ options: options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label })),
5136
+ value,
5137
+ onChange: (v) => onChange(v),
5138
+ className: cn("inline-block", fullWidth ? "w-full" : "w-auto", className)
5139
+ }
5140
+ );
5141
+ }
4909
5142
  return /* @__PURE__ */ jsxs(
4910
5143
  "div",
4911
5144
  {
@@ -6535,7 +6768,7 @@ function EmptyState({ icon, iconFrame = "circle", title, description, action, va
6535
6768
  ),
6536
6769
  children: [
6537
6770
  iconFrame === "none" && iconNode ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-ds-4", children: iconNode }) : /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-[var(--ds-radius-full)] bg-[var(--ds-bg-emphasis)] flex items-center justify-center mb-ds-4", children: iconNode ?? /* @__PURE__ */ jsx("span", { className: "text-2xl text-[var(--ds-text-muted)]", children: defaultIcons[variant] }) }),
6538
- /* @__PURE__ */ jsx("h3", { className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: title }),
6771
+ /* @__PURE__ */ jsx("h3", { className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] text-center", children: title }),
6539
6772
  description && /* @__PURE__ */ jsx("p", { className: "mt-ds-1 text-sm text-[var(--ds-text-muted)] max-w-sm text-center", children: description }),
6540
6773
  action && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: action })
6541
6774
  ]
@@ -6559,11 +6792,16 @@ function SkipLink({ href = "#main", children = "Skip to content", className }) {
6559
6792
  );
6560
6793
  }
6561
6794
  function useCompact(ref, breakpoint) {
6562
- const [compact, setCompact] = useState(false);
6795
+ const px = typeof breakpoint === "number" ? breakpoint : readBreakpointPx(breakpoint);
6796
+ const width = useElementWidth(ref);
6797
+ return width > 0 && width < px;
6798
+ }
6799
+ function useElementWidth(ref) {
6800
+ const [width, setWidth] = useState(0);
6563
6801
  const check = useCallback(() => {
6564
- const width = ref.current?.offsetWidth ?? 0;
6565
- if (width > 0) setCompact(width < breakpoint);
6566
- }, [ref, breakpoint]);
6802
+ const next = ref.current?.offsetWidth ?? 0;
6803
+ if (next > 0) setWidth(next);
6804
+ }, [ref]);
6567
6805
  useLayoutEffect(() => {
6568
6806
  check();
6569
6807
  if (!ref.current || typeof ResizeObserver === "undefined") return;
@@ -6571,7 +6809,7 @@ function useCompact(ref, breakpoint) {
6571
6809
  observer.observe(ref.current);
6572
6810
  return () => observer.disconnect();
6573
6811
  }, [check, ref]);
6574
- return compact;
6812
+ return width;
6575
6813
  }
6576
6814
  function TableToolbar({
6577
6815
  search,
@@ -6699,11 +6937,11 @@ function ExpandableDescription({ children, className }) {
6699
6937
  }
6700
6938
  );
6701
6939
  }
6702
- function HorizontalLayout({ items, className }) {
6703
- return /* @__PURE__ */ jsx("div", { className: cn("flex items-start", className), children: items.map((item, i) => {
6940
+ function HorizontalLayout({ items, minItemWidth, className }) {
6941
+ return /* @__PURE__ */ jsx("div", { "data-timeline-row": "", className: cn("flex items-start overflow-x-auto snap-x", className), children: items.map((item, i) => {
6704
6942
  const isFirst = i === 0;
6705
6943
  const isLast = i === items.length - 1;
6706
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center min-w-[100px]", children: [
6944
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 shrink-0 flex-col items-center snap-start", style: { minWidth: minItemWidth }, children: [
6707
6945
  /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full", children: [
6708
6946
  /* @__PURE__ */ jsx(
6709
6947
  "div",
@@ -6762,17 +7000,17 @@ function VerticalLayout({ items, className }) {
6762
7000
  ] })
6763
7001
  ] }, i)) });
6764
7002
  }
6765
- function Timeline({ items, orientation = "vertical", className, ref }) {
6766
- if (orientation === "horizontal") {
6767
- return /* @__PURE__ */ jsx("div", { ref, className, children: /* @__PURE__ */ jsx(HorizontalLayout, { items }) });
6768
- }
6769
- if (orientation === "responsive") {
6770
- return /* @__PURE__ */ jsxs("div", { ref, className, children: [
6771
- /* @__PURE__ */ jsx("div", { className: "hidden md:block", children: /* @__PURE__ */ jsx(HorizontalLayout, { items }) }),
6772
- /* @__PURE__ */ jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsx(VerticalLayout, { items }) })
6773
- ] });
6774
- }
6775
- return /* @__PURE__ */ jsx("div", { ref, className, children: /* @__PURE__ */ jsx(VerticalLayout, { items }) });
7003
+ function Timeline({ items, orientation = "vertical", minItemWidth = 200, className, ref }) {
7004
+ const measureRef = useRef(null);
7005
+ const width = useElementWidth(measureRef);
7006
+ const setRefs = useCallback((node) => {
7007
+ measureRef.current = node;
7008
+ if (typeof ref === "function") ref(node);
7009
+ else if (ref) ref.current = node;
7010
+ }, [ref]);
7011
+ const fits = width === 0 || width >= items.length * minItemWidth;
7012
+ const horizontal = orientation === "horizontal" || orientation === "responsive" && fits;
7013
+ return /* @__PURE__ */ jsx("div", { ref: setRefs, "data-timeline-orientation": horizontal ? "horizontal" : "vertical", className, children: horizontal ? /* @__PURE__ */ jsx(HorizontalLayout, { items, minItemWidth }) : /* @__PURE__ */ jsx(VerticalLayout, { items }) });
6776
7014
  }
6777
7015
  var CollapsibleContext = createContext(null);
6778
7016
  function useCollapsible() {
@@ -7282,147 +7520,6 @@ function Carousel({
7282
7520
  }
7283
7521
  );
7284
7522
  }
7285
- var variantStyles4 = {
7286
- info: "border-[var(--ds-accent-primary)] bg-[var(--ds-accent-subtle)]",
7287
- success: "border-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
7288
- warning: "border-[var(--ds-status-warning)] bg-[var(--ds-status-warning-bg)]",
7289
- error: "border-[var(--ds-status-error)] bg-[var(--ds-status-error-bg)]"
7290
- };
7291
- var indicators = {
7292
- info: "bg-[var(--ds-accent-primary)]",
7293
- success: "bg-[var(--ds-status-success)]",
7294
- warning: "bg-[var(--ds-status-warning)]",
7295
- error: "bg-[var(--ds-status-error)]"
7296
- };
7297
- var actionColorsByVariant = {
7298
- info: "text-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-muted)]",
7299
- success: "text-[var(--ds-status-success)] hover:bg-[color-mix(in_srgb,var(--ds-status-success)_12%,transparent)]",
7300
- warning: "text-[var(--ds-status-warning)] hover:bg-[color-mix(in_srgb,var(--ds-status-warning)_15%,transparent)]",
7301
- error: "text-[var(--ds-status-error)] hover:bg-[color-mix(in_srgb,var(--ds-status-error)_12%,transparent)]"
7302
- };
7303
- var Alert = forwardRef(function Alert2({
7304
- variant,
7305
- title,
7306
- children,
7307
- dismissible,
7308
- onDismiss,
7309
- action,
7310
- actions,
7311
- placement = "inline",
7312
- expandableContent,
7313
- className
7314
- }, ref) {
7315
- const [detailsExpanded, setDetailsExpanded] = useState(false);
7316
- const isMobile = useBreakpoint("sm");
7317
- const resolvedActions = actions ?? (action ? [action] : []);
7318
- const shouldCollapseActions = isMobile && resolvedActions.length > 1;
7319
- const placementClasses = placement === "page-top" ? "sticky top-0 z-[var(--ds-z-sticky)] rounded-none" : placement === "section-top" ? "rounded-[var(--ds-radius-md)] my-ds-4" : "rounded-[var(--ds-radius-md)]";
7320
- return /* @__PURE__ */ jsxs(
7321
- "div",
7322
- {
7323
- ref,
7324
- role: "alert",
7325
- className: cn(
7326
- "flex items-start gap-ds-3 p-ds-4",
7327
- "border-[length:var(--ds-border-width)]",
7328
- "shadow-[var(--ds-shadow-sm)]",
7329
- "text-sm text-[var(--ds-text-primary)]",
7330
- variantStyles4[variant],
7331
- placementClasses,
7332
- className
7333
- ),
7334
- children: [
7335
- /* @__PURE__ */ jsx("div", { className: cn("mt-[0.3125em] w-3 h-3 shrink-0 rounded-[var(--ds-radius-sm)]", indicators[variant]) }),
7336
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
7337
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-3", children: [
7338
- /* @__PURE__ */ jsx("p", { className: "text-sm font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: title }),
7339
- resolvedActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: shouldCollapseActions ? /* @__PURE__ */ jsx(
7340
- DropdownMenu,
7341
- {
7342
- trigger: /* @__PURE__ */ jsx(
7343
- "button",
7344
- {
7345
- className: cn(
7346
- "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
7347
- "transition-colors cursor-pointer",
7348
- "border-[length:var(--ds-border-width)] border-current",
7349
- actionColorsByVariant[variant]
7350
- ),
7351
- children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-1", children: [
7352
- /* @__PURE__ */ jsx(Icon, { name: "more-vertical", size: 12 }),
7353
- "Actions"
7354
- ] })
7355
- }
7356
- ),
7357
- items: resolvedActions.map((a) => ({
7358
- label: a.label,
7359
- onClick: a.onClick
7360
- })),
7361
- align: "end"
7362
- }
7363
- ) : resolvedActions.map((a, i) => {
7364
- const effectiveVariant = a.variant ?? (i === 0 ? "secondary" : "outline");
7365
- const isAccent = effectiveVariant === "secondary";
7366
- return /* @__PURE__ */ jsxs(
7367
- "button",
7368
- {
7369
- onClick: a.onClick,
7370
- className: cn(
7371
- "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
7372
- "transition-colors cursor-pointer",
7373
- isAccent ? cn("border-[length:var(--ds-border-width)] border-current", actionColorsByVariant[variant]) : cn("border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]", "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]")
7374
- ),
7375
- children: [
7376
- a.iconLeft && /* @__PURE__ */ jsx("span", { className: "inline-flex mr-ds-1", children: a.iconLeft }),
7377
- a.label
7378
- ]
7379
- },
7380
- i
7381
- );
7382
- }) })
7383
- ] }),
7384
- children && /* @__PURE__ */ jsx("div", { className: "mt-ds-1 text-xs text-[var(--ds-text-secondary)]", children }),
7385
- expandableContent && /* @__PURE__ */ jsxs("div", { className: "mt-ds-2", children: [
7386
- /* @__PURE__ */ jsxs(
7387
- "button",
7388
- {
7389
- onClick: () => setDetailsExpanded(!detailsExpanded),
7390
- "aria-expanded": detailsExpanded,
7391
- className: cn(
7392
- "inline-flex items-center gap-ds-1 text-xs font-medium cursor-pointer",
7393
- "transition-colors duration-[var(--ds-transition-speed)]",
7394
- actionColorsByVariant[variant],
7395
- "rounded-[var(--ds-radius-sm)] px-ds-1.5 py-ds-0.5"
7396
- ),
7397
- children: [
7398
- /* @__PURE__ */ jsx(
7399
- Icon,
7400
- {
7401
- name: "chevron-right",
7402
- size: 12,
7403
- className: cn("transition-transform duration-[var(--ds-transition-speed)]", detailsExpanded && "rotate-90")
7404
- }
7405
- ),
7406
- detailsExpanded ? "Hide details" : "Show details"
7407
- ]
7408
- }
7409
- ),
7410
- detailsExpanded && /* @__PURE__ */ jsx("div", { className: "mt-ds-2 text-xs text-[var(--ds-text-secondary)] border-l-2 border-current/20 pl-ds-3", children: expandableContent })
7411
- ] })
7412
- ] }),
7413
- dismissible && /* @__PURE__ */ jsx(
7414
- "button",
7415
- {
7416
- onClick: onDismiss,
7417
- className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
7418
- "aria-label": "Dismiss",
7419
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 16 })
7420
- }
7421
- )
7422
- ]
7423
- }
7424
- );
7425
- });
7426
7523
  function formatTime2(d) {
7427
7524
  return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
7428
7525
  }
@@ -7922,7 +8019,7 @@ function useCornerInset(corner, inset, priority, target) {
7922
8019
  return claimCornerInset(corner, inset, priority, el);
7923
8020
  }, [corner, inset, priority, target]);
7924
8021
  }
7925
- function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92], container, flush, keepMounted = false, id, labelledBy }) {
8022
+ function Sheet({ open, onClose, title, children, footer, confirm, snapPoints = [0.5, 0.92], container, flush, keepMounted = false, id, labelledBy }) {
7926
8023
  const titleId = useId();
7927
8024
  const panelRef = useRef(null);
7928
8025
  const [mounted, setMounted] = useState(open);
@@ -8092,6 +8189,7 @@ function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92
8092
8189
  children
8093
8190
  }
8094
8191
  ),
8192
+ confirm,
8095
8193
  footer && /* @__PURE__ */ jsx(
8096
8194
  "div",
8097
8195
  {
@@ -8341,6 +8439,66 @@ function defaultMaxWidth(container) {
8341
8439
  const base = container instanceof HTMLElement ? container.clientWidth : typeof window !== "undefined" ? window.innerWidth : 1200;
8342
8440
  return Math.round(base * 0.6);
8343
8441
  }
8442
+ function useDirtyGuard(dirty, onClose, onDiscard) {
8443
+ const [confirming, setConfirming] = useState(false);
8444
+ useEffect(() => {
8445
+ if (!dirty) setConfirming(false);
8446
+ }, [dirty]);
8447
+ const requestClose = useCallback(() => {
8448
+ if (!dirty) {
8449
+ onClose();
8450
+ return;
8451
+ }
8452
+ setConfirming((c) => !c);
8453
+ }, [dirty, onClose]);
8454
+ const keep = useCallback(() => setConfirming(false), []);
8455
+ const discard = useCallback(() => {
8456
+ setConfirming(false);
8457
+ onDiscard?.();
8458
+ onClose();
8459
+ }, [onClose, onDiscard]);
8460
+ return { confirming, requestClose, keep, discard };
8461
+ }
8462
+ function DiscardConfirm({ open, onKeep, onDiscard }) {
8463
+ const keepRef = useRef(null);
8464
+ const returnTo = useRef(null);
8465
+ const overlay = useRef(null);
8466
+ useEffect(() => {
8467
+ if (!open) return;
8468
+ const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
8469
+ overlay.current = keepRef.current?.closest("[role='dialog'], [role='alertdialog']") ?? null;
8470
+ returnTo.current = active && active !== document.body && overlay.current?.contains(active) ? active : null;
8471
+ keepRef.current?.focus();
8472
+ return () => {
8473
+ if (document.activeElement !== document.body) return;
8474
+ const back = returnTo.current;
8475
+ if (back && back.isConnected) {
8476
+ back.focus();
8477
+ return;
8478
+ }
8479
+ const first = overlay.current?.querySelector("button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])");
8480
+ (first ?? overlay.current)?.focus();
8481
+ };
8482
+ }, [open]);
8483
+ if (!open) return null;
8484
+ return /* @__PURE__ */ jsxs(
8485
+ "div",
8486
+ {
8487
+ role: "group",
8488
+ "aria-label": "Unsaved changes",
8489
+ "data-discard-confirm": "",
8490
+ className: "shrink-0 flex flex-wrap items-center justify-between gap-ds-3 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-status-warning-bg)]",
8491
+ style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
8492
+ children: [
8493
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--ds-text-primary)]", children: "You have unsaved changes." }),
8494
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
8495
+ /* @__PURE__ */ jsx(Button, { ref: keepRef, variant: "secondary", size: "sm", onClick: onKeep, children: "Keep editing" }),
8496
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: onDiscard, children: "Discard changes" })
8497
+ ] })
8498
+ ]
8499
+ }
8500
+ );
8501
+ }
8344
8502
  var slideClasses = {
8345
8503
  right: {
8346
8504
  panel: "top-0 right-0 h-full",
@@ -8364,15 +8522,17 @@ var borderByPosition = {
8364
8522
  bottom: "border-t-[length:var(--ds-border-width)] border-t-[var(--ds-border-default)]"
8365
8523
  };
8366
8524
  function Drawer(props) {
8367
- const { position = "right", mobile, snapPoints, open, onClose, title, children, footer, container, flush, keepMounted, id, labelledBy } = props;
8525
+ const { position = "right", mobile, snapPoints, open, onClose, title, children, footer, container, flush, keepMounted, id, labelledBy, dirty, onDiscard } = props;
8368
8526
  const isPhone = useBreakpoint("sm");
8369
8527
  const mobileMode = mobile ?? (position === "left" ? "panel" : "sheet");
8528
+ const guard = useDirtyGuard(dirty, onClose, onDiscard);
8529
+ const confirm = /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard });
8370
8530
  if (isPhone && mobileMode === "sheet") {
8371
- return /* @__PURE__ */ jsx(Sheet, { open, onClose, title, footer, container, flush, snapPoints, keepMounted, id, labelledBy, children });
8531
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose: guard.requestClose, title, footer, confirm, container, flush, snapPoints, keepMounted, id, labelledBy, children });
8372
8532
  }
8373
- return /* @__PURE__ */ jsx(DrawerPanel, { ...props });
8533
+ return /* @__PURE__ */ jsx(DrawerPanel, { ...props, onClose: guard.requestClose, confirm });
8374
8534
  }
8375
- function DrawerPanel({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset, modal = true, keepMounted = false, id, labelledBy, resizable = false, maxWidth }) {
8535
+ function DrawerPanel({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset, modal = true, keepMounted = false, id, labelledBy, resizable = false, maxWidth, confirm }) {
8376
8536
  const nonModal = !modal;
8377
8537
  const [chosen, remember] = useRememberedWidth(id ?? labelledBy);
8378
8538
  const canResize = nonModal && resizable && position !== "bottom";
@@ -8518,6 +8678,7 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
8518
8678
  children
8519
8679
  }
8520
8680
  ),
8681
+ confirm,
8521
8682
  footer && /* @__PURE__ */ jsx(
8522
8683
  "div",
8523
8684
  {
@@ -8812,6 +8973,7 @@ var StackedList = forwardRef(function StackedList2({
8812
8973
  divided = true,
8813
8974
  flush = false,
8814
8975
  selectedId,
8976
+ hover = "tint",
8815
8977
  className
8816
8978
  }, ref) {
8817
8979
  return /* @__PURE__ */ jsx(
@@ -8859,7 +9021,7 @@ var StackedList = forwardRef(function StackedList2({
8859
9021
  // (1.5× the tab rail; still 0 under tint and pill). Every row carries
8860
9022
  // the rail width as a transparent placeholder so content never shifts.
8861
9023
  "border-l-[length:calc(var(--ds-nav-v-active-rail)*1.5)]",
8862
- isSelected ? "bg-[var(--ds-nav-v-active-bg)] text-[var(--ds-nav-v-active-text)] border-[var(--ds-accent-primary)]" : cn("border-transparent", interactive && "hover:bg-[var(--ds-tab-hover-bg)]")
9024
+ isSelected ? "bg-[var(--ds-nav-v-active-bg)] text-[var(--ds-nav-v-active-text)] border-[var(--ds-accent-primary)]" : cn("border-transparent", interactive && hover === "tint" && "hover:bg-[var(--ds-tab-hover-bg)]", interactive && hover === "title" && "group/row")
8863
9025
  ),
8864
9026
  onClick: item.onClick,
8865
9027
  role: interactive ? "button" : void 0,
@@ -8880,7 +9042,9 @@ var StackedList = forwardRef(function StackedList2({
8880
9042
  {
8881
9043
  className: cn(
8882
9044
  "text-sm",
8883
- isSelected ? "font-medium text-[var(--ds-nav-v-active-text)]" : hasEmphasis && !item.unread ? "text-[var(--ds-text-secondary)]" : "font-medium text-[var(--ds-text-primary)]"
9045
+ isSelected ? "font-medium text-[var(--ds-nav-v-active-text)]" : hasEmphasis && !item.unread ? "text-[var(--ds-text-secondary)]" : "font-medium text-[var(--ds-text-primary)]",
9046
+ // hover="title": the title answers the pointer (accent, underline) instead of the row's background
9047
+ interactive && hover === "title" && !isSelected && "transition-colors group-hover/row:text-[var(--ds-accent-primary)] group-hover/row:underline underline-offset-2"
8884
9048
  ),
8885
9049
  children: item.title
8886
9050
  }
@@ -9471,6 +9635,104 @@ var Tabs = Object.assign(TabsRoot, {
9471
9635
  Trigger: TabsTrigger,
9472
9636
  Content: TabsContent
9473
9637
  });
9638
+ var MIN_PANEL_WIDTH = 480;
9639
+ function SettingsLayout({
9640
+ sections,
9641
+ value,
9642
+ onChange,
9643
+ layout: layoutProp,
9644
+ railWidth = 220,
9645
+ collapseBelow = railWidth + MIN_PANEL_WIDTH,
9646
+ "aria-label": ariaLabel = "Settings sections",
9647
+ children,
9648
+ className
9649
+ }) {
9650
+ const shell = useAppShell();
9651
+ const layout = layoutProp ?? shell?.theme?.settingsLayout ?? "sidebar";
9652
+ const rootRef = useRef(null);
9653
+ const compact = useCompact(rootRef, collapseBelow);
9654
+ const tabs = sections.map((s) => ({ id: s.id, label: s.label, icon: s.icon, group: s.group, disabled: s.disabled }));
9655
+ const stickyTop = shell ? "var(--ds-sticky-offset, 0px)" : "calc(var(--ds-navbar-height) + var(--ds-sticky-offset, 0px))";
9656
+ if (layout === "tabs") {
9657
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-settings-layout": "tabs", className: cn("flex flex-col gap-ds-6", className), children: [
9658
+ /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(Tabs, { orientation: "horizontal", mobile: "dropdown", tabs, activeTab: value, onTabChange: onChange }) }),
9659
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children })
9660
+ ] });
9661
+ }
9662
+ if (compact) {
9663
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-settings-layout": "sidebar", "data-collapsed": "true", className: cn("flex flex-col gap-ds-6", className), children: [
9664
+ /* @__PURE__ */ jsx(
9665
+ Dropdown,
9666
+ {
9667
+ "aria-label": ariaLabel,
9668
+ options: sections.filter((s) => !s.disabled).map((s) => ({ id: s.id, label: s.label, group: s.group })),
9669
+ value,
9670
+ onChange: (v) => onChange(v)
9671
+ }
9672
+ ),
9673
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children })
9674
+ ] });
9675
+ }
9676
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-settings-layout": "sidebar", className: cn("flex flex-row items-start gap-ds-8", className), children: [
9677
+ /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, className: "sticky self-start shrink-0", style: { width: `${railWidth}px`, top: stickyTop }, children: /* @__PURE__ */ jsx(Tabs, { orientation: "vertical", tabs, activeTab: value, onTabChange: onChange, className: "w-full" }) }),
9678
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children })
9679
+ ] });
9680
+ }
9681
+ function SplitView({
9682
+ list,
9683
+ detail,
9684
+ aside,
9685
+ asideTitle = "Details",
9686
+ detailOpen = false,
9687
+ onDetailClose,
9688
+ detailMode = "stack",
9689
+ backLabel = "Back",
9690
+ listWidth = 35,
9691
+ listMinWidth = 25,
9692
+ asideWidth = 25,
9693
+ collapseBelow = 768,
9694
+ asideCollapseBelow = 1024,
9695
+ bordered = false,
9696
+ "aria-label": ariaLabel,
9697
+ className
9698
+ }) {
9699
+ const rootRef = useRef(null);
9700
+ const compact = useCompact(rootRef, collapseBelow);
9701
+ const asideCollapsed = useCompact(rootRef, asideCollapseBelow);
9702
+ const [asideOpen, setAsideOpen] = useState(false);
9703
+ useEffect(() => {
9704
+ if (!asideCollapsed) setAsideOpen(false);
9705
+ }, [asideCollapsed]);
9706
+ const region = ariaLabel ? { role: "region", "aria-label": ariaLabel } : {};
9707
+ const surface = cn(
9708
+ "h-full min-h-0 w-full bg-[var(--ds-bg-surface)]",
9709
+ bordered && "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-[var(--ds-radius-md)] overflow-hidden",
9710
+ className
9711
+ );
9712
+ if (compact) {
9713
+ const showDetail = detailMode === "stack" && detailOpen;
9714
+ return /* @__PURE__ */ jsx("div", { ref: rootRef, "data-split-view": showDetail ? "detail" : "list", ...region, className: cn(surface, "flex flex-col"), children: showDetail ? /* @__PURE__ */ jsxs(Fragment, { children: [
9715
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "arrow-left", size: 14 }), onClick: onDetailClose, children: backLabel }) }),
9716
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: detail })
9717
+ ] }) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: list }) });
9718
+ }
9719
+ const asideToggle = aside && asideCollapsed ? /* @__PURE__ */ jsx("div", { className: "shrink-0 flex justify-end px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "info", size: 14 }), onClick: () => setAsideOpen(true), "aria-expanded": asideOpen, children: asideTitle }) }) : null;
9720
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-split-view": "split", ...region, className: surface, children: [
9721
+ /* @__PURE__ */ jsxs(ResizablePanelGroup, { direction: "horizontal", stackOnMobile: false, className: "h-full", children: [
9722
+ /* @__PURE__ */ jsx(ResizablePanel, { id: "list", defaultSize: `${listWidth}%`, minSize: `${listMinWidth}%`, children: /* @__PURE__ */ jsx("div", { className: "h-full min-h-0 overflow-y-auto", children: list }) }),
9723
+ /* @__PURE__ */ jsx(ResizableHandle, { withHandle: true }),
9724
+ /* @__PURE__ */ jsx(ResizablePanel, { id: "detail", minSize: "30%", children: /* @__PURE__ */ jsxs("div", { className: "h-full min-h-0 flex flex-col", children: [
9725
+ asideToggle,
9726
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: detail })
9727
+ ] }) }),
9728
+ aside && !asideCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
9729
+ /* @__PURE__ */ jsx(ResizableHandle, { withHandle: true }),
9730
+ /* @__PURE__ */ jsx(ResizablePanel, { id: "aside", defaultSize: `${asideWidth}%`, minSize: "15%", children: /* @__PURE__ */ jsx("div", { className: "h-full min-h-0 overflow-y-auto", children: aside }) })
9731
+ ] })
9732
+ ] }),
9733
+ aside && asideCollapsed && /* @__PURE__ */ jsx(Drawer, { open: asideOpen, onClose: () => setAsideOpen(false), title: asideTitle, position: "right", children: aside })
9734
+ ] });
9735
+ }
9474
9736
  function SeparatorIcon({ type, size }) {
9475
9737
  const iconSize = size === "sm" ? 10 : 12;
9476
9738
  if (type === "chevron") return /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: iconSize });
@@ -11031,6 +11293,16 @@ var PageContainer = forwardRef(function PageContainer2({
11031
11293
  renderHeader
11032
11294
  }, ref) {
11033
11295
  const shell = useAppShell();
11296
+ const rootRef = useRef(null);
11297
+ const setRoot = useCallback((node) => {
11298
+ rootRef.current = node;
11299
+ if (typeof ref === "function") ref(node);
11300
+ else if (ref) ref.current = node;
11301
+ }, [ref]);
11302
+ const setPinnedHeight = useCallback((px) => {
11303
+ if (px) rootRef.current?.style.setProperty("--ds-sticky-offset", `${px}px`);
11304
+ else rootRef.current?.style.removeProperty("--ds-sticky-offset");
11305
+ }, []);
11034
11306
  const resolvedMaxWidth = width ? widthToMaxWidth[width] : maxWidth;
11035
11307
  const maxWidthValue = maxWidthValues[resolvedMaxWidth];
11036
11308
  const kids = Children.toArray(children);
@@ -11075,14 +11347,14 @@ var PageContainer = forwardRef(function PageContainer2({
11075
11347
  ) : null;
11076
11348
  const columnClass = (pad2) => cn("w-full", paddingClasses[pad2], centered && "mx-auto");
11077
11349
  const columnStyle = { maxWidth: maxWidthValue, containerType: "inline-size" };
11078
- const header = /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement, containedBreadcrumbs: contained, breadcrumbsInsideHeader, noBottomMargin: block }, children: renderHeader ? renderHeader(headerEl) : headerEl });
11350
+ const header = /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement, containedBreadcrumbs: contained, breadcrumbsInsideHeader, noBottomMargin: block, setPinnedHeight }, children: renderHeader ? renderHeader(headerEl) : headerEl });
11079
11351
  const contentPadding = floatingHeader && shell?.isMobile ? "none" : padding;
11080
11352
  const wrapperStyle = floatingHeader || overlap ? {
11081
11353
  ...floatingHeader ? { paddingRight: "var(--ds-space-3)", paddingLeft: r.sidebarFloating ? 0 : "var(--ds-space-3)" } : {},
11082
11354
  ...overlap ? { marginTop: -overlap, position: "relative", zIndex: 1 } : {}
11083
11355
  } : void 0;
11084
11356
  const column = /* @__PURE__ */ jsx("div", { className: cn(columnClass(contentPadding), bottomPaddingClasses[verticalPadding ?? "default"], className), style: columnStyle, children: rest });
11085
- return /* @__PURE__ */ jsxs("div", { ref, "data-page-container": "", className: "w-full", children: [
11357
+ return /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-page-container": "", className: "w-full", children: [
11086
11358
  placement === "block" ? /* @__PURE__ */ jsx("div", { style: floatingBlockPadding(r.navbarFloating, r.sidebarFloating, overlap > 0), children: /* @__PURE__ */ jsxs("div", { className: FLOATING_BLOCK, children: [
11087
11359
  strip,
11088
11360
  colourful ? header : /* @__PURE__ */ jsx("div", { className: columnClass(padding), style: columnStyle, children: header })
@@ -11614,6 +11886,31 @@ function SnappingHeader({
11614
11886
  stickyTop = "top-[var(--ds-navbar-height)]"
11615
11887
  }) {
11616
11888
  const [collapsed, setCollapsed] = useState(false);
11889
+ const container = usePageContainer();
11890
+ const stickyRef = useRef(null);
11891
+ const setStickyRef = useCallback((node) => {
11892
+ stickyRef.current = node;
11893
+ if (typeof headerRef === "function") headerRef(node);
11894
+ else if (headerRef) headerRef.current = node;
11895
+ }, [headerRef]);
11896
+ useLayoutEffect(() => {
11897
+ const report = container?.setPinnedHeight;
11898
+ if (!report) return;
11899
+ const el = stickyRef.current;
11900
+ if (!(pinned && collapsed) || !el) {
11901
+ report(null);
11902
+ return;
11903
+ }
11904
+ const sync = () => report(el.offsetHeight);
11905
+ sync();
11906
+ if (typeof ResizeObserver === "undefined") return () => report(null);
11907
+ const obs = new ResizeObserver(sync);
11908
+ obs.observe(el);
11909
+ return () => {
11910
+ obs.disconnect();
11911
+ report(null);
11912
+ };
11913
+ }, [container?.setPinnedHeight, pinned, collapsed]);
11617
11914
  const sentinelRef = useRef(null);
11618
11915
  const collapsedRef = useRef(false);
11619
11916
  const rafRef = useRef(0);
@@ -11682,7 +11979,7 @@ function SnappingHeader({
11682
11979
  const headerEl = /* @__PURE__ */ jsxs(
11683
11980
  "header",
11684
11981
  {
11685
- ref: !isFloating ? headerRef : void 0,
11982
+ ref: !isFloating ? setStickyRef : void 0,
11686
11983
  className: cn(
11687
11984
  "transition-all duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
11688
11985
  !isFloating && pinned && collapsed && cn("sticky", stickyTop, "z-[calc(var(--ds-z-sticky,40)-1)]"),
@@ -11820,7 +12117,7 @@ function SnappingHeader({
11820
12117
  /* @__PURE__ */ jsx(
11821
12118
  "div",
11822
12119
  {
11823
- ref: headerRef,
12120
+ ref: setStickyRef,
11824
12121
  className: cn(
11825
12122
  pinned && collapsed && "sticky z-[calc(var(--ds-z-sticky,40)-1)]",
11826
12123
  pinned && collapsed && "top-0"
@@ -12533,7 +12830,9 @@ var sizeClasses = {
12533
12830
  xl: "max-w-4xl",
12534
12831
  full: "max-w-[calc(100vw-2rem)]"
12535
12832
  };
12536
- function Dialog({ open, onClose, title, description, children, footer, variant = "default", size = "md", alert = false, container, onSubmit }) {
12833
+ function Dialog({ open, onClose: onCloseProp, title, description, children, footer, variant = "default", size = "md", alert = false, container, onSubmit, dirty, onDiscard }) {
12834
+ const guard = useDirtyGuard(dirty, onCloseProp, onDiscard);
12835
+ const onClose = guard.requestClose;
12537
12836
  const titleId = useId();
12538
12837
  const descriptionId = useId();
12539
12838
  const isMobile = useBreakpoint("sm");
@@ -12660,6 +12959,7 @@ function Dialog({ open, onClose, title, description, children, footer, variant =
12660
12959
  children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children })
12661
12960
  ] })
12662
12961
  ] }) }),
12962
+ /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
12663
12963
  footer && /* @__PURE__ */ jsx("div", { className: cn(
12664
12964
  "flex justify-end gap-ds-2 p-ds-4",
12665
12965
  "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
@@ -13846,6 +14146,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
13846
14146
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
13847
14147
  });
13848
14148
 
13849
- export { Accordion, ActionPanel, ActionPanelGroup, Alert, AppShell, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Button, ButtonSizeContext, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Drawer, Dropdown, DropdownMenu, EmptyState, Field, FileUpload, FormWrapper, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, Sheet, Sidebar, SidebarNav, SkipLink, SliderRange, Spinner, StackedList, StatsCard, StatusDot, Stepper, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimePicker, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, useAppShell, useFormContext, useSidebar, useToast };
14149
+ export { Accordion, ActionPanel, ActionPanelGroup, Alert, AppShell, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Button, ButtonSizeContext, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Drawer, Dropdown, DropdownMenu, EmptyState, Field, FileUpload, FormWrapper, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sheet, Sidebar, SidebarNav, SkipLink, SliderRange, Spinner, SplitView, StackedList, StatsCard, StatusDot, Stepper, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimePicker, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, useAppShell, useFormContext, useSidebar, useToast };
13850
14150
  //# sourceMappingURL=index.js.map
13851
14151
  //# sourceMappingURL=index.js.map