@stll/ui 0.44.1 → 0.46.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.
Files changed (71) hide show
  1. package/README.md +2 -1
  2. package/dist/components/accordion.d.ts +6 -2
  3. package/dist/components/accordion.js +4 -4
  4. package/dist/components/ai-label.d.ts +33 -0
  5. package/dist/components/ai-label.js +42 -0
  6. package/dist/components/alert-dialog.js +3 -2
  7. package/dist/components/animated-count.d.ts +13 -0
  8. package/dist/components/animated-count.js +63 -0
  9. package/dist/components/application-rail.js +1 -1
  10. package/dist/components/brand-icons.d.ts +1 -0
  11. package/dist/components/brand-icons.js +8 -1
  12. package/dist/components/button.d.ts +2 -2
  13. package/dist/components/button.js +12 -2
  14. package/dist/components/command.js +1 -1
  15. package/dist/components/court-badge.d.ts +7 -1
  16. package/dist/components/court-badge.js +13 -3
  17. package/dist/components/date-picker-popover.d.ts +12 -2
  18. package/dist/components/date-picker-popover.js +105 -9
  19. package/dist/components/date-range-filter.d.ts +14 -0
  20. package/dist/components/date-range-filter.js +74 -0
  21. package/dist/components/dialog.js +3 -2
  22. package/dist/components/document-identity-badge.d.ts +12 -0
  23. package/dist/components/document-identity-badge.js +94 -0
  24. package/dist/components/document-identity-badge.logic.d.ts +25 -0
  25. package/dist/components/document-identity-badge.logic.js +25 -0
  26. package/dist/components/input-group.d.ts +1 -1
  27. package/dist/components/input-otp.js +1 -1
  28. package/dist/components/landing.d.ts +1 -1
  29. package/dist/components/landing.js +2 -2
  30. package/dist/components/list.d.ts +8 -2
  31. package/dist/components/list.js +10 -4
  32. package/dist/components/matter-icon.d.ts +16 -0
  33. package/dist/components/matter-icon.js +56 -0
  34. package/dist/components/menu.d.ts +9 -1
  35. package/dist/components/menu.js +6 -6
  36. package/dist/components/outline-rail.d.ts +8 -1
  37. package/dist/components/outline-rail.js +95 -48
  38. package/dist/components/popover.js +1 -1
  39. package/dist/components/search-field.js +30 -34
  40. package/dist/components/select.js +1 -1
  41. package/dist/components/sheet.js +22 -4
  42. package/dist/components/sidebar.d.ts +1 -1
  43. package/dist/components/sidebar.js +8 -8
  44. package/dist/components/status-mark.d.ts +17 -0
  45. package/dist/components/status-mark.js +74 -0
  46. package/dist/components/swap-text.d.ts +16 -0
  47. package/dist/components/swap-text.js +52 -0
  48. package/dist/components/switch.d.ts +5 -0
  49. package/dist/components/switch.js +16 -0
  50. package/dist/components/table-chart.d.ts +20 -0
  51. package/dist/components/table-chart.js +274 -0
  52. package/dist/components/tabs.js +1 -1
  53. package/dist/components/toast.js +42 -25
  54. package/dist/components/workspace-shell.js +5 -4
  55. package/dist/icons.js +1 -2
  56. package/dist/index.d.ts +3 -3
  57. package/dist/index.js +3 -3
  58. package/dist/inspector/dock.js +1 -1
  59. package/dist/inspector/entity-tab.js +1 -2
  60. package/dist/kanban/virtual-cell.js +1 -1
  61. package/dist/lib/control-size.d.ts +1 -1
  62. package/dist/lib/matter-colors.d.ts +38 -0
  63. package/dist/lib/matter-colors.js +65 -0
  64. package/dist/lib/panel-inset.d.ts +11 -0
  65. package/dist/lib/panel-inset.js +33 -0
  66. package/dist/lib/safe-area.d.ts +11 -0
  67. package/dist/lib/safe-area.js +12 -0
  68. package/dist/review/review-word-diff.d.ts +13 -0
  69. package/dist/review/review-word-diff.js +78 -0
  70. package/dist/styles/theme.css +79 -0
  71. package/package.json +64 -8
@@ -1,19 +1,20 @@
1
1
  "use client";
2
- import { ChevronRightIcon } from "../icons.js";
2
+ import { ChevronRightIcon, PanelLeftIcon } from "../icons.js";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./tooltip.js";
5
+ import { ScrollArea } from "./scroll-area.js";
5
6
  import { DirectionalIcon } from "./directional-icon.js";
7
+ import "../lib/panel-inset.js";
6
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
9
  import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
8
10
  import { Temporal } from "temporal-polyfill/full";
9
11
  //#region src/components/outline-rail.tsx
10
12
  /**
11
- * Outline rail — the shared right-edge navigation rail.
13
+ * Outline navigation: an embedded panel, a host-owned rail, or a disclosure.
12
14
  *
13
- * Always-visible thin column of ticks (width tapering by nesting level).
14
- * Hovering reveals a single popover panel with the outline as a collapsible
15
- * tree; the entry currently in view is emphasised. Click a tick or a row to
16
- * jump.
15
+ * Embedded presentations fill their host track. The standalone disclosure
16
+ * owns a toggle above a thin column of ticks; hovering or pressing it reveals
17
+ * the collapsible tree. Click a tick or a row to jump.
17
18
  *
18
19
  * Generic over the position source: callers supply `resolvePct` (vertical % for
19
20
  * a tick) and `onJump`. Active tracking is derived from `resolvePct` by default,
@@ -29,8 +30,9 @@ const OUTLINE_EMPHASIS = {
29
30
  primary: "primary",
30
31
  secondary: "secondary"
31
32
  };
32
- const RAIL_WIDTH = 20;
33
- const ROW_H = 29;
33
+ const OUTLINE_CONTROL_MIN_SIZE = 32;
34
+ const OUTLINE_CONTROL_TARGET_CLASS = "relative pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11";
35
+ const RAIL_WIDTH = 32;
34
36
  const TICK_BASE_WIDTH = 6;
35
37
  const TICK_LEVEL_STEP = 2;
36
38
  const TICK_MAX_LEVEL = 5;
@@ -87,7 +89,7 @@ const Chevron = ({ open }) => /* @__PURE__ */ jsx(DirectionalIcon, {
87
89
  flip: !open,
88
90
  icon: ChevronRightIcon
89
91
  });
