@ciridae/ui 1.0.2 → 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 (209) hide show
  1. package/dist/app/auth.d.ts +10 -0
  2. package/dist/app/auth.js +60 -0
  3. package/dist/app/error-boundary.d.ts +18 -0
  4. package/dist/app/error-boundary.js +45 -0
  5. package/dist/app/feedback.d.ts +12 -0
  6. package/dist/app/feedback.js +128 -0
  7. package/dist/app/index.d.ts +8 -0
  8. package/dist/app/index.js +8 -0
  9. package/dist/app/request-errors.d.ts +63 -0
  10. package/dist/app/request-errors.js +172 -0
  11. package/dist/app/sentry.d.ts +18 -0
  12. package/dist/app/sentry.js +52 -0
  13. package/dist/app/telemetry.d.ts +7 -0
  14. package/dist/app/telemetry.js +28 -0
  15. package/dist/app/trail.d.ts +9 -0
  16. package/dist/app/trail.js +18 -0
  17. package/dist/chat/chat-query-keys.d.ts +3 -3
  18. package/dist/chat/contract.d.ts +15 -15
  19. package/dist/chat/conversation-items-query-options.d.ts +114 -114
  20. package/dist/chat/conversation-source.d.ts +89 -0
  21. package/dist/chat/conversation-source.js +1 -0
  22. package/dist/chat/describe-tool-call.d.ts +3 -3
  23. package/dist/chat/describe-tool-call.js +3 -3
  24. package/dist/chat/message-text.d.ts +1 -1
  25. package/dist/chat/message-text.js +116 -48
  26. package/dist/chat/use-conversation-list.d.ts +6 -6
  27. package/dist/ciridae.css +16 -4
  28. package/dist/components/activity-feed.d.ts +3 -2
  29. package/dist/components/activity-feed.js +1 -0
  30. package/dist/components/agent-work.d.ts +3 -3
  31. package/dist/components/app-frame.d.ts +91 -0
  32. package/dist/components/app-frame.js +134 -0
  33. package/dist/components/app-navigation.d.ts +30 -3
  34. package/dist/components/app-navigation.js +105 -14
  35. package/dist/components/badge.d.ts +30 -5
  36. package/dist/components/badge.js +134 -15
  37. package/dist/components/brand-icons.d.ts +3 -3
  38. package/dist/components/bulk-action-bar.d.ts +1 -1
  39. package/dist/components/bulk-action-bar.js +3 -3
  40. package/dist/components/button.d.ts +12 -5
  41. package/dist/components/button.js +77 -44
  42. package/dist/components/calendar.d.ts +1 -1
  43. package/dist/components/calendar.js +44 -17
  44. package/dist/components/centered-page.d.ts +1 -1
  45. package/dist/components/centered-page.js +2 -2
  46. package/dist/components/chart.d.ts +22 -2
  47. package/dist/components/chart.js +251 -51
  48. package/dist/components/chat-client-source.d.ts +16 -0
  49. package/dist/components/chat-client-source.js +297 -0
  50. package/dist/components/chat-page.d.ts +9 -8
  51. package/dist/components/chat-page.js +7 -11
  52. package/dist/components/chat-sheet-controller.d.ts +11 -14
  53. package/dist/components/chat-sheet-controller.js +3 -3
  54. package/dist/components/chat-sheet.d.ts +18 -11
  55. package/dist/components/chat-sheet.js +188 -289
  56. package/dist/components/chat.d.ts +109 -19
  57. package/dist/components/chat.js +520 -124
  58. package/dist/components/checkbox-item.d.ts +6 -3
  59. package/dist/components/checkbox-item.js +9 -5
  60. package/dist/components/checkbox-list.d.ts +1 -1
  61. package/dist/components/chip.d.ts +6 -1
  62. package/dist/components/chip.js +27 -1
  63. package/dist/components/collapsible.d.ts +1 -1
  64. package/dist/components/color-theme.d.ts +1 -1
  65. package/dist/components/column-settings.d.ts +1 -1
  66. package/dist/components/column-settings.js +1 -1
  67. package/dist/components/command-menu.d.ts +5 -1
  68. package/dist/components/command-menu.js +98 -32
  69. package/dist/components/command-palette.d.ts +7 -3
  70. package/dist/components/command-palette.js +20 -11
  71. package/dist/components/copyable-id.d.ts +22 -5
  72. package/dist/components/copyable-id.js +46 -6
  73. package/dist/components/data-display.d.ts +39 -8
  74. package/dist/components/data-display.js +138 -19
  75. package/dist/components/data-grid.d.ts +23 -11
  76. package/dist/components/data-grid.js +61 -26
  77. package/dist/components/date-range-picker.d.ts +1 -1
  78. package/dist/components/date-range-picker.js +9 -20
  79. package/dist/components/date-time.d.ts +1 -1
  80. package/dist/components/dialog-height-context.d.ts +1 -1
  81. package/dist/components/document-viewer.d.ts +2 -2
  82. package/dist/components/drawer.d.ts +11 -6
  83. package/dist/components/drawer.js +91 -54
  84. package/dist/components/dropzone.d.ts +4 -4
  85. package/dist/components/editable-field.d.ts +12 -3
  86. package/dist/components/editable-field.js +57 -27
  87. package/dist/components/email-input.d.ts +1 -1
  88. package/dist/components/email-input.js +6 -5
  89. package/dist/components/environment-frame.d.ts +2 -2
  90. package/dist/components/environment-frame.js +37 -6
  91. package/dist/components/expandable-text.d.ts +1 -1
  92. package/dist/components/feedback-screenshot.d.ts +1 -1
  93. package/dist/components/feedback-sheet.d.ts +18 -3
  94. package/dist/components/feedback-sheet.js +216 -55
  95. package/dist/components/field-context.d.ts +1 -1
  96. package/dist/components/filter-bar.d.ts +3 -3
  97. package/dist/components/filter-bar.js +43 -29
  98. package/dist/components/form-controls.d.ts +12 -3
  99. package/dist/components/form-controls.js +46 -22
  100. package/dist/components/form-layout.d.ts +14 -10
  101. package/dist/components/form-layout.js +68 -23
  102. package/dist/components/glass-menu.d.ts +3 -0
  103. package/dist/components/glass-menu.js +63 -0
  104. package/dist/components/gradient-edge.d.ts +9 -0
  105. package/dist/components/gradient-edge.js +31 -0
  106. package/dist/components/hotkeys.d.ts +2 -2
  107. package/dist/components/icon.d.ts +1 -1
  108. package/dist/components/input.d.ts +7 -5
  109. package/dist/components/input.js +29 -17
  110. package/dist/components/keyboard-shortcuts.d.ts +2 -2
  111. package/dist/components/keycap.d.ts +10 -6
  112. package/dist/components/keycap.js +57 -19
  113. package/dist/components/layout.d.ts +13 -13
  114. package/dist/components/lightbox.d.ts +9 -3
  115. package/dist/components/lightbox.js +13 -9
  116. package/dist/components/link.d.ts +1 -1
  117. package/dist/components/locale.d.ts +42 -2
  118. package/dist/components/locale.js +43 -3
  119. package/dist/components/log-entry.d.ts +40 -0
  120. package/dist/components/log-entry.js +146 -0
  121. package/dist/components/log-list.d.ts +3 -3
  122. package/dist/components/mention-input.d.ts +2 -2
  123. package/dist/components/mention-input.js +4 -3
  124. package/dist/components/menu.d.ts +7 -3
  125. package/dist/components/menu.js +34 -37
  126. package/dist/components/meter.d.ts +10 -2
  127. package/dist/components/meter.js +64 -6
  128. package/dist/components/navigation.d.ts +21 -4
  129. package/dist/components/navigation.js +84 -19
  130. package/dist/components/notification-tray.d.ts +1 -1
  131. package/dist/components/notification-tray.js +5 -4
  132. package/dist/components/overlays.d.ts +6 -4
  133. package/dist/components/overlays.js +69 -40
  134. package/dist/components/page-frame.d.ts +4 -4
  135. package/dist/components/page-frame.js +1 -1
  136. package/dist/components/panel.d.ts +4 -4
  137. package/dist/components/panel.js +18 -12
  138. package/dist/components/profile-header.d.ts +1 -1
  139. package/dist/components/progress.d.ts +1 -1
  140. package/dist/components/project-avatar.d.ts +1 -1
  141. package/dist/components/radio-card.d.ts +2 -2
  142. package/dist/components/radio-card.js +10 -9
  143. package/dist/components/record.d.ts +29 -5
  144. package/dist/components/record.js +147 -18
  145. package/dist/components/resolved-file-url.d.ts +19 -0
  146. package/dist/components/resolved-file-url.js +49 -0
  147. package/dist/components/scroll-area.d.ts +1 -1
  148. package/dist/components/secret-dialog.d.ts +1 -1
  149. package/dist/components/segmented-bar.d.ts +4 -2
  150. package/dist/components/segmented-bar.js +16 -6
  151. package/dist/components/select.d.ts +9 -6
  152. package/dist/components/select.js +103 -127
  153. package/dist/components/settings.d.ts +4 -4
  154. package/dist/components/shell-skeleton.d.ts +1 -1
  155. package/dist/components/shell-skeleton.js +1 -1
  156. package/dist/components/shell.d.ts +21 -21
  157. package/dist/components/shell.js +43 -43
  158. package/dist/components/side-panel.d.ts +3 -3
  159. package/dist/components/sidebar.d.ts +13 -8
  160. package/dist/components/sidebar.js +55 -27
  161. package/dist/components/single-line-text.d.ts +2 -0
  162. package/dist/components/single-line-text.js +16 -0
  163. package/dist/components/site-banner.d.ts +5 -2
  164. package/dist/components/site-banner.js +17 -6
  165. package/dist/components/skeleton.d.ts +8 -7
  166. package/dist/components/skeleton.js +28 -16
  167. package/dist/components/skeletons.d.ts +17 -5
  168. package/dist/components/skeletons.js +71 -8
  169. package/dist/components/stages.d.ts +83 -11
  170. package/dist/components/stages.js +512 -156
  171. package/dist/components/stat-card.d.ts +12 -4
  172. package/dist/components/stat-card.js +92 -37
  173. package/dist/components/status-banner.d.ts +1 -1
  174. package/dist/components/status-banner.js +1 -1
  175. package/dist/components/status.d.ts +2 -2
  176. package/dist/components/steps.d.ts +1 -1
  177. package/dist/components/switch.d.ts +1 -1
  178. package/dist/components/tabs.d.ts +8 -8
  179. package/dist/components/tabs.js +26 -13
  180. package/dist/components/theme-customizer.d.ts +2 -2
  181. package/dist/components/theme-toggle.d.ts +4 -0
  182. package/dist/components/theme-toggle.js +67 -0
  183. package/dist/components/toast.d.ts +10 -3
  184. package/dist/components/toast.js +78 -29
  185. package/dist/components/use-new-version-available.d.ts +6 -0
  186. package/dist/components/use-new-version-available.js +29 -0
  187. package/dist/components/user-chip.d.ts +3 -3
  188. package/dist/components/wizard.d.ts +2 -2
  189. package/dist/feedback/contract.d.ts +136 -136
  190. package/dist/feedback/feedback-controller.d.ts +6 -6
  191. package/dist/feedback/generated/zod.gen.d.ts +28 -28
  192. package/dist/feedback/operator-contract.d.ts +11 -11
  193. package/dist/feedback/operator-controller.d.ts +32 -13
  194. package/dist/feedback/operator-controller.js +44 -11
  195. package/dist/preset.js +289 -33
  196. package/dist/styled-system/styles.css +3294 -1079
  197. package/dist/styled-system/tokens/index.mjs +598 -22
  198. package/dist/styled-system/tokens/tokens.d.ts +11 -11
  199. package/dist/styled-system/types/prop-type.d.ts +2 -2
  200. package/dist/styles.css +16 -4
  201. package/dist/theme/color.d.ts +3 -0
  202. package/dist/theme/color.js +5 -0
  203. package/dist/theme/derive.d.ts +9 -2
  204. package/dist/theme/derive.js +72 -12
  205. package/dist/theme/palettes.d.ts +49 -6
  206. package/dist/theme/palettes.js +84 -15
  207. package/dist/theme/tokens.d.ts +3 -3
  208. package/dist/theme/tokens.js +107 -32
  209. package/package.json +40 -3
