@ciridae/ui 1.0.4 → 1.0.9

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 (147) hide show
  1. package/README.md +3 -2
  2. package/dist/chat/conversation-source.d.ts +1 -3
  3. package/dist/chat/message-text.js +6 -6
  4. package/dist/chat/use-chat-conversation.d.ts +7 -2
  5. package/dist/chat/use-chat-conversation.js +2 -1
  6. package/dist/components/activity-feed.d.ts +0 -1
  7. package/dist/components/activity-feed.js +6 -7
  8. package/dist/components/agent-work.d.ts +5 -1
  9. package/dist/components/agent-work.js +27 -13
  10. package/dist/components/app-frame.d.ts +3 -17
  11. package/dist/components/app-frame.js +11 -30
  12. package/dist/components/app-navigation.d.ts +5 -6
  13. package/dist/components/app-navigation.js +70 -40
  14. package/dist/components/badge.d.ts +0 -4
  15. package/dist/components/badge.js +17 -27
  16. package/dist/components/bulk-action-bar.js +2 -2
  17. package/dist/components/button.js +41 -50
  18. package/dist/components/calendar.js +5 -8
  19. package/dist/components/centered-page.js +4 -4
  20. package/dist/components/chart.js +3 -4
  21. package/dist/components/chat-client-source.js +55 -34
  22. package/dist/components/chat-page.js +1 -1
  23. package/dist/components/chat-sheet.d.ts +30 -20
  24. package/dist/components/chat-sheet.js +112 -54
  25. package/dist/components/chat.d.ts +27 -56
  26. package/dist/components/chat.js +153 -132
  27. package/dist/components/checkbox-item.js +4 -4
  28. package/dist/components/checkbox-list.js +6 -6
  29. package/dist/components/chip.js +2 -2
  30. package/dist/components/collapsible.js +3 -3
  31. package/dist/components/color-theme.d.ts +3 -6
  32. package/dist/components/color-theme.js +10 -11
  33. package/dist/components/column-settings.js +3 -3
  34. package/dist/components/command-menu.d.ts +1 -2
  35. package/dist/components/command-menu.js +56 -77
  36. package/dist/components/command-palette.js +5 -2
  37. package/dist/components/copyable-id.js +5 -5
  38. package/dist/components/data-display.d.ts +1 -7
  39. package/dist/components/data-display.js +32 -55
  40. package/dist/components/data-grid.d.ts +4 -7
  41. package/dist/components/data-grid.js +106 -90
  42. package/dist/components/date-range-picker.js +4 -5
  43. package/dist/components/document-viewer.js +23 -23
  44. package/dist/components/drawer.d.ts +2 -1
  45. package/dist/components/drawer.js +45 -20
  46. package/dist/components/dropzone.js +13 -13
  47. package/dist/components/editable-field.d.ts +2 -7
  48. package/dist/components/editable-field.js +14 -27
  49. package/dist/components/email-input.js +2 -3
  50. package/dist/components/environment-frame.js +7 -7
  51. package/dist/components/expandable-text.js +2 -2
  52. package/dist/components/feedback-screenshot.js +7 -7
  53. package/dist/components/feedback-sheet.js +8 -8
  54. package/dist/components/filter-bar.js +41 -27
  55. package/dist/components/form-controls.d.ts +3 -6
  56. package/dist/components/form-controls.js +15 -22
  57. package/dist/components/form-layout.d.ts +6 -6
  58. package/dist/components/form-layout.js +10 -16
  59. package/dist/components/glass-menu.d.ts +2 -0
  60. package/dist/components/glass-menu.js +14 -18
  61. package/dist/components/gradient-edge.d.ts +0 -1
  62. package/dist/components/gradient-edge.js +8 -9
  63. package/dist/components/icon.js +1 -1
  64. package/dist/components/input.js +18 -17
  65. package/dist/components/keycap.d.ts +2 -6
  66. package/dist/components/keycap.js +4 -12
  67. package/dist/components/layout.js +11 -10
  68. package/dist/components/lightbox.js +8 -8
  69. package/dist/components/link.js +1 -1
  70. package/dist/components/locale.d.ts +23 -7
  71. package/dist/components/locale.js +19 -7
  72. package/dist/components/log-entry.d.ts +2 -14
  73. package/dist/components/log-entry.js +11 -22
  74. package/dist/components/log-list.js +1 -1
  75. package/dist/components/mention-input.js +13 -14
  76. package/dist/components/menu.d.ts +7 -4
  77. package/dist/components/menu.js +23 -10
  78. package/dist/components/meter.d.ts +2 -3
  79. package/dist/components/meter.js +12 -16
  80. package/dist/components/navigation.d.ts +0 -7
  81. package/dist/components/navigation.js +3 -12
  82. package/dist/components/notification-tray.js +7 -7
  83. package/dist/components/overlays.d.ts +5 -2
  84. package/dist/components/overlays.js +24 -24
  85. package/dist/components/panel.js +3 -3
  86. package/dist/components/progress.js +2 -2
  87. package/dist/components/radio-card.d.ts +1 -1
  88. package/dist/components/radio-card.js +9 -9
  89. package/dist/components/record.d.ts +2 -4
  90. package/dist/components/record.js +19 -32
  91. package/dist/components/scroll-area.js +10 -10
  92. package/dist/components/segmented-bar.js +9 -9
  93. package/dist/components/select.d.ts +3 -5
  94. package/dist/components/select.js +26 -44
  95. package/dist/components/settings.js +1 -1
  96. package/dist/components/shell-skeleton.js +1 -1
  97. package/dist/components/shell.d.ts +1 -1
  98. package/dist/components/shell.js +69 -24
  99. package/dist/components/side-panel.js +3 -3
  100. package/dist/components/sidebar.d.ts +0 -5
  101. package/dist/components/sidebar.js +25 -33
  102. package/dist/components/single-line-text.js +3 -4
  103. package/dist/components/site-banner.js +4 -4
  104. package/dist/components/skeleton.js +19 -22
  105. package/dist/components/skeletons.d.ts +0 -3
  106. package/dist/components/skeletons.js +5 -10
  107. package/dist/components/stages.d.ts +6 -3
  108. package/dist/components/stages.js +77 -105
  109. package/dist/components/stat-card.d.ts +2 -5
  110. package/dist/components/stat-card.js +23 -31
  111. package/dist/components/status-banner.js +6 -6
  112. package/dist/components/status.js +3 -3
  113. package/dist/components/steps.js +2 -2
  114. package/dist/components/switch.js +1 -1
  115. package/dist/components/tabs.js +15 -18
  116. package/dist/components/theme-customizer.d.ts +5 -8
  117. package/dist/components/theme-customizer.js +9 -41
  118. package/dist/components/theme-toggle.d.ts +0 -1
  119. package/dist/components/theme-toggle.js +6 -11
  120. package/dist/components/toast.d.ts +1 -7
  121. package/dist/components/toast.js +19 -22
  122. package/dist/components/user-chip.js +8 -8
  123. package/dist/components/wizard.js +6 -4
  124. package/dist/preset.d.ts +1 -2
  125. package/dist/preset.js +41 -89
  126. package/dist/styled-system/css/conditions.mjs +1 -1
  127. package/dist/styled-system/css/css.mjs +1 -1
  128. package/dist/styled-system/styles.css +820 -1297
  129. package/dist/styled-system/tokens/index.mjs +78 -1926
  130. package/dist/styled-system/tokens/tokens.d.ts +18 -33
  131. package/dist/styled-system/types/conditions.d.ts +0 -74
  132. package/dist/styled-system/types/prop-type.d.ts +15 -40
  133. package/dist/styled-system/types/style-props.d.ts +705 -705
  134. package/dist/theme/color.d.ts +0 -1
  135. package/dist/theme/color.js +0 -16
  136. package/dist/theme/derive.d.ts +3 -3
  137. package/dist/theme/derive.js +9 -32
  138. package/dist/theme/index.d.ts +1 -2
  139. package/dist/theme/index.js +3 -5
  140. package/dist/theme/options.d.ts +2 -4
  141. package/dist/theme/options.js +0 -1
  142. package/dist/theme/{palettes.d.ts → palette.d.ts} +12 -23
  143. package/dist/theme/palette.js +127 -0
  144. package/dist/theme/tokens.d.ts +8 -13
  145. package/dist/theme/tokens.js +39 -92
  146. package/package.json +1 -1
  147. package/dist/theme/palettes.js +0 -254