90
- const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId, header, collapsedFromLevel, topOffset = 0, panelWidth = 300, ariaLabel = "Outline" }) => {
92
+ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId, header, formatMetaLabel, collapsedFromLevel, presentation = "popover", bottomInset, topOffset = 0, panelWidth = 300, ariaLabel = "Outline" }) => {
91
93
  const [pctById, setPctById] = useState(/* @__PURE__ */ new Map());
92
94
  const [derivedActive, setDerivedActive] = useState(null);
93
95
  const [hovered, setHovered] = useState(false);
@@ -97,7 +99,7 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
97
99
  const [hoveredId, setHoveredId] = useState(null);
98
100
  const panelId = useId();
99
101
  const triggerId = useId();
100
- const panelOpen = hovered || pinned;
102
+ const panelOpen = presentation === "panel" || hovered || pinned;
101
103
  const closeTimer = useRef(null);
102
104
  const manualLockUntil = useRef(0);
103
105
  const panelRef = useRef(null);
@@ -232,7 +234,13 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
232
234
  const activeIndex = items.findIndex((item) => item.id === activeId);
233
235
  useLayoutEffect(() => {
234
236
  if (activeIndex === -1 || !panelOpen) return;
235
- panelRef.current?.querySelector(`[${ACTIVE_ROW_ATTRIBUTE}]`)?.scrollIntoView({ block: "nearest" });
237
+ const panel = panelRef.current;
238
+ const row = panel?.querySelector(`[${ACTIVE_ROW_ATTRIBUTE}]`);
239
+ if (!panel || !(row instanceof HTMLElement)) return;
240
+ const panelBox = panel.getBoundingClientRect();
241
+ const rowBox = row.getBoundingClientRect();
242
+ if (rowBox.top < panelBox.top) panel.scrollTop += rowBox.top - panelBox.top;
243
+ else if (rowBox.bottom > panelBox.bottom) panel.scrollTop += rowBox.bottom - panelBox.bottom;
236
244
  }, [activeIndex, panelOpen]);
237
245
  const openPanel = useCallback(() => {
238
246
  if (closeTimer.current !== null) {
@@ -246,7 +254,7 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
246
254
  closeTimer.current = setTimeout(() => setHovered(false), 120);
247
255
  }, []);
248
256
  useEffect(() => {
249
- if (pinned) panelRef.current?.focus();
257
+ if (pinned) panelRef.current?.closest("nav")?.focus();
250
258
  }, [pinned]);
251
259
  const closePanel = useCallback(() => {
252
260
  if (closeTimer.current !== null) {
@@ -267,23 +275,21 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
267
275
  const highlighted = isActive || isHovered;
268
276
  const indent = 8 + Math.min(node.item.level, maxLevel) * 12;
269
277
  return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsxs("div", {
270
- className: cn("flex items-center rounded-md pe-2.5", !hasChildren && highlighted && "bg-accent"),
278
+ className: cn("flex items-center rounded-md pe-2.5", highlighted && "bg-accent"),
271
279
  ...isActive ? { [ACTIVE_ROW_ATTRIBUTE]: "" } : {},
272
280
  onMouseEnter: () => setHoveredId(node.item.id),
273
281
  onMouseLeave: () => setHoveredId(null),
274
- style: hasChildren ? {
275
- position: "sticky",
276
- top: node.item.level * ROW_H,
277
- zIndex: 40 - node.item.level,
278
- background: highlighted ? "linear-gradient(var(--color-accent), var(--color-accent)), var(--color-popover)" : "var(--color-popover)"
279
- } : void 0,
280
282
  children: [
281
283
  hasChildren ? /* @__PURE__ */ jsx("button", {
282
284
  "aria-expanded": !isCollapsed,
283
285
  "aria-label": isCollapsed ? "Expand" : "Collapse",
284
- className: "text-muted-foreground hover:text-foreground flex size-5 shrink-0 items-center justify-center",
286
+ className: cn("text-muted-foreground hover:text-foreground flex shrink-0 items-center justify-center", OUTLINE_CONTROL_TARGET_CLASS),
287
+ style: {
288
+ minWidth: 32,
289
+ minHeight: 32,
290
+ marginInlineStart: indent - 4
291
+ },
285
292
  onClick: (event) => toggleCollapse(node.item.id, event.currentTarget.parentElement),
286
- style: { marginInlineStart: indent - 4 },
287
293
  type: "button",
288
294
  children: /* @__PURE__ */ jsx(Chevron, { open: !isCollapsed })
289
295
  }) : /* @__PURE__ */ jsx("span", {
@@ -314,10 +320,22 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
314
320
  children: node.item.title
315
321
  })] })
316
322
  }), /* @__PURE__ */ jsx(TooltipPopup, { children: outlineEntryText(node.item) })] }),