@@ -13,7 +13,12 @@ export type ButtonProps = Omit<BaseButtonProps, "className"> & {
13
13
  /** The hotkey that does what the button does, shown after its label; the host binds it. */
14
14
  shortcut?: Hotkey;
15
15
  };
16
- export declare function Button({ className, variant, size, fullWidth, loading, icon, shortcut, children, disabled, ...props }: ButtonProps): import("@types/react").JSX.Element;
16
+ /**
17
+ * The size a `Button` takes where it does not set its own, so the container decides: a dialog's
18
+ * footer holds the default 38px buttons, where everywhere else the small one is the everyday.
19
+ */
20
+ export declare const ButtonSizeContext: import("react").Context<NonNullable<"medium" | "large" | "small" | undefined>>;
21
+ export declare function Button({ className, variant, size: ownSize, fullWidth, loading, icon, shortcut, children, disabled, ...props }: ButtonProps): import("react").JSX.Element;
17
22
  export type DataButtonState = "idle" | "pending" | "error" | "success";
18
23
  export type DataButtonProps = Omit<ButtonProps, "children" | "icon" | "loading"> & {
19
24
  state: DataButtonState;
@@ -24,19 +29,21 @@ export type DataButtonProps = Omit<ButtonProps, "children" | "icon" | "loading">
24
29
  icon?: ReactNode;
25
30
  };