@@ -6,11 +6,7 @@ import { Fragment } from "react";
6
6
  import { css, cva, cx } from "#primitives/css";
7
7
  import { CopyButton } from "./copyable-id.js";
8
8
  import { Skeleton } from "./skeleton.js";
9
- // The neutral avatar is a person or the agent on the raised glass gradient inside its gradient
10
- // hairline, an organization the text-colored tile the workspace switcher shows. A colored one
11
- // derives its fill, text, and border from one `--avatar-hue`, mixed toward the opaque surface or
12
- // the text color so it holds in both themes. The hues are brighter than the accent tokens so
13
- // avatars read apart.
9
+ // A colored avatar derives its fill, text, and border from one `--avatar-hue`, mixed toward the opaque surface or the text color so it holds in both themes. The hues are brighter than the accent tokens so avatars read apart.
14
10
  const avatar = css({
15
11
  display: "inline-grid",
16
12
  placeItems: "center",
@@ -23,8 +19,7 @@ const avatar = css({
23
19
  fontWeight: "medium",
24
20
  lineHeight: "tight",
25
21
  position: "relative",
26
- // Each hue is the palette's: an accent, or the mix of two for the hues a palette has no accent
27
- // for, so avatars follow whichever palette the product picked.
22
+ // Each hue is the palette's: an accent, or the mix of two for the hues it has no accent for.
28
23
  "&[data-color=blue]": { "--avatar-hue": "token(colors.accent.blue)" },
29
24
  "&[data-color=violet]": {
30
25
  "--avatar-hue": "color-mix(in oklch, token(colors.accent.blue) 55%, token(colors.accent.red))",
@@ -42,41 +37,41 @@ const avatar = css({
42
37
  // A person, or the agent: a circle on the glass face, or a pale circle in its hue.
43
38
  "&[data-kind=user], &[data-kind=agent]": {
44
39
  borderRadius: "round",
45
- background: "token(gradients.raised)",
40
+ background: "[token(gradients.raised)]",
46
41
  color: "fg.primary",
47
42
  },
48
43
  "--edge": "token(gradients.border.raised)",
49
44
  "&[data-kind=agent], &[data-icon]": { color: "fg.secondary" },
50
45
  // An icon fills the same share of every size, so marks match whatever avatar they sit beside.
51
- "&:is([data-kind=agent], [data-icon]) svg": { width: "58%", height: "58%" },
46
+ "&:is([data-kind=agent], [data-icon]) svg": { width: "[58%]", height: "[58%]" },
52
47
  // An icon stands on its own, with no circle or ring, so it fills most of its box.
53
- "&[data-icon]": { background: "transparent", "& svg": { width: "80%", height: "80%" } },
48
+ "&[data-icon]": { background: "transparent", "& svg": { width: "[80%]", height: "[80%]" } },
54
49
  "&:is([data-kind=user], [data-kind=agent]):not([data-color=neutral])": {
55
- borderColor: "color-mix(in srgb, var(--avatar-hue) 30%, token(colors.bg.surface))",
56
- background: "color-mix(in srgb, var(--avatar-hue) 16%, token(colors.bg.surface))",
57
- color: "color-mix(in srgb, var(--avatar-hue) 70%, token(colors.fg.primary))",
50
+ borderColor: "[color-mix(in srgb, var(--avatar-hue) 30%, token(colors.bg.surface))]",
51
+ background: "[color-mix(in srgb, var(--avatar-hue) 16%, token(colors.bg.surface))]",
52
+ color: "[color-mix(in srgb, var(--avatar-hue) 70%, token(colors.fg.primary))]",
58
53
  },
59
54
  // A project: a pale tile edged and lettered in the hue's strong shade.
60
55
  "&[data-kind=project]": {
61
- borderRadius: "20%",
62
- borderColor: "color-mix(in srgb, var(--avatar-hue) 70%, token(colors.bg.surface))",
63
- background: "color-mix(in srgb, var(--avatar-hue) 14%, token(colors.bg.surface))",
64
- color: "color-mix(in srgb, var(--avatar-hue) 75%, token(colors.fg.primary))",
56
+ borderRadius: "[20%]",
57
+ borderColor: "[color-mix(in srgb, var(--avatar-hue) 70%, token(colors.bg.surface))]",
58
+ background: "[color-mix(in srgb, var(--avatar-hue) 14%, token(colors.bg.surface))]",
59
+ color: "[color-mix(in srgb, var(--avatar-hue) 75%, token(colors.fg.primary))]",
65
60
  fontWeight: "semibold",
66
61
  },
67
62
  "&[data-kind=project][data-color=neutral]": { "--avatar-hue": "token(colors.fg.muted)" },
68
63
  // An organization's logo: a squircle tile, in the text color or in its hue, bold enough to anchor
69
64
  // a person or project. Its radius scales with the box, so even a micro badge never turns round.
70
65
  "&[data-kind=organization]": {
71
- borderRadius: "28%",
66
+ borderRadius: "[28%]",
72
67
  background: "fg.primary",
73
68
  color: "bg.canvas",
74
69
  fontWeight: "semibold",
75
70
  },
76
71
  "&[data-kind=organization]:not([data-color=neutral])": {
77
- borderColor: "color-mix(in srgb, var(--avatar-hue) 75%, black)",
72
+ borderColor: "[color-mix(in srgb, var(--avatar-hue) 75%, black)]",
78
73
  background: "var(--avatar-hue)",
79
- color: "white",
74
+ color: "[white]",
80
75
  },
81
76
  // A photo fills the whole box: the fill and edge behind it would show as a ring around it.
82
77
  "&[data-kind]:has(img)": { border: "0", background: "transparent" },
@@ -85,13 +80,11 @@ const avatar = css({
85
80
  height: "avatar.large",
86
81
  fontSize: "body",
87
82
  },
88
- // A list row's owner, and the workspace switcher's mark.
83
+ // A list row's owner.
89
84
  "&[data-size=row]": { width: "avatar.row", height: "avatar.row" },
90
- // A record's owner, and the empty slot where one goes.
91
85
  "&[data-size=owner]": { width: "avatar.owner", height: "avatar.owner" },
92
86
  // A menu item's leading avatar.
93
87
  "&[data-size=menu]": { width: "avatar.menu", height: "avatar.menu" },
94
- // The agent standing on its own, the kit's Agent avatar.
95
88
  "&[data-size=agent]": { width: "avatar.agent", height: "avatar.agent" },
96
89
  "&[data-size=small]": { width: "avatar.small", height: "avatar.small", fontSize: "avatar.small" },
97
90
  // The inline mark's size, so a project's tile lines up with icons and provider logos.
@@ -103,20 +96,20 @@ const avatar = css({
103
96
  const avatarFallback = css({
104
97
  display: "grid",
105
98
  placeItems: "center",
106
- width: "100%",
107
- height: "100%",
99
+ width: "full",
100
+ height: "full",
108
101
  });
109
102
  const avatarImage = css({
110
- width: "100%",
111
- height: "100%",
112
- borderRadius: "inherit",
103
+ width: "full",
104
+ height: "full",
105
+ borderRadius: "[inherit]",
113
106
  objectFit: "cover",
114
107
  });
115
108
  const avatarBadge = css({
116
109
  position: "absolute",
117
110
  // Inside the circle: an overhang is clipped by scroll containers such as the compact header.
118
- insetInlineEnd: "-1px",
119
- bottom: "-1px",
111
+ insetInlineEnd: "[-1px]",
112
+ bottom: "[-1px]",
120
113
  display: "inline-flex",
121
114
  // The badge separates with its own edge: a ring would have to match every surface the
122
115
  // avatar sits on, and its corners never matched the badge's.
@@ -133,7 +126,6 @@ export function Avatar({ label, src, size = "medium", kind = "user", icon, badge
133
126
  const glass = !icon && color === "neutral" && (kind === "user" || kind === "agent");
134
127
  return (_jsxs(BaseAvatar.Root, { ...props, "aria-label": label, "data-ciridae-ui": "", "data-size": size, "data-kind": kind, "data-color": color, "data-icon": icon ? "" : undefined, "data-edge": glass ? "gradient" : undefined, className: cx(avatar, className), children: [src && _jsx(BaseAvatar.Image, { src: src, alt: "", className: avatarImage }), _jsx(BaseAvatar.Fallback, { className: avatarFallback, children: icon ?? (kind === "agent" ? _jsx(SparkleIcon, { weight: "fill" }) : initials) }), badge && _jsx("span", { className: avatarBadge, children: badge })] }));
135
128
  }
136
- // No owner yet: the avatar's circle as a dashed ring, empty.
137
129
  const unassignedAvatar = css({
138
130
  display: "inline-block",
139
131
  flexShrink: "0",
@@ -144,7 +136,6 @@ const unassignedAvatar = css({
144
136
  "&[data-size=small]": { width: "avatar.small", height: "avatar.small" },
145
137
  "&[data-size=row]": { width: "avatar.row", height: "avatar.row" },
146
138
  });
147
- /** The slot where an owner's avatar goes, before anyone owns the record. */
148
139
  export function UnassignedAvatar({ size = "owner", className, ...props }) {
149
140
  return (_jsx("span", { ...props, className: cx(unassignedAvatar, className), "data-ciridae-ui": "", "data-size": size }));
150
141
  }
@@ -158,12 +149,10 @@ function labelInitials(label, count) {
158
149
  .toUpperCase();
159
150
  }
160
151
  const divider = cva({
161
- base: { width: "100%", height: "border.hairline", border: "0", background: "border.subtle" },
152
+ base: { width: "full", height: "border.hairline", border: "0", background: "border.subtle" },
162
153
  variants: {
163
154
  inset: {
164
155
  none: {},
165
- // Between a menu's or a popover's groups, as the glass menu's divider: inset to its rows'
166
- // text, a little room above and below.
167
156
  menu: {
168
157
  width: "auto",
169
158
  marginBlock: "layout.tightGap",
@@ -176,11 +165,6 @@ const divider = cva({
176
165
  export function Divider({ inset = "none", className, ...props }) {
177
166
  return (_jsx(BaseSeparator, { ...props, "data-ciridae-ui": "", className: cx(divider({ inset }), className) }));
178
167
  }
179
- /*
180
- * Labels are the muted grey, values the text color. `facts` sets each label against its value at
181
- * the far edge, a hairline under every row but the last; `compact` is the key-value pair, its
182
- * labels a 120px column of 12px type.
183
- */
184
168
  const descriptionList = cva({
185
169
  base: {
186
170
  display: "grid",
@@ -188,7 +172,7 @@ const descriptionList = cva({
188
172
  margin: "none",
189
173
  "& dt": { color: "fg.label" },
190
174
  "& dd": { margin: "none", overflowWrap: "anywhere" },
191
- "& a": { color: "inherit" },
175
+ "& a": { color: "[inherit]" },
192
176
  },
193
177
  variants: {
194
178
  density: {
@@ -206,7 +190,7 @@ const descriptionList = cva({
206
190
  facts: {
207
191
  gridTemplateColumns: "minmax(0, 1fr) auto",
208
192
  alignItems: "center",
209
- rowGap: "0",
193
+ rowGap: "none",
210
194
  columnGap: "layout.contentGap",
211
195
  "& dt, & dd": {
212
196
  paddingBlock: "panel.default",
@@ -250,7 +234,7 @@ const codeBlockFrame = css({
250
234
  position: "relative",
251
235
  minWidth: "0",
252
236
  "& > pre": {
253
- paddingInlineEnd: "calc(token(sizes.control.compact) + token(spacing.panel.default) * 2)",
237
+ paddingInlineEnd: "[calc(token(sizes.control.compact) + token(spacing.panel.default) * 2)]",
254
238
  },
255
239
  });
256
240
  const codeBlockCopy = css({
@@ -269,13 +253,7 @@ export function CodeBlock({ className, copy, copyLabel = "text", ...props }) {
269
253
  return block;
270
254
  return (_jsxs("div", { className: codeBlockFrame, children: [block, _jsx("span", { className: codeBlockCopy, children: _jsx(CopyButton, { value: copy, label: copyLabel }) })] }));
271
255
  }
272
- /*
273
- * Detail field: one field of a record as a row, the kit's Detail field. A 150px label column of
274
- * 12px grey beside the 14px value and, under it, where the value came from and who vouches for it,
275
- * or a description when a badge rides beside the value. `selected` is the open field; `inset`
276
- * sets it in the selected glass between hairlines; `highlighted` is the warm band for a field that
277
- * needs a look, such as two sources that disagree.
278
- */
256
+ /* `highlighted` is the warm band for a field that needs a look, such as two sources that disagree. */
279
257
  const detailField = css({
280
258
  display: "flex",
281
259
  alignItems: "flex-start",
@@ -287,11 +265,11 @@ const detailField = css({
287
265
  lineHeight: "tight",
288
266
  "&[data-divider]": { borderBottom: "1px solid token(colors.border.divider)" },
289
267
  "&[data-state=default]:hover": { background: "bg.rowHover" },
290
- "&[data-state=selected]": { background: "token(gradients.selected)" },
268
+ "&[data-state=selected]": { background: "[token(gradients.selected)]" },
291
269
  "&:is([data-state=inset], [data-state=highlighted])": {
292
270
  borderBlock: "1px solid token(colors.border.divider)",
293
271
  },
294
- "&[data-state=inset]": { background: "token(gradients.selected)" },
272
+ "&[data-state=inset]": { background: "[token(gradients.selected)]" },
295
273
  "&[data-state=highlighted]": { background: "bg.warningTint" },
296
274
  });
297
275
  // The label sits on the value's first line: 14px of text, or the badge's 24.
@@ -299,14 +277,13 @@ const detailFieldLabel = css({
299
277
  display: "flex",
300
278
  alignItems: "center",
301
279
  flexShrink: "0",
302
- width: "150px",
303
- minHeight: "token(fontSizes.body)",
280
+ width: "[150px]",
281
+ minHeight: "[token(fontSizes.body)]",
304
282
  color: "fg.label",
305
283
  fontSize: "code",
306
284
  "[data-badge] > &": { minHeight: "control.shortcut" },
307
285
  });
308
286
  const detailFieldValue = css({ display: "grid", gap: "control.iconGap", flex: "1", minWidth: "0" });
309
- // A badge that does not fit beside the value drops under it.
310
287
  const detailFieldValueLine = css({
311
288
  display: "flex",
312
289
  alignItems: "center",
@@ -83,7 +83,7 @@ export type DataGridProps<Row extends RowData> = Omit<ComponentProps<"div">, "ch
83
83
  data: readonly Row[];
84
84
  columns: TableColumns<Row>;
85
85
  getRowId: (row: Row) => string;
86
- /** `framed` draws the list as a card with sentence-case heads; `plain` sits flush, heads in caps. */
86
+ /** `framed` draws the list as a card; `plain` sits flush with its section. */
87
87
  variant?: "framed" | "plain";
88
88
  density?: "default" | "compact";
89
89
  sorting?: SortingState;
@@ -91,10 +91,7 @@ export type DataGridProps<Row extends RowData> = Omit<ComponentProps<"div">, "ch
91
91
  onSortingChange?: (sorting: SortingState) => void;
92
92
  /** The rows arrive in the order `sorting` asks for, such as from a server. */
93
93
  manualSorting?: boolean;
94
- /**
95
- * Shows one page of the sorted rows, `number` counting from 1, for a list paged in the browser
96
- * under a `Pagination`. Leave it out when `data` is already the page.
97
- */
94
+ /** `number` counts from 1. Leave it out when `data` is already the page. */
98
95
  page?: {
99
96
  number: number;
100
97
  rowsPerPage: number;
@@ -176,7 +173,7 @@ export type TableProps<Row extends RowData> = Omit<DataGridProps<Row>, "defaultS
176
173
  emptyMessage?: ReactNode;
177
174
  };
178
175
  /**
179
- * `DataGrid` with the plain look: flush with its section, heads in caps, rows that light on hover
180
- * when they lead somewhere.
176
+ * `DataGrid` with the plain look: flush with its section, rows that light on hover when they lead
177
+ * somewhere.
181
178
  */
182
179
  export declare function Table<Row extends RowData>({ variant, initialSorting, emptyMessage, ...props }: TableProps<Row>): import("react").JSX.Element;
@@ -336,50 +336,57 @@ export function DataGrid({ data, columns, getRowId, variant = "framed", density
336
336
  setSelected(range.filter((id) => selectableIds.includes(id)), true);
337
337
  anchorRowId.current = rowIds[end] ?? null;
338
338
  };
339
- useHotkeys([
340
- {
341
- hotkey: "J",
342
- callback: () => moveCursor(1),
343
- options: {
344
- meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.nextRow },
339
+ // Only a grid that takes the page's list keys registers them: a row's own `enabled` overrides
340
+ // a shared one, and a disabled hotkey still registers, so two grids would clash.
341
+ useHotkeys(keyboardNavigation
342
+ ? [
343
+ {
344
+ hotkey: "J",
345
+ callback: () => moveCursor(1),
346
+ options: {
347
+ meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.nextRow },
348
+ },
345
349
  },
346
- },
347
- {
348
- hotkey: "K",
349
- callback: () => moveCursor(-1),
350
- options: {
351
- meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.previousRow },
350
+ {
351
+ hotkey: "K",
352
+ callback: () => moveCursor(-1),
353
+ options: {
354
+ meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.previousRow },
355
+ },
352
356
  },
353
- },
354
- {
355
- hotkey: "X",
356
- callback: toggleCursorRow,
357
- options: {
358
- enabled: selectable,
359
- meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.selectRow },
357
+ {
358
+ hotkey: "X",
359
+ callback: toggleCursorRow,
360
+ options: {
361
+ enabled: selectable,
362
+ meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.selectRow },
363
+ },
360
364
  },
361
- },
362
- {
363
- hotkey: "Shift+X",
364
- callback: selectToCursor,
365
- options: {
366
- enabled: selectable,
367
- meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.selectRange },
365
+ {
366
+ hotkey: "Shift+X",
367
+ callback: selectToCursor,
368
+ options: {
369
+ enabled: selectable,
370
+ meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.selectRange },
371
+ },
368
372
  },
369
- },
370
- {
371
- hotkey: "Escape",
372
- callback: () => setRowSelection({}),
373
- options: {
374
- enabled: Object.values(rowSelection).some(Boolean),
375
- // Escape in a field is the field's, such as clearing a search.
376
- ignoreInputs: true,
377
- // The shell's Escape closes its phone menu, which covers the list.
378
- conflictBehavior: "allow",
379
- meta: { group: localeLabels.keyboard.list, name: localeLabels.keyboard.clearSelection },
373
+ {
374
+ hotkey: "Escape",
375
+ callback: () => setRowSelection({}),
376
+ options: {
377
+ enabled: Object.values(rowSelection).some(Boolean),
378
+ // Escape in a field is the field's, such as clearing a search.
379
+ ignoreInputs: true,
380
+ // The shell's Escape closes its phone menu, which covers the list.
381
+ conflictBehavior: "allow",
382
+ meta: {
383
+ group: localeLabels.keyboard.list,
384
+ name: localeLabels.keyboard.clearSelection,
385
+ },
386
+ },
380
387
  },
381
- },
382
- ], { enabled: keyboardNavigation });
388
+ ]
389
+ : []);
383
390
  const cellClass = (column) => cx(cell, column.columnDef.meta?.truncate && truncateCell, column.id === stickyColumnId && pinnedCell);
384
391
  const renderCellContent = (tableRow, column) => {
385
392
  const cells = tableRow.getAllCells();
@@ -495,14 +502,14 @@ export function DataGrid({ data, columns, getRowId, variant = "framed", density
495
502
  const showEmpty = !loading && rows.length === 0;
496
503
  const isFiltered = filtered ?? (filters !== undefined && filters.length > 0);
497
504
  const emptyContent = isFiltered && noResults !== undefined ? noResults : (empty ?? labels.nothingToShow);
498
- return (_jsxs("div", { ref: frameRef, className: cx(frame, frameClassName), "data-ciridae-ui": "", "data-variant": variant, "data-density": density, "data-fill": fill || undefined, "data-stacked": narrowScreenLayout === "stacked" || undefined, children: [_jsxs("div", { ...props, ref: gridRef, role: "table", "aria-busy": loading || undefined, className: cx(grid, className), style: { ...style, gridTemplateColumns: template }, children: [!hideHeader && (_jsxs("div", { role: "row", className: headRow, children: [selectable && (_jsx("div", { ref: selectHeadRef, role: "columnheader", className: cx(headCell, selectCell, stickySelectColumn && pinnedCell), children: _jsx(Checkbox, { "aria-label": labels.selectAll, checked: allSelected === "all" && selectableIds.length > 0, indeterminate: allSelected === "some", disabled: loading || selectableIds.length === 0, onCheckedChange: (checked) => setSelected(selectableIds, checked) }) })), shownColumns.map(headerCell), rowActions && (_jsx("div", { role: "columnheader", className: headCell, children: _jsx("span", { className: visuallyHidden, children: labels.actions }) }))] })), loading ? (skeletonRows.map((rowIndex) => (_jsxs("div", { role: "row", className: bodyRow, "data-skeleton": "", style: { opacity: 1 - rowIndex * skeletonRowFade }, children: [selectable && (_jsx("div", { role: "cell", className: cx(cell, selectCell), children: _jsx(Skeleton, { shape: "square", className: skeletonCheckbox }) })), shownColumns.map((column, columnIndex) => (_jsx("div", { role: "cell", className: cellClass(column), "data-align": column.columnDef.meta?.align, children: _jsx(SkeletonCell, { shape: column.columnDef.meta?.skeleton, width: skeletonWidths[(rowIndex + columnIndex) % skeletonWidths.length] }) }, column.id))), rowActions && _jsx("div", { role: "cell", className: cell })] }, rowIndex)))) : showEmpty ? (_jsx("div", { role: "row", className: emptyRow, children: _jsx("div", { role: "cell", "aria-colspan": columnCount, className: emptyCell, children: emptyContent }) })) : (drawBody())] }), footer, filterColumn && filterField && onFiltersChange && (_jsx(Popover, { open: true, onOpenChange: (open) => {
505
+ return (_jsxs("div", { ref: frameRef, className: cx(frame, frameClassName), "data-ciridae-ui": "", "data-variant": variant, "data-density": density, "data-fill": fill || undefined, "data-stacked": narrowScreenLayout === "stacked" || undefined, children: [_jsxs("div", { ...props, ref: gridRef, role: "table", "aria-busy": loading || undefined, className: cx(grid, className), style: { ...style, gridTemplateColumns: template }, children: [!hideHeader && (_jsxs("div", { role: "row", className: headRow, children: [selectable && (_jsx("div", { ref: selectHeadRef, role: "columnheader", className: cx(headCell, selectCell, stickySelectColumn && pinnedCell), children: _jsx(Checkbox, { "aria-label": labels.selectAll, checked: allSelected === "all" && selectableIds.length > 0, indeterminate: allSelected === "some", disabled: loading || selectableIds.length === 0, onCheckedChange: (checked) => setSelected(selectableIds, checked) }) })), shownColumns.map(headerCell), rowActions && (_jsx("div", { role: "columnheader", className: headCell, children: _jsx("span", { className: visuallyHidden, children: labels.actions }) }))] })), loading ? (skeletonRows.map((rowIndex) => (_jsxs("div", { role: "row", className: bodyRow, "data-skeleton": "", style: { opacity: 1 - rowIndex * skeletonRowFade }, children: [selectable && (_jsx("div", { role: "cell", className: cx(cell, selectCell), children: _jsx(Skeleton, { shape: "square" }) })), shownColumns.map((column, columnIndex) => (_jsx("div", { role: "cell", className: cellClass(column), "data-align": column.columnDef.meta?.align, children: _jsx(SkeletonCell, { shape: column.columnDef.meta?.skeleton, width: skeletonWidths[(rowIndex + columnIndex) % skeletonWidths.length] }) }, column.id))), rowActions && _jsx("div", { role: "cell", className: cell })] }, rowIndex)))) : showEmpty ? (_jsx("div", { role: "row", className: emptyRow, children: _jsx("div", { role: "cell", "aria-colspan": columnCount, className: emptyCell, children: emptyContent }) })) : (drawBody())] }), footer, filterColumn && filterField && onFiltersChange && (_jsx(Popover, { open: true, onOpenChange: (open) => {
499
506
  if (!open)
500
507
  setFilterColumnId(null);
501
508
  }, anchor: headRefs.current.get(filterColumn.id) ?? null, density: "compact", children: _jsx("div", { className: columnFilter, children: _jsx(FilterValueEditor, { field: filterField, value: filters?.find((filter) => filter.field === filterField.key), onChange: (filter) => onFiltersChange(replaceFilter(filters ?? [], filter)) }) }) }))] }));
502
509
  }
503
510
  /**
504
- * `DataGrid` with the plain look: flush with its section, heads in caps, rows that light on hover
505
- * when they lead somewhere.
511
+ * `DataGrid` with the plain look: flush with its section, rows that light on hover when they lead
512
+ * somewhere.
506
513
  */
507
514
  export function Table({ variant = "plain", initialSorting, emptyMessage, ...props }) {
508
515
  return (_jsx(DataGrid, { variant: variant, defaultSorting: initialSorting, empty: emptyMessage, ...props }));
@@ -512,7 +519,6 @@ const emptySelection = {};
512
519
  const emptyVisibility = {};
513
520
  const emptyOrder = [];
514
521
  const skeletonRows = [0, 1, 2, 3, 4];
515
- // Each loading row a little fainter than the one above, so the list reads as fading out.
516
522
  const skeletonRowFade = 0.08;
517
523
  const skeletonWidths = ["70%", "45%", "85%", "55%"];
518
524
  /** What takes a click of its own inside a row, so pressing it leaves the row alone. */
@@ -579,14 +585,14 @@ const frame = css({
579
585
  position: "relative",
580
586
  display: "flex",
581
587
  flexDirection: "column",
582
- width: "100%",
588
+ width: "full",
583
589
  minWidth: "0",
584
590
  overflowX: "auto",
585
591
  overflowY: "hidden",
586
592
  "&[data-variant=framed]": {
587
593
  border: "1px solid token(colors.border.panel)",
588
594
  borderRadius: "action",
589
- background: "token(gradients.card)",
595
+ background: "[token(gradients.card)]",
590
596
  boxShadow: "card",
591
597
  _light: {
592
598
  "--pinned-background": "token(colors.bg.card)",
@@ -614,8 +620,8 @@ const grid = css({
614
620
  alignContent: "start",
615
621
  columnGap: "var(--row-gap-inline)",
616
622
  // As wide as the frame, or as its columns' least widths when those outgrow it, so it scrolls.
617
- width: "100%",
618
- minWidth: "min-content",
623
+ width: "full",
624
+ minWidth: "[min-content]",
619
625
  color: "fg.primary",
620
626
  fontSize: "body",
621
627
  letterSpacing: "compact",
@@ -636,8 +642,8 @@ const grid = css({
636
642
  rowGap: "layout.tightGap",
637
643
  },
638
644
  "& > [role=row]:first-child:has([role=columnheader])": { srOnly: true },
639
- "& [role=cell]": { display: "block", padding: "0", minWidth: "0" },
640
- "& [role=cell]:first-child": { flexBasis: "100%" },
645
+ "& [role=cell]": { display: "block", padding: "none", minWidth: "0" },
646
+ "& [role=cell]:first-child": { flexBasis: "full" },
641
647
  "& [role=cell][data-truncate]": { whiteSpace: "normal", overflowWrap: "anywhere" },
642
648
  "& [role=cell][data-align=end]": { marginInlineStart: "auto" },
643
649
  },
@@ -661,8 +667,8 @@ const headRow = css(rowBase, {
661
667
  lineHeight: "tight",
662
668
  "[data-fill] &": {
663
669
  position: "sticky",
664
- top: "0",
665
- zIndex: "3",
670
+ top: "none",
671
+ zIndex: "[3]",
666
672
  background: "var(--pinned-background)",
667
673
  boxShadow: "var(--pinned-layers)",
668
674
  },
@@ -671,18 +677,18 @@ const headRow = css(rowBase, {
671
677
  const spacerRow = css({ gridColumn: "1 / -1" });
672
678
  const bodyRow = css(rowBase, {
673
679
  position: "relative",
674
- outline: "none",
680
+ outline: "0",
675
681
  "&:last-child": { borderBottom: "0" },
676
682
  // 56px: a 32px avatar or two-line cell inside the padding, the bottom hairline inside that.
677
683
  "[data-variant=framed][data-density=default] &": {
678
- minHeight: "calc(token(sizes.control.compact) + var(--row-pad-block) * 2)",
684
+ minHeight: "[calc(token(sizes.control.compact) + var(--row-pad-block) * 2)]",
679
685
  },
680
686
  // The row's wash is one layer over every cell, the pinned ones included, so they tint together.
681
687
  _after: {
682
688
  content: '""',
683
689
  position: "absolute",
684
- inset: "0",
685
- zIndex: "2",
690
+ inset: "none",
691
+ zIndex: "[2]",
686
692
  background: "var(--row-wash, transparent)",
687
693
  pointerEvents: "none",
688
694
  transitionProperty: "background",
@@ -692,26 +698,36 @@ const bodyRow = css(rowBase, {
692
698
  // glass, and keeps it under the pointer.
693
699
  "&[data-pressable]": { cursor: "pointer" },
694
700
  "&[data-pressable]:not([data-active]):hover": { "--row-wash": "token(colors.bg.rowHover)" },
695
- "&[data-active]": { "--row-wash": "token(gradients.selected)" },
701
+ // The open row wears the selected glass. In dark it is a 10% wash over the cells, tinting the
702
+ // pinned ones with the rest; in light it is a near-opaque face, so it goes under the cells,
703
+ // where it would otherwise fade their text, and pinned cells lay it over their own surface.
704
+ "&[data-active]": {
705
+ "--row-wash": "token(gradients.selected)",
706
+ _light: {
707
+ "--row-wash": "transparent",
708
+ "--row-glass": "token(gradients.selected)",
709
+ background: "[var(--row-glass), var(--pinned-background)]",
710
+ },
711
+ },
696
712
  _focusVisible: { focusRing: "inset", "--row-wash": "token(colors.bg.rowHover)" },
697
713
  // Its anchor stands for the row and sits in a cell that may clip, so the row draws the ring.
698
714
  "&:has(a[data-row-link]:focus-visible)": {
699
715
  focusRing: "inset",
700
716
  "--row-wash": "token(colors.bg.rowHover)",
701
717
  },
702
- "& a[data-row-link]:focus-visible": { outline: "none" },
718
+ "& a[data-row-link]:focus-visible": { outline: "0" },
703
719
  "& a[data-row-link]:hover": { textDecoration: "none" },
704
720
  // Row actions show while the row is pointed at, focused, ticked or open, and always on touch.
705
721
  "& [data-row-actions] > *": {
706
- opacity: "0",
707
- transitionProperty: "opacity",
722
+ opacity: "[0]",
723
+ transitionProperty: "[opacity]",
708
724
  transitionDuration: "fast",
709
725
  },
710
- "&:is(:hover, :focus-within, [data-selected], [data-active]) [data-row-actions] > *, & [data-row-actions]:has([data-popup-open]) > *": { opacity: "1" },
711
- "@media (hover: none)": { "& [data-row-actions] > *": { opacity: "1" } },
726
+ "&:is(:hover, :focus-within, [data-selected], [data-active]) [data-row-actions] > *, & [data-row-actions]:has([data-popup-open]) > *": { opacity: "[1]" },
727
+ "@media (hover: none)": { "& [data-row-actions] > *": { opacity: "[1]" } },
712
728
  });
713
729
  const groupRow = css(rowBase, {
714
- minHeight: "40px",
730
+ minHeight: "[40px]",
715
731
  background: "bg.field",
716
732
  "&:last-child": { borderBottom: "0" },
717
733
  });
@@ -725,10 +741,10 @@ const cellBase = {
725
741
  };
726
742
  // The row's hairline sits inside its bottom padding, as the kit's 56px row draws it.
727
743
  const cell = css(cellBase, {
728
- paddingBottom: "calc(var(--row-pad-block) - token(sizes.border.hairline))",
744
+ paddingBottom: "[calc(var(--row-pad-block) - token(sizes.border.hairline))]",
729
745
  fontWeight: "medium",
730
- "[data-variant=plain] &": { fontWeight: "normal" },
731
- "& a": { color: "inherit", textDecoration: "none" },
746
+ "[data-variant=plain] &": { fontWeight: "[normal]" },
747
+ "& a": { color: "[inherit]", textDecoration: "none" },
732
748
  "& a:hover": { textDecoration: "underline" },
733
749
  });
734
750
  const headCell = css(cellBase, {
@@ -754,23 +770,23 @@ const selectCell = css({
754
770
  // the list's surface on the solid canvas.
755
771
  const pinnedCell = css({
756
772
  position: "sticky",
757
- zIndex: "1",
758
- insetInlineStart: "0",
773
+ zIndex: "[1]",
774
+ insetInlineStart: "none",
759
775
  alignSelf: "stretch",
760
776
  display: "flex",
761
777
  alignItems: "center",
762
- marginInlineStart: "calc(var(--row-pad-inline) * -1)",
763
- marginInlineEnd: "calc(var(--row-gap-inline) * -1)",
778
+ marginInlineStart: "[calc(var(--row-pad-inline) * -1)]",
779
+ marginInlineEnd: "[calc(var(--row-gap-inline) * -1)]",
764
780
  paddingInlineStart: "var(--row-pad-inline)",
765
781
  paddingInlineEnd: "var(--row-gap-inline)",
766
- background: "var(--pinned-background)",
782
+ background: "[var(--row-glass, none), var(--pinned-background)]",
767
783
  boxShadow: "var(--pinned-layers)",
768
784
  "[role=row] > & + &": {
769
785
  insetInlineStart: "var(--data-grid-sticky-offset)",
770
- marginInlineStart: "0",
771
- paddingInlineStart: "0",
786
+ marginInlineStart: "none",
787
+ paddingInlineStart: "none",
772
788
  },
773
- "[role=columnheader]&": { zIndex: "4" },
789
+ "[role=columnheader]&": { zIndex: "[4]" },
774
790
  });
775
791
  const actionsCell = css({
776
792
  display: "flex",
@@ -787,7 +803,7 @@ const groupCell = css({
787
803
  const detailCell = css({
788
804
  gridColumn: "1 / -1",
789
805
  minWidth: "0",
790
- marginTop: "calc(var(--row-pad-block) * -1)",
806
+ marginTop: "[calc(var(--row-pad-block) * -1)]",
791
807
  paddingBottom: "var(--row-pad-block)",
792
808
  });
793
809
  const emptyCell = css({
@@ -812,53 +828,53 @@ const headButton = css({
812
828
  display: "inline-flex",
813
829
  alignItems: "center",
814
830
  gap: "control.iconGap",
815
- maxWidth: "100%",
831
+ maxWidth: "full",
816
832
  padding: "none",
817
833
  border: "0",
818
834
  background: "transparent",
819
- color: "inherit",
835
+ color: "[inherit]",
820
836
  font: "inherit",
821
- letterSpacing: "inherit",
837
+ letterSpacing: "[inherit]",
822
838
  textTransform: "inherit",
823
839
  cursor: "pointer",
824
840
  borderRadius: "control",
825
841
  _focusVisible: { focusRing: "outer" },
826
- // The sort caret shows only while the column is sorted, pointed at, focused or its menu open.
827
- "& svg": { flexShrink: "0", color: "fg.muted", opacity: "0", transition: "opacity .15s" },
842
+ "& svg": { flexShrink: "0", color: "fg.muted", opacity: "[0]", transition: "[opacity .15s]" },
828
843
  "&[data-sorted] svg": { color: "fg.primary" },
829
- "&:is(:hover, :focus-visible, [data-sorted], [data-popup-open]) svg": { opacity: "1" },
844
+ "&:is(:hover, :focus-visible, [data-sorted], [data-popup-open]) svg": { opacity: "[1]" },
830
845
  "[data-align=end] > &": { marginInlineStart: "auto" },
831
846
  });
832
- // Past the skeleton's own shape sizes, which match on its `data-shape` too.
833
- const skeletonCheckbox = css({
834
- "&[data-shape][data-ciridae-ui]": { width: "control.checkbox", borderRadius: "control" },
835
- });
836
847
  // A loading cell in its column's shape: the kit's skeleton row draws two lines for the primary
837
848
  // column, a 20px pill for a badge and a 24px circle beside a line for a person.
838
849
  const skeletonLines = css({
839
850
  display: "grid",
840
851
  gap: "layout.controlGap",
841
- width: "100%",
852
+ width: "full",
842
853
  lineHeight: "tight",
843
854
  });
855
+ // The kit's 80px badge pill, narrower than the skeleton's own; matching `data-shape` too puts this
856
+ // rule past the skeleton's shape rules.
844
857
  const skeletonPill = css({
845
- "&[data-shape][data-ciridae-ui]": { width: "80px", height: "control.mark", borderRadius: "pill" },
858
+ "&[data-shape][data-ciridae-ui]": {
859
+ width: "[80px]",
860
+ height: "control.mark",
861
+ borderRadius: "pill",
862
+ },
846
863
  });
847
864
  const skeletonPerson = css({
848
865
  display: "flex",
849
866
  alignItems: "center",
850
867
  gap: "layout.controlGap",
851
- width: "100%",
868
+ width: "full",
852
869
  });
853
- const skeletonAvatar = css({ "&[data-shape][data-ciridae-ui]": { width: "avatar.menu" } });
854
870
  function SkeletonCell({ shape, width, }) {
855
871
  if (shape === "lines")
856
872
  return (_jsxs("span", { className: skeletonLines, children: [_jsx(Skeleton, { shape: "text", width: width }), _jsx(Skeleton, { shape: "text", height: "caption", width: "45%" })] }));
857
873
  if (shape === "pill")
858
874
  return _jsx(Skeleton, { className: skeletonPill });
859
875
  if (shape === "person")
860
- return (_jsxs("span", { className: skeletonPerson, children: [_jsx(Skeleton, { shape: "circle", className: skeletonAvatar }), _jsx(Skeleton, { shape: "text", width: width })] }));
876
+ return (_jsxs("span", { className: skeletonPerson, children: [_jsx(Skeleton, { shape: "circle" }), _jsx(Skeleton, { shape: "text", width: width })] }));
861
877
  return shape ?? _jsx(Skeleton, { shape: "text", width: width });
862
878
  }
863
- const columnFilter = css({ display: "flex", flexDirection: "column", width: "260px" });
879
+ const columnFilter = css({ display: "flex", flexDirection: "column", width: "[260px]" });
864
880
  const visuallyHidden = css({ srOnly: true });