@ciridae/ui 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/chat/conversation-source.d.ts +15 -1
  2. package/dist/chat/describe-tool-call.d.ts +3 -3
  3. package/dist/chat/describe-tool-call.js +3 -3
  4. package/dist/chat/message-text.js +2 -2
  5. package/dist/ciridae.css +16 -4
  6. package/dist/components/activity-feed.d.ts +1 -0
  7. package/dist/components/activity-feed.js +1 -0
  8. package/dist/components/app-frame.d.ts +13 -1
  9. package/dist/components/app-frame.js +45 -3
  10. package/dist/components/app-navigation.d.ts +28 -1
  11. package/dist/components/app-navigation.js +110 -14
  12. package/dist/components/badge.d.ts +29 -4
  13. package/dist/components/badge.js +134 -15
  14. package/dist/components/bulk-action-bar.js +3 -3
  15. package/dist/components/button.d.ts +10 -3
  16. package/dist/components/button.js +77 -44
  17. package/dist/components/calendar.js +44 -17
  18. package/dist/components/centered-page.js +2 -2
  19. package/dist/components/chart.d.ts +1 -1
  20. package/dist/components/chart.js +16 -11
  21. package/dist/components/chat-client-source.js +47 -6
  22. package/dist/components/chat-sheet-controller.d.ts +1 -0
  23. package/dist/components/chat-sheet-controller.js +1 -0
  24. package/dist/components/chat-sheet.d.ts +10 -2
  25. package/dist/components/chat-sheet.js +74 -37
  26. package/dist/components/chat.d.ts +90 -14
  27. package/dist/components/chat.js +401 -101
  28. package/dist/components/checkbox-item.d.ts +5 -2
  29. package/dist/components/checkbox-item.js +9 -5
  30. package/dist/components/chip.d.ts +5 -0
  31. package/dist/components/chip.js +27 -1
  32. package/dist/components/column-settings.js +1 -1
  33. package/dist/components/command-menu.d.ts +5 -1
  34. package/dist/components/command-menu.js +98 -32
  35. package/dist/components/command-palette.d.ts +6 -2
  36. package/dist/components/command-palette.js +20 -10
  37. package/dist/components/data-display.d.ts +35 -4
  38. package/dist/components/data-display.js +138 -19
  39. package/dist/components/data-grid.d.ts +14 -2
  40. package/dist/components/data-grid.js +61 -26
  41. package/dist/components/date-range-picker.js +9 -20
  42. package/dist/components/drawer.d.ts +5 -0
  43. package/dist/components/drawer.js +91 -54
  44. package/dist/components/editable-field.d.ts +17 -3
  45. package/dist/components/editable-field.js +57 -27
  46. package/dist/components/email-input.js +6 -5
  47. package/dist/components/environment-frame.d.ts +1 -1
  48. package/dist/components/environment-frame.js +37 -6
  49. package/dist/components/filter-bar.js +39 -28
  50. package/dist/components/form-controls.d.ts +9 -0
  51. package/dist/components/form-controls.js +46 -22
  52. package/dist/components/form-layout.d.ts +7 -3
  53. package/dist/components/form-layout.js +68 -23
  54. package/dist/components/glass-menu.d.ts +3 -0
  55. package/dist/components/glass-menu.js +63 -0
  56. package/dist/components/gradient-edge.d.ts +9 -0
  57. package/dist/components/gradient-edge.js +31 -0
  58. package/dist/components/input.d.ts +4 -2
  59. package/dist/components/input.js +26 -15
  60. package/dist/components/keycap.d.ts +9 -5
  61. package/dist/components/keycap.js +57 -19
  62. package/dist/components/lightbox.js +2 -1
  63. package/dist/components/locale.d.ts +10 -0
  64. package/dist/components/locale.js +18 -2
  65. package/dist/components/log-entry.d.ts +40 -0
  66. package/dist/components/log-entry.js +146 -0
  67. package/dist/components/mention-input.js +4 -3
  68. package/dist/components/menu.d.ts +6 -2
  69. package/dist/components/menu.js +34 -37
  70. package/dist/components/meter.d.ts +9 -1
  71. package/dist/components/meter.js +64 -6
  72. package/dist/components/navigation.d.ts +21 -4
  73. package/dist/components/navigation.js +84 -19
  74. package/dist/components/notification-tray.js +5 -4
  75. package/dist/components/overlays.d.ts +3 -1
  76. package/dist/components/overlays.js +69 -40
  77. package/dist/components/page-frame.js +1 -1
  78. package/dist/components/panel.d.ts +1 -1
  79. package/dist/components/panel.js +18 -12
  80. package/dist/components/radio-card.js +10 -9
  81. package/dist/components/record.d.ts +25 -1
  82. package/dist/components/record.js +147 -18
  83. package/dist/components/segmented-bar.d.ts +3 -1
  84. package/dist/components/segmented-bar.js +16 -6
  85. package/dist/components/select.d.ts +5 -2
  86. package/dist/components/select.js +103 -127
  87. package/dist/components/shell-skeleton.js +1 -1
  88. package/dist/components/shell.d.ts +2 -2
  89. package/dist/components/shell.js +43 -45
  90. package/dist/components/sidebar.d.ts +7 -2
  91. package/dist/components/sidebar.js +55 -27
  92. package/dist/components/single-line-text.d.ts +2 -0
  93. package/dist/components/single-line-text.js +16 -0
  94. package/dist/components/site-banner.d.ts +5 -2
  95. package/dist/components/site-banner.js +17 -6
  96. package/dist/components/skeleton.d.ts +7 -6
  97. package/dist/components/skeleton.js +28 -16
  98. package/dist/components/skeletons.d.ts +12 -0
  99. package/dist/components/skeletons.js +71 -8
  100. package/dist/components/stages.d.ts +78 -6
  101. package/dist/components/stages.js +512 -156
  102. package/dist/components/stat-card.d.ts +10 -2
  103. package/dist/components/stat-card.js +92 -37
  104. package/dist/components/status-banner.js +1 -1
  105. package/dist/components/tabs.js +26 -13
  106. package/dist/components/theme-toggle.d.ts +4 -0
  107. package/dist/components/theme-toggle.js +67 -0
  108. package/dist/components/toast.d.ts +9 -2
  109. package/dist/components/toast.js +78 -29
  110. package/dist/preset.js +291 -33
  111. package/dist/styled-system/styles.css +3107 -1039
  112. package/dist/styled-system/tokens/index.mjs +602 -22
  113. package/dist/styled-system/tokens/tokens.d.ts +11 -11
  114. package/dist/styled-system/types/prop-type.d.ts +2 -2
  115. package/dist/styles.css +16 -4
  116. package/dist/theme/color.d.ts +3 -0
  117. package/dist/theme/color.js +5 -0
  118. package/dist/theme/derive.d.ts +9 -2
  119. package/dist/theme/derive.js +72 -12
  120. package/dist/theme/palettes.d.ts +49 -6
  121. package/dist/theme/palettes.js +84 -15
  122. package/dist/theme/tokens.d.ts +3 -3
  123. package/dist/theme/tokens.js +108 -32
  124. package/package.json +9 -1