317
- node.item.meta !== void 0 && /* @__PURE__ */ jsx("span", {
323
+ node.item.meta !== void 0 && (formatMetaLabel ? /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
324
+ render: /* @__PURE__ */ jsx("button", {
325
+ "aria-label": formatMetaLabel(node.item.meta),
326
+ className: cn("text-foreground-placeholder text-2xs shrink-0 tabular-nums", OUTLINE_CONTROL_TARGET_CLASS),
327
+ style: {
328
+ minWidth: 32,
329
+ minHeight: 32
330
+ },
331
+ onClick: () => jumpTo(node.item.id),
332
+ type: "button"
333
+ }),
334
+ children: node.item.meta
335
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: formatMetaLabel(node.item.meta) })] }) : /* @__PURE__ */ jsx("span", {
318
336
  className: "text-foreground-placeholder text-2xs shrink-0 ps-2 tabular-nums",
319
337
  children: node.item.meta
320
- })
338
+ }))
321
339
  ]
322
340
  }), hasChildren && !isCollapsed && /* @__PURE__ */ jsx("ul", {
323
341
  className: "m-0 list-none p-0",
@@ -337,18 +355,26 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
337
355
  const ghostItem = ghostPct === void 0 ? void 0 : items.find((item) => item.id === hoveredId);
338
356
  return /* @__PURE__ */ jsxs("div", {
339
357
  "aria-label": ariaLabel,
340
- className: "absolute end-0 z-20 max-lg:hidden",
358
+ className: OUTLINE_PRESENTATION_CLASS[presentation],
341
359
  role: "group",
342
360
  style: {
343
- top: topOffset,
344
- bottom: 0,
345
- width: RAIL_WIDTH
361
+ marginBlockEnd: bottomInset ?? "max(env(safe-area-inset-bottom, 0px), var(--document-panel-bottom-inset, 0px))",
362
+ ...presentation === "popover" ? {
363
+ top: topOffset,
364
+ bottom: 0,
365
+ width: RAIL_WIDTH
366
+ } : {}
346
367
  },
347
368
  children: [
348
- /* @__PURE__ */ jsx("button", {
369
+ presentation === "popover" && /* @__PURE__ */ jsx("button", {
349
370
  "aria-controls": panelId,
350
371
  "aria-expanded": panelOpen,
351
- className: "focus-visible:ring-ring bg-popover text-popover-foreground sr-only end-0 top-2 z-30 -translate-x-6 text-xs focus:not-sr-only focus:absolute focus:w-max focus:rounded-md focus:border focus:px-2 focus:py-1 focus-visible:ring-2 focus-visible:outline-none",
372
+ className: cn("focus-visible:ring-ring bg-popover text-popover-foreground flex shrink-0 items-center justify-center self-start rounded-md focus-visible:ring-2 focus-visible:outline-none", "pointer-coarse:my-1.5", OUTLINE_CONTROL_TARGET_CLASS),
373
+ "aria-label": ariaLabel,
374
+ style: {
375
+ minWidth: 32,
376
+ minHeight: 32
377
+ },
352
378
  id: triggerId,
353
379
  onClick: () => {
354
380
  if (pinned) {
@@ -359,10 +385,15 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
359
385
  openPanel();
360
386
  },
361
387
  type: "button",
362
- children: ariaLabel
388
+ children: /* @__PURE__ */ jsx(PanelLeftIcon, {
389
+ "aria-hidden": true,
390
+ className: "size-4 rtl:-scale-x-100"
391
+ })
363
392
  }),
364
- /* @__PURE__ */ jsxs("div", {
365
- className: "relative h-full",
393
+ presentation !== "panel" && /* @__PURE__ */ jsxs("div", {
394
+ className: "relative min-h-0 flex-1",
395
+ "data-outline-ticks": true,
396
+ style: presentation === "popover" ? { height: `calc(100% - 32px)` } : void 0,
366
397
  onMouseEnter: openPanel,
367
398
  onMouseLeave: scheduleClose,
368
399
  onWheel: (event) => {
@@ -379,7 +410,7 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
379
410
  onMouseEnter: () => setHoveredId(item.id),
380
411
  onMouseLeave: () => setHoveredId(null),
381
412
  style: {
382
- top: `${pct}%`,
413
+ top: `clamp(2px, ${pct}%, calc(100% - 2px))`,
383
414
  transform: "translateY(-50%)",
384
415
  width: isHovered ? tickWidth(item.level) + 8 : tickWidth(item.level),
385
416
  height: tickHeight(isHovered, isActive),
@@ -391,7 +422,7 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
391
422
  "aria-hidden": true,
392
423
  className: "absolute end-0 rounded-full opacity-100",
393
424
  style: {
394
- top: `${ghostPct}%`,
425
+ top: `clamp(2px, ${ghostPct}%, calc(100% - 2px))`,
395
426
  transform: "translateY(-50%)",
396
427
  width: tickWidth(ghostItem.level) + 8,
397
428
  height: 4,
@@ -399,13 +430,15 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
399
430
  }
400
431
  })]
401
432
  }),
402
- /* @__PURE__ */ jsxs("nav", {
433
+ presentation !== "rail" && /* @__PURE__ */ jsxs("nav", {
434
+ "aria-label": ariaLabel,
403
435
  "aria-hidden": !panelOpen,
404
436
  id: panelId,
405
437
  inert: panelOpen ? void 0 : true,
406
438
  tabIndex: -1,
407
- className: cn("border-border bg-popover text-popover-foreground absolute overflow-y-auto rounded-xl border pb-2 shadow-lg transition-[opacity,transform] duration-150", panelOpen ? "translate-x-0 opacity-100" : "pointer-events-none translate-x-2 opacity-0"),
439
+ className: cn("bg-popover text-popover-foreground flex min-h-0 flex-col", presentation === "popover" && "border-border absolute rounded-xl border shadow-lg transition-[opacity,transform] duration-150", outlinePanelVisibilityClass(presentation, panelOpen)),
408
440
  onBlurCapture: (event) => {
441
+ if (event.relatedTarget instanceof HTMLElement && event.relatedTarget.id === triggerId) return;
409
442
  if (pinned && !event.currentTarget.contains(event.relatedTarget)) {
410
443
  closePanel();
411
444
  return;
@@ -422,28 +455,42 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
422
455
  onMouseEnter: openPanel,
423
456
  onMouseLeave: scheduleClose,
424
457
  onWheel: (event) => {
425
- const panel = event.currentTarget;
458
+ const panel = panelRef.current;
459
+ if (!panel) return;
426
460
  const room = panel.scrollHeight - panel.clientHeight;
427
461
  if (room > WHEEL_EDGE_TOLERANCE && (event.deltaY < 0 ? panel.scrollTop > WHEEL_EDGE_TOLERANCE : panel.scrollTop < room - WHEEL_EDGE_TOLERANCE)) return;
428
462
  scrollContainerRef.current?.scrollBy(0, event.deltaY);
429
463
  },
430
- ref: panelRef,
431
- style: {
464
+ style: presentation === "popover" ? {
432
465
  top: 0,
433
- insetInlineEnd: 26,
434
- width: panelWidth,
435
- maxHeight: "calc(100% - 24px)"
436
- },
466
+ insetInlineEnd: 38,
467
+ width: `min(${panelWidth}px, calc(100vw - 54px))`,
468
+ height: "calc(100% - 24px)"
469
+ } : void 0,
437
470
  children: [header !== void 0 && /* @__PURE__ */ jsx("div", {
438
- className: "bg-popover sticky top-0 z-50 border-b p-2",
471
+ className: "bg-popover z-50 shrink-0 border-b p-2",
439
472
  children: header
440
- }), /* @__PURE__ */ jsx("ul", {
441
- className: "m-0 list-none p-0",
442
- children: tree.map(renderNode)
473
+ }), /* @__PURE__ */ jsx(ScrollArea, {
474
+ axis: "vertical",
475
+ className: "min-h-0 flex-1",
476
+ viewportRef: panelRef,
477
+ children: /* @__PURE__ */ jsx("ul", {
478
+ className: "m-0 list-none p-0 pb-2",
479
+ children: tree.map(renderNode)
480
+ })
443
481
  })]
444
482
  })
445
483
  ]
446
484
  });
447
485
  };
486
+ const OUTLINE_PRESENTATION_CLASS = {
487
+ popover: "absolute end-0 z-20 flex flex-col pointer-coarse:min-w-11",
488
+ panel: "relative flex min-h-0 w-full flex-1 flex-col",
489
+ rail: "relative flex min-h-0 w-full flex-1 flex-col"
490
+ };
491
+ function outlinePanelVisibilityClass(presentation, open) {
492
+ if (presentation === "panel") return "flex-1";
493
+ return open ? "translate-x-0 opacity-100" : "pointer-events-none translate-x-2 opacity-0";
494
+ }
448
495
  //#endregion
449
- export { OUTLINE_EMPHASIS, OutlineRail, outlineEntryText };
496
+ export { OUTLINE_CONTROL_MIN_SIZE, OUTLINE_EMPHASIS, OutlineRail, outlineEntryText };
@@ -42,7 +42,7 @@ const PopoverPopup = ({ children, className, side = "bottom", align = "center",
42
42
  "data-slot": "popover-popup",
43
43
  ...props,
44
44
  children: /* @__PURE__ */ jsx(Popover$1.Viewport, {
45
- className: cn("relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) outline-none **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-current:transition-opacity **:data-current:data-ending-style:opacity-0 data-instant:transition-none **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:opacity-100 **:data-previous:transition-opacity **:data-previous:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-starting-style:opacity-0", tooltipStyle ? "py-1 [--viewport-inline-padding:--spacing(2)]" : [POPOVER_PADDING[padding], "not-data-transitioning:overflow-y-auto"]),
45
+ className: cn("relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) outline-none **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-current:transition-opacity **:data-current:data-ending-style:opacity-0 data-instant:transition-none **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:opacity-100 **:data-previous:transition-opacity **:data-previous:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-starting-style:opacity-0", tooltipStyle ? "py-1 [--viewport-inline-padding:--spacing(2)]" : [POPOVER_PADDING[padding], "overscroll-contain not-data-transitioning:overflow-y-auto"]),
46
46
  "data-slot": "popover-viewport",
47
47
  children
48
48
  })
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { SearchIcon, XIcon } from "../icons.js";
2
+ import { XIcon } from "../icons.js";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { Button } from "./button.js";
5
5
  import { InputGroup, InputGroupAddon, InputGroupInput } from "./input-group.js";
@@ -13,41 +13,37 @@ import { jsx, jsxs } from "react/jsx-runtime";
13
13
  */
14
14
  const SearchField = ({ value, onValueChange, clearLabel, groupClassName, className, onKeyDown, ...props }) => /* @__PURE__ */ jsxs(InputGroup, {
15
15
  className: cn("min-h-11 sm:min-h-0", groupClassName),
16
- children: [
17
- /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
18
- /* @__PURE__ */ jsx(InputGroupInput, {
19
- autoComplete: "off",
20
- spellCheck: false,
21
- ...props,
22
- className: cn("max-sm:h-11 max-sm:leading-11", className),
23
- onChange: (event) => {
24
- onValueChange(event.target.value);
16
+ children: [/* @__PURE__ */ jsx(InputGroupInput, {
17
+ autoComplete: "off",
18
+ spellCheck: false,
19
+ ...props,
20
+ className: cn("max-sm:h-11 max-sm:leading-11", className),
21
+ onChange: (event) => {
22
+ onValueChange(event.target.value);
23
+ },
24
+ onKeyDown: (event) => {
25
+ if (event.key === "Escape" && value.length > 0) {
26
+ event.preventDefault();
27
+ event.stopPropagation();
28
+ onValueChange("");
29
+ }
30
+ onKeyDown?.(event);
31
+ },
32
+ type: "search",
33
+ value
34
+ }), value.length > 0 && /* @__PURE__ */ jsx(InputGroupAddon, {
35
+ align: "inline-end",
36
+ children: /* @__PURE__ */ jsx(Button, {
37
+ "aria-label": clearLabel,
38
+ onClick: () => {
39
+ onValueChange("");
25
40
  },
26
- onKeyDown: (event) => {
27
- if (event.key === "Escape" && value.length > 0) {
28
- event.preventDefault();
29
- event.stopPropagation();
30
- onValueChange("");
31
- }
32
- onKeyDown?.(event);
33
- },
34
- type: "search",
35
- value
36
- }),
37
- value.length > 0 && /* @__PURE__ */ jsx(InputGroupAddon, {
38
- align: "inline-end",
39
- children: /* @__PURE__ */ jsx(Button, {
40
- "aria-label": clearLabel,
41
- onClick: () => {
42
- onValueChange("");
43
- },
44
- size: "icon-xs",
45
- type: "button",
46
- variant: "ghost",
47
- children: /* @__PURE__ */ jsx(XIcon, {})
48
- })
41
+ size: "icon-xs",
42
+ type: "button",
43
+ variant: "ghost",
44
+ children: /* @__PURE__ */ jsx(XIcon, {})
49
45
  })
50
- ]
46
+ })]
51
47
  });
52
48
  //#endregion
53
49
  export { SearchField };
@@ -83,7 +83,7 @@ const SelectPopup = ({ className, children, side = "bottom", sideOffset = 4, ali
83
83
  /* @__PURE__ */ jsx("div", {
84
84
  className: "bg-popover text-popover-foreground relative h-full min-w-(--anchor-width) rounded-lg border shadow-lg/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
85
85
  children: /* @__PURE__ */ jsx(Select$1.List, {
86
- className: cn("max-h-(--available-height) overflow-y-auto p-1", className),
86
+ className: cn("max-h-(--available-height) overflow-y-auto overscroll-contain p-1", className),
87
87
  "data-slot": "select-list",
88
88
  children
89
89
  })
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
  import { XIcon } from "../icons.js";
3
3
  import { cn } from "../lib/utils.js";
4
+ import { OVERLAY_LAYER_CLASS_NAMES } from "../lib/overlay-layer.js";
5
+ import "../lib/safe-area.js";
4
6
  import { renderTooltipTrigger } from "./tooltip-trigger-helper.js";
5
7
  import { Button } from "./button.js";
6
8
  import { ScrollArea } from "./scroll-area.js";
@@ -28,12 +30,12 @@ function SheetClose({ tooltip, ...props }) {
28
30
  });
29
31
  }
30
32
  const SheetBackdrop = ({ className, ...props }) => /* @__PURE__ */ jsx(Dialog.Backdrop, {
31
- className: cn("fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", className),
33
+ className: cn("fixed inset-0 bg-black/32 backdrop-blur-sm transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", OVERLAY_LAYER_CLASS_NAMES.default, className),
32
34
  "data-slot": "sheet-backdrop",
33
35
  ...props
34
36
  });
35
37
  const SheetViewport = ({ className: _className, side, variant = "default", ...props }) => /* @__PURE__ */ jsx(Dialog.Viewport, {
36
- className: cn("fixed inset-0 z-50", side === "block-end" && "grid grid-rows-[1fr_auto] pt-12", side === "block-start" && "grid grid-rows-[auto_1fr] pb-12", side === "inline-start" && "flex justify-start", side === "inline-end" && "flex justify-end", variant === "inset" && "sm:p-4"),
38
+ className: cn("fixed inset-0", OVERLAY_LAYER_CLASS_NAMES.default, side === "block-end" && "grid grid-rows-[1fr_auto] pt-12", side === "block-start" && "grid grid-rows-[auto_1fr] pb-12", side === "inline-start" && "flex justify-start", side === "inline-end" && "flex justify-end", variant === "inset" && "sm:p-4"),
37
39
  "data-slot": "sheet-viewport",
38
40
  ...props
39
41
  });
@@ -41,12 +43,28 @@ const SheetPopup = ({ className, children, showCloseButton = true, side = "inlin
41
43
  side,
42
44
  variant,
43
45
  children: /* @__PURE__ */ jsxs(Dialog.Popup, {
44
- className: cn("bg-popover text-popover-foreground relative flex max-h-full min-h-0 w-full min-w-0 flex-col shadow-lg/5 transition-[opacity,translate] duration-200 ease-in-out will-change-transform not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 max-sm:before:hidden dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", side === "block-end" && "row-start-2 border-t data-ending-style:translate-y-8 data-starting-style:translate-y-8", side === "block-start" && "border-b data-ending-style:-translate-y-8 data-starting-style:-translate-y-8", side === "inline-start" && "w-[calc(100%-(--spacing(12)))] max-w-md border-e data-ending-style:-translate-x-8 data-starting-style:-translate-x-8 rtl:data-ending-style:translate-x-8 rtl:data-starting-style:translate-x-8", side === "inline-end" && "w-[calc(100%-(--spacing(12)))] max-w-md border-s data-ending-style:translate-x-8 data-starting-style:translate-x-8 rtl:data-ending-style:-translate-x-8 rtl:data-starting-style:-translate-x-8", variant === "inset" && "before:hidden sm:rounded-2xl sm:border sm:before:rounded-[calc(var(--radius-2xl)-1px)] sm:**:data-[slot=sheet-footer]:rounded-b-[calc(var(--radius-2xl)-1px)]", className),
46
+ className: cn("bg-popover text-popover-foreground relative flex max-h-full min-h-0 w-full min-w-0 flex-col shadow-lg/5 transition-[opacity,translate] duration-200 ease-out will-change-transform not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:opacity-0 max-sm:before:hidden dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", side === "block-end" && [
47
+ "row-start-2 border-t data-ending-style:translate-y-8 data-starting-style:translate-y-8",
48
+ "pb-(--safe-area-bottom)",
49
+ "ps-(--safe-area-inline-start) pe-(--safe-area-inline-end)"
50
+ ], side === "block-start" && [
51
+ "border-b data-ending-style:-translate-y-8 data-starting-style:-translate-y-8",
52
+ "pt-(--safe-area-top)",
53
+ "ps-(--safe-area-inline-start) pe-(--safe-area-inline-end)"
54
+ ], side === "inline-start" && [
55
+ "w-[calc(100%-(--spacing(12)))] max-w-md border-e ps-(--safe-area-inline-start) data-ending-style:-translate-x-8 data-starting-style:-translate-x-8 rtl:data-ending-style:translate-x-8 rtl:data-starting-style:translate-x-8 [&.w-full.max-w-none]:pe-(--safe-area-inline-end)",
56
+ "pt-(--safe-area-top)",
57
+ "pb-(--safe-area-bottom)"
58
+ ], side === "inline-end" && [
59
+ "w-[calc(100%-(--spacing(12)))] max-w-md border-s pe-(--safe-area-inline-end) data-ending-style:translate-x-8 data-starting-style:translate-x-8 rtl:data-ending-style:-translate-x-8 rtl:data-starting-style:-translate-x-8 [&.w-full.max-w-none]:ps-(--safe-area-inline-start)",
60
+ "pt-(--safe-area-top)",
61
+ "pb-(--safe-area-bottom)"
62
+ ], variant === "inset" && "before:hidden sm:rounded-2xl sm:border sm:before:rounded-[calc(var(--radius-2xl)-1px)] sm:**:data-[slot=sheet-footer]:rounded-b-[calc(var(--radius-2xl)-1px)]", className),
45
63
  "data-slot": "sheet-popup",
46
64
  ...props,
47
65
  children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog.Close, {
48
66
  "aria-label": "Close",
49
- className: "absolute end-2 top-2",
67
+ className: cn("absolute end-[calc(--spacing(2)+var(--safe-area-inline-end))] top-2", side !== "block-end" && "top-[calc(--spacing(2)+var(--safe-area-top))]"),
50
68
  render: /* @__PURE__ */ jsx(Button, {
51
69
  size: "icon",
52
70
  variant: "ghost"
@@ -86,7 +86,7 @@ declare const SidebarMenu: ({ className, ...props }: React.ComponentProps<"ul">)
86
86
  declare const SidebarMenuItem: ({ className, ...props }: React.ComponentProps<"li">) => import("react").JSX.Element;
87
87
  declare const sidebarMenuButtonVariants: (props?: ({
88
88
  variant?: "default" | "outline" | null | undefined;
89
- size?: "rail" | "sm" | "default" | "lg" | null | undefined;
89
+ size?: "default" | "lg" | "sm" | "rail" | null | undefined;
90
90
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
91
91
  /**
92
92
  * The footer's avatar trigger, signed in (user menu) or not (sign-in): a
@@ -118,7 +118,7 @@ const Sidebar = ({ side = "left", variant = "sidebar", collapsible = "offcanvas"
118
118
  open: openMobile,
119
119
  ...props,
120
120
  children: /* @__PURE__ */ jsxs(SheetPopup, {
121
- className: "bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
121
+ className: "bg-sidebar text-sidebar-foreground w-(--sidebar-width) [&>button]:hidden",
122
122
  "data-mobile": "true",
123
123
  "data-sidebar": "sidebar",
124
124
  "data-slot": "sidebar",
@@ -135,17 +135,17 @@ const Sidebar = ({ side = "left", variant = "sidebar", collapsible = "offcanvas"
135
135
  });
136
136
  }
137
137
  return /* @__PURE__ */ jsxs("div", {
138
- className: "group peer text-sidebar-foreground hidden md:block",
138
+ className: cn("group peer text-sidebar-foreground hidden [--sidebar-inline-size:calc(var(--sidebar-width)+var(--sidebar-safe-area-inline))] md:block", side === "left" ? "[--sidebar-safe-area-inline:var(--safe-area-inline-start)]" : "[--sidebar-safe-area-inline:var(--safe-area-inline-end)]", variant === "floating" || variant === "inset" ? "data-[collapsible=icon]:[--sidebar-inline-size:calc(var(--sidebar-width-icon)+--spacing(4)+var(--sidebar-safe-area-inline))]" : "data-[collapsible=icon]:[--sidebar-inline-size:calc(var(--sidebar-width-icon)+var(--sidebar-safe-area-inline))]"),
139
139
  "data-collapsible": state === "collapsed" ? collapsible : "",
140
140
  "data-side": side,
141
141
  "data-slot": "sidebar",
142
142
  "data-state": state,
143
143
  "data-variant": variant,
144
144
  children: [/* @__PURE__ */ jsx("div", {
145
- className: cn("relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"),
145
+ className: cn("relative w-(--sidebar-inline-size) bg-transparent transition-[width] duration-200 ease-in-out", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180"),
146
146
  "data-slot": "sidebar-gap"
147
147
  }), /* @__PURE__ */ jsx("div", {
148
- className: cn("fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[inset-inline-start,inset-inline-end,width] duration-200 ease-linear md:flex", side === "left" ? "start-0 group-data-[collapsible=offcanvas]:start-[calc(var(--sidebar-width)*-1)]" : "end-0 group-data-[collapsible=offcanvas]:end-[calc(var(--sidebar-width)*-1)]", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-e group-data-[side=right]:border-s", className),
148
+ className: cn("fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-inline-size) transition-[inset-inline-start,inset-inline-end,width] duration-200 ease-in-out md:flex", "pt-(--safe-area-top) pb-(--safe-area-bottom)", side === "left" ? "start-0 ps-(--safe-area-inline-start) group-data-[collapsible=offcanvas]:start-[calc(var(--sidebar-inline-size)*-1)]" : "end-0 pe-(--safe-area-inline-end) group-data-[collapsible=offcanvas]:end-[calc(var(--sidebar-inline-size)*-1)]", variant === "floating" || variant === "inset" ? ["p-2 pt-[calc(var(--safe-area-top)+--spacing(2))] pb-[calc(var(--safe-area-bottom)+--spacing(2))] group-data-[collapsible=icon]:w-[calc(var(--sidebar-inline-size)+2px)]", side === "left" ? "ps-[calc(var(--safe-area-inline-start)+--spacing(2))]" : "pe-[calc(var(--safe-area-inline-end)+--spacing(2))]"] : "group-data-[side=left]:border-e group-data-[side=right]:border-s", className),
149
149
  "data-slot": "sidebar-container",
150
150
  ...props,
151
151
  children: /* @__PURE__ */ jsx("div", {
@@ -182,7 +182,7 @@ const SidebarRail = ({ className, label = DEFAULT_TOGGLE_LABEL, "aria-label": ar
182
182
  return /* @__PURE__ */ jsx("button", {
183
183
  "aria-label": ariaLabel ?? label,
184
184
  type: "button",
185
- className: cn("hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 transition-transform ease-linear group-data-[side=left]:-end-4 group-data-[side=right]:start-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] sm:flex", "ltr:-translate-x-1/2 rtl:translate-x-1/2", "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "hover:group-data-[collapsible=offcanvas]:bg-sidebar", "group-data-[collapsible=offcanvas]:ltr:translate-x-0 group-data-[collapsible=offcanvas]:rtl:translate-x-0", "group-data-[collapsible=offcanvas]:group-data-[side=left]:after:end-full group-data-[collapsible=offcanvas]:group-data-[side=right]:after:start-full", "[[data-side=left][data-collapsible=offcanvas]_&]:-end-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-start-2", className),
185
+ className: cn("hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 transition-transform ease-in-out group-data-[side=left]:-end-4 group-data-[side=right]:start-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] sm:flex", "ltr:-translate-x-1/2 rtl:translate-x-1/2", "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "hover:group-data-[collapsible=offcanvas]:bg-sidebar", "group-data-[collapsible=offcanvas]:ltr:translate-x-0 group-data-[collapsible=offcanvas]:rtl:translate-x-0", "group-data-[collapsible=offcanvas]:group-data-[side=left]:after:end-full group-data-[collapsible=offcanvas]:group-data-[side=right]:after:start-full", "[[data-side=left][data-collapsible=offcanvas]_&]:-end-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-start-2", className),
186
186
  "data-sidebar": "rail",
187
187
  "data-slot": "sidebar-rail",
188
188
  onClick: toggleSidebar,
@@ -220,7 +220,7 @@ const SidebarSeparator = ({ className, ...props }) => /* @__PURE__ */ jsx(Separa
220
220
  ...props
221
221
  });
222
222
  const SidebarContent = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
223
- className: cn("flex min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto group-data-[collapsible=icon]:[scrollbar-width:none] group-data-[collapsible=icon]:[&::-webkit-scrollbar]:hidden", className),
223
+ className: cn("flex min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto overscroll-contain group-data-[collapsible=icon]:[scrollbar-width:none] group-data-[collapsible=icon]:[&::-webkit-scrollbar]:hidden", className),
224
224
  "data-sidebar": "content",
225
225
  "data-slot": "sidebar-content",
226
226
  ...props
@@ -233,7 +233,7 @@ const SidebarGroup = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
233
233
  });
234
234
  const SidebarGroupLabel = ({ className, asChild = false, ...props }) => {
235
235
  return /* @__PURE__ */ jsx(asChild ? Slot : "div", {
236
- className: cn("text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "group-data-[collapsible=icon]:hidden", className),
236
+ className: cn("text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-in-out focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "group-data-[collapsible=icon]:hidden", className),
237
237
  "data-sidebar": "group-label",
238
238
  "data-slot": "sidebar-group-label",
239
239
  ...props
@@ -265,7 +265,7 @@ const SidebarMenuItem = ({ className, ...props }) => /* @__PURE__ */ jsx("li", {
265
265
  "data-slot": "sidebar-menu-item",
266
266
  ...props
267
267
  });
268
- const sidebarMenuButtonVariants = cva("peer/menu-button ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-[active=true]:bg-accent data-[active=true]:text-sidebar-accent-foreground data-[active=true]:hover:bg-sidebar-accent data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-start text-sm outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pe-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>span:last-child]:transition-opacity [&>span:last-child]:duration-200 group-data-[collapsible=icon]:[&>span:last-child]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0", {
268
+ const sidebarMenuButtonVariants = cva("peer/menu-button ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-[active=true]:bg-accent data-[active=true]:text-sidebar-accent-foreground data-[active=true]:hover:bg-sidebar-accent data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-start text-sm outline-hidden transition-[width,height,padding] select-none group-has-data-[sidebar=menu-action]/menu-item:pe-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>span:last-child]:transition-opacity [&>span:last-child]:duration-200 group-data-[collapsible=icon]:[&>span:last-child]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0", {
269
269
  variants: {
270
270
  variant: {
271
271
  default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
@@ -0,0 +1,17 @@
1
+ //#region src/components/status-mark.d.ts
2
+ type StatusMarkStatus = "pending" | "running" | "done" | "failed";
3
+ type StatusMarkProps = {
4
+ status: StatusMarkStatus;
5
+ /** Accessible name; omit when adjacent text already states the status. */
6
+ label?: string | undefined;
7
+ className?: string;
8
+ };
9
+ /**
10
+ * One glyph for a unit of work across its life: dashes while it waits, a
11
+ * turning arc while it runs, then a closed ring with a check or a cross
12
+ * drawn in. Monochrome by design: it takes the text colour, and only a
13
+ * failure eases to destructive.
14
+ */
15
+ declare const StatusMark: ({ className, label, status }: StatusMarkProps) => import("react").JSX.Element;
16
+ //#endregion
17
+ export { StatusMark, type StatusMarkStatus };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/components/status-mark.tsx
5
+ const RING_RADIUS = 6.25;
6
+ const CHECK_PATH = "M5.4 8.3 7.2 10.1 10.6 6.3";
7
+ const CROSS_PATH = "M6.1 6.1 9.9 9.9M9.9 6.1 6.1 9.9";
8
+ const on = {
9
+ active: "group-data-[status=running]/status:opacity-100 group-data-[status=done]/status:opacity-50 group-data-[status=failed]/status:opacity-60",
10
+ settled: "group-data-[status=done]/status:[stroke-dasharray:100_0] group-data-[status=failed]/status:[stroke-dasharray:100_0]"
11
+ };
12
+ const RING_CLASS = cn("opacity-55 [stroke-dasharray:3.75_8.75]", "transition-[stroke-dasharray,opacity] duration-300 ease-in-out motion-reduce:transition-[opacity]", "group-data-[status=running]/status:[stroke-dasharray:30_70]", on.active, on.settled, "motion-reduce:group-data-[status=running]/status:animate-status-breathe");
13
+ const SPIN_CLASS = "origin-center [transform-box:view-box] animate-status-spin [animation-play-state:paused] motion-safe:group-data-[status=running]/status:[animation-play-state:running]";
14
+ const markClass = (status) => cn("opacity-0 [stroke-dasharray:1_2] [stroke-dashoffset:1.05]", "[transition:stroke-dashoffset_150ms_cubic-bezier(0.22,1,0.36,1),opacity_0ms_linear_150ms]", status === "done" ? "group-data-[status=done]/status:opacity-100 group-data-[status=done]/status:[stroke-dashoffset:0] group-data-[status=done]/status:[transition:stroke-dashoffset_300ms_cubic-bezier(0.22,1,0.36,1)_140ms,opacity_0ms_linear_140ms]" : "group-data-[status=failed]/status:opacity-100 group-data-[status=failed]/status:[stroke-dashoffset:0] group-data-[status=failed]/status:[transition:stroke-dashoffset_300ms_cubic-bezier(0.22,1,0.36,1)_140ms,opacity_0ms_linear_140ms]", "motion-reduce:[stroke-dashoffset:0] motion-reduce:[transition:opacity_200ms_ease]");
15
+ /**
16
+ * One glyph for a unit of work across its life: dashes while it waits, a
17
+ * turning arc while it runs, then a closed ring with a check or a cross
18
+ * drawn in. Monochrome by design: it takes the text colour, and only a
19
+ * failure eases to destructive.
20
+ */
21
+ const StatusMark = ({ className, label, status }) => /* @__PURE__ */ jsxs("svg", {
22
+ className: cn("group/status size-4 shrink-0 transition-[color] duration-200", status === "failed" && "text-destructive", className),
23
+ "data-slot": "status-mark",
24
+ "data-status": status,
25
+ fill: "none",
26
+ stroke: "currentColor",
27
+ strokeLinecap: "round",
28
+ strokeLinejoin: "round",
29
+ strokeWidth: 1.5,
30
+ viewBox: "0 0 16 16",
31
+ ...label === void 0 ? { "aria-hidden": true } : {
32
+ "aria-label": label,
33
+ role: "img"
34
+ },
35
+ children: [
36
+ /* @__PURE__ */ jsx("circle", {
37
+ className: "opacity-0 transition-opacity duration-200 group-data-[status=done]/status:opacity-[0.06] group-data-[status=failed]/status:opacity-[0.08]",
38
+ cx: "8",
39
+ cy: "8",
40
+ fill: "currentColor",
41
+ r: RING_RADIUS,
42
+ stroke: "none"
43
+ }),
44
+ /* @__PURE__ */ jsx("circle", {
45
+ className: "opacity-0 transition-opacity duration-200 group-data-[status=running]/status:opacity-20",
46
+ cx: "8",
47
+ cy: "8",
48
+ r: RING_RADIUS
49
+ }),
50
+ /* @__PURE__ */ jsx("g", {
51
+ className: SPIN_CLASS,
52
+ children: /* @__PURE__ */ jsx("circle", {
53
+ className: RING_CLASS,
54
+ cx: "8",
55
+ cy: "8",
56
+ pathLength: 100,
57
+ r: RING_RADIUS,
58
+ transform: "rotate(-90 8 8)"
59
+ })
60
+ }),
61
+ /* @__PURE__ */ jsx("path", {
62
+ className: markClass("done"),
63
+ d: CHECK_PATH,
64
+ pathLength: 1
65
+ }),
66
+ /* @__PURE__ */ jsx("path", {
67
+ className: markClass("failed"),
68
+ d: CROSS_PATH,
69
+ pathLength: 1
70
+ })
71
+ ]
72
+ });
73
+ //#endregion
74
+ export { StatusMark };
@@ -0,0 +1,16 @@
1
+ //#region src/components/swap-text.d.ts
2
+ /**
3
+ * A short label that changes in place (a live status line): the old text
4
+ * lifts out with a slight blur while the new one rises into the same cell,
5
+ * so the change reads as one motion instead of a blink. Both layers share a
6
+ * grid cell, so nothing reflows mid-swap. The first render is still, and
7
+ * under reduced motion the label simply changes.
8
+ */
9
+ declare const SwapText: ({ className, text }: {
10
+ /** Applied to the text inside each layer, so a text effect with its own
11
+ * animation (a shimmer) runs alongside the swap instead of replacing it. */
12
+ className?: string;
13
+ text: string;
14
+ }) => import("react").JSX.Element;
15
+ //#endregion
16
+ export { SwapText };