@ciridae/ui 1.0.3 → 1.0.4

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 +105 -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 +76 -39
  26. package/dist/components/chat.d.ts +90 -14
  27. package/dist/components/chat.js +402 -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 +12 -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 +289 -33
  111. package/dist/styled-system/styles.css +3115 -1044
  112. package/dist/styled-system/tokens/index.mjs +598 -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 +107 -32
  124. package/package.json +9 -1
@@ -16,7 +16,7 @@ import { useCiridaeLocale } from "./locale.js";
16
16
  const fieldFace = {
17
17
  border: "1px solid token(colors.border.control)",
18
18
  borderRadius: "control",
19
- background: "bg.field",
19
+ background: "bg.formField",
20
20
  color: "fg.primary",
21
21
  fontFamily: "sans",
22
22
  fontWeight: "medium",
@@ -34,13 +34,19 @@ const field = cva({
34
34
  base: { ...fieldFace, width: "100%", lineHeight: "tight" },
35
35
  variants: {
36
36
  size: {
37
- /** Matches the small button, for forms and filters. */
37
+ /** The editor an inline-edit row opens in place of its value. */
38
+ inline: {
39
+ height: "field.inline",
40
+ paddingInline: "menu.itemInline",
41
+ fontSize: "body",
42
+ },
43
+ /** For a row of filters, beside compact selects and small buttons. */
38
44
  small: {
39
45
  height: "control.compact",
40
46
  paddingInline: "control.compactInline",
41
47
  fontSize: "caption",
42
48
  },
43
- /** The Round 2 field: the height of the default button. */
49
+ /** The field: the height of the default button. */
44
50
  medium: {
45
51
  height: "control.default",
46
52
  paddingInline: "control.compactInline",
@@ -55,9 +61,9 @@ const field = cva({
55
61
  },
56
62
  },
57
63
  },
58
- defaultVariants: { size: "small" },
64
+ defaultVariants: { size: "medium" },
59
65
  });
60
- export function Input({ className, size = "small", ...props }) {
66
+ export function Input({ className, size = "medium", ...props }) {
61
67
  const fieldIds = useFieldControl(props);
62
68
  return (_jsx(BaseInput, { ...props, ...fieldIds, className: cx(field({ size }), className), "data-ciridae-ui": "" }));
63
69
  }
@@ -179,12 +185,13 @@ export function SearchInput({ variant = "regular", size: ownSize, width = "fill"
179
185
  onValueCommit(event.currentTarget.value);
180
186
  }
181
187
  onKeyDown?.(event);
182
- }, className: cx(searchText({ variant, size }), className) }), shortcut && _jsx(ShortcutHint, { hotkey: shortcut })] }));
188
+ }, className: cx(searchText({ variant, size }), className) }), shortcut && _jsx(ShortcutHint, { boxed: true, hotkey: shortcut })] }));
183
189
  }
184
190
  /*
185
191
  * The Round 2 search field is an underline, not a box: an icon, the text, and a clear button on
186
- * one hairline. Focus turns that hairline into a 2px accent line, as the frames draw it, rather
187
- * than boxing a field that has no box.
192
+ * one hairline. Hovered it takes the hover wash, and focused the ring, both on its own nearly
193
+ * square corners. Quiet, in a menu or popover, the hairline is the sheet's divider and nothing
194
+ * else changes: the popup opens with the field focused, and its caret shows that.
188
195
  */