26
31
  /** Async action button with consistent pending, retry, and completion feedback. */
27
- export declare function DataButton({ state, idleLabel, pendingLabel, errorLabel, successLabel, variant, icon, disabled, ...props }: DataButtonProps): import("@types/react").JSX.Element;
28
- type IconButtonSize = "compact" | "small" | "medium" | "large";
32
+ export declare function DataButton({ state, idleLabel, pendingLabel, errorLabel, successLabel, variant, icon, disabled, ...props }: DataButtonProps): import("react").JSX.Element;
33
+ type IconButtonSize = "compact" | "send" | "small" | "medium" | "large";
29
34
  /**
30
35
  * The size an `IconButton` takes where it does not set its own, so the container decides: the
31
36
  * sidebar rail's icons are large, and the same icon shown in the phone header matches the header's
32
37
  * small buttons.
33
38
  */
34
- export declare const IconButtonSizeContext: import("@types/react").Context<IconButtonSize>;
39
+ export declare const IconButtonSizeContext: import("react").Context<IconButtonSize>;
35
40
  export type IconButtonProps = Omit<BaseButtonProps, "className" | "aria-label"> & {
36
41
  className?: string;
37
42
  "aria-label": string;
38
43
  size?: IconButtonSize;
39
44
  variant?: "raised" | "ghost" | "primary";
45
+ /** `square` is the 8-round square that leads a page title; buttons are otherwise round. */
46
+ shape?: "round" | "square";
40
47
  /**
41
48
  * `true` marks the button with a dot; a number shows that count, nothing at zero, and joins the
42
49
  * button's name so assistive tech hears it too.
@@ -48,5 +55,5 @@ export type IconButtonProps = Omit<BaseButtonProps, "className" | "aria-label">
48
55
  */
49
56
  shortcut?: Hotkey;
50
57
  };
51
- export declare function IconButton({ className, size, variant, badge, shortcut, children, "aria-label": label, ...props }: IconButtonProps): import("@types/react").JSX.Element;
58
+ export declare function IconButton({ className, size, variant, shape, badge, shortcut, children, "aria-label": label, ...props }: IconButtonProps): import("react").JSX.Element;
52
59
  export {};