@@ -7,14 +7,13 @@ import { IconButton, IconButtonSizeContext } from "./button.js";
7
7
  import { PageHotkeys, useHotkey } from "./hotkeys.js";
8
8
  import { icon } from "./icon.js";
9
9
  import { useCiridaeLocale } from "./locale.js";
10
+ import { singleLineTextClass } from "./single-line-text.js";
10
11
  // The sidebar sits flush against the window; only the main panel is inset from the edges.
11
12
  const shell = css({
12
13
  display: "flex",
13
14
  height: "100dvh",
14
- paddingBlock: "panel.compact",
15
- paddingInlineEnd: "panel.compact",
16
15
  overflow: "hidden",
17
- background: "bg.canvas",
16
+ background: "token(gradients.canvas)",
18
17
  color: "fg.primary",
19
18
  // Compact line height leaves clipped text room for its descenders throughout the app frame.
20
19
  textStyle: "body",
@@ -23,26 +22,23 @@ const shell = css({
23
22
  const sidebar = css({
24
23
  display: "flex",
25
24
  flexDirection: "column",
26
- gap: "sidebar.groupGap",
27
- paddingBlock: "panel.sidebarBlock",
28
- // The first row, a control's height such as the workspace switcher, centres on the header's
29
- // line beside it: half the difference, plus the main panel's 1px edge.
30
- // The last row, the account, mirrors it at the foot: as far inside the main panel's bottom edge
31
- // as the first row sits inside its top.
32
- _notPhone: {
33
- paddingBlock: "calc((token(sizes.shell.header) - token(sizes.control.default)) / 2 + 1px)",
34
- },
25
+ // Between the navigation and the footer; the navigation's own groups stand `sidebar.groupGap` apart.
26
+ gap: "sidebar.footerGap",
27
+ paddingBlock: "sidebar.block",
35
28
  paddingInline: "sidebar.inline",
36
29
  width: "shell.sidebar",
37
30
  flexShrink: "0",
38
31
  overflowY: "auto",
39
32
  // The rail swaps every expanded control for its `data-compact-sidebar` icon twin.
40
33
  "& [data-compact-sidebar]": { display: "none" },
41
- "&[data-collapsed]": {
42
- width: "shell.collapsed",
43
- alignItems: "center",
44
- "& [data-expanded-sidebar]": { display: "none" },
45
- "& [data-compact-sidebar]": { display: "flex" },
34
+ // A phone's menu always shows the labelled navigation, whatever the desktop sidebar's state.
35
+ _notPhone: {
36
+ "&[data-collapsed]": {
37
+ width: "shell.collapsed",
38
+ alignItems: "center",
39
+ "& [data-expanded-sidebar]": { display: "none" },
40
+ "& [data-compact-sidebar]": { display: "flex" },
41
+ },
46
42
  },
47
43
  _rail: {
48
44
  width: "shell.collapsed",
@@ -62,7 +58,7 @@ const sidebar = css({
62
58
  zIndex: "drawer",
63
59
  width: "shell.sidebar",
64
60
  alignItems: "stretch",
65
- background: "bg.canvas",
61
+ background: "token(gradients.canvas)",
66
62
  borderInlineEnd: "1px solid token(colors.border.panel)",
67
63
  boxShadow: "card",
68
64
  transform: "translateX(-100%)",
@@ -89,12 +85,15 @@ const main = css({
89
85
  flexDirection: "column",
90
86
  flex: "1",
91
87
  minWidth: "0",
92
- border: "1px solid token(colors.border.panel)",
88
+ marginBlock: "panel.compact",
89
+ marginInlineEnd: "panel.compact",
90
+ border: "1px solid token(colors.border.canvas)",
93
91
  borderRadius: "action",
94
- background: "bg.panel",
92
+ background: "token(gradients.panel)",
95
93
  overflow: "hidden",
96
94
  // Only once the panel spans the whole window does rounding read as a gap.
97
95
  _phone: {
96
+ margin: "none",
98
97
  borderRadius: "0",
99
98
  height: "100dvh",
100
99
  border: "0",
@@ -109,7 +108,7 @@ const header = css({
109
108
  flexShrink: "0",
110
109
  paddingInlineEnd: "control.compactInline",
111
110
  paddingInlineStart: "control.inline",
112
- borderBottom: "1px solid token(colors.border.panel)",
111
+ borderBottom: "1px solid token(colors.border.divider)",
113
112
  "& > :last-child": { minWidth: "0", maxWidth: "100%" },
114
113
  // Once the sidebar is gone the header stays one row: the title truncates, the actions keep
115
114
  // their width.
@@ -118,22 +117,25 @@ const header = css({
118
117
  padding: "panel.default",
119
118
  "& > [data-header-title]": {
120
119
  minWidth: "0",
121
- overflow: "hidden",
122
- whiteSpace: "nowrap",
120
+ overflowX: "clip",
121
+ overflowY: "visible",
123
122
  textOverflow: "ellipsis",
124
- lineHeight: "compact",
123
+ whiteSpace: "nowrap",
125
124
  },
126
125
  // The actions hold one line: a slicer keeps its value and drops the dimension it names.
127
126
  "& > :last-child": { flexShrink: "0", flexWrap: "nowrap" },
128
127
  "& [data-slicer-label]": { srOnly: true },
129
128
  },
130
129
  });
130
+ // The trail's parents; the page itself, last, is in the primary text.
131
131
  const headerTitle = css({
132
132
  flex: "1",
133
133
  minWidth: "0",
134
134
  color: "fg.muted",
135
+ _light: { color: "fg.secondary" },
135
136
  fontSize: "caption",
136
137
  fontWeight: "medium",
138
+ lineHeight: "tight",
137
139
  });
138
140
  const content = css({
139
141
  flex: "1",
@@ -201,14 +203,14 @@ const sidebarRail = css({
201
203
  display: "flex",
202
204
  flexDirection: "column",
203
205
  alignItems: "center",
204
- gap: "layout.tightGap",
206
+ gap: "sidebar.groupGap",
205
207
  });
206
208
  /* One sidebar's content stacks at its container's own gap, so a footer's controls keep their room. */
207
209
  const sidebarOnly = css({ display: "flex", flexDirection: "column", gap: "inherit" });
208
210
  const sidebarNav = css({
209
211
  display: "flex",
210
212
  flexDirection: "column",
211
- gap: "layout.controlGap",
213
+ gap: "sidebar.groupGap",
212
214
  minHeight: "0",
213
215
  overflowY: "auto",
214
216
  // Scrolling would clip the ring of anything outer-ringed placed among the items.
@@ -216,11 +218,9 @@ const sidebarNav = css({
216
218
  });
217
219
  const sidebarFooter = css({
218
220
  marginTop: "auto",
219
- // Room above the footer's first control, so it never sits tight under the last nav row.
220
- paddingTop: "layout.contentGap",
221
221
  display: "flex",
222
222
  flexDirection: "column",
223
- gap: "layout.contentGap",
223
+ gap: "layout.controlGap",
224
224
  // A phone shows the footer in its menu at full width, as the expanded sidebar does.
225
225
  _rail: { _notPhone: { alignItems: "center" } },
226
226
  });
@@ -270,7 +270,7 @@ const headerActions = css({
270
270
  display: "flex",
271
271
  alignItems: "center",
272
272
  justifyContent: "flex-end",
273
- gap: "layout.contentGap",
273
+ gap: "layout.tightGap",
274
274
  minWidth: "0",
275
275
  });
276
276
  /** The header's copy of the rail, for widths where the sidebar is gone altogether. */
@@ -290,9 +290,9 @@ const crumbs = css({
290
290
  whiteSpace: "nowrap",
291
291
  "& > :last-child": {
292
292
  minWidth: "0",
293
- overflow: "hidden",
293
+ overflowX: "clip",
294
+ overflowY: "visible",
294
295
  textOverflow: "ellipsis",
295
- lineHeight: "compact",
296
296
  },
297
297
  _phone: { gap: "layout.tightGap" },
298
298
  "& a:hover": { textDecoration: "underline" },
@@ -331,7 +331,7 @@ export function AppBreadcrumbs({ className, ...props }) {
331
331
  const { labels } = useCiridaeLocale();
332
332
  return _jsx("nav", { "aria-label": labels.shell.breadcrumb, ...props, className: cx(crumbs, className) });
333
333
  }
334
- const crumbSeparator = css({ color: "fg.muted" });
334
+ const crumbSeparator = css({ color: "fg.subtle" });
335
335
  // The page's title, as its one h1, set in the trail's own type.
336
336
  const crumbCurrent = css({
337
337
  margin: "none",
@@ -373,9 +373,9 @@ const account = css({
373
373
  alignItems: "center",
374
374
  gap: "sidebar.markGap",
375
375
  width: "100%",
376
- padding: "layout.tightGap",
376
+ padding: "control.compactBlock",
377
377
  border: "0",
378
- borderRadius: "action",
378
+ borderRadius: "keycap",
379
379
  background: "transparent",
380
380
  color: "inherit",
381
381
  font: "inherit",
@@ -393,25 +393,23 @@ const accountAvatarOnly = css({
393
393
  cursor: "pointer",
394
394
  _focusVisible: { focusRing: "outer" },
395
395
  });
396
+ // The name and the detail under it, 4px apart as the design's account row stacks them: each line
397
+ // sits at its type's own height, so without the gap the two would touch.
396
398
  const accountCopy = css({
397
399
  display: "grid",
400
+ gap: "layout.tightGap",
398
401
  flex: "1",
399
402
  minWidth: "0",
400
- "& > *": {
401
- overflow: "hidden",
402
- textOverflow: "ellipsis",
403
- whiteSpace: "nowrap",
404
- lineHeight: "compact",
405
- },
406
- "& > :last-child": { color: "fg.muted", fontSize: "caption" },
403
+ "& > strong": { fontWeight: "medium" },
404
+ "& > :nth-child(2)": { color: "fg.muted", fontSize: "caption" },
407
405
  });
408
406
  /**
409
- * The signed-in account as a menu trigger: avatar, name, and a detail line, or with `compact` the
410
- * avatar alone.
407
+ * The signed-in account as a menu trigger: avatar, name, and a detail line if given, or with
408
+ * `compact` the avatar alone.
411
409
  */
412
410
  export function AppAccountButton({ avatar, name, detail, trailing, compact = false, className, ...props }) {
413
411
  if (compact) {
414
412
  return (_jsx("button", { type: "button", ...props, className: cx(accountAvatarOnly, className), children: avatar }));
415
413
  }
416
- return (_jsxs("button", { type: "button", ...props, className: cx(account, className), children: [avatar, _jsxs("span", { className: accountCopy, children: [_jsx("strong", { children: name }), _jsx("span", { children: detail })] }), trailing] }));
414
+ return (_jsxs("button", { type: "button", ...props, className: cx(account, className), children: [avatar, _jsxs("span", { className: accountCopy, children: [_jsx("strong", { className: singleLineTextClass, children: name }), detail && _jsx("span", { className: singleLineTextClass, children: detail })] }), trailing] }));
417
415
  }
@@ -2,6 +2,8 @@ import { useRender } from "@base-ui/react/use-render";
2
2
  import type { ComponentProps, ReactNode } from "react";
3
3
  export declare function SidebarNavigation({ className, ...props }: ComponentProps<"nav">): import("react").JSX.Element;
4
4
  export declare function SidebarDivider({ className, ...props }: ComponentProps<"hr">): import("react").JSX.Element;
5
+ /** Opens a labelled section in the rail, as its label does in the expanded sidebar. */
6
+ export declare function SidebarRailRule({ className, ...props }: ComponentProps<"hr">): import("react").JSX.Element;
5
7
  export declare function SidebarPanel({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
6
8
  export declare function SidebarSection({ label, action, iconTone, className, children, ...props }: ComponentProps<"div"> & {
7
9
  label?: ReactNode;
@@ -29,7 +31,10 @@ export type SidebarItemProps = useRender.ComponentProps<"button"> & {
29
31
  active?: boolean;
30
32
  /** Tint the leading icon bronze while inactive, as the Projects hub sidebar does. */
31
33
  iconTone?: "bronze";
32
- /** `compact` is a secondary list's row, such as recently opened records. */
33
- size?: "regular" | "compact";
34
+ /**
35
+ * `compact` is a secondary list's row, such as recently opened records; `short` a shorter row
36
+ * in the body type, such as the settings sidebar's Back to app.
37
+ */
38
+ size?: "regular" | "compact" | "short";
34
39
  };
35
40
  export declare function SidebarItem({ icon, trailing, action, active, iconTone, size, href, render, className, children, ...props }: SidebarItemProps): import("react").JSX.Element;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useRender } from "@base-ui/react/use-render";
3
3
  import { css, cx } from "#primitives/css";
4
4
  import { IconButtonSizeContext } from "./button.js";
5
+ import { singleLineText } from "./single-line-text.js";
5
6
  // Navigation sits in the shell's sidebar; it is not a panel of its own.
6
7
  const navigation = css({
7
8
  display: "flex",
@@ -33,6 +34,17 @@ const sectionLabel = css({
33
34
  "&[data-icon-tone=bronze] > svg": { color: "accent.bronze" },
34
35
  });
35
36
  const sectionAction = css({ display: "inline-flex", marginInlineStart: "auto" });
37
+ // A labelled section's mark in the rail, where there is no room for its label: a short rule.
38
+ const railRule = css({
39
+ flexShrink: "0",
40
+ width: "sidebar.railRule",
41
+ height: "border.hairline",
42
+ marginTop: "none",
43
+ marginInline: "auto",
44
+ marginBottom: "panel.default",
45
+ border: "0",
46
+ background: "border.divider",
47
+ });
36
48
  const divider = css({
37
49
  width: "100%",
38
50
  height: "border.hairline",
@@ -40,15 +52,20 @@ const divider = css({
40
52
  border: "0",
41
53
  background: "border.hairline",
42
54
  });
55
+ /*
56
+ * A row is square-cornered at rest; hovered, open or focused it rounds into a pill. The open page
57
+ * sits on the canvas's field face inside its edge; a hover is a faint wash in light and the
58
+ * selected glass in dark.
59
+ */
43
60
  const item = css({
44
61
  display: "flex",
45
62
  alignItems: "center",
46
63
  gap: "layout.controlGap",
47
64
  width: "100%",
48
- height: "control.small",
65
+ height: "control.row",
49
66
  paddingInline: "control.compactInline",
50
67
  border: "0",
51
- borderRadius: "action",
68
+ borderRadius: "nav",
52
69
  background: "transparent",
53
70
  // Muted against the dark canvas; light keeps every item at full contrast, as Round 2 does.
54
71
  color: "fg.muted",
@@ -60,24 +77,30 @@ const item = css({
60
77
  lineHeight: "tight",
61
78
  textAlign: "start",
62
79
  cursor: "pointer",
63
- transitionProperty: "background, color",
80
+ transitionProperty: "background, color, border-radius",
64
81
  transitionDuration: "fast",
65
82
  "& > svg:first-child": { opacity: ".7" },
66
83
  "&[data-icon-tone=bronze] > svg:first-child": { color: "accent.bronze" },
67
84
  "&:hover, &[aria-pressed=true]": {
68
- background: "bg.hover",
85
+ borderRadius: "shell",
86
+ background: "token(gradients.selected)",
87
+ _light: { background: "bg.rowHover" },
69
88
  color: "fg.primary",
70
89
  "& > svg:first-child": { color: "inherit" },
71
90
  },
72
- // The open page keeps the hover shape and size, so moving between pages shifts nothing; the
73
- // accent wash and the panel's own edge are what mark it.
91
+ // The open page keeps the hover shape and size; the field face and the canvas's edge mark it.
92
+ // The design counts that edge inside the row, so its content sits a hairline further in.
74
93
  "&[aria-current=page]": {
75
- background: "bg.selected",
76
- boxShadow: "inset 0 0 0 1px token(colors.border.panel)",
94
+ borderRadius: "shell",
95
+ background: "bg.field",
96
+ boxShadow: "inset 0 0 0 1px token(colors.border.canvas)",
77
97
  color: "fg.primary",
78
98
  "& > svg:first-child": { color: "inherit", opacity: "1" },
99
+ "&:not([data-icon-only])": {
100
+ paddingInline: "calc(token(spacing.control.compactInline) + token(sizes.border.hairline))",
101
+ },
79
102
  },
80
- _focusVisible: { focusRing: "inset" },
103
+ _focusVisible: { borderRadius: "shell", focusRing: "outer" },
81
104
  // A secondary list under the navigation, such as recently opened records: a shorter row in
82
105
  // smaller, quieter text, so it reads apart from the pages above it.
83
106
  "&[data-size=compact]": {
@@ -85,23 +108,19 @@ const item = css({
85
108
  color: "fg.secondary",
86
109
  fontSize: "caption",
87
110
  },
88
- // With no label it is the rail's square: the same hover, open-page fill and edge, icon only.
111
+ // A shorter row in the body type, such as the settings sidebar's way back to the app.
112
+ "&[data-size=short]": { height: "sidebar.shortRow" },
113
+ // With no label it is the rail's round button: the same hover, open-page face and edge, icon only.
89
114
  "&[data-icon-only]": {
90
115
  justifyContent: "center",
91
- width: "control.default",
92
- height: "control.default",
116
+ width: "control.compact",
117
+ height: "control.compact",
93
118
  paddingInline: "none",
94
- "& > svg:first-child": { opacity: "1", fontSize: "icon.rail" },
119
+ borderRadius: "round",
120
+ "& > svg:first-child": { opacity: "1", fontSize: "icon.small" },
95
121
  },
96
122
  });
97
- const label = css({
98
- minWidth: "0",
99
- flex: "1",
100
- overflow: "hidden",
101
- textOverflow: "ellipsis",
102
- whiteSpace: "nowrap",
103
- lineHeight: "compact",
104
- });
123
+ const label = css(singleLineText, { flex: "1" });
105
124
  const trailingStyle = css({ display: "inline-flex", flexShrink: "0", color: "fg.subtle" });
106
125
  // A row with an action: the group draws the row's hover, open-page fill and edge, so they span the
107
126
  // action too, and the row inside it stays transparent.
@@ -110,21 +129,26 @@ const itemGroup = css({
110
129
  alignItems: "center",
111
130
  width: "100%",
112
131
  minWidth: "0",
113
- borderRadius: "action",
114
- transitionProperty: "background",
132
+ borderRadius: "nav",
133
+ transitionProperty: "background, border-radius",
115
134
  transitionDuration: "fast",
116
- "&:hover": { background: "bg.hover" },
135
+ "&:hover": {
136
+ borderRadius: "shell",
137
+ background: "token(gradients.selected)",
138
+ _light: { background: "bg.rowHover" },
139
+ },
117
140
  "&:hover > [data-sidebar-row]": { color: "fg.primary" },
118
141
  "&:has(> [data-sidebar-row][aria-current=page])": {
119
- background: "bg.selected",
120
- boxShadow: "inset 0 0 0 1px token(colors.border.panel)",
142
+ borderRadius: "shell",
143
+ background: "bg.field",
144
+ boxShadow: "inset 0 0 0 1px token(colors.border.canvas)",
121
145
  },
122
146
  // The group draws the one wash; the row inside never adds its own on hover or when current.
123
147
  "& > [data-sidebar-row]": { minWidth: "0" },
124
148
  "& > [data-sidebar-row], & > [data-sidebar-row]:is(:hover, [aria-current=page], [aria-pressed=true])": { background: "transparent", boxShadow: "none" },
125
149
  // Focus rings the whole row, action included, not just the link inside it.
126
150
  "& > [data-sidebar-row]:focus-visible": { outline: "none" },
127
- "&:has(> [data-sidebar-row]:focus-visible)": { focusRing: "inset" },
151
+ "&:has(> [data-sidebar-row]:focus-visible)": { borderRadius: "shell", focusRing: "outer" },
128
152
  "&:hover > [data-sidebar-action], & > [data-sidebar-action]:focus-within": { opacity: "1" },
129
153
  });
130
154
  // Revealed on hover or focus; always shown where there is no hover to reveal it.
@@ -171,6 +195,10 @@ export function SidebarNavigation({ className, ...props }) {
171
195
  export function SidebarDivider({ className, ...props }) {
172
196
  return _jsx("hr", { ...props, className: cx(divider, className), "data-ciridae-ui": "" });
173
197
  }
198
+ /** Opens a labelled section in the rail, as its label does in the expanded sidebar. */
199
+ export function SidebarRailRule({ className, ...props }) {
200
+ return _jsx("hr", { ...props, className: cx(railRule, className), "data-ciridae-ui": "" });
201
+ }
174
202
  export function SidebarPanel({ className, ...props }) {
175
203
  return _jsx("div", { ...props, className: cx(panel, className), "data-ciridae-ui": "" });
176
204
  }
@@ -0,0 +1,2 @@
1
+ export declare const singleLineText: import("../styled-system/types/index.js").SystemStyleObject;
2
+ export declare const singleLineTextClass: string;
@@ -0,0 +1,16 @@
1
+ import { css } from "#primitives/css";
2
+ /*
3
+ * One line of text at the type's own height, as the design sets every single-line label, cut
4
+ * with an ellipsis when it runs out of room. It clips sideways only, so the descenders that reach
5
+ * below a line box that tight stay whole. Compose it at the top level of a `css()` call, or as the
6
+ * class `singleLineTextClass`: Panda does not extract it from under a nested selector.
7
+ */
8
+ export const singleLineText = css.raw({
9
+ minWidth: "0",
10
+ overflowX: "clip",
11
+ overflowY: "visible",
12
+ textOverflow: "ellipsis",
13
+ whiteSpace: "nowrap",
14
+ lineHeight: "tight",
15
+ });
16
+ export const singleLineTextClass = css(singleLineText);
@@ -19,7 +19,10 @@ declare const banner: import("../styled-system/types/index.js").RecipeRuntimeFn<
19
19
  };
20
20
  };
21
21
  }>;
22
- export type SiteBannerProps = ComponentProps<"div"> & RecipeVariantProps<typeof banner>;
22
+ export type SiteBannerProps = ComponentProps<"div"> & RecipeVariantProps<typeof banner> & {
23
+ /** Adds a close button at the strip's end. */
24
+ onDismiss?: () => void;
25
+ };
23
26
  /** A quiet, full-width site notice, separate from page-level status banners. */
24
- export declare function SiteBanner({ tone, className, ...props }: SiteBannerProps): import("react").JSX.Element;
27
+ export declare function SiteBanner({ tone, onDismiss, className, children, ...props }: SiteBannerProps): import("react").JSX.Element;
25
28
  export {};
@@ -1,12 +1,19 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { cva, cx } from "#primitives/css";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { XIcon } from "@phosphor-icons/react";
3
+ import { css, cva, cx } from "#primitives/css";
4
+ import { IconButton } from "./button.js";
5
+ import { icon } from "./icon.js";
6
+ import { useCiridaeLocale } from "./locale.js";
3
7
  const banner = cva({
4
8
  base: {
9
+ display: "flex",
10
+ alignItems: "center",
11
+ gap: "layout.controlGap",
5
12
  flexShrink: "0",
6
13
  width: "100%",
7
14
  boxSizing: "border-box",
8
- paddingBlock: "2px",
9
- paddingInline: "16px",
15
+ paddingBlock: "control.trackInset",
16
+ paddingInline: "control.inline",
10
17
  textAlign: "start",
11
18
  fontSize: "11px",
12
19
  fontWeight: "medium",
@@ -24,7 +31,11 @@ const banner = cva({
24
31
  },
25
32
  defaultVariants: { tone: "info" },
26
33
  });
34
+ const message = css({ flex: "1", minWidth: "0" });
35
+ // The strip keeps its height: the button reaches past the line by as much as it is taller.
36
+ const dismissButton = css({ marginBlock: "calc((1lh - token(sizes.control.shortcut)) / 2)" });
27
37
  /** A quiet, full-width site notice, separate from page-level status banners. */
28
- export function SiteBanner({ tone, className, ...props }) {
29
- return _jsx("div", { ...props, className: cx(banner({ tone }), className) });
38
+ export function SiteBanner({ tone, onDismiss, className, children, ...props }) {
39
+ const { labels } = useCiridaeLocale();
40
+ return (_jsxs("div", { ...props, className: cx(banner({ tone }), className), children: [_jsx("span", { className: message, children: children }), onDismiss && (_jsx(IconButton, { "aria-label": labels.common.dismiss, variant: "ghost", size: "compact", className: dismissButton, onClick: onDismiss, children: _jsx(XIcon, { className: icon({ size: "compact" }) }) }))] }));
30
41
  }
@@ -1,14 +1,15 @@
1
1
  import type { ComponentProps } from "react";
2
2
  export type SkeletonWidth = "short" | "medium" | "long" | "full" | `${number}%`;
3
- export type SkeletonHeight = "text" | "caption" | "control" | "row" | "block" | "chart" | "chart.small" | "chart.medium" | "chart.large";
3
+ export type SkeletonHeight = "text" | "caption" | "line" | "smallLine" | "bar" | "badge" | "control" | "row" | "block" | "chart" | "chart.small" | "chart.medium" | "chart.large";
4
4
  /**
5
- * Loading placeholder with a shimmer. `block` fills its box, `text` takes one line of the
6
- * surrounding text, `circle` stands in for avatars and icons, and `square` for logos and tiles. Size it with `width` (a token
7
- * length, `full`, or a percentage of its box) and `height` (a line of text or caption, a control,
8
- * a list row, the whole box, or a chart's height).
5
+ * Loading placeholder. `block` fills its box, `text` takes one line of the surrounding text,
6
+ * `circle` stands in for an avatar, `square` for a checkbox, and `pill` for a status chip. Size it
7
+ * with `width` (a token length, `full`, or a percentage of its box) and `height` (a line of text
8
+ * or small print, the design's fixed line or small line, a bar, a small badge, a control, a list
9
+ * row, the whole box, or a chart's height).
9
10
  */
10
11
  export declare function Skeleton({ shape, width, height, className, style, ...props }: ComponentProps<"span"> & {
11
- shape?: "block" | "text" | "circle" | "square";
12
+ shape?: "block" | "text" | "circle" | "square" | "pill";
12
13
  /** A token width, for a value of roughly known length, or a share of the box such as `"60%"`. */
13
14
  width?: SkeletonWidth;
14
15
  height?: SkeletonHeight;
@@ -1,43 +1,54 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { css, cx } from "#primitives/css";
3
+ // The design draws placeholders flat, with no shimmer: the canvas's gradient in light, a white
4
+ // wash in dark. Every bar is fully rounded except the block, the box and the bar.
3
5
  const skeleton = css({
4
6
  display: "block",
5
7
  flexShrink: "0",
6
- background: "token(gradients.skeleton)",
7
- backgroundSize: "200% 100%",
8
- animation: "skeletonShimmer 1.6s ease-in-out infinite",
9
- _motionReduce: { animation: "none" },
10
- "&[data-shape=block]": { minHeight: "skeleton.line", borderRadius: "action" },
8
+ background: "token(gradients.skeletonFill)",
9
+ "&[data-shape=block]": { minHeight: "skeleton.line", borderRadius: "control" },
11
10
  // One line of the surrounding text: the bar is shorter than the line box and centered in it.
12
11
  "&[data-shape=text]": {
13
12
  width: "100%",
14
13
  height: "0.75em",
15
14
  marginBlock: "calc((1lh - 0.75em) / 2)",
16
- borderRadius: "keycap",
15
+ borderRadius: "round",
17
16
  },
18
17
  "&[data-shape=circle]": {
19
- width: "control.compact",
18
+ width: "avatar.menu",
20
19
  aspectRatio: "1",
21
20
  borderRadius: "round",
22
21
  },
23
22
  "&[data-shape=square]": {
24
- width: "control.compact",
23
+ width: "control.checkbox",
25
24
  aspectRatio: "1",
26
- borderRadius: "action",
25
+ borderRadius: "control",
26
+ },
27
+ // A status chip's or a badge's placeholder.
28
+ "&[data-shape=pill]": {
29
+ width: "skeleton.pill",
30
+ height: "skeleton.bar",
31
+ borderRadius: "round",
27
32
  },
28
33
  "&[data-shape][data-width=short]": { width: "skeleton.short" },
29
34
  "&[data-shape][data-width=medium]": { width: "skeleton.medium" },
30
35
  "&[data-shape][data-width=long]": { width: "skeleton.long" },
31
36
  "&[data-shape][data-width=full]": { width: "100%" },
32
- // A line of caption text, as `text` is a line of the surrounding text.
37
+ // A line of small print, such as a row's meta line, under a line of text.
33
38
  "&[data-shape][data-height=caption]": {
34
- height: "calc(token(fontSizes.caption) * 0.75)",
35
- marginBlock: "calc((1lh - token(fontSizes.caption) * 0.75) / 2)",
39
+ height: "skeleton.caption",
40
+ marginBlock: "calc((1lh - token(sizes.skeleton.caption)) / 2)",
36
41
  },
37
42
  "&[data-shape][data-height=text]": {
38
43
  height: "0.75em",
39
44
  marginBlock: "calc((1lh - 0.75em) / 2)",
40
45
  },
46
+ // The design's fixed text bars, with no line box round them: a line, and a small one.
47
+ "&[data-shape][data-height=line]": { height: "skeleton.textLine", marginBlock: "0" },
48
+ "&[data-shape][data-height=smallLine]": { height: "skeleton.caption", marginBlock: "0" },
49
+ // A bar in a chart's or a breakdown's placeholder, and a small badge's.
50
+ "&[data-shape][data-height=bar]": { height: "skeleton.bar", marginBlock: "0" },
51
+ "&[data-shape][data-height=badge]": { height: "skeleton.badge", marginBlock: "0" },
41
52
  "&[data-shape][data-height=control]": { height: "control.default", marginBlock: "0" },
42
53
  "&[data-shape][data-height=row]": { height: "control.field", marginBlock: "0" },
43
54
  "&[data-shape][data-height=block]": { height: "100%", marginBlock: "0" },
@@ -49,10 +60,11 @@ const skeleton = css({
49
60
  "&[data-shape][data-height='chart.large']": { height: "chart.large", marginBlock: "0" },
50
61
  });
51
62
  /**
52
- * Loading placeholder with a shimmer. `block` fills its box, `text` takes one line of the
53
- * surrounding text, `circle` stands in for avatars and icons, and `square` for logos and tiles. Size it with `width` (a token
54
- * length, `full`, or a percentage of its box) and `height` (a line of text or caption, a control,
55
- * a list row, the whole box, or a chart's height).
63
+ * Loading placeholder. `block` fills its box, `text` takes one line of the surrounding text,
64
+ * `circle` stands in for an avatar, `square` for a checkbox, and `pill` for a status chip. Size it
65
+ * with `width` (a token length, `full`, or a percentage of its box) and `height` (a line of text
66
+ * or small print, the design's fixed line or small line, a bar, a small badge, a control, a list
67
+ * row, the whole box, or a chart's height).
56
68
  */
57
69
  export function Skeleton({ shape = "block", width, height, className, style, ...props }) {
58
70
  const percent = width?.endsWith("%") ? width : undefined;
@@ -13,6 +13,18 @@ export declare function PageHeaderSkeleton({ avatar, actions, }: {
13
13
  export declare function PanelSkeleton({ rows }: {
14
14
  rows?: number;
15
15
  }): import("react").JSX.Element;
16
+ /** Stands in for list rows: a line of text over a line of small print, then a status chip. */
17
+ export declare function ListRowsSkeleton({ rows }: {
18
+ rows?: number;
19
+ }): import("react").JSX.Element;
20
+ /** Stands in for a stat tile: its label, a small badge for the figure, and a line of small print. */
21
+ export declare function StatTileSkeleton({ className }: {
22
+ className?: string;
23
+ }): import("react").JSX.Element;
24
+ /** Stands in for a card: a title and its meta line, labelled bars, and a takeaway under a rule. */
25
+ export declare function CardSkeleton({ bars }: {
26
+ bars?: number;
27
+ }): import("react").JSX.Element;
16
28
  /** Placeholder for a row's compact edit and delete icon buttons. */
17
29
  export declare function RowActionsSkeleton(): import("react").JSX.Element;
18
30
  /**