@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.
- package/dist/chat/conversation-source.d.ts +15 -1
- package/dist/chat/describe-tool-call.d.ts +3 -3
- package/dist/chat/describe-tool-call.js +3 -3
- package/dist/chat/message-text.js +2 -2
- package/dist/ciridae.css +16 -4
- package/dist/components/activity-feed.d.ts +1 -0
- package/dist/components/activity-feed.js +1 -0
- package/dist/components/app-frame.d.ts +13 -1
- package/dist/components/app-frame.js +45 -3
- package/dist/components/app-navigation.d.ts +28 -1
- package/dist/components/app-navigation.js +110 -14
- package/dist/components/badge.d.ts +29 -4
- package/dist/components/badge.js +134 -15
- package/dist/components/bulk-action-bar.js +3 -3
- package/dist/components/button.d.ts +10 -3
- package/dist/components/button.js +77 -44
- package/dist/components/calendar.js +44 -17
- package/dist/components/centered-page.js +2 -2
- package/dist/components/chart.d.ts +1 -1
- package/dist/components/chart.js +16 -11
- package/dist/components/chat-client-source.js +47 -6
- package/dist/components/chat-sheet-controller.d.ts +1 -0
- package/dist/components/chat-sheet-controller.js +1 -0
- package/dist/components/chat-sheet.d.ts +10 -2
- package/dist/components/chat-sheet.js +74 -37
- package/dist/components/chat.d.ts +90 -14
- package/dist/components/chat.js +401 -101
- package/dist/components/checkbox-item.d.ts +5 -2
- package/dist/components/checkbox-item.js +9 -5
- package/dist/components/chip.d.ts +5 -0
- package/dist/components/chip.js +27 -1
- package/dist/components/column-settings.js +1 -1
- package/dist/components/command-menu.d.ts +5 -1
- package/dist/components/command-menu.js +98 -32
- package/dist/components/command-palette.d.ts +6 -2
- package/dist/components/command-palette.js +20 -10
- package/dist/components/data-display.d.ts +35 -4
- package/dist/components/data-display.js +138 -19
- package/dist/components/data-grid.d.ts +14 -2
- package/dist/components/data-grid.js +61 -26
- package/dist/components/date-range-picker.js +9 -20
- package/dist/components/drawer.d.ts +5 -0
- package/dist/components/drawer.js +91 -54
- package/dist/components/editable-field.d.ts +17 -3
- package/dist/components/editable-field.js +57 -27
- package/dist/components/email-input.js +6 -5
- package/dist/components/environment-frame.d.ts +1 -1
- package/dist/components/environment-frame.js +37 -6
- package/dist/components/filter-bar.js +39 -28
- package/dist/components/form-controls.d.ts +9 -0
- package/dist/components/form-controls.js +46 -22
- package/dist/components/form-layout.d.ts +7 -3
- package/dist/components/form-layout.js +68 -23
- package/dist/components/glass-menu.d.ts +3 -0
- package/dist/components/glass-menu.js +63 -0
- package/dist/components/gradient-edge.d.ts +9 -0
- package/dist/components/gradient-edge.js +31 -0
- package/dist/components/input.d.ts +4 -2
- package/dist/components/input.js +26 -15
- package/dist/components/keycap.d.ts +9 -5
- package/dist/components/keycap.js +57 -19
- package/dist/components/lightbox.js +2 -1
- package/dist/components/locale.d.ts +10 -0
- package/dist/components/locale.js +18 -2
- package/dist/components/log-entry.d.ts +40 -0
- package/dist/components/log-entry.js +146 -0
- package/dist/components/mention-input.js +4 -3
- package/dist/components/menu.d.ts +6 -2
- package/dist/components/menu.js +34 -37
- package/dist/components/meter.d.ts +9 -1
- package/dist/components/meter.js +64 -6
- package/dist/components/navigation.d.ts +21 -4
- package/dist/components/navigation.js +84 -19
- package/dist/components/notification-tray.js +5 -4
- package/dist/components/overlays.d.ts +3 -1
- package/dist/components/overlays.js +69 -40
- package/dist/components/page-frame.js +1 -1
- package/dist/components/panel.d.ts +1 -1
- package/dist/components/panel.js +18 -12
- package/dist/components/radio-card.js +10 -9
- package/dist/components/record.d.ts +25 -1
- package/dist/components/record.js +147 -18
- package/dist/components/segmented-bar.d.ts +3 -1
- package/dist/components/segmented-bar.js +16 -6
- package/dist/components/select.d.ts +5 -2
- package/dist/components/select.js +103 -127
- package/dist/components/shell-skeleton.js +1 -1
- package/dist/components/shell.d.ts +2 -2
- package/dist/components/shell.js +43 -45
- package/dist/components/sidebar.d.ts +7 -2
- package/dist/components/sidebar.js +55 -27
- package/dist/components/single-line-text.d.ts +2 -0
- package/dist/components/single-line-text.js +16 -0
- package/dist/components/site-banner.d.ts +5 -2
- package/dist/components/site-banner.js +17 -6
- package/dist/components/skeleton.d.ts +7 -6
- package/dist/components/skeleton.js +28 -16
- package/dist/components/skeletons.d.ts +12 -0
- package/dist/components/skeletons.js +71 -8
- package/dist/components/stages.d.ts +78 -6
- package/dist/components/stages.js +512 -156
- package/dist/components/stat-card.d.ts +10 -2
- package/dist/components/stat-card.js +92 -37
- package/dist/components/status-banner.js +1 -1
- package/dist/components/tabs.js +26 -13
- package/dist/components/theme-toggle.d.ts +4 -0
- package/dist/components/theme-toggle.js +67 -0
- package/dist/components/toast.d.ts +9 -2
- package/dist/components/toast.js +78 -29
- package/dist/preset.js +291 -33
- package/dist/styled-system/styles.css +3107 -1039
- package/dist/styled-system/tokens/index.mjs +602 -22
- package/dist/styled-system/tokens/tokens.d.ts +11 -11
- package/dist/styled-system/types/prop-type.d.ts +2 -2
- package/dist/styles.css +16 -4
- package/dist/theme/color.d.ts +3 -0
- package/dist/theme/color.js +5 -0
- package/dist/theme/derive.d.ts +9 -2
- package/dist/theme/derive.js +72 -12
- package/dist/theme/palettes.d.ts +49 -6
- package/dist/theme/palettes.js +84 -15
- package/dist/theme/tokens.d.ts +3 -3
- package/dist/theme/tokens.js +108 -32
- package/package.json +9 -1
package/dist/components/badge.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
3
|
* A badge: a short label, usually with an icon, that says what state something is in. Its
|
|
4
4
|
* `tone` carries the meaning: `warning`, `danger`, `success` and `info` tint the glass toward
|
|
5
|
-
* their hue, `neutral` stays grey
|
|
6
|
-
*
|
|
5
|
+
* their hue, `neutral` stays grey, and `ink` is the solid face of what a run actually did. A
|
|
6
|
+
* `pill` carries a judgment, such as a verdict or a run mode; a `square` badge carries a fact,
|
|
7
|
+
* such as where a value came from, in a field's flat well. `LifecycleBadge` is the pill for where
|
|
8
|
+
* a record sits in its stages, and `BadgeGlyph` and `AssuranceGlyph` the small marks badges and
|
|
9
|
+
* fields put in their icon slot.
|
|
7
10
|
*/
|
|
8
11
|
import { useRender } from "@base-ui/react/use-render";
|
|
9
|
-
import {
|
|
12
|
+
import { CheckIcon, ClockIcon, HourglassIcon, SparkleIcon, StopIcon } from "@phosphor-icons/react";
|
|
13
|
+
import { css, cva, cx } from "#primitives/css";
|
|
14
|
+
/*
|
|
15
|
+
* A tinted pill is its hue's glass, falling from light to its hue, inside that hue's gradient
|
|
16
|
+
* hairline, which the `[data-edge="gradient"]` ring draws from `--edge`. Neutral is the raised
|
|
17
|
+
* glass, warning the tinted button's warm glass, and ink the primary face.
|
|
18
|
+
*/
|
|
10
19
|
const badge = cva({
|
|
11
20
|
base: {
|
|
12
21
|
display: "inline-flex",
|
|
@@ -29,16 +38,23 @@ const badge = cva({
|
|
|
29
38
|
variants: {
|
|
30
39
|
shape: {
|
|
31
40
|
pill: { borderRadius: "pill" },
|
|
41
|
+
// The well of a form field in dark, the card's face in light, lettered in the label grey.
|
|
32
42
|
square: {
|
|
33
43
|
borderRadius: "control",
|
|
34
44
|
borderColor: "border.control",
|
|
35
|
-
background: "bg.
|
|
36
|
-
|
|
45
|
+
background: "bg.formField",
|
|
46
|
+
color: "fg.label",
|
|
47
|
+
_light: { background: "bg.card" },
|
|
37
48
|
},
|
|
38
49
|
},
|
|
39
|
-
|
|
50
|
+
/*
|
|
51
|
+
* `filled` is the tone's glass; `outline` the card's face inside the tone's edge, as an
|
|
52
|
+
* outcome that changed nothing; `bare` a fact told in passing: the icon and the words, muted,
|
|
53
|
+
* with no box, the tone on the icon alone.
|
|
54
|
+
*/
|
|
40
55
|
appearance: {
|
|
41
56
|
filled: {},
|
|
57
|
+
outline: {},
|
|
42
58
|
bare: {
|
|
43
59
|
height: "auto",
|
|
44
60
|
gap: "layout.tightGap",
|
|
@@ -54,21 +70,80 @@ const badge = cva({
|
|
|
54
70
|
neutral: {},
|
|
55
71
|
info: { color: "accent.blue" },
|
|
56
72
|
success: { color: "accent.green" },
|
|
57
|
-
warning: { color: "accent.yellow" },
|
|
73
|
+
warning: { color: "accent.yellow", "& svg": { color: "accent.yellowIcon" } },
|
|
58
74
|
danger: { color: "accent.red" },
|
|
75
|
+
ink: { color: "fg.primary" },
|
|
59
76
|
},
|
|
60
77
|
},
|
|
61
78
|
compoundVariants: [
|
|
62
|
-
{
|
|
63
|
-
|
|
79
|
+
{
|
|
80
|
+
appearance: "filled",
|
|
81
|
+
shape: "pill",
|
|
82
|
+
tone: "neutral",
|
|
83
|
+
css: {
|
|
84
|
+
background: "token(gradients.raised)",
|
|
85
|
+
color: "fg.label",
|
|
86
|
+
"--edge": "token(gradients.border.raised)",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
appearance: "filled",
|
|
91
|
+
shape: "pill",
|
|
92
|
+
tone: "info",
|
|
93
|
+
css: {
|
|
94
|
+
background: "token(gradients.infoGlass)",
|
|
95
|
+
"--edge": "token(gradients.border.infoGlass)",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
64
98
|
{
|
|
65
99
|
appearance: "filled",
|
|
66
100
|
shape: "pill",
|
|
67
101
|
tone: "success",
|
|
68
|
-
css: {
|
|
102
|
+
css: {
|
|
103
|
+
background: "token(gradients.successGlass)",
|
|
104
|
+
"--edge": "token(gradients.border.successGlass)",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
appearance: "filled",
|
|
109
|
+
shape: "pill",
|
|
110
|
+
tone: "warning",
|
|
111
|
+
css: {
|
|
112
|
+
background: "token(gradients.tinted)",
|
|
113
|
+
"--edge": "token(gradients.border.raised)",
|
|
114
|
+
_light: { "--edge": "token(gradients.border.tinted)" },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
appearance: "filled",
|
|
119
|
+
shape: "pill",
|
|
120
|
+
tone: "danger",
|
|
121
|
+
css: {
|
|
122
|
+
background: "token(gradients.dangerGlass)",
|
|
123
|
+
"--edge": "token(gradients.border.dangerGlass)",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
appearance: "filled",
|
|
128
|
+
shape: "pill",
|
|
129
|
+
tone: "ink",
|
|
130
|
+
css: {
|
|
131
|
+
background: "token(gradients.primary)",
|
|
132
|
+
color: "bg.canvas",
|
|
133
|
+
"--edge": "token(gradients.border.primary)",
|
|
134
|
+
_light: { color: "fg.onAccent" },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
appearance: "outline",
|
|
139
|
+
shape: "pill",
|
|
140
|
+
css: {
|
|
141
|
+
background: "token(gradients.raised)",
|
|
142
|
+
color: "fg.primary",
|
|
143
|
+
"--edge": "token(gradients.border.raised)",
|
|
144
|
+
_light: { background: "bg.card", "--edge": "token(gradients.border.primary)" },
|
|
145
|
+
},
|
|
69
146
|
},
|
|
70
|
-
{ appearance: "filled", shape: "pill", tone: "warning", css: { background: "bg.tinted" } },
|
|
71
|
-
{ appearance: "filled", shape: "pill", tone: "danger", css: { background: "bg.errorSubtle" } },
|
|
72
147
|
{
|
|
73
148
|
appearance: "bare",
|
|
74
149
|
tone: "info",
|
|
@@ -82,7 +157,7 @@ const badge = cva({
|
|
|
82
157
|
{
|
|
83
158
|
appearance: "bare",
|
|
84
159
|
tone: "warning",
|
|
85
|
-
css: { color: "fg.muted", "& svg": { color: "accent.
|
|
160
|
+
css: { color: "fg.muted", "& svg": { color: "accent.yellowIcon" } },
|
|
86
161
|
},
|
|
87
162
|
{
|
|
88
163
|
appearance: "bare",
|
|
@@ -100,8 +175,52 @@ export function Badge({ tone = "neutral", shape = "pill", appearance = "filled",
|
|
|
100
175
|
...props,
|
|
101
176
|
className: cx(badge({ tone, shape, appearance }), className),
|
|
102
177
|
"data-ciridae-ui": "",
|
|
103
|
-
"data-edge": shape === "pill" && appearance
|
|
178
|
+
"data-edge": shape === "pill" && appearance !== "bare" ? "gradient" : undefined,
|
|
104
179
|
children: (_jsxs(_Fragment, { children: [icon, children] })),
|
|
105
180
|
},
|
|
106
181
|
});
|
|
107
182
|
}
|
|
183
|
+
/*
|
|
184
|
+
* The glyphs the kit draws itself, in a 14px box (12px for the progress step's check and stop),
|
|
185
|
+
* in the color of the badge's or field's label.
|
|
186
|
+
*/
|
|
187
|
+
const glyph = css({ flexShrink: "0", width: "1em", height: "1em", fontSize: "icon.small" });
|
|
188
|
+
const smallGlyph = css({ flexShrink: "0", width: "1em", height: "1em", fontSize: "icon.compact" });
|
|
189
|
+
/** A badge's drawn glyph: a lifecycle position, a contested verdict, an unchanged outcome. */
|
|
190
|
+
export function BadgeGlyph({ kind, className, ...props }) {
|
|
191
|
+
const small = kind === "check" || kind === "stop";
|
|
192
|
+
return (_jsxs("svg", { "aria-hidden": "true", viewBox: small ? "0 0 12 12" : "0 0 14 14", fill: "none", ...props, className: cx(small ? smallGlyph : glyph, className), children: [kind === "notReached" && (_jsx("circle", { cx: "7", cy: "7", r: "3.875", stroke: "currentColor", strokeWidth: "1.25", strokeDasharray: "2 1.6" })), kind === "hereNow" && _jsx("circle", { cx: "7", cy: "7", r: "3", fill: "currentColor" }), kind === "terminal" && (_jsx("rect", { x: "3.5", y: "3.5", width: "7", height: "7", rx: "1.5", fill: "currentColor" })), kind === "contested" && (_jsx("circle", { cx: "7", cy: "7", r: "3.875", fill: "currentColor", stroke: "currentColor", strokeWidth: "1.25" })), kind === "noChange" && (_jsx("circle", { cx: "7", cy: "7", r: "3.375", stroke: "currentColor", strokeWidth: "1.25" })), kind === "check" && (_jsx("path", { d: "M2.4 6.2 4.6 8.5 9.7 3.4", stroke: "currentColor", strokeWidth: "0.857", strokeLinecap: "round", strokeLinejoin: "round" })), kind === "stop" && (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "6", cy: "6", r: "3.95", stroke: "currentColor", strokeWidth: "0.857" }), _jsx("path", { d: "M4.1 6h3.8", stroke: "currentColor", strokeWidth: "0.857", strokeLinecap: "round" })] }))] }));
|
|
193
|
+
}
|
|
194
|
+
const ASSURANCE_BARS = [
|
|
195
|
+
{ x: 2, y: 7.5, height: 5 },
|
|
196
|
+
{ x: 5.8, y: 4.5, height: 8 },
|
|
197
|
+
{ x: 9.5, y: 1.5, height: 11 },
|
|
198
|
+
];
|
|
199
|
+
/** A three-bar meter, lit to the level, so a value's assurance reads without color. */
|
|
200
|
+
export function AssuranceGlyph({ level, className, ...props }) {
|
|
201
|
+
return (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 14 14", fill: "currentColor", ...props, className: cx(glyph, className), children: ASSURANCE_BARS.map((bar, index) => (_jsx("rect", { x: bar.x, y: bar.y, width: "2.5", height: bar.height, rx: "0.75", opacity: index < level ? 1 : 0.28 }, bar.x))) }));
|
|
202
|
+
}
|
|
203
|
+
const LIFECYCLE_TONES = {
|
|
204
|
+
notReached: "neutral",
|
|
205
|
+
hereNow: "info",
|
|
206
|
+
done: "success",
|
|
207
|
+
blocked: "warning",
|
|
208
|
+
agentWaiting: "info",
|
|
209
|
+
expired: "neutral",
|
|
210
|
+
terminal: "neutral",
|
|
211
|
+
awaiting: "warning",
|
|
212
|
+
};
|
|
213
|
+
const LIFECYCLE_GLYPHS = {
|
|
214
|
+
notReached: _jsx(BadgeGlyph, { kind: "notReached" }),
|
|
215
|
+
hereNow: _jsx(BadgeGlyph, { kind: "hereNow" }),
|
|
216
|
+
done: _jsx(CheckIcon, { weight: "bold" }),
|
|
217
|
+
blocked: _jsx(StopIcon, { weight: "bold" }),
|
|
218
|
+
agentWaiting: _jsx(SparkleIcon, { weight: "fill" }),
|
|
219
|
+
expired: _jsx(ClockIcon, { weight: "bold" }),
|
|
220
|
+
terminal: _jsx(BadgeGlyph, { kind: "terminal" }),
|
|
221
|
+
awaiting: _jsx(HourglassIcon, { weight: "bold" }),
|
|
222
|
+
};
|
|
223
|
+
/** A lifecycle pill: the state picks the glyph and the hue; the words are the stage's name. */
|
|
224
|
+
export function LifecycleBadge({ state, ...props }) {
|
|
225
|
+
return _jsx(Badge, { ...props, tone: LIFECYCLE_TONES[state], icon: LIFECYCLE_GLYPHS[state] });
|
|
226
|
+
}
|
|
@@ -14,13 +14,13 @@ const bar = css({
|
|
|
14
14
|
paddingBlock: "panel.compact",
|
|
15
15
|
paddingInlineStart: "panel.comfortable",
|
|
16
16
|
paddingInlineEnd: "panel.compact",
|
|
17
|
-
border: "1px solid token(colors.border.
|
|
17
|
+
border: "1px solid token(colors.border.divider)",
|
|
18
18
|
borderRadius: "action",
|
|
19
|
-
background: "
|
|
19
|
+
background: "token(gradients.card)",
|
|
20
20
|
fontSize: "body",
|
|
21
21
|
fontWeight: "medium",
|
|
22
22
|
letterSpacing: "compact",
|
|
23
|
-
|
|
23
|
+
lineHeight: "tight",
|
|
24
24
|
});
|
|
25
25
|
const leading = css({
|
|
26
26
|
display: "flex",
|
|
@@ -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
|
-
|
|
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;
|
|
@@ -25,7 +30,7 @@ export type DataButtonProps = Omit<ButtonProps, "children" | "icon" | "loading">
|
|
|
25
30
|
};
|
|
26
31
|
/** Async action button with consistent pending, retry, and completion feedback. */
|
|
27
32
|
export declare function DataButton({ state, idleLabel, pendingLabel, errorLabel, successLabel, variant, icon, disabled, ...props }: DataButtonProps): import("react").JSX.Element;
|
|
28
|
-
type IconButtonSize = "compact" | "small" | "medium" | "large";
|
|
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
|
|
@@ -37,6 +42,8 @@ export type IconButtonProps = Omit<BaseButtonProps, "className" | "aria-label">
|
|
|
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("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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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: "
|
|
51
|
+
background: "token(gradients.primary)",
|
|
42
52
|
color: "bg.canvas",
|
|
43
53
|
_light: { color: "fg.onAccent" },
|
|
44
|
-
|
|
45
|
-
|
|
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: "
|
|
61
|
+
background: "token(gradients.raised)",
|
|
50
62
|
color: "fg.primary",
|
|
51
|
-
|
|
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,
|
|
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.
|
|
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: "
|
|
82
|
+
background: "token(gradients.tinted)",
|
|
73
83
|
color: "accent.yellow",
|
|
74
84
|
_light: { color: "accent.bronze" },
|
|
75
|
-
|
|
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: "
|
|
92
|
+
background: "token(gradients.raised)",
|
|
84
93
|
color: "accent.red",
|
|
85
|
-
|
|
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: "
|
|
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
|
-
//
|
|
141
|
+
// Each gradient face draws its own gradient edge; the flat faces draw a hairline or none.
|
|
138
142
|
const buttonEdges = {
|
|
139
|
-
primary:
|
|
140
|
-
secondary: "
|
|
143
|
+
primary: "primary",
|
|
144
|
+
secondary: "raised",
|
|
141
145
|
subtle: "glass",
|
|
142
146
|
ghost: undefined,
|
|
143
|
-
tinted:
|
|
144
|
-
secondaryDanger: "
|
|
145
|
-
danger:
|
|
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
|
-
|
|
152
|
-
|
|
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: "
|
|
202
|
+
background: "token(gradients.raised)",
|
|
190
203
|
cursor: "pointer",
|
|
191
204
|
flexShrink: "0",
|
|
192
205
|
transitionProperty: "translate, background, color",
|
|
193
206
|
transitionDuration: "fast",
|
|
194
|
-
|
|
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: "
|
|
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: "
|
|
231
|
+
background: "token(gradients.primary)",
|
|
214
232
|
color: "bg.canvas",
|
|
215
233
|
_light: { color: "fg.onAccent" },
|
|
216
|
-
_hover: {
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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: "
|
|
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.
|
|
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
|
-
|
|
139
|
-
|
|
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
|
|
@@ -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: "
|
|
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.
|
|
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" });
|