@@ -3,10 +3,14 @@ import { Button as BaseButton } from "@base-ui/react/button";
3
3
  import { CheckCircleIcon, WarningCircleIcon } from "@phosphor-icons/react";
4
4
  import { createContext, useContext } from "react";
5
5
  import { css, cva, cx } from "#primitives/css";
6
+ import { faceEdge } from "./gradient-edge.js";
6
7
  import { formatForDisplay } from "./hotkeys.js";
7
8
  import { icon as iconStyle, Spinner } from "./icon.js";
8
9
  import { ShortcutHint } from "./keycap.js";
9
10
  import { useCiridaeLocale } from "./locale.js";
11
+ /* The hover wash laid over a gradient face, as the design's state layer sits over it. */
12
+ const raisedHoverLight = "linear-gradient(token(colors.bg.hover), token(colors.bg.hover)), token(gradients.raised)";
13
+ const tintedHover = "linear-gradient(token(colors.bg.hover), token(colors.bg.hover)), token(gradients.tinted)";
10
14
  const action = cva({
11
15
  base: {
12
16
  display: "inline-flex",
@@ -14,8 +18,10 @@ const action = cva({
14
18
  alignItems: "center",
15
19
  justifyContent: "center",
16
20
  gap: "control.iconGap",
17
- border: "1px solid",
18
- borderRadius: "action",
21
+ // The design lays a button's edge over its padding rather than inside it, so no border.
22
+ border: "0",
23
+ // Buttons are pills at both heights.
24
+ borderRadius: "pill",
19
25
  height: "control.default",
20
26
  paddingInline: "control.inline",
21
27
  fontFamily: "sans",
@@ -36,67 +42,67 @@ const action = cva({
36
42
  },
37
43
  variants: {
38
44
  variant: {
39
- // Round 2 buttons cast no shadow: each face is told apart by its fill and its edge alone.
45
+ /*
46
+ * Buttons cast no shadow: each face is told apart by its gradient fill and its gradient
47
+ * edge alone. Light has no hover on the primary face; dark lets a little of the surface
48
+ * through. A raised face takes the hover wash over its gradient in light, and thins in dark.
49
+ */
40
50
  primary: {
41
- background: "bg.primary",
51
+ background: "token(gradients.primary)",
42
52
  color: "bg.canvas",
43
53
  _light: { color: "fg.onAccent" },
44
- borderColor: "transparent",
45
- _hover: { background: "bg.primaryHover" },
54
+ _hover: {
55
+ background: "bg.primaryHover",
56
+ _light: { background: "token(gradients.primary)" },
57
+ },
46
58
  _active: { translate: "0 1px" },
47
59
  },
48
60
  secondary: {
49
- background: "bg.raised",
61
+ background: "token(gradients.raised)",
50
62
  color: "fg.primary",
51
- borderColor: "transparent",
52
- _hover: { background: "bg.raisedHover" },
63
+ _hover: { background: "bg.raisedHover", _light: { background: raisedHoverLight } },
53
64
  _active: { translate: "0 1px" },
54
65
  },
55
66
  subtle: {
56
67
  background: "bg.fill",
57
68
  color: "fg.muted",
58
- borderColor: "transparent",
59
69
  _hover: { color: "fg.primary" },
60
70
  _active: { translate: "0 1px" },
61
71
  },
62
- // White in dark, muted in light: the one face whose text follows the theme differently.
72
+ // White in dark, the secondary grey in light: the one face whose text follows the theme
73
+ // differently.
63
74
  ghost: {
64
75
  background: "transparent",
65
76
  color: "fg.primary",
66
- _light: { color: "fg.muted" },
67
- borderColor: "transparent",
77
+ _light: { color: "fg.secondary" },
68
78
  _hover: { color: "fg.primary", background: "bg.hover" },
69
79
  },
70
80
  // Warm glass with an amber edge and the warm accent for its text.
71
81
  tinted: {
72
- background: "bg.tinted",
82
+ background: "token(gradients.tinted)",
73
83
  color: "accent.yellow",
74
84
  _light: { color: "accent.bronze" },
75
- borderColor: "transparent",
76
- _hover: { background: "bg.raisedHover" },
85
+ _hover: { background: tintedHover },
77
86
  _active: { translate: "0 1px" },
78
87
  },
79
88
  // The secondary face with red text, for a destructive action that asks for confirmation:
80
89
  // it names the risk without being the loudest thing on the page. The confirmation's own
81
90
  // button is `danger`.
82
91
  secondaryDanger: {
83
- background: "bg.raised",
92
+ background: "token(gradients.raised)",
84
93
  color: "accent.red",
85
- borderColor: "transparent",
86
- _hover: { background: "bg.raisedHover" },
94
+ _hover: { background: "bg.raisedHover", _light: { background: raisedHoverLight } },
87
95
  _active: { translate: "0 1px" },
88
96
  },
89
97
  danger: {
90
- background: "bg.danger",
98
+ background: "token(gradients.danger)",
91
99
  color: "fg.onAccent",
92
- borderColor: "transparent",
93
100
  _hover: { filter: "brightness(1.05)" },
94
101
  _active: { translate: "0 1px" },
95
102
  },
96
103
  success: {
97
104
  background: "accent.green",
98
105
  color: "bg.canvas",
99
- borderColor: "transparent",
100
106
  _hover: { filter: "brightness(1.05)" },
101
107
  _active: { translate: "0 1px" },
102
108
  },
@@ -104,8 +110,6 @@ const action = cva({
104
110
  link: {
105
111
  background: "transparent",
106
112
  color: "fg.primary",
107
- borderColor: "transparent",
108
- borderWidth: "0",
109
113
  fontFamily: "inherit",
110
114
  fontWeight: "inherit",
111
115
  letterSpacing: "inherit",
@@ -134,22 +138,30 @@ const action = cva({
134
138
  // Small is the everyday button; medium and large are for a page's one main action.
135
139
  defaultVariants: { variant: "primary", size: "small" },
136
140
  });
137
- // The glass faces draw a hairline, which is their outline; a filled face draws none.
141
+ // Each gradient face draws its own gradient edge; the flat faces draw a hairline or none.
138
142
  const buttonEdges = {
139
- primary: undefined,
140
- secondary: "glass",
143
+ primary: "primary",
144
+ secondary: "raised",
141
145
  subtle: "glass",
142
146
  ghost: undefined,
143
- tinted: undefined,
144
- secondaryDanger: "glass",
145
- danger: undefined,
147
+ tinted: "tinted",
148
+ secondaryDanger: "raised",
149
+ danger: "danger",
146
150
  success: undefined,
147
151
  link: undefined,
148
152
  };
149
153
  // In the button's own color, quieter than its label.
150
154
  const buttonHint = css({ color: "inherit", opacity: "0.6" });
151
- export function Button({ className, variant = "primary", size = "small", fullWidth, loading, icon, shortcut, children, disabled, ...props }) {
152
- return (_jsxs(BaseButton, { ...props, disabled: disabled || loading, "aria-busy": loading || undefined, className: cx(action({ variant, size, fullWidth }), className), "data-ciridae-ui": "", "data-edge": buttonEdges[variant], children: [loading ? _jsx(Spinner, { "aria-hidden": "true" }) : icon, children, shortcut && _jsx(ShortcutHint, { hotkey: shortcut, className: buttonHint })] }));
155
+ /**
156
+ * The size a `Button` takes where it does not set its own, so the container decides: a dialog's
157
+ * footer holds the default 38px buttons, where everywhere else the small one is the everyday.
158
+ */
159
+ export const ButtonSizeContext = createContext("small");
160
+ export function Button({ className, variant = "primary", size: ownSize, fullWidth, loading, icon, shortcut, children, disabled, ...props }) {
161
+ const containerSize = useContext(ButtonSizeContext);
162
+ const size = ownSize ?? containerSize;
163
+ const edge = faceEdge(buttonEdges[variant]);
164
+ return (_jsxs(BaseButton, { ...props, disabled: disabled || loading, "aria-busy": loading || undefined, className: cx(action({ variant, size, fullWidth }), edge.className, className), "data-ciridae-ui": "", "data-edge": edge["data-edge"], children: [loading ? _jsx(Spinner, { "aria-hidden": "true" }) : icon, children, shortcut && _jsx(ShortcutHint, { hotkey: shortcut, className: buttonHint })] }));
153
165
  }
154
166
  /** Async action button with consistent pending, retry, and completion feedback. */
155
167
  export function DataButton({ state, idleLabel, pendingLabel, errorLabel, successLabel, variant = "primary", icon, disabled, ...props }) {
@@ -183,19 +195,22 @@ const round = cva({
183
195
  alignItems: "center",
184
196
  justifyContent: "center",
185
197
  padding: "none",
198
+ // The edge's own 1px, as the design counts it; the bare button has neither.
186
199
  border: "1px solid transparent",
187
200
  borderRadius: "pill",
188
201
  color: "fg.muted",
189
- background: "bg.raised",
202
+ background: "token(gradients.raised)",
190
203
  cursor: "pointer",
191
204
  flexShrink: "0",
192
205
  transitionProperty: "translate, background, color",
193
206
  transitionDuration: "fast",
194
- _hover: { color: "fg.primary", background: "bg.raisedHover" },
207
+ // Hover keeps the face in light and thins it in dark; the icon takes the primary ink.
208
+ _hover: { color: "fg.primary", background: "token(gradients.raisedHover)" },
195
209
  _active: { translate: "0 1px" },
196
210
  _focusVisible: { focusRing: "outer" },
211
+ // A round face fades further than other controls when it cannot be pressed.
197
212
  "&:disabled, &[data-disabled]": {
198
- opacity: "disabled",
213
+ opacity: "disabledRound",
199
214
  cursor: "not-allowed",
200
215
  translate: "none",
201
216
  },
@@ -203,27 +218,44 @@ const round = cva({
203
218
  variants: {
204
219
  variant: {
205
220
  raised: {},
221
+ // The bare icon button: no face until hovered, and disabled it fades as other controls do.
206
222
  ghost: {
223
+ border: "0",
207
224
  background: "transparent",
208
- _hover: { background: "bg.hover" },
225
+ _hover: { background: "bg.hover", _light: { background: "bg.hover" } },
209
226
  _active: { translate: "none" },
227
+ "&:disabled, &[data-disabled]": { opacity: "disabled" },
210
228
  },
211
229
  // The primary face as a round button: a composer's send.
212
230
  primary: {
213
- background: "bg.primary",
231
+ background: "token(gradients.primary)",
214
232
  color: "bg.canvas",
215
233
  _light: { color: "fg.onAccent" },
216
- _hover: { color: "bg.canvas", background: "bg.primaryHover" },
234
+ _hover: {
235
+ color: "bg.canvas",
236
+ background: "bg.primaryHover",
237
+ _light: { color: "fg.onAccent", background: "token(gradients.primary)" },
238
+ },
217
239
  },
218
240
  },
241
+ // 24 is the bare button's in a column or group header; 26 the composer's send on the
242
+ // primary face; the other round faces are 28, 34 and 38.
219
243
  size: {
220
244
  compact: { width: "control.shortcut", height: "control.shortcut" },
221
- small: { width: "control.small", height: "control.small" },
222
- medium: { width: "control.compact", height: "control.compact" },
245
+ send: { width: "control.send", height: "control.send" },
246
+ small: { width: "control.roundSmall", height: "control.roundSmall" },
247
+ medium: { width: "control.round", height: "control.round" },
223
248
  large: { width: "control.default", height: "control.default" },
224
249
  },
250
+ // A page's leading icon button is a rounded square rather than a circle.
251
+ shape: { round: {}, square: { borderRadius: "action" } },
225
252
  },
226
- defaultVariants: { size: "medium", variant: "raised" },
253
+ compoundVariants: [
254
+ // Bare, the button is a rounded square too: 6 round at 24 and 8 round at 28.
255
+ { variant: "ghost", size: "compact", css: { borderRadius: "keycap" } },
256
+ { variant: "ghost", size: "small", css: { borderRadius: "action" } },
257
+ ],
258
+ defaultVariants: { size: "medium", variant: "raised", shape: "round" },
227
259
  });
228
260
  const badgeDot = css({
229
261
  position: "absolute",
@@ -263,10 +295,11 @@ const badgeCount = css({
263
295
  * small buttons.
264
296
  */
265
297
  export const IconButtonSizeContext = createContext("medium");
266
- const iconButtonEdges = { raised: "glass", ghost: undefined, primary: undefined };
267
- export function IconButton({ className, size, variant = "raised", badge, shortcut, children, "aria-label": label, ...props }) {
298
+ const iconButtonEdges = { raised: "raised", ghost: undefined, primary: "primary" };
299
+ export function IconButton({ className, size, variant = "raised", shape = "round", badge, shortcut, children, "aria-label": label, ...props }) {
268
300
  const containerSize = useContext(IconButtonSizeContext);
269
301
  const { labels } = useCiridaeLocale();
270
302
  const count = typeof badge === "number" && badge > 0 ? badge : 0;
271
- return (_jsxs(BaseButton, { ...props, "aria-label": count > 0 ? labels.common.withCount(label, count) : label, "aria-keyshortcuts": shortcut && formatForDisplay(shortcut, { useSymbols: false }), className: cx(round({ size: size ?? containerSize, variant }), className), "data-ciridae-ui": "", "data-edge": iconButtonEdges[variant], children: [children, badge === true && _jsx("span", { className: badgeDot, "aria-hidden": "true" }), count > 0 && (_jsx("span", { className: badgeCount, "aria-hidden": "true", children: count > 99 ? "99+" : count }))] }));
303
+ const edge = faceEdge(iconButtonEdges[variant]);
304
+ return (_jsxs(BaseButton, { ...props, "aria-label": count > 0 ? labels.common.withCount(label, count) : label, "aria-keyshortcuts": shortcut && formatForDisplay(shortcut, { useSymbols: false }), className: cx(round({ size: size ?? containerSize, variant, shape }), edge.className, className), "data-ciridae-ui": "", "data-edge": edge["data-edge"], children: [children, badge === true && _jsx("span", { className: badgeDot, "aria-hidden": "true" }), count > 0 && (_jsx("span", { className: badgeCount, "aria-hidden": "true", children: count > 99 ? "99+" : count }))] }));
272
305
  }
@@ -10,5 +10,5 @@ export type CalendarProps = {
10
10
  weekStartsOn?: WeekStartsOn;
11
11
  labels?: Partial<CiridaeLabels["calendar"]>;
12
12
  };
13
- export declare function Calendar({ month, selected, onMonthChange, onSelect, locale: ownLocale, weekStartsOn, labels: ownLabels, }: CalendarProps): import("@types/react").JSX.Element;
13
+ export declare function Calendar({ month, selected, onMonthChange, onSelect, locale: ownLocale, weekStartsOn, labels: ownLabels, }: CalendarProps): import("react").JSX.Element;
14
14
  export {};
@@ -5,11 +5,39 @@ import { css, cx } from "#primitives/css";
5
5
  import { IconButton } from "./button.js";
6
6
  import { directional, icon } from "./icon.js";
7
7
  import { useCiridaeLocale } from "./locale.js";
8
+ /*
9
+ * The month alone, no face of its own: it sits on the popover or picker that opens it. Seven 32px
10
+ * day columns inside an 8px inset, which with the glass sheet's 4px makes the design's 12px.
11
+ */
12
+ const calendar = css({
13
+ display: "grid",
14
+ gap: "layout.controlGap",
15
+ width: "calc(7 * token(sizes.control.compact) + 2 * token(spacing.panel.compact))",
16
+ padding: "panel.compact",
17
+ });
18
+ const header = css({
19
+ display: "flex",
20
+ alignItems: "center",
21
+ justifyContent: "space-between",
22
+ fontSize: "caption",
23
+ fontWeight: "medium",
24
+ letterSpacing: "compact",
25
+ });
8
26
  const grid = css({
9
27
  display: "grid",
10
28
  gridTemplateColumns: "repeat(7, minmax(0, 1fr))",
11
29
  justifyItems: "center",
12
- gap: "layout.tightGap",
30
+ rowGap: "control.trackInset",
31
+ });
32
+ // The weekdays sit as far above the days as the month sits above them.
33
+ const weekDay = css({
34
+ paddingBlock: "layout.tightGap",
35
+ marginBottom: "control.iconGap",
36
+ textAlign: "center",
37
+ color: "fg.muted",
38
+ fontSize: "code",
39
+ fontWeight: "medium",
40
+ lineHeight: "tight",
13
41
  });
14
42
  const row = css({
15
43
  display: "grid",
@@ -18,20 +46,29 @@ const row = css({
18
46
  width: "100%",
19
47
  });
20
48
  const previousMonthIcon = css({ transform: "rotate(180deg)" });
49
+ // Today wears a hairline ring; the chosen day the primary button's face.
21
50
  const day = css({
22
51
  width: "control.compact",
23
52
  height: "control.compact",
24
- border: "0",
53
+ border: "1px solid transparent",
25
54
  borderRadius: "round",
26
55
  background: "transparent",
27
56
  color: "fg.primary",
57
+ fontSize: "caption",
58
+ fontWeight: "medium",
28
59
  cursor: "pointer",
29
60
  transitionProperty: "background, color",
30
61
  transitionDuration: "fast",
62
+ "&[aria-current=date]": { borderColor: "border.control" },
31
63
  "&:hover:not(:disabled):not([aria-selected=true])": {
32
- background: "bg.fill",
64
+ background: "bg.hover",
65
+ },
66
+ "&[aria-selected=true]": {
67
+ borderColor: "transparent",
68
+ background: "token(gradients.primary)",
69
+ color: "bg.canvas",
70
+ _light: { color: "fg.onAccent" },
33
71
  },
34
- "&[aria-selected=true]": { background: "fg.primary", color: "bg.canvas" },
35
72
  _disabled: { color: "fg.muted", opacity: "disabled", cursor: "default" },
36
73
  _focusVisible: { focusRing: "outer" },
37
74
  });
@@ -135,20 +172,10 @@ export function Calendar({ month, selected, onMonthChange, onSelect, locale: own
135
172
  });
136
173
  const weekDayFormatter = new Intl.DateTimeFormat(locale, { weekday: "narrow" });
137
174
  const fullWeekDayFormatter = new Intl.DateTimeFormat(locale, { weekday: "long" });
138
- return (_jsxs("section", { className: css({
139
- width: "calendar.width",
140
- padding: "panel.compact",
141
- border: "1px solid token(colors.border.control)",
142
- borderRadius: "inset",
143
- background: "bg.card",
144
- }), "data-ciridae-ui": "", children: [_jsxs("header", { className: css({
145
- display: "flex",
146
- alignItems: "center",
147
- justifyContent: "space-between",
148
- marginBottom: "panel.compact",
149
- }), children: [_jsx(IconButton, { "aria-label": labels.previousMonth, size: "compact", type: "button", onClick: () => onMonthChange(new Date(month.getFullYear(), month.getMonth() - 1, 1)), children: _jsx(ArrowRightIcon, { className: cx(icon(), directional, previousMonthIcon) }) }), _jsx("strong", { id: monthLabelId, children: monthFormatter.format(month) }), _jsx(IconButton, { "aria-label": labels.nextMonth, size: "compact", type: "button", onClick: () => onMonthChange(new Date(month.getFullYear(), month.getMonth() + 1, 1)), children: _jsx(ArrowRightIcon, { className: cx(icon(), directional) }) })] }), _jsxs("div", { className: grid, role: "grid", "aria-labelledby": monthLabelId, children: [_jsx("div", { className: row, role: "row", children: weekDays.map((date) => (_jsx("span", { "aria-label": fullWeekDayFormatter.format(date), className: css({ textAlign: "center", color: "fg.muted", fontSize: "caption" }), role: "columnheader", children: weekDayFormatter.format(date) }, date.getDay()))) }), Array.from({ length: 6 }, (_, weekIndex) => (_jsx("div", { className: row, role: "row", children: days.slice(weekIndex * 7, weekIndex * 7 + 7).map((date) => {
175
+ const today = new Date();
176
+ return (_jsxs("section", { className: calendar, "data-ciridae-ui": "", children: [_jsxs("header", { className: header, children: [_jsx(IconButton, { "aria-label": labels.previousMonth, size: "compact", variant: "ghost", type: "button", onClick: () => onMonthChange(new Date(month.getFullYear(), month.getMonth() - 1, 1)), children: _jsx(ArrowRightIcon, { className: cx(icon({ size: "small" }), directional, previousMonthIcon) }) }), _jsx("span", { id: monthLabelId, children: monthFormatter.format(month) }), _jsx(IconButton, { "aria-label": labels.nextMonth, size: "compact", variant: "ghost", type: "button", onClick: () => onMonthChange(new Date(month.getFullYear(), month.getMonth() + 1, 1)), children: _jsx(ArrowRightIcon, { className: cx(icon({ size: "small" }), directional) }) })] }), _jsxs("div", { className: grid, role: "grid", "aria-labelledby": monthLabelId, children: [_jsx("div", { className: row, role: "row", children: weekDays.map((date) => (_jsx("span", { "aria-label": fullWeekDayFormatter.format(date), className: weekDay, role: "columnheader", children: weekDayFormatter.format(date) }, date.getDay()))) }), Array.from({ length: 6 }, (_, weekIndex) => (_jsx("div", { className: row, role: "row", children: days.slice(weekIndex * 7, weekIndex * 7 + 7).map((date) => {
150
177
  const inCurrentMonth = isSameMonth(date, currentMonth);
151
- return (_jsx("button", { type: "button", "aria-label": dayFormatter.format(date), "aria-selected": inCurrentMonth && isSameDay(date, selected), className: day, disabled: !inCurrentMonth, onClick: () => onSelect(date), onFocus: () => setFocusedDate(date), onKeyDown: (event) => handleDayKeyDown(event, date), ref: (element) => {
178
+ return (_jsx("button", { type: "button", "aria-label": dayFormatter.format(date), "aria-selected": inCurrentMonth && isSameDay(date, selected), "aria-current": isSameDay(date, today) ? "date" : undefined, className: day, disabled: !inCurrentMonth, onClick: () => onSelect(date), onFocus: () => setFocusedDate(date), onKeyDown: (event) => handleDayKeyDown(event, date), ref: (element) => {
152
179
  if (element)
153
180
  dayButtons.current.set(dateKey(date), element);
154
181
  else
@@ -18,4 +18,4 @@ export type CenteredPageProps = {
18
18
  * content. `form` width narrows both for a short form; `wide` suits a card holding a list whose
19
19
  * rows carry a label, a detail, and a control.
20
20
  */
21
- export declare function CenteredPage({ brand, aside, title, description, header, width, children, }: CenteredPageProps): import("@types/react").JSX.Element;
21
+ export declare function CenteredPage({ brand, aside, title, description, header, width, children, }: CenteredPageProps): import("react").JSX.Element;
@@ -10,7 +10,7 @@ const page = css({
10
10
  // A phone keeps a thin margin around the card rather than giving its width away.
11
11
  padding: "panel.compact",
12
12
  "@media(min-width: 640px)": { padding: "page.inline" },
13
- background: "bg.canvas",
13
+ background: "token(gradients.canvas)",
14
14
  color: "fg.primary",
15
15
  });
16
16
  const column = css({
@@ -31,7 +31,7 @@ const headerRow = css({
31
31
  paddingInline: "panel.roomy",
32
32
  });
33
33
  const brandMark = css({ width: "brand.logo", height: "brand.logo", color: "fg.primary" });
34
- const content = css({ display: "flex", flexDirection: "column", gap: "layout.sectionGap" });
34
+ const content = css({ display: "flex", flexDirection: "column", gap: "layout.centeredCardGap" });
35
35
  const titleGroup = css({ display: "grid", gap: "layout.tightGap" });
36
36
  const titleText = css({ margin: "none", fontSize: "heading.section", fontWeight: "medium" });
37
37
  const descriptionText = css({ margin: "none", color: "fg.muted", fontSize: "caption" });
@@ -1,9 +1,15 @@
1
- import type { ComponentProps } from "react";
1
+ import { type ComponentProps } from "react";
2
2
  /** One line on the chart: a label and its `[unixSeconds, value]` samples. */
3
3
  export type TimeSeries = {
4
4
  label: string;
5
5
  points: readonly (readonly [number, number])[];
6
6
  };
7
+ /** One bar or line point per category, such as a month or a team, for a series. */
8
+ export type CategorySeries = {
9
+ label: string;
10
+ /** One value per category, in the categories' order; `null` leaves it out. */
11
+ values: readonly (number | null)[];
12
+ };
7
13
  /**
8
14
  * Lines over a fixed time window. Samples from different series are joined on their timestamp, so
9
15
  * a series missing a sample leaves a gap rather than a line drawn through it.
@@ -19,4 +25,18 @@ export declare function TimeSeriesChart({ series, start, end, formatValue, forma
19
25
  height?: number;
20
26
  /** Disable fractional axis ticks for discrete counts such as users. */
21
27
  allowDecimals?: boolean;
22
- }): import("@types/react").JSX.Element;
28
+ }): import("react").JSX.Element;
29
+ /**
30
+ * Values by category as bars, side by side per series, or as lines, such as counts per month.
31
+ * Each series takes the next chart colour; a legend names them once there is more than one.
32
+ */
33
+ export declare function CategoryChart({ categories, series, variant, formatValue, height, allowDecimals, className, ...props }: ComponentProps<"div"> & {
34
+ categories: readonly string[];
35
+ series: readonly CategorySeries[];
36
+ /** `line` joins each series' values across the categories instead. */
37
+ variant?: "bar" | "line";
38
+ formatValue?: (value: number) => string;
39
+ height?: number;
40
+ /** Disable fractional axis ticks for discrete counts such as users. */
41
+ allowDecimals?: boolean;
42
+ }): import("react").JSX.Element;