189
196
  const underline = cva({
190
197
  base: {
@@ -195,10 +202,6 @@ const underline = cva({
195
202
  paddingInline: "search.inline",
196
203
  borderBottom: "1px solid token(colors.border.control)",
197
204
  color: "fg.muted",
198
- "&:has(:focus-visible)": {
199
- borderBottomColor: "border.focus",
200
- boxShadow: "inset 0 -1px 0 0 token(colors.border.focus)",
201
- },
202
205
  "&:has(:disabled)": { opacity: "disabled" },
203
206
  },
204
207
  variants: {
@@ -206,8 +209,16 @@ const underline = cva({
206
209
  regular: { height: "control.default" },
207
210
  compact: { height: "control.compact" },
208
211
  },
212
+ quiet: {
213
+ false: {
214
+ borderRadius: "control",
215
+ _hover: { background: "bg.controlHover" },
216
+ "&:has(:focus-visible)": { focusRing: "outer" },
217
+ },
218
+ true: { paddingInline: "none", borderBottomColor: "border.divider" },
219
+ },
209
220
  },
210
- defaultVariants: { size: "regular" },
221
+ defaultVariants: { size: "regular", quiet: false },
211
222
  });
212
223
  const underlineText = css({
213
224
  flex: "1",
@@ -242,9 +253,9 @@ const clear = css({
242
253
  _hover: { color: "fg.primary", background: "bg.hover" },
243
254
  _focusVisible: { focusRing: "inset" },
244
255
  });
245
- export function SearchField({ size: ownSize, onClear, shortcut, wrapperClassName, className, disabled, ...props }) {
256
+ export function SearchField({ size: ownSize, onClear, quiet = false, shortcut, wrapperClassName, className, disabled, ...props }) {
246
257
  const inToolbar = useInToolbar();
247
258
  const { labels } = useCiridaeLocale();
248
259
  const size = ownSize ?? (inToolbar ? "compact" : "regular");
249
- return (_jsxs("label", { className: cx(underline({ size }), wrapperClassName), "data-ciridae-ui": "", children: [_jsx(MagnifyingGlassIcon, { className: icon({ size: "small" }) }), _jsx(BaseInput, { ...props, type: "search", disabled: disabled, className: cx(underlineText, className) }), onClear && (_jsx("button", { type: "button", className: clear, "aria-label": labels.common.clear, disabled: disabled, onClick: onClear, children: _jsx(XIcon, { className: icon({ size: "small" }) }) })), shortcut && !onClear && _jsx(ShortcutHint, { hotkey: shortcut })] }));
260
+ return (_jsxs("label", { className: cx(underline({ size, quiet }), wrapperClassName), "data-ciridae-ui": "", children: [_jsx(MagnifyingGlassIcon, { className: icon({ size: "small" }) }), _jsx(BaseInput, { ...props, type: "search", disabled: disabled, className: cx(underlineText, className) }), onClear && (_jsx("button", { type: "button", className: clear, "aria-label": labels.common.clear, disabled: disabled, onClick: onClear, children: _jsx(XIcon, { className: icon({ size: "small" }) }) })), shortcut && !onClear && _jsx(ShortcutHint, { boxed: true, hotkey: shortcut })] }));
250
261
  }
@@ -1,12 +1,16 @@
1
1
  import { type Hotkey } from "./hotkeys.js";
2
2
  import type { ComponentProps } from "react";
3
- /** One key as a 24px cap, such as the `K` after a search field. */
4
- export declare function Keycap({ className, ...props }: ComponentProps<"kbd">): import("react").JSX.Element;
3
+ /**
4
+ * One key as a 22px square cap, such as the `K` after a search field. A label of more than one
5
+ * character, such as `Esc`, takes the wider word cap on its own.
6
+ */
7
+ export declare function Keycap({ className, children, ...props }: ComponentProps<"kbd">): import("react").JSX.Element;
5
8
  /**
6
9
  * A hotkey as one quiet line, as the hotkeys layer writes it: `Mod` is "⌘" on a Mac and "Ctrl"
7
- * elsewhere, and a sequence's steps are spaced ("G I"). `Keycap` is the boxed key for a place
8
- * that wants it.
10
+ * elsewhere, and a sequence's steps are spaced ("G I"). `boxed` sets each key in its own
11
+ * `Keycap`, 2px apart, as a menu row or a search field shows it.
9
12
  */
10
- export declare function ShortcutHint({ hotkey, className, ...props }: ComponentProps<"kbd"> & {
13
+ export declare function ShortcutHint({ hotkey, boxed, className, ...props }: ComponentProps<"kbd"> & {
11
14
  hotkey: Hotkey;
15
+ boxed?: boolean;
12
16
  }): import("react").JSX.Element;
@@ -1,18 +1,44 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { css, cx } from "#primitives/css";
2
+ import { css, cva, cx } from "#primitives/css";
3
3
  import { detectPlatform, formatForDisplay } from "./hotkeys.js";
4
- const keycap = css({
5
- display: "grid",
6
- placeItems: "center",
7
- width: "control.shortcut",
8
- height: "control.shortcut",
9
- border: "1px solid token(colors.border.glass)",
10
- borderRadius: "keycap",
11
- background: "bg.hover",
12
- color: "fg.muted",
13
- fontSize: "body",
14
- fontFamily: "sans",
15
- lineHeight: "tight",
4
+ /*
5
+ * A key on the raised face: a glyph such as ⌘ or ↵ sits in a 22px square, and a word such as Esc
6
+ * in a wider cap. The edge is the canvas hairline.
7
+ */
8
+ const keycap = cva({
9
+ base: {
10
+ display: "inline-grid",
11
+ placeItems: "center",
12
+ flexShrink: "0",
13
+ width: "control.chip",
14
+ height: "control.chip",
15
+ paddingInline: "none",
16
+ border: "1px solid token(colors.border.canvas)",
17
+ borderRadius: "keycap",
18
+ background: "token(gradients.raised)",
19
+ color: "fg.primary",
20
+ fontFamily: "sans",
21
+ fontSize: "code",
22
+ fontWeight: "medium",
23
+ letterSpacing: "compact",
24
+ lineHeight: "tight",
25
+ whiteSpace: "nowrap",
26
+ "& > svg": { fontSize: "icon.small" },
27
+ },
28
+ variants: {
29
+ // A longer word than Esc, such as Shift, still widens the cap rather than spilling out of it.
30
+ label: {
31
+ glyph: {},
32
+ word: { width: "auto", minWidth: "control.wordKeycap", paddingInline: "control.iconGap" },
33
+ },
34
+ },
35
+ });
36
+ const keycaps = css({
37
+ display: "inline-flex",
38
+ alignItems: "center",
39
+ gap: "control.trackInset",
40
+ flexShrink: "0",
41
+ _phone: { display: "none" },
16
42
  });
17
43
  // A quiet inline hint, as a menu writes its shortcuts: no box, no extra height, and none on a
18
44
  // phone, which has no keyboard to press it on.
@@ -31,15 +57,27 @@ const shortcut = css({
31
57
  });
32
58
  // "⌘K" on a Mac, as a menu writes it; "Ctrl+K" elsewhere.
33
59
  const separatorToken = detectPlatform() === "mac" ? "" : undefined;
34
- /** One key as a 24px cap, such as the `K` after a search field. */
35
- export function Keycap({ className, ...props }) {
36
- return _jsx("kbd", { ...props, className: cx(keycap, className), "data-ciridae-ui": "" });
60
+ // Between the keys of a boxed hint, split apart again so each takes its own cap.
61
+ const keySeparator = "\n";
62
+ /**
63
+ * One key as a 22px square cap, such as the `K` after a search field. A label of more than one
64
+ * character, such as `Esc`, takes the wider word cap on its own.
65
+ */
66
+ export function Keycap({ className, children, ...props }) {
67
+ const holdsWord = typeof children === "string" && Array.from(children).length > 1;
68
+ return (_jsx("kbd", { ...props, className: cx(keycap({ label: holdsWord ? "word" : "glyph" }), className), "data-ciridae-ui": "", children: children }));
37
69
  }
38
70
  /**
39
71
  * A hotkey as one quiet line, as the hotkeys layer writes it: `Mod` is "⌘" on a Mac and "Ctrl"
40
- * elsewhere, and a sequence's steps are spaced ("G I"). `Keycap` is the boxed key for a place
41
- * that wants it.
72
+ * elsewhere, and a sequence's steps are spaced ("G I"). `boxed` sets each key in its own
73
+ * `Keycap`, 2px apart, as a menu row or a search field shows it.
42
74
  */
43
- export function ShortcutHint({ hotkey, className, ...props }) {
75
+ export function ShortcutHint({ hotkey, boxed = false, className, ...props }) {
76
+ if (boxed) {
77
+ const keys = formatForDisplay(hotkey, { separatorToken: keySeparator })
78
+ .split(/[\n ]/)
79
+ .filter((key) => key !== "");
80
+ return (_jsx("kbd", { ...props, dir: "ltr", "aria-label": formatForDisplay(hotkey, { useSymbols: false }), className: cx(keycaps, className), "data-ciridae-ui": "", children: keys.map((key, index) => (_jsx(Keycap, { "aria-hidden": "true", children: key }, index))) }));
81
+ }
44
82
  return (_jsx("kbd", { ...props, dir: "ltr", "aria-label": formatForDisplay(hotkey, { useSymbols: false }), className: cx(shortcut, className), "data-ciridae-ui": "", children: formatForDisplay(hotkey, { separatorToken }) }));
45
83
  }
@@ -58,7 +58,8 @@ const stage = css({
58
58
  height: "100%",
59
59
  border: "0",
60
60
  borderRadius: "action",
61
- background: "bg.card",
61
+ // Solid, so a document with no background of its own does not show the scrim through it.
62
+ background: "bg.surface",
62
63
  },
63
64
  });
64
65
  const step = css({
@@ -63,6 +63,9 @@ export declare const englishLabels: {
63
63
  label: string;
64
64
  previous: string;
65
65
  next: string;
66
+ /** The rows a page shows and the page size, from numbers already formatted. */
67
+ summary: (first: string, last: string, total: string) => string;
68
+ perPage: (count: string) => string;
66
69
  };
67
70
  calendar: {
68
71
  previousMonth: string;
@@ -76,6 +79,9 @@ export declare const englishLabels: {
76
79
  };
77
80
  commandMenu: {
78
81
  title: string;
82
+ /** The footer's key hints beside the close key. */
83
+ move: string;
84
+ open: string;
79
85
  };
80
86
  keyboard: {
81
87
  shortcuts: string;
@@ -230,6 +236,8 @@ export declare const englishLabels: {
230
236
  sendMessage: string;
231
237
  stopped: string;
232
238
  failed: string;
239
+ /** The title of a failed reply's notice. */
240
+ couldNotAnswer: string;
233
241
  notSent: string;
234
242
  typing: string;
235
243
  resize: string;
@@ -372,6 +380,8 @@ export declare const englishLabels: {
372
380
  runToGo: string;
373
381
  runDoneLabel: (count: number) => string;
374
382
  runToGoLabel: (count: number) => string;
383
+ /** The folded finished stages, with their count. */
384
+ runDoneShort: (count: number) => string;
375
385
  checklistProgress: (done: number, total: number) => string;
376
386
  };
377
387
  settings: {
@@ -50,7 +50,14 @@ export const englishLabels = {
50
50
  parentPages: "Parent pages",
51
51
  environment: (label) => `${label} environment`,
52
52
  },
53
- pagination: { label: "Pagination", previous: "Previous", next: "Next" },
53
+ pagination: {
54
+ label: "Pagination",
55
+ previous: "Previous",
56
+ next: "Next",
57
+ /** The rows a page shows and the page size, from numbers already formatted. */
58
+ summary: (first, last, total) => `Showing ${first} to ${last} of ${total}`,
59
+ perPage: (count) => `${count} per page`,
60
+ },
54
61
  calendar: { previousMonth: "Previous month", nextMonth: "Next month" },
55
62
  select: {
56
63
  placeholder: "Select…",
@@ -58,7 +65,12 @@ export const englishLabels = {
58
65
  searchLabel: "Search options",
59
66
  filters: "Filters",
60
67
  },
61
- commandMenu: { title: "Command menu" },
68
+ commandMenu: {
69
+ title: "Command menu",
70
+ /** The footer's key hints beside the close key. */
71
+ move: "Move",
72
+ open: "Open",
73
+ },
62
74
  keyboard: {
63
75
  shortcuts: "Keyboard shortcuts",
64
76
  /** The group of a shortcut registered without one. */
@@ -206,6 +218,8 @@ export const englishLabels = {
206
218
  sendMessage: "Send message",
207
219
  stopped: "Stopped",
208
220
  failed: "Failed",
221
+ /** The title of a failed reply's notice. */
222
+ couldNotAnswer: "Couldn't finish that answer",
209
223
  notSent: "Not sent",
210
224
  typing: "Typing",
211
225
  resize: "Resize",
@@ -342,6 +356,8 @@ export const englishLabels = {
342
356
  runToGo: "To go",
343
357
  runDoneLabel: (count) => `${count} stages done`,
344
358
  runToGoLabel: (count) => `${count} stages to go`,
359
+ /** The folded finished stages, with their count. */
360
+ runDoneShort: (count) => `${count} done`,
345
361
  checklistProgress: (done, total) => `${done} of ${total}`,
346
362
  },
347
363
  settings: { navigation: "Settings" },
@@ -0,0 +1,40 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ export type LogEntryProps = Omit<ComponentProps<"div">, "children"> & {
3
+ /** When it happened, epoch milliseconds, written as the time of day. */
4
+ at: number;
5
+ /** Who did it: a person's avatar, or a system's or an agent's icon. */
6
+ actor: ReactNode;
7
+ /** What happened, such as "The agent drafted remittance advice for INV-4799". */
8
+ headline: ReactNode;
9
+ /** A line more about it, such as what it waits on. */
10
+ detail?: ReactNode;
11
+ /** At the end of the row, such as a badge naming whether a person, a model or an agent did it. */
12
+ badge?: ReactNode;
13
+ /** `comfortable` for a page's log, `compact` for one line per entry, `panel` in a drawer. */
14
+ density?: "comfortable" | "compact" | "panel";
15
+ selected?: boolean;
16
+ /** A hairline under the row, between it and the next. */
17
+ divider?: boolean;
18
+ /** Makes the row a button, such as one that opens the entry beside the log. */
19
+ onSelect?: () => void;
20
+ };
21
+ /** One thing that happened, as a row of a log. */
22
+ export declare function LogEntry({ at, actor, headline, detail, badge, density, selected, divider, onSelect, className, ...props }: LogEntryProps): import("react").JSX.Element;
23
+ export type LogGroupHeaderProps = Omit<ComponentProps<"div">, "children"> & {
24
+ /** What the group is, such as "Today". */
25
+ label: ReactNode;
26
+ /** Beside it, quieter, such as the date. */
27
+ meta?: ReactNode;
28
+ };
29
+ /** A log's heading over a group of its rows, such as a day's: "Today", then "Oct 6". */
30
+ export declare function LogGroupHeader({ label, meta, className, ...props }: LogGroupHeaderProps): import("react").JSX.Element;
31
+ export type LogActorProps = {
32
+ /** Who or what acted, read out in place of the mark. */
33
+ name: string;
34
+ /** A person's photo; their initials stand in without one. */
35
+ src?: string | null;
36
+ /** For an agent, a sync or a rule: its icon in place of a person's photo or initials. */
37
+ icon?: ReactNode;
38
+ };
39
+ /** Who did a log entry's thing, as the mark for its `actor`. */
40
+ export declare function LogActor({ name, src, icon }: LogActorProps): import("react").JSX.Element;
@@ -0,0 +1,146 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * A record's activity as a log: a row per thing that happened, under a heading per day. A row is
4
+ * the time, who did it, what happened and a line more about it, and a trailing badge such as
5
+ * whether a person, a model or an agent did it. `comfortable` is a page's log, `compact` puts the
6
+ * line more beside what happened, and `panel` is the log in a drawer or side panel.
7
+ */
8
+ import { css, cx } from "#primitives/css";
9
+ import { Avatar } from "./data-display.js";
10
+ import { useCiridaeLocale } from "./locale.js";
11
+ const entry = css({
12
+ display: "flex",
13
+ alignItems: "flex-start",
14
+ gap: "layout.clusterGap",
15
+ width: "100%",
16
+ minWidth: "0",
17
+ paddingBlock: "log.entryBlock",
18
+ paddingInline: "log.entryInline",
19
+ color: "fg.primary",
20
+ _hover: { background: "token(gradients.rowHover)" },
21
+ "&[data-selected], &[data-selected]:hover": { background: "token(gradients.selected)" },
22
+ "&[role=button]": { cursor: "pointer" },
23
+ _focusVisible: { focusRing: "inset" },
24
+ "&[data-divider]": { borderBottom: "1px solid token(colors.border.divider)" },
25
+ "&[data-density=compact]": { alignItems: "center", paddingBlock: "log.entryCompactBlock" },
26
+ // A compact row's rule sits inside its padding, so it stays the kit's 40px with or without one.
27
+ "&[data-density=compact][data-divider]": {
28
+ paddingBottom: "calc(token(spacing.log.entryCompactBlock) - token(sizes.border.hairline))",
29
+ },
30
+ "&[data-density=panel]": {
31
+ paddingBlock: "log.entryPanelBlock",
32
+ paddingInline: "log.entryPanelInline",
33
+ },
34
+ // The time, the actor and the badge each sit in a line as tall as the actor, level with the
35
+ // headline beside them.
36
+ "& > [data-time], & > [data-actor], & > [data-badge]": {
37
+ display: "flex",
38
+ flexShrink: "0",
39
+ alignItems: "center",
40
+ height: "avatar.menu",
41
+ },
42
+ "& > [data-time]": {
43
+ width: "log.timeColumn",
44
+ color: "fg.subtle",
45
+ fontSize: "code",
46
+ fontWeight: "medium",
47
+ lineHeight: "tight",
48
+ fontVariantNumeric: "tabular-nums",
49
+ whiteSpace: "nowrap",
50
+ },
51
+ "& > [data-actor]": { justifyContent: "center", width: "avatar.menu" },
52
+ "& > [data-body]": {
53
+ display: "grid",
54
+ gridTemplateColumns: "minmax(0, 1fr)",
55
+ flex: "1",
56
+ minWidth: "0",
57
+ },
58
+ // One line as tall as the actor, ending in an ellipsis where the row is too narrow for it.
59
+ "& [data-headline]": {
60
+ display: "flex",
61
+ alignItems: "center",
62
+ minHeight: "avatar.menu",
63
+ fontSize: "body",
64
+ fontWeight: "medium",
65
+ lineHeight: "tight",
66
+ "& > span": {
67
+ minWidth: "0",
68
+ overflow: "hidden",
69
+ textOverflow: "ellipsis",
70
+ whiteSpace: "nowrap",
71
+ },
72
+ },
73
+ "& [data-detail]": {
74
+ color: "fg.label",
75
+ fontSize: "code",
76
+ fontWeight: "medium",
77
+ lineHeight: "log",
78
+ },
79
+ "&[data-density=compact] > [data-body]": {
80
+ display: "flex",
81
+ alignItems: "center",
82
+ gap: "layout.controlGap",
83
+ "& > *": { minWidth: "0", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
84
+ // Block, so a long headline ends in an ellipsis; its own line height, so it centres with
85
+ // the detail.
86
+ "& > [data-headline]": { display: "block", minHeight: "auto" },
87
+ "& > [data-detail]": { flex: "1", lineHeight: "tight" },
88
+ },
89
+ });
90
+ // The log's actor mark: a person's photo or initials, or an icon for an agent, a sync or a rule,
91
+ // on the raised glass circle. The icon is the small icon's size at every density.
92
+ const actorIcon = css({
93
+ display: "inline-grid",
94
+ placeItems: "center",
95
+ flexShrink: "0",
96
+ width: "avatar.menu",
97
+ height: "avatar.menu",
98
+ borderRadius: "round",
99
+ background: "token(gradients.raised)",
100
+ color: "fg.label",
101
+ "--edge": "token(gradients.border.raised)",
102
+ "& > svg": { fontSize: "icon.small" },
103
+ });
104
+ // Initials sit in the label grey here, where the avatar's own are the primary text colour; the
105
+ // extra attribute outranks the avatar's kind rule.
106
+ const actorPerson = css({ "&[data-ciridae-ui][data-kind=user]": { color: "fg.label" } });
107
+ const groupHeader = css({
108
+ display: "flex",
109
+ alignItems: "center",
110
+ gap: "layout.controlGap",
111
+ paddingTop: "panel.comfortable",
112
+ paddingInline: "panel.default",
113
+ paddingBottom: "layout.tightGap",
114
+ fontSize: "code",
115
+ fontWeight: "medium",
116
+ lineHeight: "tight",
117
+ "& > [data-label]": { color: "fg.primary" },
118
+ "& > [data-meta]": { color: "fg.subtle" },
119
+ });
120
+ /** One thing that happened, as a row of a log. */
121
+ export function LogEntry({ at, actor, headline, detail, badge, density = "comfortable", selected = false, divider = true, onSelect, className, ...props }) {
122
+ const { formatTime } = useCiridaeLocale();
123
+ const selectOnKey = (event) => {
124
+ if (event.target !== event.currentTarget || (event.key !== "Enter" && event.key !== " "))
125
+ return;
126
+ event.preventDefault();
127
+ onSelect?.();
128
+ };
129
+ return (_jsxs("div", { ...props, ...(onSelect && {
130
+ role: "button",
131
+ tabIndex: 0,
132
+ "aria-pressed": selected,
133
+ onClick: onSelect,
134
+ onKeyDown: selectOnKey,
135
+ }), className: cx(entry, className), "data-ciridae-ui": "", "data-density": density, "data-selected": selected || undefined, "data-divider": divider || undefined, children: [_jsx("time", { "data-time": "", dateTime: new Date(at).toISOString(), children: formatTime(at) }), _jsx("span", { "data-actor": "", children: actor }), _jsxs("div", { "data-body": "", children: [_jsx("span", { "data-headline": "", children: _jsx("span", { children: headline }) }), detail && _jsx("span", { "data-detail": "", children: detail })] }), badge && _jsx("span", { "data-badge": "", children: badge })] }));
136
+ }
137
+ /** A log's heading over a group of its rows, such as a day's: "Today", then "Oct 6". */
138
+ export function LogGroupHeader({ label, meta, className, ...props }) {
139
+ return (_jsxs("div", { ...props, className: cx(groupHeader, className), "data-ciridae-ui": "", children: [_jsx("span", { "data-label": "", children: label }), meta && _jsx("span", { "data-meta": "", children: meta })] }));
140
+ }
141
+ /** Who did a log entry's thing, as the mark for its `actor`. */
142
+ export function LogActor({ name, src, icon }) {
143
+ if (icon)
144
+ return (_jsx("span", { role: "img", "aria-label": name, className: actorIcon, "data-ciridae-ui": "", "data-edge": "gradient", children: icon }));
145
+ return _jsx(Avatar, { label: name, src: src, size: "menu", className: actorPerson });
146
+ }
@@ -244,7 +244,7 @@ const field = css({
244
244
  },
245
245
  "&[aria-disabled=true]": { cursor: "default", opacity: "0.5" },
246
246
  });
247
- /* The picker: a flush menu over the caret, its rows edge to edge. */
247
+ /* The picker: a flush menu over the caret on the popovers' glass, its rows edge to edge. */
248
248
  const picker = css({
249
249
  zIndex: "popover",
250
250
  display: "grid",
@@ -254,9 +254,10 @@ const picker = css({
254
254
  overflowY: "auto",
255
255
  border: "1px solid token(colors.border.control)",
256
256
  borderRadius: "action",
257
- background: "bg.card",
257
+ background: "bg.popover",
258
+ backdropFilter: "blur(token(blurs.popover))",
258
259
  color: "fg.primary",
259
- boxShadow: "card",
260
+ boxShadow: "soft",
260
261
  outline: "none",
261
262
  });
262
263
  const pickerRow = css({
@@ -12,7 +12,7 @@ export type MenuItem = {
12
12
  variant?: "default" | "danger";
13
13
  /** Shows a check mark, for the active choice in a set. */
14
14
  checked?: boolean;
15
- /** Muted after the label, such as what a choice covers. */
15
+ /** Muted at the row's end, such as what a choice covers. */
16
16
  meta?: string;
17
17
  /** The hotkey that does what the item does, at the row's end; the host binds it. */
18
18
  shortcut?: Hotkey;
@@ -59,8 +59,12 @@ export type MenuProps = Omit<ComponentProps<typeof BaseMenu.Root>, "children"> &
59
59
  items: readonly MenuItem[];
60
60
  /** Which edge of the trigger the menu lines up with; `end` keeps a right-hand menu inside its container. */
61
61
  align?: "start" | "end";
62
+ /** How far the menu floats from its trigger, in px. Defaults to the kit's 4. */
63
+ sideOffset?: number;
64
+ /** Makes the menu exactly as wide as its trigger rather than at least `overlay.menu`. */
65
+ matchTriggerWidth?: boolean;
62
66
  };
63
- export declare function Menu({ trigger, items, align, ...rootProps }: MenuProps): import("react").JSX.Element;
67
+ export declare function Menu({ trigger, items, align, sideOffset, matchTriggerWidth, ...rootProps }: MenuProps): import("react").JSX.Element;
64
68
  export type OverflowMenuProps = Omit<MenuProps, "trigger" | "align"> & {
65
69
  /** The button's accessible name, such as "Record actions" or "More for INV-4821". */
66
70
  label: string;