@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
@@ -9,55 +9,51 @@ import { HotkeyLayer } from "./hotkeys.js";
9
9
  import { icon } from "./icon.js";
10
10
  import { Eyebrow, heading } from "./layout.js";
11
11
  import { useCiridaeLocale } from "./locale.js";
12
- const backdrop = css({
13
- position: "fixed",
14
- inset: "none",
15
- zIndex: "drawer",
16
- background: "bg.backdrop",
17
- transitionProperty: "opacity",
18
- transitionDuration: "fast",
19
- "&[data-starting-style], &[data-ending-style]": { opacity: "0" },
20
- _motionReduce: { transitionDuration: "0ms" },
21
- });
22
12
  const viewport = css({ position: "fixed", inset: "none", zIndex: "drawer", pointerEvents: "none" });
13
+ /*
14
+ * A drawer sits beside the page, which stays usable: no scrim, docked to the window's right edge
15
+ * at full height, a glass shell holding the sheet. The shell's padding is what keeps the sheet off
16
+ * the window's edges, level with the main panel; a phone takes the sheet to the edges.
17
+ */
23
18
  const popup = cva({
24
19
  base: {
25
20
  position: "absolute",
26
- // Inset from the window by the shell's gap and rounded like the main panel, so a sheet reads
27
- // as the docked agent sheet does; a phone takes it to the edges, where rounding is a gap.
28
- insetBlock: "panel.compact",
29
- insetInlineEnd: "panel.compact",
21
+ insetBlock: "none",
22
+ insetInlineEnd: "none",
30
23
  display: "flex",
31
- overflow: "hidden",
24
+ padding: "panel.compact",
32
25
  pointerEvents: "auto",
33
26
  outline: "none",
34
27
  textStyle: "body",
35
- background: "bg.surface",
36
- border: "1px solid token(colors.border.control)",
37
- borderRadius: "action",
38
- boxShadow: "card",
39
- _phone: { insetBlock: "none", insetInlineEnd: "none", border: "0", borderRadius: "0" },
28
+ border: "1px solid token(colors.border.divider)",
29
+ borderRadius: "shell",
30
+ background: "bg.drawerShell",
31
+ backdropFilter: "blur(token(blurs.drawerShell))",
32
+ boxShadow: "drawerShell",
33
+ _phone: { padding: "none", border: "0", borderRadius: "0" },
40
34
  // Base UI drives live swipe drag through this custom property; the resting
41
35
  // transform must reference it or dragging won't visually track the pointer.
42
36
  transform: "translateX(var(--drawer-swipe-movement-x, 0px))",
43
37
  transitionProperty: "transform",
44
38
  transitionDuration: "default",
45
39
  "&[data-starting-style], &[data-ending-style]": {
46
- transform: "translateX(calc(100% + token(spacing.panel.compact)))",
47
- _rtl: { transform: "translateX(calc(-100% - token(spacing.panel.compact)))" },
40
+ transform: "translateX(100%)",
41
+ _rtl: { transform: "translateX(-100%)" },
48
42
  },
49
43
  "&[data-swiping]": { transitionDuration: "0ms" },
50
44
  _motionReduce: { transitionDuration: "0ms" },
51
45
  },
46
+ // The sheet's width, plus the shell's padding and its 1px edge on each side.
52
47
  variants: {
53
48
  size: {
54
- // A conversation or a short form, such as the agent sheet.
49
+ // The assistant and a preview.
55
50
  narrow: {
56
- width: "min(calc(100vw - token(spacing.panel.compact) * 2), token(sizes.overlay.sheetNarrow))",
51
+ width: "min(100vw, calc(token(sizes.overlay.sheetNarrow) + token(spacing.panel.compact) * 2 + 2px))",
57
52
  _phone: { width: "100vw" },
58
53
  },
54
+ // Create, edit and act.
59
55
  regular: {
60
- width: "min(calc(100vw - token(spacing.panel.compact) * 2), token(sizes.overlay.sheet))",
56
+ width: "min(100vw, calc(token(sizes.overlay.sheet) + token(spacing.panel.compact) * 2 + 2px))",
61
57
  _phone: { width: "100vw" },
62
58
  },
63
59
  // Near the full viewport, for content such as logs that needs the room.
@@ -72,26 +68,51 @@ const panel = css({
72
68
  minWidth: "0",
73
69
  minHeight: "0",
74
70
  flexDirection: "column",
71
+ overflow: "hidden",
72
+ border: "1px solid token(colors.border.divider)",
73
+ borderRadius: "action",
74
+ background: "bg.drawerSheet",
75
+ boxShadow: "soft",
75
76
  color: "fg.primary",
77
+ _phone: { border: "0", borderRadius: "0" },
76
78
  });
77
- // At least the app header's height, so on a desktop the sheet's header rule lines up with the main
78
- // panel's beside it: both start one shell gap from the window's top.
79
- const header = css({
80
- display: "flex",
81
- flexDirection: "column",
82
- justifyContent: "center",
83
- gap: "layout.contentGap",
84
- minHeight: "shell.header",
85
- paddingBlock: "layout.tightGap",
86
- paddingInlineStart: "panel.comfortable",
87
- paddingInlineEnd: "panel.default",
88
- borderBottom: "1px solid token(colors.border.control)",
79
+ // At least the app header's height, so on a desktop a title-only sheet's header rule lines up with
80
+ // the main panel's beside it: both start one shell gap from the window's top. Under a subtitle it
81
+ // takes the design's even 16px padding, its close button at the top. The narrow sheet's header is
82
+ // one line exactly the app header's height, its 34px round buttons centred in it.
83
+ const header = cva({
84
+ base: {
85
+ display: "flex",
86
+ flexDirection: "column",
87
+ justifyContent: "center",
88
+ gap: "layout.contentGap",
89
+ minHeight: "shell.header",
90
+ paddingBlock: "panel.default",
91
+ paddingInlineStart: "panel.comfortable",
92
+ paddingInlineEnd: "panel.default",
93
+ borderBottom: "1px solid token(colors.border.divider)",
94
+ "&:has([data-sheet-subtitle])": {
95
+ paddingBlock: "panel.comfortable",
96
+ "& > [data-header-line]": { alignItems: "flex-start" },
97
+ },
98
+ },
99
+ // The narrow header is the Ask Ciridae drawer's one line: 8px between its title and actions.
100
+ variants: {
101
+ size: {
102
+ narrow: { gap: "layout.controlGap", height: "shell.header", paddingBlock: "none" },
103
+ regular: { paddingInlineEnd: "panel.comfortable" },
104
+ large: { paddingInlineEnd: "panel.comfortable" },
105
+ },
106
+ },
89
107
  });
90
- const headerLine = css({
91
- display: "flex",
92
- alignItems: "center",
93
- justifyContent: "space-between",
94
- gap: "layout.contentGap",
108
+ const headerLine = cva({
109
+ base: {
110
+ display: "flex",
111
+ alignItems: "center",
112
+ justifyContent: "space-between",
113
+ gap: "layout.contentGap",
114
+ },
115
+ variants: { size: { narrow: { gap: "layout.controlGap" }, regular: {}, large: {} } },
95
116
  });
96
117
  const titleBlock = css({
97
118
  display: "flex",
@@ -107,12 +128,21 @@ const titleLine = css({
107
128
  gap: "layout.contentGap",
108
129
  minWidth: "0",
109
130
  });
110
- const titleStyle = cx(heading(), css({ minWidth: "0", lineHeight: "tight" }));
111
- // The sheet's mark before its title, muted like the agent sheet's sparkle.
131
+ const titleStyle = css({ minWidth: "0", lineHeight: "tight" });
132
+ const subtitleStyle = css({
133
+ margin: "none",
134
+ color: "fg.label",
135
+ fontSize: "code",
136
+ fontWeight: "medium",
137
+ letterSpacing: "compact",
138
+ lineHeight: "tight",
139
+ });
140
+ // The sheet's mark before its title, muted like the agent sheet's sparkle. A flex row of its own,
141
+ // so the heading stays the title's line high rather than an inline box's.
112
142
  const titleMark = css({
113
- display: "inline-flex",
143
+ display: "flex",
114
144
  alignItems: "center",
115
- gap: "control.iconGap",
145
+ gap: "layout.controlGap",
116
146
  minWidth: "0",
117
147
  "& > svg": { flexShrink: "0", color: "fg.muted" },
118
148
  });
@@ -154,10 +184,9 @@ const footer = css({
154
184
  alignItems: "center",
155
185
  justifyContent: "flex-end",
156
186
  flexWrap: "wrap",
157
- gap: "layout.controlGap",
187
+ gap: "layout.tightGap",
158
188
  padding: "panel.comfortable",
159
- borderTop: "1px solid token(colors.border.control)",
160
- background: "bg.card",
189
+ borderTop: "1px solid token(colors.border.divider)",
161
190
  });
162
191
  const footerNote = css({
163
192
  flex: "1",
@@ -166,12 +195,20 @@ const footerNote = css({
166
195
  fontWeight: "medium",
167
196
  letterSpacing: "compact",
168
197
  });
169
- const footerActions = css({ display: "flex", alignItems: "center", gap: "layout.controlGap" });
198
+ const footerActions = css({ display: "flex", alignItems: "center", gap: "layout.tightGap" });
199
+ /**
200
+ * A sheet docked at the window's right edge beside the page, which stays usable: it is not modal
201
+ * and a click outside leaves it open, so Escape or its close button closes it. Pass `modal` or
202
+ * `disablePointerDismissal` to change either.
203
+ */
170
204
  export function Drawer({ title, icon: titleIcon, eyebrow, subtitle, titleAccessory, actions, size = "regular", children, ...rootProps }) {
171
205
  const { labels, dir } = useCiridaeLocale();
172
- const close = (_jsx(BaseDrawer.Close, { render: _jsx(IconButton, { "aria-label": labels.common.close, variant: "ghost", size: "small" }), children: _jsx(XIcon, { className: icon({ size: "small" }) }) }));
206
+ // The narrow sheet's title is body size, as the app header's is, beside a 34px close.
207
+ const narrow = size === "narrow";
208
+ const titleClassName = cx(heading({ size: narrow ? "small" : "medium" }), titleStyle);
209
+ const close = (_jsx(BaseDrawer.Close, { render: _jsx(IconButton, { "aria-label": labels.common.close, size: narrow ? "medium" : "small" }), children: _jsx(XIcon, { className: icon({ size: "small" }) }) }));
173
210
  const titleContent = titleIcon ? (_jsxs("span", { className: titleMark, children: [titleIcon, title] })) : (title);
174
- return (_jsx(BaseDrawer.Root, { ...rootProps, swipeDirection: dir === "rtl" ? "left" : "right", children: _jsx(BaseDrawer.Portal, { children: _jsxs(HotkeyLayer, { children: [_jsx(BaseDrawer.Backdrop, { className: backdrop, "data-ciridae-ui": "" }), _jsx(BaseDrawer.Viewport, { className: viewport, children: _jsx(BaseDrawer.Popup, { className: popup({ size }), "data-ciridae-ui": "", children: _jsxs("div", { className: panel, children: [_jsx("header", { className: header, children: eyebrow ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: eyebrowLine, children: [eyebrow, _jsxs("div", { className: headerActions, children: [actions, close] })] }), _jsxs("div", { className: css({ display: "grid", gap: "layout.controlGap" }), children: [_jsx(BaseDrawer.Title, { className: displayTitle, children: title }), subtitle && _jsx("p", { className: displaySubtitle, children: subtitle })] })] })) : (_jsxs("div", { className: headerLine, children: [_jsxs("div", { className: titleBlock, children: [titleAccessory ? (_jsxs("div", { className: titleLine, children: [_jsx(BaseDrawer.Title, { className: titleStyle, children: titleContent }), titleAccessory] })) : (_jsx(BaseDrawer.Title, { className: titleStyle, children: titleContent })), subtitle && _jsx(Eyebrow, { as: "p", children: subtitle })] }), _jsxs("div", { className: headerActions, children: [actions, close] })] })) }), children] }) }) })] }) }) }));
211
+ return (_jsx(BaseDrawer.Root, { modal: false, disablePointerDismissal: true, ...rootProps, swipeDirection: dir === "rtl" ? "left" : "right", children: _jsx(BaseDrawer.Portal, { children: _jsx(HotkeyLayer, { children: _jsx(BaseDrawer.Viewport, { className: viewport, children: _jsx(BaseDrawer.Popup, { className: popup({ size }), "data-ciridae-ui": "", children: _jsxs("div", { className: panel, children: [_jsx("header", { className: header({ size }), children: eyebrow ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: eyebrowLine, children: [eyebrow, _jsxs("div", { className: headerActions, children: [actions, close] })] }), _jsxs("div", { className: css({ display: "grid", gap: "layout.controlGap" }), children: [_jsx(BaseDrawer.Title, { className: displayTitle, children: title }), subtitle && _jsx("p", { className: displaySubtitle, children: subtitle })] })] })) : (_jsxs("div", { className: headerLine({ size }), "data-header-line": "", children: [_jsxs("div", { className: titleBlock, children: [titleAccessory ? (_jsxs("div", { className: titleLine, children: [_jsx(BaseDrawer.Title, { className: titleClassName, children: titleContent }), titleAccessory] })) : (_jsx(BaseDrawer.Title, { className: titleClassName, children: titleContent })), subtitle && (_jsx("p", { className: subtitleStyle, "data-sheet-subtitle": "", children: subtitle }))] }), _jsxs("div", { className: headerActions, children: [actions, close] })] })) }), children] }) }) }) }) }) }));
175
212
  }
