@ciridae/ui 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/chat/conversation-source.d.ts +15 -1
  2. package/dist/chat/describe-tool-call.d.ts +3 -3
  3. package/dist/chat/describe-tool-call.js +3 -3
  4. package/dist/chat/message-text.js +2 -2
  5. package/dist/ciridae.css +16 -4
  6. package/dist/components/activity-feed.d.ts +1 -0
  7. package/dist/components/activity-feed.js +1 -0
  8. package/dist/components/app-frame.d.ts +13 -1
  9. package/dist/components/app-frame.js +45 -3
  10. package/dist/components/app-navigation.d.ts +28 -1
  11. package/dist/components/app-navigation.js +110 -14
  12. package/dist/components/badge.d.ts +29 -4
  13. package/dist/components/badge.js +134 -15
  14. package/dist/components/bulk-action-bar.js +3 -3
  15. package/dist/components/button.d.ts +10 -3
  16. package/dist/components/button.js +77 -44
  17. package/dist/components/calendar.js +44 -17
  18. package/dist/components/centered-page.js +2 -2
  19. package/dist/components/chart.d.ts +1 -1
  20. package/dist/components/chart.js +16 -11
  21. package/dist/components/chat-client-source.js +47 -6
  22. package/dist/components/chat-sheet-controller.d.ts +1 -0
  23. package/dist/components/chat-sheet-controller.js +1 -0
  24. package/dist/components/chat-sheet.d.ts +10 -2
  25. package/dist/components/chat-sheet.js +74 -37
  26. package/dist/components/chat.d.ts +90 -14
  27. package/dist/components/chat.js +401 -101
  28. package/dist/components/checkbox-item.d.ts +5 -2
  29. package/dist/components/checkbox-item.js +9 -5
  30. package/dist/components/chip.d.ts +5 -0
  31. package/dist/components/chip.js +27 -1
  32. package/dist/components/column-settings.js +1 -1
  33. package/dist/components/command-menu.d.ts +5 -1
  34. package/dist/components/command-menu.js +98 -32
  35. package/dist/components/command-palette.d.ts +6 -2
  36. package/dist/components/command-palette.js +20 -10
  37. package/dist/components/data-display.d.ts +35 -4
  38. package/dist/components/data-display.js +138 -19
  39. package/dist/components/data-grid.d.ts +14 -2
  40. package/dist/components/data-grid.js +61 -26
  41. package/dist/components/date-range-picker.js +9 -20
  42. package/dist/components/drawer.d.ts +5 -0
  43. package/dist/components/drawer.js +91 -54
  44. package/dist/components/editable-field.d.ts +17 -3
  45. package/dist/components/editable-field.js +57 -27
  46. package/dist/components/email-input.js +6 -5
  47. package/dist/components/environment-frame.d.ts +1 -1
  48. package/dist/components/environment-frame.js +37 -6
  49. package/dist/components/filter-bar.js +39 -28
  50. package/dist/components/form-controls.d.ts +9 -0
  51. package/dist/components/form-controls.js +46 -22
  52. package/dist/components/form-layout.d.ts +7 -3
  53. package/dist/components/form-layout.js +68 -23
  54. package/dist/components/glass-menu.d.ts +3 -0
  55. package/dist/components/glass-menu.js +63 -0
  56. package/dist/components/gradient-edge.d.ts +9 -0
  57. package/dist/components/gradient-edge.js +31 -0
  58. package/dist/components/input.d.ts +4 -2
  59. package/dist/components/input.js +26 -15
  60. package/dist/components/keycap.d.ts +9 -5
  61. package/dist/components/keycap.js +57 -19
  62. package/dist/components/lightbox.js +2 -1
  63. package/dist/components/locale.d.ts +10 -0
  64. package/dist/components/locale.js +18 -2
  65. package/dist/components/log-entry.d.ts +40 -0
  66. package/dist/components/log-entry.js +146 -0
  67. package/dist/components/mention-input.js +4 -3
  68. package/dist/components/menu.d.ts +6 -2
  69. package/dist/components/menu.js +34 -37
  70. package/dist/components/meter.d.ts +9 -1
  71. package/dist/components/meter.js +64 -6
  72. package/dist/components/navigation.d.ts +21 -4
  73. package/dist/components/navigation.js +84 -19
  74. package/dist/components/notification-tray.js +5 -4
  75. package/dist/components/overlays.d.ts +3 -1
  76. package/dist/components/overlays.js +69 -40
  77. package/dist/components/page-frame.js +1 -1
  78. package/dist/components/panel.d.ts +1 -1
  79. package/dist/components/panel.js +18 -12
  80. package/dist/components/radio-card.js +10 -9
  81. package/dist/components/record.d.ts +25 -1
  82. package/dist/components/record.js +147 -18
  83. package/dist/components/segmented-bar.d.ts +3 -1
  84. package/dist/components/segmented-bar.js +16 -6
  85. package/dist/components/select.d.ts +5 -2
  86. package/dist/components/select.js +103 -127
  87. package/dist/components/shell-skeleton.js +1 -1
  88. package/dist/components/shell.d.ts +2 -2
  89. package/dist/components/shell.js +43 -45
  90. package/dist/components/sidebar.d.ts +7 -2
  91. package/dist/components/sidebar.js +55 -27
  92. package/dist/components/single-line-text.d.ts +2 -0
  93. package/dist/components/single-line-text.js +16 -0
  94. package/dist/components/site-banner.d.ts +5 -2
  95. package/dist/components/site-banner.js +17 -6
  96. package/dist/components/skeleton.d.ts +7 -6
  97. package/dist/components/skeleton.js +28 -16
  98. package/dist/components/skeletons.d.ts +12 -0
  99. package/dist/components/skeletons.js +71 -8
  100. package/dist/components/stages.d.ts +78 -6
  101. package/dist/components/stages.js +512 -156
  102. package/dist/components/stat-card.d.ts +10 -2
  103. package/dist/components/stat-card.js +92 -37
  104. package/dist/components/status-banner.js +1 -1
  105. package/dist/components/tabs.js +26 -13
  106. package/dist/components/theme-toggle.d.ts +4 -0
  107. package/dist/components/theme-toggle.js +67 -0
  108. package/dist/components/toast.d.ts +9 -2
  109. package/dist/components/toast.js +78 -29
  110. package/dist/preset.js +291 -33
  111. package/dist/styled-system/styles.css +3107 -1039
  112. package/dist/styled-system/tokens/index.mjs +602 -22
  113. package/dist/styled-system/tokens/tokens.d.ts +11 -11
  114. package/dist/styled-system/types/prop-type.d.ts +2 -2
  115. package/dist/styles.css +16 -4
  116. package/dist/theme/color.d.ts +3 -0
  117. package/dist/theme/color.js +5 -0
  118. package/dist/theme/derive.d.ts +9 -2
  119. package/dist/theme/derive.js +72 -12
  120. package/dist/theme/palettes.d.ts +49 -6
  121. package/dist/theme/palettes.js +84 -15
  122. package/dist/theme/tokens.d.ts +3 -3
  123. package/dist/theme/tokens.js +108 -32
  124. package/package.json +9 -1