176
213
  export function DrawerBody({ flush = false, className, ...props }) {
177
214
  return (_jsx(BaseDrawer.Content, { ...props, className: cx(body({ flush }), className), "data-ciridae-ui": "" }));
@@ -186,7 +223,7 @@ export function DrawerFooter({ note, className, children, ...props }) {
186
223
  const field = css({
187
224
  display: "flex",
188
225
  flexDirection: "column",
189
- gap: "control.iconGap",
226
+ gap: "layout.controlGap",
190
227
  minWidth: "0",
191
228
  });
192
229
  const fieldLabelRow = css({
@@ -198,7 +235,7 @@ const fieldLabelRow = css({
198
235
  letterSpacing: "compact",
199
236
  lineHeight: "tight",
200
237
  });
201
- const fieldHint = css({ color: "fg.muted" });
238
+ const fieldHint = css({ color: "fg.subtle" });
202
239
  const fieldError = css({
203
240
  margin: "none",
204
241
  color: "accent.red",
@@ -65,16 +65,23 @@ export type EditableFieldProps = Omit<ComponentProps<"div">, "children"> & {
65
65
  badge?: ReactNode;
66
66
  /** Under the value while the row is idle. */
67
67
  note?: ReactNode;
68
- /** The field needs the reader: the row lifts and this button sits at its end. */
68
+ /** The field needs the reader: the row lifts on the selected glass and this button sits at its end. */
69
69
  action?: {
70
70
  label: ReactNode;
71
71
  onClick: () => void;
72
72
  disabled?: boolean;
73
73
  };
74
+ /** `bottom` by default; `none` for the last row of a list whose container draws its own edge. */
75
+ divider?: "bottom" | "none";
76
+ /**
77
+ * `selected` washes the row with the selected glass, such as the field a side panel is
78
+ * showing; `inset` sets it into the list between hairlines, such as a field set apart.
79
+ */
80
+ emphasis?: "selected" | "inset" | "highlighted";
74
81
  edit?: FieldEdit;
75
82
  labels?: Partial<CiridaeLabels["editableField"]>;
76
83
  };
77
- export declare function EditableField({ label, value, provenance, badge, note, action, edit, labels: ownLabels, className, ...props }: EditableFieldProps): import("react").JSX.Element;
84
+ export declare function EditableField({ label, value, provenance, badge, note, action, divider, emphasis, edit, labels: ownLabels, className, ...props }: EditableFieldProps): import("react").JSX.Element;
78
85
  export type ConflictingSource = {
79
86
  id: string;
80
87
  label: ReactNode;
@@ -92,8 +99,10 @@ export type SourceConflictProps = Omit<ComponentProps<"div">, "children"> & {
92
99
  };
93
100
  /** At the effective row's end, such as a Resolve button. */
94
101
  action?: ReactNode;
102
+ /** `highlighted` while the agent points at the field; `neutral` by default. */
103
+ tone?: "highlighted" | "neutral";
95
104
  labels?: Partial<{
96
105
  effectiveValue: string;
97
106
  }>;
98
107
  };
99
- export declare function SourceConflict({ sources, effective, action, labels: ownLabels, className, ...props }: SourceConflictProps): import("react").JSX.Element;
108
+ export declare function SourceConflict({ sources, effective, action, tone, labels: ownLabels, className, ...props }: SourceConflictProps): import("react").JSX.Element;
@@ -10,6 +10,7 @@ import { useEffect, useId, useRef, useState, } from "react";
10
10
  import { css, cx } from "#primitives/css";
11
11
  import { Button } from "./button.js";
12
12
  import { Calendar } from "./calendar.js";
13
+ import { glassMenuSurface } from "./glass-menu.js";
13
14
  import { useHotkey } from "./hotkeys.js";
14
15
  import { icon, Spinner } from "./icon.js";
15
16
  import { Input } from "./input.js";
@@ -18,24 +19,48 @@ import { Tooltip } from "./overlays.js";
18
19
  import { Combobox } from "./select.js";
19
20
  /* The row stacks its label over the value in a narrow box, so each field is its own container. */
20
21
  const fieldBox = css({ containerType: "inline-size" });
22
+ /*
23
+ * The kit's Detail field: 12 · 16 padding, a hairline under it. A field that needs the reader
24
+ * (the inline edit's Needs confirm) and an inset row wear the selected glass between the selected
25
+ * edge's hairlines; a selected row wears the glass alone; a highlighted row, while the agent points
26
+ * at it, is the flat yellow band between hairlines.
27
+ */
21
28
  const fieldRow = css({
22
29
  position: "relative",
23
30
  display: "grid",
24
31
  gridTemplateColumns: "150px minmax(0, 1fr) auto",
25
- alignItems: "center",
32
+ // The label sits on the value's first line, as the kit's does; the trailing control centres.
33
+ alignItems: "baseline",
26
34
  gap: "layout.contentGap",
27
35
  paddingBlock: "panel.default",
28
36
  paddingInline: "panel.comfortable",
29
- borderBottom: "1px solid token(colors.border.panel)",
37
+ borderBottom: "1px solid token(colors.border.divider)",
30
38
  fontSize: "caption",
31
39
  letterSpacing: "compact",
40
+ "& > :last-child": { alignSelf: "center" },
32
41
  "&[data-needs]": {
33
- background: "bg.raised",
34
- borderBlock: "1px solid token(colors.border.glass)",
42
+ background: "token(gradients.selected)",
43
+ borderBlock: "1px solid",
44
+ borderImage: "token(gradients.border.selected) 1",
45
+ },
46
+ // After the band's hairlines, so a last highlighted row keeps only its top one, as the kit's.
47
+ "&[data-divider=none]": { borderBottom: "0" },
48
+ "&[data-emphasis=selected]": { background: "token(gradients.selected)" },
49
+ "&[data-emphasis=inset]": {
50
+ background: "token(gradients.selected)",
51
+ borderBlock: "1px solid",
52
+ borderImage: "token(gradients.border.selected) 1",
53
+ },
54
+ // The kit's Highlighted detail field: the yellow band while the agent points at the field.
55
+ "&[data-emphasis=highlighted]": {
56
+ background: "bg.warningTint",
57
+ borderBlock: "1px solid token(colors.border.divider)",
58
+ },
59
+ "&[data-editable]": {
60
+ cursor: "pointer",
61
+ _hover: { background: "token(gradients.card)", _light: { background: "bg.rowHover" } },
35
62
  },
36
- "&[data-editable]": { cursor: "pointer", _hover: { background: "bg.rowHover" } },
37
63
  "&[data-editable]:is(:hover, :focus-within) [data-affordance], &[data-editing] [data-affordance]": { opacity: "1" },
38
- "&[data-editable]:hover > [data-label]": { color: "fg.secondary" },
39
64
  "@container (max-width: 520px)": {
40
65
  gridTemplateColumns: "minmax(0, 1fr) auto",
41
66
  rowGap: "layout.tightGap",
@@ -43,10 +68,10 @@ const fieldRow = css({
43
68
  },
44
69
  });
45
70
  const fieldLabel = css({
46
- color: "fg.muted",
71
+ color: "fg.secondary",
47
72
  fontSize: "code",
48
73
  fontWeight: "medium",
49
- "[data-needs] > &": { color: "fg.secondary" },
74
+ lineHeight: "tight",
50
75
  });
51
76
  const fieldValue = css({
52
77
  display: "flex",
@@ -101,9 +126,10 @@ const provenanceLine = css({
101
126
  gap: "layout.contentGap",
102
127
  marginTop: "control.iconGap",
103
128
  minHeight: "control.knob",
104
- color: "fg.muted",
129
+ color: "fg.label",
105
130
  fontSize: "code",
106
131
  fontWeight: "medium",
132
+ lineHeight: "tight",
107
133
  "& > span, & > button, & > a": {
108
134
  display: "inline-flex",
109
135
  alignItems: "center",
@@ -133,31 +159,29 @@ const fieldNote = css({
133
159
  color: "fg.secondary",
134
160
  fontSize: "code",
135
161
  fontWeight: "medium",
162
+ lineHeight: "tight",
136
163
  });
137
- const editor = css({ width: "200px", maxWidth: "100%", fontSize: "body" });
164
+ const editor = css({ width: "200px", maxWidth: "100%" });
138
165
  const editorValue = css({ position: "relative", display: "flex", alignItems: "center" });
139
166
  const tooltipTitle = css({ display: "block", fontWeight: "medium" });
140
167
  const tooltipDetail = css({ display: "block", fontSize: "code", opacity: "0.8" });
141
168
  const trailingActions = css({ display: "inline-flex", gap: "layout.tightGap" });
142
- /* The calendar opens above the date editor. */
143
- const calendarPicker = css({
169
+ /* The calendar opens above the date editor, on the menus' glass sheet. */
170
+ const calendarPicker = css(glassMenuSurface, {
144
171
  position: "absolute",
145
172
  bottom: "calc(100% + token(spacing.control.iconGap))",
146
173
  insetInlineStart: "0",
147
174
  zIndex: "popover",
148
175
  cursor: "default",
149
- border: "1px solid token(colors.border.control)",
150
- borderRadius: "inset",
151
- background: "bg.card",
152
- boxShadow: "card",
153
- "& > section": { border: "0", background: "transparent" },
154
176
  });
177
+ // Under the days, inset as the calendar is, such as the value it had before.
155
178
  const calendarFooter = css({
156
- paddingInline: "panel.default",
179
+ paddingInline: "panel.compact",
157
180
  paddingBottom: "panel.compact",
158
181
  color: "fg.muted",
159
182
  fontSize: "code",
160
183
  fontWeight: "medium",
184
+ lineHeight: "body",
161
185
  });
162
186
  /** An editor opens with its text selected, so typing replaces the value. */
163
187
  function selectTyped(event) {
@@ -167,7 +191,7 @@ function selectTyped(event) {
167
191
  function localMidnight(date) {
168
192
  return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
169
193
  }
170
- export function EditableField({ label, value, provenance, badge, note, action, edit, labels: ownLabels, className, ...props }) {
194
+ export function EditableField({ label, value, provenance, badge, note, action, divider = "bottom", emphasis, edit, labels: ownLabels, className, ...props }) {
171
195
  const locale = useCiridaeLocale();
172
196
  const labels = { ...locale.labels.editableField, ...ownLabels };
173
197
  const formatDate = (timestamp) => locale.formatDate(timestamp);
@@ -307,7 +331,7 @@ export function EditableField({ label, value, provenance, badge, note, action, e
307
331
  cancel();
308
332
  } }) }) }));
309
333
  }
310
- return (_jsxs("span", { className: editorValue, onFocus: selectTyped, children: [_jsx(Input, { ref: inputRef, className: editor, "aria-labelledby": labelId, "aria-invalid": status.mode === "error" || undefined, value: draft, onChange: (event) => {
334
+ return (_jsxs("span", { className: editorValue, onFocus: selectTyped, children: [_jsx(Input, { ref: inputRef, size: "inline", className: editor, "aria-labelledby": labelId, "aria-invalid": status.mode === "error" || undefined, value: draft, onChange: (event) => {
311
335
  setDraft(event.target.value);
312
336
  if (status.mode === "error")
313
337
  setStatus({ mode: "editing" });
@@ -364,18 +388,24 @@ export function EditableField({ label, value, provenance, badge, note, action, e
364
388
  }
365
389
  return _jsx("span", {});
366
390
  };
367
- return (_jsx("div", { ...props, className: cx(fieldBox, className), "data-ciridae-ui": "", children: _jsxs("div", { ref: rowRef, className: fieldRow, "data-needs": action && !editing ? "" : undefined, "data-editable": editable && !editing ? "" : undefined, "data-editing": editing ? "" : undefined, onClick: begin, children: [_jsx("span", { className: fieldLabel, id: labelId, "data-label": "", children: label }), _jsxs("span", { className: css({ minWidth: "0" }), children: [valueLine(), _jsx("span", { role: "status", children: statusLine() }), note !== undefined && status.mode === "idle" && (_jsx("span", { className: fieldNote, children: note }))] }), trailing()] }) }));
391
+ return (_jsx("div", { ...props, className: cx(fieldBox, className), "data-ciridae-ui": "", children: _jsxs("div", { ref: rowRef, className: fieldRow, "data-needs": action && !editing ? "" : undefined, "data-divider": divider, "data-emphasis": emphasis, "data-editable": editable && !editing ? "" : undefined, "data-editing": editing ? "" : undefined, onClick: begin, children: [_jsx("span", { className: fieldLabel, id: labelId, "data-label": "", children: label }), _jsxs("span", { className: css({ minWidth: "0" }), children: [valueLine(), _jsx("span", { role: "status", children: statusLine() }), note !== undefined && status.mode === "idle" && (_jsx("span", { className: fieldNote, children: note }))] }), trailing()] }) }));
368
392
  }
369
- /* Source conflict: each source's value under a disputed field, then the value in effect. */
393
+ /*
394
+ * Source conflict: each source's value under a disputed field, then the value in effect. The
395
+ * kit's Contested sources: flat and square between hairlines, the yellow band while the agent
396
+ * points at the field, the panel's face otherwise (the grey well in light).
397
+ */
370
398
  const conflictBlock = css({
371
399
  display: "grid",
372
400
  gap: "layout.controlGap",
373
401
  paddingBlock: "panel.default",
374
402
  paddingInline: "panel.comfortable",
375
- borderBottom: "1px solid token(colors.border.panel)",
376
- background: "bg.panel",
403
+ borderBlock: "1px solid token(colors.border.divider)",
377
404
  letterSpacing: "compact",
405
+ lineHeight: "tight",
378
406
  containerType: "inline-size",
407
+ "&[data-tone=neutral]": { background: "bg.panel", _light: { background: "bg.field" } },
408
+ "&[data-tone=highlighted]": { background: "bg.warningTint" },
379
409
  });
380
410
  const conflictRow = css({
381
411
  display: "grid",
@@ -406,7 +436,7 @@ const sourceMeta = css({
406
436
  });
407
437
  const effectiveRow = css({
408
438
  paddingTop: "panel.default",
409
- borderTop: "1px solid token(colors.border.panel)",
439
+ borderTop: "1px solid token(colors.border.divider)",
410
440
  });
411
441
  const effectiveLabel = css({ fontSize: "code", fontWeight: "medium" });
412
442
  const effectiveNote = css({
@@ -416,7 +446,7 @@ const effectiveNote = css({
416
446
  fontSize: "code",
417
447
  fontWeight: "medium",
418
448
  });
419
- export function SourceConflict({ sources, effective, action, labels: ownLabels, className, ...props }) {
449
+ export function SourceConflict({ sources, effective, action, tone = "neutral", labels: ownLabels, className, ...props }) {
420
450
  const labels = { ...useCiridaeLocale().labels.editableField, ...ownLabels };
421
- return (_jsxs("div", { ...props, className: cx(conflictBlock, className), "data-ciridae-ui": "", children: [sources.map((source) => (_jsxs("div", { className: conflictRow, children: [_jsxs("span", { className: sourceName, children: [source.icon, source.label] }), _jsx("span", { className: sourceValue, children: source.value }), _jsx("span", { className: sourceMeta, children: source.meta })] }, source.id))), _jsxs("div", { className: cx(conflictRow, effectiveRow), children: [_jsx("span", { className: effectiveLabel, children: labels.effectiveValue }), _jsxs("span", { children: [_jsx("span", { className: sourceValue, children: effective.value }), effective.note !== undefined && _jsx("span", { className: effectiveNote, children: effective.note })] }), action ?? _jsx("span", {})] })] }));
451
+ return (_jsxs("div", { ...props, className: cx(conflictBlock, className), "data-ciridae-ui": "", "data-tone": tone, children: [sources.map((source) => (_jsxs("div", { className: conflictRow, children: [_jsxs("span", { className: sourceName, children: [source.icon, source.label] }), _jsx("span", { className: sourceValue, children: source.value }), _jsx("span", { className: sourceMeta, children: source.meta })] }, source.id))), _jsxs("div", { className: cx(conflictRow, effectiveRow), children: [_jsx("span", { className: effectiveLabel, children: labels.effectiveValue }), _jsxs("span", { children: [_jsx("span", { className: sourceValue, children: effective.value }), effective.note !== undefined && _jsx("span", { className: effectiveNote, children: effective.note })] }), action ?? _jsx("span", {})] })] }));
422
452
  }
@@ -2,18 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { css, cx } from "#primitives/css";
3
3
  import { Chip } from "./chip.js";
4
4
  import { useFieldControl } from "./field-context.js";
5
+ // The field face `Input` wears, holding chips that wrap onto more lines as they fill it.
5
6
  const emailField = css({
6
7
  display: "flex",
7
8
  flexWrap: "wrap",
8
9
  alignItems: "center",
9
10
  gap: "layout.tightGap",
10
11
  padding: "control.compactBlock",
11
- minHeight: "control.compact",
12
+ minHeight: "control.default",
12
13
  border: "1px solid token(colors.border.control)",
13
- borderRadius: "field",
14
- background: "bg.control",
14
+ borderRadius: "control",
15
+ background: "bg.formField",
15
16
  _focusWithin: { focusRing: "outer" },
16
- "&[data-invalid]": { borderColor: "accent.red" },
17
+ "&[data-invalid]": { borderColor: "border.error" },
17
18
  "&[data-disabled]": { opacity: "disabled" },
18
19
  });
19
20
  const draftInput = css({
@@ -28,7 +29,7 @@ const draftInput = css({
28
29
  height: "control.shortcut",
29
30
  paddingInline: "control.compactInline",
30
31
  outline: "none",
31
- _placeholder: { color: "fg.muted" },
32
+ _placeholder: { color: "fg.subtle" },
32
33
  _focusVisible: { outline: "none", boxShadow: "none" },
33
34
  });
34
35
  /** Comma, semicolon, whitespace, Enter, Tab, or blur commits addresses; duplicates are ignored. */
@@ -9,5 +9,5 @@ export type EnvironmentFrameProps = ComponentProps<"div"> & {
9
9
  /** The strip's tint: `warning` for a staging build, `info` for dev. */
10
10
  tone?: SiteBannerProps["tone"];
11
11
  };
12
- /** A persistent environment strip and an inset AppShell viewport. */
12
+ /** An environment strip, which the reader can dismiss for the session, and an inset AppShell viewport. */
13
13
  export declare function EnvironmentFrame({ label, tone, children, className, ...props }: EnvironmentFrameProps): import("react").JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
2
3
  import { css, cx } from "#primitives/css";
3
4
  import { useCiridaeLocale } from "./locale.js";
4
5
  import { SiteBanner } from "./site-banner.js";
@@ -17,26 +18,56 @@ const viewport = css({
17
18
  flex: "1",
18
19
  minHeight: "0",
19
20
  overflow: "hidden",
20
- background: "bg.canvas",
21
+ background: "token(gradients.canvas)",
21
22
  // Only under the environment strip does the app read as inset below it.
22
23
  "&[data-under-banner]": {
23
24
  borderTopRadius: "field",
24
25
  boxShadow: "inset 0 2px 4px rgba(0, 0, 0, 0.18)",
25
26
  },
26
- // Keep the shell and its phone layout within the space below the site banner.
27
+ // Keep the shell and its phone layout within the space below the site banner. On a desktop the
28
+ // shell's row sizes the main panel inside its block margins, so a full height would push it past
29
+ // them; a phone's main panel is a full window tall, so there it takes the space under the strip.
27
30
  "& > [data-ciridae-ui]": { height: "100%", background: "transparent" },
28
31
  "& > [data-ciridae-ui] > main": {
29
- height: "100%",
30
32
  borderRadius: "inset",
31
- _phone: { borderRadius: "0" },
33
+ _phone: { borderRadius: "0", height: "100%" },
32
34
  },
33
35
  "& > [data-ciridae-ui] > main > header": {
34
36
  paddingInlineStart: "page.inline",
35
37
  _phone: { paddingInlineStart: "panel.default" },
36
38
  },
37
39
  });
38
- /** A persistent environment strip and an inset AppShell viewport. */
40
+ /*
41
+ * Dismissing the strip hides it for the rest of the browser tab's session. Where the browser blocks
42
+ * storage, such as a sandboxed frame or site data turned off, even reading `sessionStorage` throws;
43
+ * the strip then shows on each load and a dismissal lasts until the page reloads.
44
+ */
45
+ const dismissedStorageKey = "ciridae-ui-environment-banner-dismissed";
46
+ function readDismissedForSession() {
47
+ try {
48
+ return sessionStorage.getItem(dismissedStorageKey) === "1";
49
+ }
50
+ catch (error) {
51
+ console.warn(`Showing the environment strip: could not read ${dismissedStorageKey}`, error);
52
+ return false;
53
+ }
54
+ }
55
+ function rememberDismissedForSession() {
56
+ try {
57
+ sessionStorage.setItem(dismissedStorageKey, "1");
58
+ }
59
+ catch (error) {
60
+ console.warn(`Could not save ${dismissedStorageKey}; the strip stays hidden until the page reloads`, error);
61
+ }
62
+ }
63
+ /** An environment strip, which the reader can dismiss for the session, and an inset AppShell viewport. */
39
64
  export function EnvironmentFrame({ label, tone = "info", children, className, ...props }) {
40
65
  const { labels } = useCiridaeLocale();
41
- return (_jsxs("div", { ...props, className: cx(frame, className), children: [label !== undefined && (_jsx(SiteBanner, { className: environmentBanner, tone: tone, children: typeof label === "string" ? labels.shell.environment(label) : label })), _jsx("div", { className: viewport, "data-under-banner": label !== undefined || undefined, children: children })] }));
66
+ const [dismissed, setDismissed] = useState(readDismissedForSession);
67
+ const shown = label !== undefined && !dismissed;
68
+ const dismiss = () => {
69
+ rememberDismissedForSession();
70
+ setDismissed(true);
71
+ };
72
+ return (_jsxs("div", { ...props, className: cx(frame, className), children: [shown && (_jsx(SiteBanner, { className: environmentBanner, tone: tone, onDismiss: dismiss, children: typeof label === "string" ? labels.shell.environment(label) : label })), _jsx("div", { className: viewport, "data-under-banner": shown || undefined, children: children })] }));
42
73
  }