@@ -6,8 +6,9 @@ import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
6
6
  import { XIcon } from "@phosphor-icons/react";
7
7
  import { useCallback, useRef, useState, } from "react";
8
8
  import { css, cx } from "#primitives/css";
9
- import { Button, IconButton } from "./button.js";
9
+ import { Button, ButtonSizeContext, IconButton } from "./button.js";
10
10
  import { DialogHeightResetContext } from "./dialog-height-context.js";
11
+ import { glassMenuSurface } from "./glass-menu.js";
11
12
  import { HotkeyLayer } from "./hotkeys.js";
12
13
  import { icon } from "./icon.js";
13
14
  import { heading } from "./layout.js";
@@ -15,18 +16,11 @@ import { useCiridaeLocale } from "./locale.js";
15
16
  /** A positioned popup carries its own stacking order; a positioner-anchored one gets it there. */
16
17
  const positioner = css({ zIndex: "popover" });
17
18
  const tooltipPositioner = css({ zIndex: "tooltip" });
18
- const popupStyle = css.raw({
19
+ // A popover is the menus' glass sheet, padded for a small form. A popup takes focus when it
20
+ // opens or when its focused content unmounts (a wizard changing step); the sheet draws no ring.
21
+ const popupStyle = css.raw(glassMenuSurface, {
19
22
  maxWidth: "overlay.dialog",
20
23
  padding: "panel.comfortable",
21
- border: "1px solid token(colors.border.control)",
22
- borderRadius: "inset",
23
- backgroundColor: "bg.card",
24
- color: "fg.primary",
25
- textStyle: "body",
26
- boxShadow: "card",
27
- // A popup takes focus when it opens or when its focused content unmounts (a wizard changing
28
- // step); it is a container, not a control, so it draws no ring.
29
- outline: "none",
30
24
  });
31
25
  const popup = css(popupStyle);
32
26
  // The title and footer stay put; a long body scrolls between them.
@@ -38,17 +32,27 @@ const dialogLayout = css.raw({
38
32
  // A dialog's own inset, not the page margin, so page density changes leave it alone.
39
33
  padding: "panel.comfortable",
40
34
  });
41
- const dialogPopup = css.raw(popupStyle, dialogLayout, {
35
+ /*
36
+ * A dialog is a glass shell, 4px round its sheet, holding the sheet itself. `--dialog-sheet` is
37
+ * the sheet's width; the shell adds its inset and edge. Dark blurs the shell and light the sheet.
38
+ */
39
+ const dialogShell = css.raw({
40
+ "--dialog-sheet": "token(sizes.overlay.dialog)",
42
41
  zIndex: "dialog",
43
42
  position: "fixed",
44
43
  top: "50%",
45
44
  left: "50%",
45
+ display: "grid",
46
+ gridTemplateRows: "minmax(0, 1fr)",
46
47
  width: "calc(100vw - token(spacing.layout.pageGap) * 2)",
48
+ maxWidth: "calc(var(--dialog-sheet) + 2 * (token(spacing.layout.tightGap) + token(sizes.border.hairline)))",
47
49
  maxHeight: "calc(100dvh - token(spacing.layout.pageGap) * 2)",
48
- border: "1px solid token(colors.border.card)",
49
- borderRadius: "inset",
50
- background: "bg.surface",
51
- boxShadow: "panel",
50
+ padding: "layout.tightGap",
51
+ border: "1px solid token(colors.border.control)",
52
+ borderRadius: "shell",
53
+ background: "bg.modalShell",
54
+ backdropFilter: "blur(token(blurs.modalShell))",
55
+ outline: "none",
52
56
  transform: "translate(-50%, -50%)",
53
57
  transitionProperty: "opacity, transform",
54
58
  transitionDuration: "fast",
@@ -58,6 +62,17 @@ const dialogPopup = css.raw(popupStyle, dialogLayout, {
58
62
  transform: "translate(-50%, calc(-50% + token(spacing.control.block)))",
59
63
  },
60
64
  });
65
+ const dialogSheetStyle = css.raw(dialogLayout, {
66
+ minHeight: "0",
67
+ border: "1px solid token(colors.border.control)",
68
+ borderRadius: "inset",
69
+ background: "bg.modalSheet",
70
+ backdropFilter: "blur(token(blurs.modalSheet))",
71
+ boxShadow: "soft",
72
+ color: "fg.primary",
73
+ textStyle: "body",
74
+ });
75
+ const dialogSheet = css(dialogSheetStyle);
61
76
  const visuallyHidden = css({ srOnly: true });
62
77
  const backdrop = css({
63
78
  position: "fixed",
@@ -77,8 +92,10 @@ const dialogTitle = cx(heading(), css({ display: "flex", alignItems: "center", g
77
92
  const dialogDescription = css({
78
93
  margin: "none",
79
94
  color: "fg.muted",
95
+ _light: { color: "fg.secondary" },
80
96
  fontSize: "body",
81
- lineHeight: "compact",
97
+ fontWeight: "medium",
98
+ lineHeight: "body",
82
99
  });
83
100
  // Bleeds past its edges by a control's focus ring, so scrolling does not clip the ring.
84
101
  const dialogBody = css({
@@ -98,16 +115,18 @@ const dialogLeadingActions = css({
98
115
  gap: "layout.controlGap",
99
116
  marginInlineEnd: "auto",
100
117
  });
118
+ // Under a divider, its buttons at the default height and close together.
101
119
  const dialogFooter = css({
102
120
  display: "flex",
103
121
  justifyContent: "flex-end",
104
- gap: "layout.controlGap",
105
- paddingTop: "layout.controlGap",
122
+ gap: "layout.tightGap",
123
+ paddingTop: "layout.clusterGap",
124
+ borderTop: "1px solid token(colors.border.divider)",
106
125
  });
107
- const narrowDialogPopup = css.raw({ maxWidth: "card.centeredForm" });
108
- const wideDialogPopup = css.raw({ maxWidth: "overlay.dialogWide" });
126
+ const narrowDialogShell = css.raw({ "--dialog-sheet": "token(sizes.card.centeredForm)" });
127
+ const wideDialogShell = css.raw({ "--dialog-sheet": "token(sizes.overlay.dialogWide)" });
109
128
  // Beside the dialog's own column, flush to its edges; below it on a narrow screen.
110
- const splitDialogPopup = css.raw({
129
+ const splitDialogSheet = css.raw({
111
130
  padding: "none",
112
131
  gap: "none",
113
132
  gridTemplateRows: "auto auto",
@@ -163,13 +182,13 @@ export function Dialog({ trigger, finalFocus, title, titleVisibility = "visible"
163
182
  const dismissInHeader = dismiss === "header";
164
183
  const showCancel = !dismissInHeader;
165
184
  const dismissalDisabled = !closable || pending;
166
- const dialogContent = (_jsxs(_Fragment, { children: [_jsxs("div", { className: dismissInHeader ? dialogHeaderRow : undefined, children: [_jsxs("div", { className: dialogHeader, children: [titleVisibility === "hidden" ? (_jsx(BaseDialog.Title, { className: visuallyHidden, children: title })) : (_jsxs(BaseDialog.Title, { className: dialogTitle, children: [titleIcon, title] })), description && (_jsx(BaseDialog.Description, { className: dialogDescription, children: description }))] }), dismissInHeader && closable && (_jsx(BaseDialog.Close, { disabled: pending, render: _jsx(IconButton, { "aria-label": closeLabel, size: "compact", variant: "ghost" }), children: _jsx(XIcon, { className: icon({ size: "small" }) }) }))] }), _jsx("div", { className: dialogBody, children: _jsx(DialogHeightResetContext, { value: holdHeight ? resetHeight : null, children: children }) }), ((showCancel && closable) || actions || leadingActions) && (_jsxs("div", { className: dialogFooter, children: [leadingActions && _jsx("div", { className: dialogLeadingActions, children: leadingActions }), showCancel && closable && (_jsx(BaseDialog.Close, { disabled: pending, render: _jsx(Button, { variant: "secondary" }), children: dismissInHeader ? labels.common.cancel : closeLabel })), actions] }))] }));
185
+ const dialogContent = (_jsxs(_Fragment, { children: [_jsxs("div", { className: dismissInHeader ? dialogHeaderRow : undefined, children: [_jsxs("div", { className: dialogHeader, children: [titleVisibility === "hidden" ? (_jsx(BaseDialog.Title, { className: visuallyHidden, children: title })) : (_jsxs(BaseDialog.Title, { className: dialogTitle, children: [titleIcon, title] })), description && (_jsx(BaseDialog.Description, { className: dialogDescription, children: description }))] }), dismissInHeader && closable && (_jsx(BaseDialog.Close, { disabled: pending, render: _jsx(IconButton, { "aria-label": closeLabel, size: "compact", variant: "ghost" }), children: _jsx(XIcon, { className: icon({ size: "small" }) }) }))] }), _jsx("div", { className: dialogBody, children: _jsx(DialogHeightResetContext, { value: holdHeight ? resetHeight : null, children: children }) }), ((showCancel && closable) || actions || leadingActions) && (_jsx("div", { className: dialogFooter, children: _jsxs(ButtonSizeContext, { value: "medium", children: [leadingActions && _jsx("div", { className: dialogLeadingActions, children: leadingActions }), showCancel && closable && (_jsx(BaseDialog.Close, { disabled: pending, render: _jsx(Button, { variant: "secondary" }), children: dismissInHeader ? labels.common.cancel : closeLabel })), actions] }) }))] }));
167
186
  return (_jsxs(BaseDialog.Root, { ...rootProps, onOpenChange: (open, details) => {
168
187
  if (!open && (dismissalDisabled || (dirty && details.reason !== "close-press"))) {
169
188
  return details.cancel();
170
189
  }
171
190
  onOpenChange?.(open, details);
172
- }, children: [trigger && _jsx(BaseDialog.Trigger, { render: trigger }), _jsx(BaseDialog.Portal, { children: _jsxs(HotkeyLayer, { children: [_jsx(BaseDialog.Backdrop, { "data-ciridae-ui": "", className: backdrop }), _jsx(BaseDialog.Popup, { finalFocus: finalFocus, ref: holdHeight ? holdTallestHeight : undefined, className: css(dialogPopup, size === "narrow" && aside === undefined && narrowDialogPopup, (size === "wide" || aside !== undefined) && wideDialogPopup, aside !== undefined && splitDialogPopup), "data-ciridae-ui": "", children: aside ? (_jsxs(_Fragment, { children: [_jsx("div", { className: splitDialogMain, children: dialogContent }), _jsx("aside", { className: splitDialogAside, "aria-label": aside.label, children: aside.content })] })) : (dialogContent) })] }) })] }));
191
+ }, children: [trigger && _jsx(BaseDialog.Trigger, { render: trigger }), _jsx(BaseDialog.Portal, { children: _jsxs(HotkeyLayer, { children: [_jsx(BaseDialog.Backdrop, { "data-ciridae-ui": "", className: backdrop }), _jsx(BaseDialog.Popup, { finalFocus: finalFocus, ref: holdHeight ? holdTallestHeight : undefined, className: css(dialogShell, size === "narrow" && aside === undefined && narrowDialogShell, (size === "wide" || aside !== undefined) && wideDialogShell), "data-ciridae-ui": "", children: aside ? (_jsxs("div", { className: css(dialogSheetStyle, splitDialogSheet), children: [_jsx("div", { className: splitDialogMain, children: dialogContent }), _jsx("aside", { className: splitDialogAside, "aria-label": aside.label, children: aside.content })] })) : (_jsx("div", { className: dialogSheet, children: dialogContent })) })] }) })] }));
173
192
  }
174
193
  /**
175
194
  * A confirmation the reader must answer. Unlike `Dialog` it carries the
@@ -207,13 +226,13 @@ export function AlertDialog({ trigger, title, children, confirmLabel, cancelLabe
207
226
  if (!open && pendingRef.current)
208
227
  return details.cancel();
209
228
  onOpenChange?.(open, details);
210
- }, children: [trigger && _jsx(BaseAlertDialog.Trigger, { render: trigger }), _jsx(BaseAlertDialog.Portal, { children: _jsxs(HotkeyLayer, { children: [_jsx(BaseAlertDialog.Backdrop, { "data-ciridae-ui": "", className: backdrop }), _jsxs(BaseAlertDialog.Popup, { className: css(dialogPopup), "data-ciridae-ui": "", children: [_jsx(BaseAlertDialog.Title, { className: dialogTitle, children: title }), children && _jsx("div", { className: dialogBody, children: children }), _jsxs("div", { className: dialogFooter, children: [_jsx(BaseAlertDialog.Close, { render: _jsx(Button, { variant: "secondary" }), disabled: pending, children: cancelLabel ?? labels.common.cancel }), _jsx(Button, { variant: variant === "danger" ? "danger" : "primary", loading: pending, onClick: () => void confirm(), children: confirmLabel })] })] })] }) })] }));
229
+ }, children: [trigger && _jsx(BaseAlertDialog.Trigger, { render: trigger }), _jsx(BaseAlertDialog.Portal, { children: _jsxs(HotkeyLayer, { children: [_jsx(BaseAlertDialog.Backdrop, { "data-ciridae-ui": "", className: backdrop }), _jsx(BaseAlertDialog.Popup, { className: css(dialogShell), "data-ciridae-ui": "", children: _jsxs("div", { className: dialogSheet, children: [_jsx(BaseAlertDialog.Title, { className: dialogTitle, children: title }), children && _jsx("div", { className: dialogBody, children: children }), _jsxs("div", { className: dialogFooter, children: [_jsx(BaseAlertDialog.Close, { render: _jsx(Button, { variant: "secondary", size: "medium" }), disabled: pending, children: cancelLabel ?? labels.common.cancel }), _jsx(Button, { variant: variant === "danger" ? "danger" : "primary", size: "medium", loading: pending, onClick: () => void confirm(), children: confirmLabel })] })] }) })] }) })] }));
211
230
  }
212
231
  // Merged rather than layered, so the density's inset replaces the popup's.
213
232
  const popoverPopups = {
214
233
  regular: popup,
215
- compact: css(popupStyle, { padding: "menu.inset", borderRadius: "action" }),
216
- flush: css(popupStyle, { padding: "none", overflow: "hidden", borderRadius: "action" }),
234
+ compact: css(popupStyle, { padding: "menu.inset" }),
235
+ flush: css(popupStyle, { padding: "none", overflow: "hidden" }),
217
236
  };
218
237
  const popoverWidths = {
219
238
  narrow: css({ width: "overlay.popoverNarrow" }),
@@ -223,18 +242,28 @@ const popoverWidths = {
223
242
  const hoverOpenDelayMs = 300;
224
243
  const hoverCloseDelayMs = 150;
225
244
  export function Popover({ trigger, anchor, children, density = "regular", width, align, openOnHover = false, ...rootProps }) {
226
- return (_jsxs(BasePopover.Root, { ...rootProps, children: [trigger && (_jsx(BasePopover.Trigger, { render: trigger, openOnHover: openOnHover, delay: hoverOpenDelayMs, closeDelay: hoverCloseDelayMs })), _jsx(BasePopover.Portal, { children: _jsx(HotkeyLayer, { enabled: !openOnHover, children: _jsx(BasePopover.Positioner, { className: positioner, sideOffset: 6, align: align, anchor: anchor, children: _jsx(BasePopover.Popup, { className: cx(popoverPopups[density], width && popoverWidths[width]), "data-ciridae-ui": "", initialFocus: openOnHover ? (openType) => openType === "keyboard" : undefined, children: children }) }) }) })] }));
245
+ return (_jsxs(BasePopover.Root, { ...rootProps, children: [trigger && (_jsx(BasePopover.Trigger, { render: trigger, openOnHover: openOnHover, delay: hoverOpenDelayMs, closeDelay: hoverCloseDelayMs })), _jsx(BasePopover.Portal, { children: _jsx(HotkeyLayer, { enabled: !openOnHover, children: _jsx(BasePopover.Positioner, { className: positioner, sideOffset: 4, align: align, anchor: anchor, children: _jsx(BasePopover.Popup, { className: cx(popoverPopups[density], width && popoverWidths[width]), "data-ciridae-ui": "", initialFocus: openOnHover ? (openType) => openType === "keyboard" : undefined, children: children }) }) }) })] }));
227
246
  }
228
- export function Tooltip({ children, content, ...rootProps }) {
229
- return (_jsx(BaseTooltip.Provider, { children: _jsxs(BaseTooltip.Root, { ...rootProps, children: [_jsx(BaseTooltip.Trigger, { render: children }), _jsx(BaseTooltip.Portal, { children: _jsx(BaseTooltip.Positioner, { className: tooltipPositioner, sideOffset: 6, children: _jsx(BaseTooltip.Popup, { className: css({
230
- zIndex: "tooltip",
231
- paddingBlock: "control.compactBlock",
232
- paddingInline: "menu.itemInline",
233
- borderRadius: "action",
234
- background: "fg.primary",
235
- color: "bg.canvas",
236
- fontSize: "caption",
237
- maxWidth: "overlay.tooltip",
238
- textWrap: "pretty",
239
- }), "data-ciridae-ui": "", children: content }) }) })] }) }));
247
+ /* An inverted chip: the primary face, ink in light and white in dark, with the text in the canvas's. */
248
+ const tooltipPopup = css({
249
+ zIndex: "tooltip",
250
+ display: "flex",
251
+ alignItems: "baseline",
252
+ gap: "layout.controlGap",
253
+ paddingBlock: "control.compactBlock",
254
+ paddingInline: "menu.itemInline",
255
+ borderRadius: "keycap",
256
+ background: "fg.primary",
257
+ color: "bg.canvas",
258
+ _light: { background: "token(gradients.primary)", color: "fg.onAccent" },
259
+ fontSize: "code",
260
+ fontWeight: "medium",
261
+ lineHeight: "tight",
262
+ letterSpacing: "compact",
263
+ maxWidth: "overlay.tooltip",
264
+ textWrap: "pretty",
265
+ });
266
+ const tooltipMeta = css({ flexShrink: "0", opacity: "0.6" });
267
+ export function Tooltip({ children, content, meta, ...rootProps }) {
268
+ return (_jsx(BaseTooltip.Provider, { children: _jsxs(BaseTooltip.Root, { ...rootProps, children: [_jsx(BaseTooltip.Trigger, { render: children }), _jsx(BaseTooltip.Portal, { children: _jsx(BaseTooltip.Positioner, { className: tooltipPositioner, sideOffset: 8, children: _jsxs(BaseTooltip.Popup, { className: tooltipPopup, "data-ciridae-ui": "", children: [content, meta !== undefined && _jsx("span", { className: tooltipMeta, children: meta })] }) }) })] }) }));
240
269
  }
@@ -61,7 +61,7 @@ const metaLine = css({
61
61
  alignItems: "center",
62
62
  flexWrap: "wrap",
63
63
  gap: "layout.controlGap",
64
- color: "fg.muted",
64
+ color: "fg.label",
65
65
  fontSize: "code",
66
66
  fontWeight: "medium",
67
67
  letterSpacing: "compact",
@@ -13,7 +13,7 @@ export type PanelProps = useRender.ComponentProps<"section"> & {
13
13
  export declare function Panel({ padding, tone, render, className, ...props }: PanelProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
14
14
  export type PanelHeaderProps = Omit<useRender.ComponentProps<"div">, "title"> & {
15
15
  title: ReactNode;
16
- /** Muted beside the title, such as a count or the period it covers. */
16
+ /** Muted at the row's far edge, before the actions, such as a count or the period it covers. */
17
17
  meta?: ReactNode;
18
18
  /** At the row's end, such as a link to the full list. */
19
19
  actions?: ReactNode;
@@ -9,13 +9,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
9
9
  */
10
10
  import { useRender } from "@base-ui/react/use-render";
11
11
  import { css, cva, cx } from "#primitives/css";
12
+ import { singleLineText } from "./single-line-text.js";
12
13
  /** The card's surface as a style object, to merge into a `css()` call that adds layout. */
13
14
  export const cardSurface = css.raw({
14
15
  minWidth: "0",
15
- border: "1px solid token(colors.border.panel)",
16
+ border: "1px solid token(colors.border.card)",
16
17
  borderRadius: "action",
17
- background: "bg.hover",
18
- _light: { background: "bg.card" },
18
+ background: "token(gradients.card)",
19
19
  });
20
20
  const card = css(cardSurface);
21
21
  const panel = cva({
@@ -39,7 +39,7 @@ const panel = cva({
39
39
  default: { gap: "layout.contentGap", padding: "panel.default" },
40
40
  },
41
41
  tone: {
42
- panel: { borderColor: "border.panel", background: "bg.panel" },
42
+ panel: { borderColor: "border.panel", background: "token(gradients.panel)" },
43
43
  outline: { borderColor: "border.control" },
44
44
  },
45
45
  },
@@ -55,19 +55,25 @@ const panelHeader = css({
55
55
  paddingInline: "panel.comfortable",
56
56
  fontSize: "body",
57
57
  fontWeight: "medium",
58
- // In a padded panel the header is one more row inside the padding.
59
- "[data-panel-padding=default] > &": { height: "auto", paddingInline: "none" },
58
+ lineHeight: "tight",
59
+ /*
60
+ * In a padded panel the header is one more row inside the padding, inset from what follows by
61
+ * the design's 16px: its own padding, less the panel's gap, which it takes back.
62
+ */
63
+ "[data-panel-padding=default] > &": {
64
+ height: "auto",
65
+ gap: "layout.controlGap",
66
+ paddingInline: "none",
67
+ paddingBottom: "panel.comfortable",
68
+ marginBottom: "calc(token(spacing.layout.contentGap) * -1)",
69
+ },
60
70
  });
61
- const panelHeaderTitle = css({
71
+ const panelHeaderTitle = css(singleLineText, {
62
72
  display: "flex",
63
73
  alignItems: "center",
64
74
  gap: "control.iconGap",
65
- minWidth: "0",
66
75
  margin: "none",
67
76
  font: "inherit",
68
- overflow: "hidden",
69
- textOverflow: "ellipsis",
70
- whiteSpace: "nowrap",
71
77
  });
72
78
  const panelHeaderMeta = css({ color: "fg.subtle", fontSize: "code" });
73
79
  const panelHeaderActions = css({ display: "flex", alignItems: "center", gap: "layout.tightGap" });
@@ -101,7 +107,7 @@ export function PanelHeader({ title, meta, actions, level, render, className, ..
101
107
  className: cx(panelHeader, className),
102
108
  "data-ciridae-ui": "",
103
109
  "data-panel-header": "",
104
- children: (_jsxs(_Fragment, { children: [title !== undefined && title !== null && (_jsxs(Title, { className: panelHeaderTitle, children: [title, meta !== undefined && _jsx("span", { className: panelHeaderMeta, children: meta })] })), actions !== undefined && _jsx("span", { className: panelHeaderActions, children: actions })] })),
110
+ children: (_jsxs(_Fragment, { children: [title !== undefined && title !== null && (_jsx(Title, { className: panelHeaderTitle, children: title })), (meta !== undefined || actions !== undefined) && (_jsxs("span", { className: panelHeaderActions, children: [meta !== undefined && _jsx("span", { className: panelHeaderMeta, children: meta }), actions] }))] })),
105
111
  },
106
112
  });
107
113
  }
@@ -2,11 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Radio as BaseRadio } from "@base-ui/react/radio";
3
3
  import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
4
4
  import { css, cx } from "#primitives/css";
5
- import { checkMark, radioDot } from "./form-controls.js";
5
+ import { checkMark, checkMarkEdge, radioDot } from "./form-controls.js";
6
6
  /*
7
7
  * A choice drawn as a card: the radio, a title with its detail, and where the option comes from
8
8
  * at the far edge. The whole card is the radio, so it takes the hover wash, the focus ring, and,
9
- * once chosen, the accent wash under the accent's edge.
9
+ * once chosen, the selected tint under an edge in the text color.
10
10
  */
11
11
  const cards = css({ display: "grid", gap: "layout.controlGap" });
12
12
  const card = css({
@@ -28,10 +28,10 @@ const card = css({
28
28
  cursor: "pointer",
29
29
  transitionProperty: "background, border-color",
30
30
  transitionDuration: "fast",
31
- _hover: { background: "bg.glassHover" },
31
+ _hover: { background: "token(gradients.raisedHover)" },
32
32
  "&[data-checked]": {
33
- background: "bg.selected",
34
- borderColor: "border.selected",
33
+ background: "token(gradients.selected)",
34
+ borderColor: "fg.primary",
35
35
  },
36
36
  _focusVisible: { focusRing: "outer" },
37
37
  "&[data-disabled]": { opacity: "disabled", cursor: "not-allowed" },
@@ -42,10 +42,11 @@ const mark = css({
42
42
  // the mark down.
43
43
  marginTop: "calc((1em - token(sizes.control.radio)) / 2)",
44
44
  "[data-checked] > &": {
45
- background: "bg.primary",
46
- borderColor: "transparent",
45
+ background: "token(gradients.primary)",
46
+ _light: { background: "token(gradients.primary)" },
47
+ _before: { background: "token(gradients.border.primary)" },
47
48
  },
48
- "[data-checked]:hover > &": { background: "bg.primaryHover" },
49
+ "[data-checked]:hover > &": { opacity: "checkedHover", _light: { opacity: "1" } },
49
50
  });
50
51
  const copy = css({ flex: "1", minWidth: "0", display: "grid", gap: "layout.tightGap" });
51
52
  const detailText = css({ color: "fg.muted", fontSize: "code" });
@@ -62,5 +63,5 @@ export function RadioCardGroup({ name, label, defaultValue, value, onValueChange
62
63
  }
63
64
  /** One option of a `RadioCardGroup`. */
64
65
  export function RadioCard({ title, detail, source: sourceContent, className, ...props }) {
65
- return (_jsxs(BaseRadio.Root, { ...props, className: cx(card, className), "data-ciridae-ui": "", children: [_jsx("span", { className: cx(checkMark({ shape: "round", focus: "row" }), mark), children: _jsx(BaseRadio.Indicator, { className: radioDot }) }), _jsxs("span", { className: copy, children: [_jsx("span", { children: title }), detail !== undefined && _jsx("span", { className: detailText, children: detail })] }), sourceContent !== undefined && _jsx("span", { className: source, children: sourceContent })] }));
66
+ return (_jsxs(BaseRadio.Root, { ...props, className: cx(card, className), "data-ciridae-ui": "", children: [_jsx("span", { ...checkMarkEdge, className: cx(checkMark({ shape: "round", focus: "row" }), mark), children: _jsx(BaseRadio.Indicator, { className: radioDot }) }), _jsxs("span", { className: copy, children: [_jsx("span", { children: title }), detail !== undefined && _jsx("span", { className: detailText, children: detail })] }), sourceContent !== undefined && _jsx("span", { className: source, children: sourceContent })] }));
66
67
  }
@@ -48,7 +48,7 @@ export type ItemCardField = {
48
48
  };
49
49
  export type ItemCardProps = Omit<useRender.ComponentProps<"button">, "children" | "onSelect"> & {
50
50
  title: ReactNode;
51
- /** One segment per stage, in its status's color. */
51
+ /** One dot per stage, in its status. */
52
52
  progress?: readonly StepStatus[];
53
53
  fields: readonly ItemCardField[];
54
54
  /** The footer's start: the stage the item is at. */
@@ -64,6 +64,30 @@ export type ItemCardProps = Omit<useRender.ComponentProps<"button">, "children"
64
64
  href?: string;
65
65
  };
66
66
  export declare function ItemCard({ title, progress, fields, status, footer, selected, onSelect, href, render, className, style, ...props }: ItemCardProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
67
+ export type PartRowProps = Omit<ComponentProps<"div">, "children" | "onSelect"> & {
68
+ /** `wide` is one line, as in a table; `compact` stacks for a narrow column. */
69
+ layout?: "wide" | "compact";
70
+ name: ReactNode;
71
+ /** Under the name, such as the part's phase and amount. */
72
+ detail?: ReactNode;
73
+ /** One dot per step, in its status; the kit draws at most four. */
74
+ progress: readonly StepStatus[];
75
+ /** The stage the part is at. */
76
+ stage: ReactNode;
77
+ /** Whose move it is: `yourMove` sets the next step in the ink, the others in grey. */
78
+ state: "yourMove" | "waiting" | "done" | "notStarted";
79
+ /** Before the next step, such as a "Your move" badge. */
80
+ badge?: ReactNode;
81
+ next: ReactNode;
82
+ /** The part's one button, for the reader's move or a nudge while waiting. */
83
+ action?: {
84
+ label: ReactNode;
85
+ onClick: () => void;
86
+ };
87
+ selected?: boolean;
88
+ onSelect?: () => void;
89
+ };
90
+ export declare function PartRow({ layout, name, detail, progress, stage, state, badge, next, action, selected, onSelect, className, ...props }: PartRowProps): import("react").JSX.Element;
67
91
  export type RelatedListItem = {
68
92
  id: string;
69
93
  title: ReactNode;
@@ -2,13 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  /*
3
3
  * The pieces a record's page is laid out with. `DetailLayout` splits it into the work and a side
4
4
  * column; `PageHeading` (also exported here) is the record's title row; `RelatedTabs` is the tabbed card of what the
5
- * record holds; `ItemCard` is one of a record's parts, such as a shipment or a draw; `RelatedList`
5
+ * record holds; `ItemCard` is one of a record's parts, such as a shipment or a draw, as a card and `PartRow` as
6
+ * a row; `RelatedList`
6
7
  * lists the records and documents it points at.
7
8
  */
8
9
  import { useRender } from "@base-ui/react/use-render";
9
10
  import { CaretRightIcon } from "@phosphor-icons/react";
10
- import { useLayoutEffect, useRef, useState, } from "react";
11
+ import { Fragment, useLayoutEffect, useRef, useState, } from "react";
11
12
  import { css, cx } from "#primitives/css";
13
+ import { Button } from "./button.js";
12
14
  import { useControllableState } from "./controllable-state.js";
13
15
  import { directional, icon } from "./icon.js";
14
16
  import { MutedText } from "./layout.js";
@@ -110,7 +112,10 @@ export function RelatedTabs({ tabs, value, defaultValue, onValueChange, classNam
110
112
  return null;
111
113
  return (_jsxs(TabsRoot, { value: open.value, onValueChange: (next) => setChosen(String(next)), ...props, className: cx(relatedCard, className), "data-ciridae-ui": "", children: [_jsxs("div", { className: relatedHeader, children: [_jsx(TabsList, { variant: "contained", children: tabs.map((tab) => (_jsx(TabsTab, { value: tab.value, count: tab.count, children: tab.label }, tab.value))) }), open.meta !== undefined && _jsx(MutedText, { className: relatedMeta, children: open.meta })] }), tabs.map((tab) => (_jsx(TabsPanel, { value: tab.value, children: tab.content }, tab.value)))] }));
112
114
  }
113
- /* Item card: one of a record's parts, with its progress as a thin segmented bar. */
115
+ /*
116
+ * Item card: one of a record's parts, the kit's Part card, with its progress as a mini strip of
117
+ * step dots. The chosen one lifts onto the solid card inside a green edge.
118
+ */
114
119
  const itemCard = css(cardSurface, {
115
120
  display: "grid",
116
121
  gap: "layout.clusterGap",
@@ -121,7 +126,11 @@ const itemCard = css(cardSurface, {
121
126
  letterSpacing: "compact",
122
127
  textDecoration: "none",
123
128
  "&:is(button, a)": { cursor: "pointer", _hover: { borderColor: "border.glass" } },
124
- "&[data-selected]": { borderColor: "accent.green", _hover: { borderColor: "accent.green" } },
129
+ "&[data-selected]": {
130
+ borderColor: "accent.green",
131
+ boxShadow: "soft",
132
+ _hover: { borderColor: "accent.green" },
133
+ },
125
134
  _focusVisible: { focusRing: "outer" },
126
135
  });
127
136
  const itemHeader = css({
@@ -131,20 +140,27 @@ const itemHeader = css({
131
140
  gap: "layout.controlGap",
132
141
  fontSize: "body",
133
142
  fontWeight: "medium",
143
+ lineHeight: "tight",
134
144
  whiteSpace: "nowrap",
135
145
  });
136
- const progressBar = css({ display: "inline-flex", alignItems: "center", gap: "2px" });
137
- const progressSegment = css({
138
- width: "14px",
139
- height: "4px",
140
- borderRadius: "1px",
141
- background: "bg.fill",
142
- "&[data-status=done]": { background: "accent.green" },
143
- "&[data-status=current]": { background: "accent.yellow" },
144
- "&[data-status=agent]": { background: "accent.blue" },
145
- "&[data-status=blocked]": { background: "accent.red" },
146
- "&[data-status=waiting]": { background: "fg.muted" },
146
+ const progressStrip = css({
147
+ display: "inline-flex",
148
+ flexShrink: "0",
149
+ alignItems: "center",
150
+ gap: "layout.tightGap",
151
+ });
152
+ // 16px between dots, green out of a finished step as the stage strip's connectors are.
153
+ const progressConnector = css({
154
+ width: "token(spacing.layout.clusterGap)",
155
+ height: "border.hairline",
156
+ borderRadius: "round",
157
+ background: "border.divider",
158
+ "&[data-after=done]": { background: "accent.green" },
147
159
  });
160
+ /** A part's progress: one 12px dot a step, joined by 16px connectors. */
161
+ function MiniStrip({ progress }) {
162
+ return (_jsx("span", { className: progressStrip, "aria-hidden": "true", children: progress.map((step, index) => (_jsxs(Fragment, { children: [index > 0 && (_jsx("span", { className: progressConnector, "data-after": progress[index - 1] === "done" ? "done" : undefined })), _jsx(StepDot, { status: step, size: "chip" })] }, index))) }));
163
+ }
148
164
  const itemFields = css({
149
165
  display: "grid",
150
166
  gap: "layout.controlGap",
@@ -155,7 +171,7 @@ const itemFields = css({
155
171
  const itemFieldLabel = css({
156
172
  display: "block",
157
173
  marginBottom: "layout.controlGap",
158
- color: "fg.muted",
174
+ color: "fg.subtle",
159
175
  fontWeight: "regular",
160
176
  letterSpacing: "normal",
161
177
  });
@@ -188,10 +204,117 @@ export function ItemCard({ title, progress, fields, status, footer, selected = f
188
204
  "data-ciridae-ui": "",
189
205
  "data-selected": selected || undefined,
190
206
  "aria-pressed": onSelect !== undefined && href === undefined ? selected : undefined,
191
- children: (_jsxs(_Fragment, { children: [_jsxs("span", { className: itemHeader, children: [title, progress && (_jsx("span", { className: progressBar, "aria-hidden": "true", children: progress.map((segment, index) => (_jsx("span", { className: progressSegment, "data-status": segment }, index))) }))] }), _jsxs("span", { className: css({ display: "grid" }), children: [_jsx("span", { className: itemFields, style: columns, children: fields.map((field, index) => (_jsxs("span", { children: [_jsx("span", { className: itemFieldLabel, children: field.label }), field.value] }, index))) }), _jsxs("span", { className: itemFooter, children: [_jsxs("span", { className: itemStatus, children: [_jsx(StepDot, { status: status.status, size: "small" }), status.label] }), footer] })] })] })),
207
+ children: (_jsxs(_Fragment, { children: [_jsxs("span", { className: itemHeader, children: [title, progress && _jsx(MiniStrip, { progress: progress })] }), _jsxs("span", { className: css({ display: "grid" }), children: [_jsx("span", { className: itemFields, style: columns, children: fields.map((field, index) => (_jsxs("span", { children: [_jsx("span", { className: itemFieldLabel, children: field.label }), field.value] }, index))) }), _jsxs("span", { className: itemFooter, children: [_jsxs("span", { className: itemStatus, children: [_jsx(StepDot, { status: status.status, size: "small" }), status.label] }), footer] })] })] })),
192
208
  },
193
209
  });
194
210
  }
211
+ /*
212
+ * Part row: one of a record's parts as a table-like row, the kit's Part in its Wide and Compact
213
+ * layouts: name over detail, the mini strip, the stage, a badge for the reader's move, the next
214
+ * step and at most one action. Wide keeps it on one 56px line, wrapping the detail and next step
215
+ * only when the row is narrow; compact stacks the name and strip over the rest. The whole row chooses the part through its name; the action stays its own button.
216
+ */
217
+ const partRow = css({
218
+ position: "relative",
219
+ display: "flex",
220
+ alignItems: "center",
221
+ gap: "layout.clusterGap",
222
+ minHeight: "calc(token(sizes.control.compact) + token(spacing.panel.default) * 2)",
223
+ paddingBlock: "panel.default",
224
+ paddingInline: "panel.comfortable",
225
+ borderBottom: "1px solid token(colors.border.divider)",
226
+ fontWeight: "medium",
227
+ letterSpacing: "compact",
228
+ lineHeight: "tight",
229
+ "&:has([data-part-name]:hover)": { background: "bg.rowHover" },
230
+ "&[data-selected]": { background: "token(gradients.selected)" },
231
+ // Wide is a grid, so the strips and stages of a list of parts line up. The name and the next
232
+ // step share what the strip, stage and action leave, two parts to three, close to the kit's
233
+ // 240 and 488 in its 1092 row, so a narrow row narrows both rather than starving one.
234
+ "&[data-layout=wide]": {
235
+ display: "grid",
236
+ gridTemplateColumns: "minmax(0, 2fr) max-content 90px minmax(0, 3fr) max-content",
237
+ },
238
+ "&[data-layout=compact]": {
239
+ flexDirection: "column",
240
+ alignItems: "stretch",
241
+ gap: "layout.tightGap",
242
+ },
243
+ });
244
+ const partLine = css({
245
+ display: "flex",
246
+ alignItems: "center",
247
+ gap: "layout.contentGap",
248
+ minWidth: "0",
249
+ "[data-layout=wide] > &": { display: "contents" },
250
+ });
251
+ const partName = css({
252
+ display: "grid",
253
+ gap: "control.trackInset",
254
+ flex: "1 1 0",
255
+ minWidth: "0",
256
+ textAlign: "start",
257
+ "& > *": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
258
+ });
259
+ // The name is the row's button: its box stretches over the whole row.
260
+ const partNameButton = css({
261
+ padding: "none",
262
+ border: "none",
263
+ background: "transparent",
264
+ color: "fg.primary",
265
+ font: "inherit",
266
+ fontSize: "body",
267
+ textAlign: "start",
268
+ cursor: "pointer",
269
+ _after: { content: '""', position: "absolute", inset: "0" },
270
+ _focusVisible: { outline: "none", _after: { focusRing: "inset" } },
271
+ });
272
+ // A narrow wide row wraps the detail rather than cut off its amount.
273
+ const partDetail = css({
274
+ color: "fg.subtle",
275
+ fontSize: "code",
276
+ "[data-layout=wide] &": { whiteSpace: "normal", lineHeight: "compact" },
277
+ });
278
+ const partStage = css({
279
+ flexShrink: "0",
280
+ color: "fg.subtle",
281
+ fontSize: "code",
282
+ whiteSpace: "nowrap",
283
+ "[data-layout=wide] &": { overflow: "hidden", textOverflow: "ellipsis" },
284
+ });
285
+ const partStatus = css({
286
+ display: "flex",
287
+ alignItems: "center",
288
+ gap: "layout.controlGap",
289
+ flexShrink: "0",
290
+ "[data-layout=wide] > * > &": { display: "contents" },
291
+ });
292
+ // Wide sets the badge after the stage, in the next step's cell; compact leads the status with it.
293
+ const partNextCell = css({
294
+ display: "flex",
295
+ alignItems: "center",
296
+ gap: "layout.clusterGap",
297
+ minWidth: "0",
298
+ "[data-layout=compact] &": { display: "contents" },
299
+ });
300
+ const partBadge = css({
301
+ display: "inline-flex",
302
+ flexShrink: "0",
303
+ "[data-layout=compact] &": { order: "-1" },
304
+ });
305
+ // The step the reader acts on wraps in a narrow row, never cut short.
306
+ const partNext = css({
307
+ minWidth: "0",
308
+ color: "fg.secondary",
309
+ fontSize: "caption",
310
+ lineHeight: "compact",
311
+ "[data-state=yourMove] &": { color: "fg.primary" },
312
+ });
313
+ // The action sits over the stretched name, so it stays its own button.
314
+ const partAction = css({ position: "relative", flexShrink: "0" });
315
+ export function PartRow({ layout = "wide", name, detail, progress, stage, state, badge, next, action, selected = false, onSelect, className, ...props }) {
316
+ return (_jsxs("div", { ...props, className: cx(partRow, className), "data-ciridae-ui": "", "data-layout": layout, "data-state": state, "data-selected": selected || undefined, children: [_jsxs("span", { className: partLine, children: [_jsxs("span", { className: partName, children: [onSelect ? (_jsx("button", { type: "button", className: partNameButton, "data-part-name": "", "aria-pressed": selected, onClick: onSelect, children: name })) : (_jsx("span", { className: css({ fontSize: "body" }), children: name })), layout === "wide" && detail !== undefined && (_jsx("span", { className: partDetail, children: detail }))] }), _jsx(MiniStrip, { progress: progress })] }), _jsxs("span", { className: partLine, children: [layout === "compact" && _jsx("span", { className: cx(partName, partDetail), children: detail }), _jsxs("span", { className: partStatus, children: [_jsx("span", { className: partStage, children: stage }), _jsxs("span", { className: partNextCell, children: [badge !== undefined && _jsx("span", { className: partBadge, children: badge }), _jsx("span", { className: partNext, children: next })] }), action && (_jsx(Button, { variant: "secondary", className: partAction, onClick: action.onClick, children: action.label }))] })] })] }));
317
+ }
195
318
  /* Related list: the records and documents a record points at, grouped by relation. */
196
319
  const groupHeader = css({
197
320
  display: "flex",
@@ -221,7 +344,11 @@ const relatedRow = css({
221
344
  font: "inherit",
222
345
  textAlign: "start",
223
346
  textDecoration: "none",
224
- "&:is(button, a)": { cursor: "pointer", _hover: { background: "bg.rowHover" } },
347
+ // The kit's Linked record hovers to the selected glass in dark and the row wash in light.
348
+ "&:is(button, a)": {
349
+ cursor: "pointer",
350
+ _hover: { background: "token(gradients.selected)", _light: { background: "bg.rowHover" } },
351
+ },
225
352
  _focusVisible: { focusRing: "outer" },
226
353
  });
227
354
  const tile = css({
@@ -234,6 +361,7 @@ const tile = css({
234
361
  borderRadius: "action",
235
362
  background: "bg.panel",
236
363
  color: "fg.secondary",
364
+ _light: { background: "bg.field" },
237
365
  "& svg": { fontSize: "icon.small" },
238
366
  });
239
367
  const rowTitle = css({
@@ -251,6 +379,7 @@ const rowMeta = css({
251
379
  color: "fg.secondary",
252
380
  fontSize: "code",
253
381
  fontWeight: "medium",
382
+ lineHeight: "tight",
254
383
  "& [data-separator]": { color: "fg.muted" },
255
384
  "& [data-tone=info]": { color: "accent.blue" },
256
385
  "& [data-tone=success]": { color: "accent.green" },
@@ -1,11 +1,13 @@
1
1
  import { type ComponentProps, type ReactNode } from "react";
2
2
  import type { Tone } from "./badge.js";
3
+ /** A segment's hue: a tone by meaning, or `indigo`, the chart's category no status hue covers. */
4
+ export type SegmentedBarHue = Tone | "indigo";
3
5
  export type SegmentedBarSegment = {
4
6
  id: string;
5
7
  label: string;
6
8
  value: number;
7
9
  /** The segment's color by meaning; `neutral` by default. */
8
- tone?: Tone;
10
+ tone?: SegmentedBarHue;
9
11
  /** A color of its own, such as a CSS variable the app themes, over `tone`. */
10
12
  color?: string;
11
13
  };