@ciridae/ui 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +105 -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 +76 -39
- package/dist/components/chat.d.ts +90 -14
- package/dist/components/chat.js +402 -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 +12 -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 +289 -33
- package/dist/styled-system/styles.css +3115 -1044
- package/dist/styled-system/tokens/index.mjs +598 -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 +107 -32
- package/package.json +9 -1
|
@@ -17,7 +17,10 @@ export type SparklineProps = Omit<ComponentProps<"div">, "children"> & {
|
|
|
17
17
|
strokeWidth?: number;
|
|
18
18
|
endPointSize?: number;
|
|
19
19
|
};
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* A run of values as a line filling its box; decorative, as the figure beside it says what it
|
|
22
|
+
* shows. Under two values there is no line, and a dashed rule stands in for it.
|
|
23
|
+
*/
|
|
21
24
|
export declare function Sparkline({ values, low: ownLow, high: ownHigh, strokeWidth, endPointSize, className, ...props }: SparklineProps): import("react").JSX.Element;
|
|
22
25
|
export type MetricCardProps = Omit<useRender.ComponentProps<"div">, "children" | "onSelect"> & {
|
|
23
26
|
label: ReactNode;
|
|
@@ -30,5 +33,10 @@ export type MetricCardProps = Omit<useRender.ComponentProps<"div">, "children" |
|
|
|
30
33
|
onSelect?: () => void;
|
|
31
34
|
/** Makes the card a link; a router's link as `render` routes it. */
|
|
32
35
|
href?: string;
|
|
36
|
+
/**
|
|
37
|
+
* `card` (the default) frames it on its own; in a row of tiles, `left` rules its leading edge
|
|
38
|
+
* and `none` leaves it bare.
|
|
39
|
+
*/
|
|
40
|
+
divider?: "card" | "left" | "none";
|
|
33
41
|
};
|
|
34
|
-
export declare function MetricCard({ label, value, change, sparkline, onSelect, href, render, className, ...props }: MetricCardProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
42
|
+
export declare function MetricCard({ label, value, change, sparkline, onSelect, href, divider, render, className, ...props }: MetricCardProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -2,12 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
/*
|
|
3
3
|
* Figures for a dashboard. `MetricCard` is one figure: its label, its value, how it changed
|
|
4
4
|
* (`Trend`) and its recent run (`Sparkline`); it can open what it counts. `Sparkline` is a blue
|
|
5
|
-
* line over
|
|
6
|
-
* point
|
|
5
|
+
* line over an area that fades to its foot, stretched to its box: the stroke keeps its width, and
|
|
6
|
+
* the area and end point are HTML, so none of them distorts.
|
|
7
7
|
*/
|
|
8
8
|
import { useRender } from "@base-ui/react/use-render";
|
|
9
9
|
import { ArrowDownIcon, ArrowRightIcon, ArrowUpIcon } from "@phosphor-icons/react";
|
|
10
|
-
import { css, cx } from "#primitives/css";
|
|
10
|
+
import { css, cva, cx } from "#primitives/css";
|
|
11
11
|
import { icon } from "./icon.js";
|
|
12
12
|
import { cardSurface } from "./panel.js";
|
|
13
13
|
/* A change beside a figure: which way it moved, and separately whether that is good. */
|
|
@@ -20,17 +20,28 @@ const trend = css({
|
|
|
20
20
|
fontWeight: "medium",
|
|
21
21
|
"&[data-tone=good]": { color: "accent.green" },
|
|
22
22
|
"&[data-tone=bad]": { color: "accent.red" },
|
|
23
|
-
"&[data-tone=neutral]": { color: "fg.
|
|
23
|
+
"&[data-tone=neutral]": { color: "fg.label" },
|
|
24
24
|
"& > svg": { flexShrink: "0" },
|
|
25
25
|
});
|
|
26
26
|
const trendText = css({ minWidth: "0", overflow: "hidden", textOverflow: "ellipsis" });
|
|
27
27
|
const TREND_ARROWS = { up: ArrowUpIcon, down: ArrowDownIcon, flat: ArrowRightIcon };
|
|
28
28
|
export function Trend({ direction, tone = "neutral", arrow = true, children }) {
|
|
29
29
|
const Arrow = TREND_ARROWS[direction];
|
|
30
|
-
return (_jsxs("span", { className: trend, "data-tone": tone, "data-ciridae-ui": "", children: [arrow && _jsx(Arrow, { className: icon({ size: "compact" }), weight: "bold", "aria-hidden": "true" }), _jsx("span", { className: trendText, children: children })] }));
|
|
30
|
+
return (_jsxs("span", { className: trend, "data-tone": tone, "data-ciridae-ui": "", children: [arrow && _jsx(Arrow, { className: icon({ size: "compact" }), weight: "bold", "aria-hidden": "true" }), _jsx("span", { className: trendText, "data-trend-text": "", children: children })] }));
|
|
31
31
|
}
|
|
32
|
-
/*
|
|
33
|
-
|
|
32
|
+
/*
|
|
33
|
+
* Sparkline: drawn into a 100x100 box that stretches to its plot. The plot fills the box unless
|
|
34
|
+
* `--sparkline-plot-width` and `--sparkline-plot-height` size it, at the box's foot and start, so
|
|
35
|
+
* the end point's ring has room above and after the line.
|
|
36
|
+
*/
|
|
37
|
+
const plot = css({ position: "relative", color: "chart.trend" });
|
|
38
|
+
const plotArea = css({
|
|
39
|
+
position: "absolute",
|
|
40
|
+
insetInlineStart: "0",
|
|
41
|
+
bottom: "0",
|
|
42
|
+
width: "var(--sparkline-plot-width, 100%)",
|
|
43
|
+
height: "var(--sparkline-plot-height, 100%)",
|
|
44
|
+
});
|
|
34
45
|
const svg = css({
|
|
35
46
|
position: "absolute",
|
|
36
47
|
inset: "0",
|
|
@@ -38,16 +49,31 @@ const svg = css({
|
|
|
38
49
|
height: "100%",
|
|
39
50
|
overflow: "visible",
|
|
40
51
|
});
|
|
52
|
+
// The area under the line, clipped to it, so its gradient runs top to foot whatever the box.
|
|
53
|
+
const area = css({ position: "absolute", inset: "0", background: "token(gradients.chart.area)" });
|
|
54
|
+
// The ring is the tile's face, so the point reads as cut out of the line.
|
|
41
55
|
const endPoint = css({
|
|
42
56
|
position: "absolute",
|
|
43
57
|
borderRadius: "round",
|
|
44
|
-
background: "
|
|
58
|
+
background: "chart.trend",
|
|
45
59
|
borderStyle: "solid",
|
|
46
|
-
borderColor: "bg.
|
|
60
|
+
borderColor: "bg.surface",
|
|
47
61
|
translate: "-50% -50%",
|
|
48
62
|
});
|
|
49
|
-
|
|
63
|
+
// No run to draw: a dashed rule across the middle holds the line's place.
|
|
64
|
+
const noRun = css({
|
|
65
|
+
position: "absolute",
|
|
66
|
+
insetInline: "0",
|
|
67
|
+
top: "50%",
|
|
68
|
+
borderTop: "2px dashed token(colors.border.divider)",
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* A run of values as a line filling its box; decorative, as the figure beside it says what it
|
|
72
|
+
* shows. Under two values there is no line, and a dashed rule stands in for it.
|
|
73
|
+
*/
|
|
50
74
|
export function Sparkline({ values, low: ownLow, high: ownHigh, strokeWidth = 1.5, endPointSize = 6, className, ...props }) {
|
|
75
|
+
if (values.length < 2)
|
|
76
|
+
return (_jsx("div", { "aria-hidden": "true", ...props, className: cx(plot, className), "data-ciridae-ui": "", children: _jsx("span", { className: noRun }) }));
|
|
51
77
|
const lowest = Math.min(...values);
|
|
52
78
|
const high = ownHigh ?? Math.max(...values);
|
|
53
79
|
const low = ownLow ?? lowest - (high - lowest) * 0.4;
|
|
@@ -56,31 +82,60 @@ export function Sparkline({ values, low: ownLow, high: ownHigh, strokeWidth = 1.
|
|
|
56
82
|
const step = 100 / Math.max(values.length - 1, 1);
|
|
57
83
|
const points = tops.map((top, index) => `${index * step},${top}`).join(" ");
|
|
58
84
|
const lastTop = tops[tops.length - 1] ?? 100;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
const areaOutline = tops.map((top, index) => `${index * step}% ${top}%`).join(", ");
|
|
86
|
+
return (_jsx("div", { "aria-hidden": "true", ...props, className: cx(plot, className), "data-ciridae-ui": "", children: _jsxs("span", { className: plotArea, children: [_jsx("span", { className: area, style: { clipPath: `polygon(0% 100%, ${areaOutline}, 100% 100%)` } }), _jsx("svg", { className: svg, viewBox: "0 0 100 100", preserveAspectRatio: "none", children: _jsx("polyline", { points: points, fill: "none", stroke: "currentColor", strokeWidth: strokeWidth, strokeLinejoin: "round", vectorEffect: "non-scaling-stroke" }) }), _jsx("span", { className: endPoint, style: {
|
|
87
|
+
insetInlineStart: "100%",
|
|
88
|
+
top: `${lastTop}%`,
|
|
89
|
+
width: endPointSize,
|
|
90
|
+
height: endPointSize,
|
|
91
|
+
borderWidth: strokeWidth,
|
|
92
|
+
} })] }) }));
|
|
66
93
|
}
|
|
67
|
-
/*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
/*
|
|
95
|
+
* Metric card: a figure with its change, and its run beside it. A tile in a row of them drops the
|
|
96
|
+
* card for a rule on its leading edge, or for nothing, and its label steps back to the label grey.
|
|
97
|
+
* The change's words are the label grey too, the kit's Stat tile: its arrow carries the tone.
|
|
98
|
+
* The run sits in the kit's 88x32 chart slot, its line in the slot's lower 84x25.
|
|
99
|
+
*/
|
|
100
|
+
const metricCard = cva({
|
|
101
|
+
base: {
|
|
102
|
+
display: "flex",
|
|
103
|
+
gap: "layout.contentGap",
|
|
104
|
+
padding: "panel.comfortable",
|
|
105
|
+
color: "fg.primary",
|
|
106
|
+
font: "inherit",
|
|
107
|
+
textAlign: "start",
|
|
108
|
+
textDecoration: "none",
|
|
109
|
+
lineHeight: "tight",
|
|
110
|
+
"&:is(button, a)": { cursor: "pointer" },
|
|
111
|
+
_focusVisible: { focusRing: "outer" },
|
|
112
|
+
"& [data-trend-text]": { color: "fg.label" },
|
|
113
|
+
"& > [data-sparkline]": {
|
|
114
|
+
"--sparkline-plot-width": "token(sizes.sparkline.plotWidth)",
|
|
115
|
+
"--sparkline-plot-height": "token(sizes.sparkline.plotHeight)",
|
|
116
|
+
width: "sparkline.width",
|
|
117
|
+
height: "sparkline.height",
|
|
118
|
+
flexShrink: "0",
|
|
119
|
+
alignSelf: "flex-end",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
variants: {
|
|
123
|
+
divider: {
|
|
124
|
+
card: { ...cardSurface, "&:is(button, a)": { _hover: { borderColor: "border.glass" } } },
|
|
125
|
+
left: {
|
|
126
|
+
minWidth: "0",
|
|
127
|
+
border: "0",
|
|
128
|
+
borderInlineStart: "1px solid token(colors.border.panel)",
|
|
129
|
+
background: "transparent",
|
|
130
|
+
"& [data-metric-label]": { color: "fg.label" },
|
|
131
|
+
},
|
|
132
|
+
none: {
|
|
133
|
+
minWidth: "0",
|
|
134
|
+
border: "0",
|
|
135
|
+
background: "transparent",
|
|
136
|
+
"& [data-metric-label]": { color: "fg.label" },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
84
139
|
},
|
|
85
140
|
});
|
|
86
141
|
const metricCopy = css({
|
|
@@ -97,7 +152,7 @@ const metricValue = css({
|
|
|
97
152
|
lineHeight: "tight",
|
|
98
153
|
fontVariantNumeric: "tabular-nums",
|
|
99
154
|
});
|
|
100
|
-
export function MetricCard({ label, value, change, sparkline, onSelect, href, render, className, ...props }) {
|
|
155
|
+
export function MetricCard({ label, value, change, sparkline, onSelect, href, divider = "card", render, className, ...props }) {
|
|
101
156
|
return useRender({
|
|
102
157
|
defaultTagName: href !== undefined ? "a" : onSelect !== undefined ? "button" : "div",
|
|
103
158
|
render,
|
|
@@ -109,9 +164,9 @@ export function MetricCard({ label, value, change, sparkline, onSelect, href, re
|
|
|
109
164
|
: {}),
|
|
110
165
|
...props,
|
|
111
166
|
onClick: onSelect,
|
|
112
|
-
className: cx(metricCard, className),
|
|
167
|
+
className: cx(metricCard({ divider }), className),
|
|
113
168
|
"data-ciridae-ui": "",
|
|
114
|
-
children: (_jsxs(_Fragment, { children: [_jsxs("span", { className: metricCopy, children: [_jsx("span", { className: metricLabel, children: label }), _jsx("span", { className: metricValue, children: value }), change && _jsx(Trend, { ...change })] }), sparkline && _jsx(Sparkline, { values: sparkline, "data-sparkline": "" })] })),
|
|
169
|
+
children: (_jsxs(_Fragment, { children: [_jsxs("span", { className: metricCopy, children: [_jsx("span", { className: metricLabel, "data-metric-label": "", children: label }), _jsx("span", { className: metricValue, children: value }), change && _jsx(Trend, { ...change })] }), sparkline && _jsx(Sparkline, { values: sparkline, "data-sparkline": "" })] })),
|
|
115
170
|
},
|
|
116
171
|
});
|
|
117
172
|
}
|
|
@@ -70,7 +70,7 @@ const bannerIcon = cva({
|
|
|
70
70
|
neutral: { color: "fg.muted" },
|
|
71
71
|
info: { color: "accent.blue" },
|
|
72
72
|
success: { color: "accent.green" },
|
|
73
|
-
warning: { color: "accent.
|
|
73
|
+
warning: { color: "accent.yellowIcon" },
|
|
74
74
|
danger: { color: "accent.red" },
|
|
75
75
|
},
|
|
76
76
|
},
|
package/dist/components/tabs.js
CHANGED
|
@@ -22,14 +22,15 @@ const list = css({
|
|
|
22
22
|
overflowX: "auto",
|
|
23
23
|
scrollbarWidth: "none",
|
|
24
24
|
});
|
|
25
|
-
/** Finalization "
|
|
25
|
+
/** Finalization "View tabs": tabs sit inside a grey pill well with a hairline inset. */
|
|
26
26
|
const containedList = css({
|
|
27
27
|
width: "max-content",
|
|
28
28
|
padding: "control.trackInset",
|
|
29
29
|
border: "1px solid token(colors.border.control)",
|
|
30
30
|
borderRadius: "pill",
|
|
31
|
-
background: "bg.
|
|
32
|
-
gap
|
|
31
|
+
background: "bg.field",
|
|
32
|
+
// Raised over the base list's own gap, as the text list's is.
|
|
33
|
+
"&[data-variant=contained]": { gap: "control.trackInset" },
|
|
33
34
|
});
|
|
34
35
|
/* Text tabs: words only, the open one in the primary text, for a tray's or a sheet's filters. */
|
|
35
36
|
const textList = css({ "&[data-variant=text]": { gap: "layout.contentGap" } });
|
|
@@ -48,8 +49,9 @@ const tab = css({
|
|
|
48
49
|
alignItems: "center",
|
|
49
50
|
justifyContent: "center",
|
|
50
51
|
gap: "control.iconGap",
|
|
51
|
-
height: "control.
|
|
52
|
+
height: "control.restingTab",
|
|
52
53
|
paddingInline: "control.tabInline",
|
|
54
|
+
// The open tab's edge's own 1px, kept at rest too so a tab keeps its width as it opens.
|
|
53
55
|
border: "1px solid transparent",
|
|
54
56
|
borderRadius: "pill",
|
|
55
57
|
background: "transparent",
|
|
@@ -62,19 +64,26 @@ const tab = css({
|
|
|
62
64
|
cursor: "pointer",
|
|
63
65
|
flexShrink: "0",
|
|
64
66
|
"&[data-icon-tone=bronze] svg": { color: "accent.bronze" },
|
|
65
|
-
|
|
67
|
+
// The open tab is raised on the secondary button's face, with its gradient edge and soft shadow.
|
|
68
|
+
_before: { display: "none", background: "token(gradients.border.raised)" },
|
|
66
69
|
_hover: {
|
|
67
|
-
background: "bg.
|
|
70
|
+
background: "bg.controlHover",
|
|
68
71
|
color: "fg.primary",
|
|
69
72
|
"& svg": { color: "inherit" },
|
|
70
|
-
_before: { display: "block" },
|
|
71
73
|
},
|
|
72
74
|
"&[data-active]": {
|
|
73
|
-
background: "
|
|
75
|
+
background: "token(gradients.raised)",
|
|
76
|
+
boxShadow: "soft",
|
|
74
77
|
color: "fg.primary",
|
|
75
78
|
"& svg": { color: "inherit" },
|
|
76
79
|
_before: { display: "block" },
|
|
77
80
|
},
|
|
81
|
+
// On a face, the words read in the primary ink in light, where only the open tab is raised, and
|
|
82
|
+
// the open tab stands 2px taller than the rest.
|
|
83
|
+
"[data-variant=plain] > &, [data-variant=contained] > &": {
|
|
84
|
+
_light: { color: "fg.primary" },
|
|
85
|
+
"&[data-active]": { height: "control.small", _phone: { height: "control.compact" } },
|
|
86
|
+
},
|
|
78
87
|
// Words only: no face, no inset, and focus underlines the word rather than boxing it.
|
|
79
88
|
"[data-variant=text] > &": {
|
|
80
89
|
height: "auto",
|
|
@@ -83,8 +92,8 @@ const tab = css({
|
|
|
83
92
|
border: "0",
|
|
84
93
|
background: "transparent",
|
|
85
94
|
_hover: { background: "transparent" },
|
|
86
|
-
"&[data-active]": { background: "transparent" },
|
|
87
|
-
// No
|
|
95
|
+
"&[data-active]": { background: "transparent", boxShadow: "none" },
|
|
96
|
+
// No edge either: the open word is the only mark.
|
|
88
97
|
"&::before, &:hover::before, &[data-active]::before": { display: "none" },
|
|
89
98
|
_focusVisible: {
|
|
90
99
|
boxShadow: "none",
|
|
@@ -103,7 +112,11 @@ const tab = css({
|
|
|
103
112
|
marginBottom: "-1px",
|
|
104
113
|
background: "transparent",
|
|
105
114
|
_hover: { background: "transparent" },
|
|
106
|
-
"&[data-active]": {
|
|
115
|
+
"&[data-active]": {
|
|
116
|
+
background: "transparent",
|
|
117
|
+
boxShadow: "none",
|
|
118
|
+
borderBottomColor: "fg.primary",
|
|
119
|
+
},
|
|
107
120
|
"&::before, &:hover::before, &[data-active]::before": { display: "none" },
|
|
108
121
|
_phone: { height: "control.search", paddingInline: "none" },
|
|
109
122
|
},
|
|
@@ -190,7 +203,7 @@ export function TabsList({ className, variant = "plain", overflow: ownOverflow,
|
|
|
190
203
|
const overflow = ownOverflow ?? (inBar ? "menu" : "scroll");
|
|
191
204
|
const { labels } = useCiridaeLocale();
|
|
192
205
|
const { listRef, moreRef, overflowed, activeHidden, activate } = useOverflowedTabs(overflow === "menu", children);
|
|
193
|
-
return (_jsxs(BaseTabs.List, { ...props, ref: listRef, className: cx(list, variant === "contained" && containedList, variant === "text" && textList, variant === "underline" && underlineList, className), "data-ciridae-ui": "", "data-variant": variant, children: [children, overflow === "menu" && overflowed.length > 0 && (_jsx(Menu, { align: "end", trigger: _jsx("button", { type: "button", ref: moreRef, className: tab, "aria-label": labels.common.moreTabs, "data-ciridae-ui": "", "data-edge": "
|
|
206
|
+
return (_jsxs(BaseTabs.List, { ...props, ref: listRef, className: cx(list, variant === "contained" && containedList, variant === "text" && textList, variant === "underline" && underlineList, className), "data-ciridae-ui": "", "data-variant": variant, children: [children, overflow === "menu" && overflowed.length > 0 && (_jsx(Menu, { align: "end", trigger: _jsx("button", { type: "button", ref: moreRef, className: tab, "aria-label": labels.common.moreTabs, "data-ciridae-ui": "", "data-edge": "gradient", "data-active": activeHidden || undefined, children: _jsx(DotsThreeIcon, { className: icon({ size: "small" }), weight: "bold" }) }), items: overflowed.map((item) => ({
|
|
194
207
|
label: item.label,
|
|
195
208
|
meta: item.count,
|
|
196
209
|
onSelect: () => activate(item.index),
|
|
@@ -198,7 +211,7 @@ export function TabsList({ className, variant = "plain", overflow: ownOverflow,
|
|
|
198
211
|
}
|
|
199
212
|
export function TabsTab({ className, iconTone, href, render, count: tabCount, children, ...props }) {
|
|
200
213
|
const link = href !== undefined || render !== undefined;
|
|
201
|
-
return (_jsxs(BaseTabs.Tab, { ...props, render: render ?? (href !== undefined ? _jsx("a", { href: href }) : undefined), nativeButton: link ? false : props.nativeButton, className: cx(tab, className), "data-ciridae-ui": "", "data-edge": "
|
|
214
|
+
return (_jsxs(BaseTabs.Tab, { ...props, render: render ?? (href !== undefined ? _jsx("a", { href: href }) : undefined), nativeButton: link ? false : props.nativeButton, className: cx(tab, className), "data-ciridae-ui": "", "data-edge": "gradient", "data-icon-tone": iconTone, children: [children, tabCount !== undefined && tabCount !== null && (_jsx("span", { className: count, "data-tab-count": "", children: tabCount }))] }));
|
|
202
215
|
}
|
|
203
216
|
/*
|
|
204
217
|
* A page's tabs share one row with the open section's controls: tabs that do not fit go into the
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { CircleHalfIcon, MoonIcon, SunIcon } from "@phosphor-icons/react";
|
|
3
|
+
import { css, cx } from "#primitives/css";
|
|
4
|
+
import { useColorTheme } from "./color-theme.js";
|
|
5
|
+
import { faceEdge } from "./gradient-edge.js";
|
|
6
|
+
import { icon } from "./icon.js";
|
|
7
|
+
import { useCiridaeLocale } from "./locale.js";
|
|
8
|
+
/*
|
|
9
|
+
* Light, dark or the system's theme, as three icon segments in a pale track: the chosen one is
|
|
10
|
+
* raised on the secondary button's face, its icon in the primary ink, the others a step quieter.
|
|
11
|
+
*/
|
|
12
|
+
const track = css({
|
|
13
|
+
display: "inline-flex",
|
|
14
|
+
alignItems: "center",
|
|
15
|
+
gap: "control.trackInset",
|
|
16
|
+
padding: "control.trackInset",
|
|
17
|
+
// The design lays the track's edge over its inset, and rounds it to its options' corners.
|
|
18
|
+
border: "0",
|
|
19
|
+
borderRadius: "calc(token(radii.inset) + token(spacing.control.trackInset))",
|
|
20
|
+
background: "token(gradients.panel)",
|
|
21
|
+
});
|
|
22
|
+
const option = css({
|
|
23
|
+
display: "inline-grid",
|
|
24
|
+
placeItems: "center",
|
|
25
|
+
width: "control.small",
|
|
26
|
+
height: "calc(token(sizes.control.small) - 2 * token(spacing.control.trackInset))",
|
|
27
|
+
padding: "none",
|
|
28
|
+
border: "0",
|
|
29
|
+
borderRadius: "inset",
|
|
30
|
+
background: "transparent",
|
|
31
|
+
color: "fg.secondary",
|
|
32
|
+
cursor: "pointer",
|
|
33
|
+
transitionProperty: "background, color",
|
|
34
|
+
transitionDuration: "fast",
|
|
35
|
+
_hover: { color: "fg.primary" },
|
|
36
|
+
_focusVisible: { focusRing: "outer" },
|
|
37
|
+
_before: { display: "none", background: "token(gradients.border.raised)" },
|
|
38
|
+
"&[aria-pressed=true]": {
|
|
39
|
+
background: "token(gradients.raised)",
|
|
40
|
+
color: "fg.primary",
|
|
41
|
+
_before: { display: "block" },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
/** Sets the color theme the `ThemeProvider` keeps: light, dark, or the operating system's. */
|
|
45
|
+
export function ThemeToggle({ className }) {
|
|
46
|
+
const { preference, setPreference } = useColorTheme();
|
|
47
|
+
const { labels } = useCiridaeLocale();
|
|
48
|
+
const trackEdge = faceEdge("raised");
|
|
49
|
+
const choices = [
|
|
50
|
+
{
|
|
51
|
+
value: "light",
|
|
52
|
+
label: labels.themeCustomizer.light,
|
|
53
|
+
glyph: _jsx(SunIcon, { className: icon({ size: "small" }) }),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
value: "dark",
|
|
57
|
+
label: labels.themeCustomizer.dark,
|
|
58
|
+
glyph: _jsx(MoonIcon, { className: icon({ size: "small" }) }),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
value: "system",
|
|
62
|
+
label: labels.themeCustomizer.system,
|
|
63
|
+
glyph: _jsx(CircleHalfIcon, { className: icon({ size: "small" }) }),
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
return (_jsx("div", { role: "group", "aria-label": labels.themeCustomizer.colorTheme, className: cx(track, trackEdge.className, className), "data-ciridae-ui": "", "data-edge": trackEdge["data-edge"], children: choices.map((choice) => (_jsx("button", { type: "button", "aria-label": choice.label, "aria-pressed": preference === choice.value, className: option, "data-ciridae-ui": "", "data-edge": "gradient", onClick: () => setPreference(choice.value), children: choice.glyph }, choice.value))) }));
|
|
67
|
+
}
|
|
@@ -11,11 +11,18 @@ export type ToastOptions = {
|
|
|
11
11
|
variant?: ToastVariant;
|
|
12
12
|
/** Milliseconds it stays; `0` keeps it until dismissed. An error stays by default. */
|
|
13
13
|
timeout?: number;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* One small secondary button, such as Undo; its children are the label. It closes the toast, and
|
|
16
|
+
* takes the close button's place.
|
|
17
|
+
*/
|
|
15
18
|
actionProps?: Pick<ComponentProps<"button">, "children" | "onClick">;
|
|
19
|
+
/** Replaces the toast already shown with this id, such as a progress toast moving along. */
|
|
20
|
+
id?: string | number;
|
|
21
|
+
/** How far a `progress` toast is, 0 to 100, shown as a track and a percentage after its message. */
|
|
22
|
+
percent?: number;
|
|
16
23
|
};
|
|
17
24
|
/** Raises a toast with the kit's options, returning its id. */
|
|
18
|
-
declare function addToast({ title:
|
|
25
|
+
declare function addToast({ title: ownMessage, description: detail, variant, timeout, actionProps, id, percent, }: ToastOptions): string | number;
|
|
19
26
|
/** The kit's toast calls: `add` raises one and returns its id, `close` dismisses it. */
|
|
20
27
|
export declare function useToast(): {
|
|
21
28
|
add: typeof addToast;
|
package/dist/components/toast.js
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
|
-
* Toasts are sonner's, in the kit's look: a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Toasts are sonner's, in the kit's look: a solid pill with a tone icon, the message, an optional
|
|
4
|
+
* muted line under it, and at its end either an action as a small secondary button or a close
|
|
5
|
+
* button. Mount
|
|
6
|
+
* one `Toaster` at the app's root; raise toasts with `toast` from anywhere, or `useToast().add`
|
|
7
|
+
* with the kit's options.
|
|
6
8
|
*/
|
|
7
9
|
import { CheckCircleIcon, InfoIcon, SpinnerGapIcon, WarningCircleIcon, WarningIcon, XIcon, } from "@phosphor-icons/react";
|
|
8
10
|
import { Toaster as SonnerToaster, toast } from "sonner";
|
|
9
11
|
import { css, cx } from "#primitives/css";
|
|
12
|
+
import { Button } from "./button.js";
|
|
10
13
|
import { icon, spinning } from "./icon.js";
|
|
11
14
|
import { useCiridaeLocale } from "./locale.js";
|
|
12
15
|
export { toast };
|
|
13
16
|
const card = css({
|
|
14
17
|
display: "flex",
|
|
15
|
-
alignItems: "
|
|
16
|
-
gap: "layout.
|
|
17
|
-
width: "
|
|
18
|
-
maxWidth: "
|
|
18
|
+
alignItems: "center",
|
|
19
|
+
gap: "layout.contentGap",
|
|
20
|
+
width: "100%",
|
|
21
|
+
maxWidth: "calc(100vw - token(spacing.layout.pageGap) * 2)",
|
|
19
22
|
paddingBlock: "control.block",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
paddingInlineStart: "control.inline",
|
|
24
|
+
paddingInlineEnd: "control.block",
|
|
25
|
+
border: "1px solid token(colors.border.divider)",
|
|
26
|
+
borderRadius: "card",
|
|
27
|
+
// Solid in both themes: the panel's glass laid on the canvas in dark.
|
|
28
|
+
background: "linear-gradient(token(colors.bg.panel), token(colors.bg.panel)), token(colors.bg.canvas)",
|
|
29
|
+
_light: { background: "bg.surface" },
|
|
24
30
|
color: "fg.primary",
|
|
25
|
-
boxShadow: "
|
|
31
|
+
boxShadow: "soft",
|
|
26
32
|
textStyle: "body",
|
|
27
33
|
fontFamily: "sans",
|
|
34
|
+
fontWeight: "medium",
|
|
28
35
|
lineHeight: "compact",
|
|
29
36
|
pointerEvents: "auto",
|
|
30
37
|
});
|
|
@@ -43,18 +50,28 @@ const iconSlot = css({
|
|
|
43
50
|
alignItems: "center",
|
|
44
51
|
flexShrink: "0",
|
|
45
52
|
});
|
|
53
|
+
/*
|
|
54
|
+
* An action raised through sonner's own `toast` with `{ label, onClick }`: the small secondary
|
|
55
|
+
* button's shape and face, with a flat hairline, since sonner draws the button. `useToast` draws a
|
|
56
|
+
* kit `Button` instead.
|
|
57
|
+
*/
|
|
46
58
|
const actionButton = css({
|
|
47
59
|
flexShrink: "0",
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
height: "control.small",
|
|
61
|
+
paddingInline: "control.compactInline",
|
|
62
|
+
border: "1px solid token(colors.border.glass)",
|
|
63
|
+
borderRadius: "pill",
|
|
64
|
+
background: "token(gradients.raised)",
|
|
65
|
+
color: "fg.primary",
|
|
66
|
+
fontFamily: "inherit",
|
|
67
|
+
fontSize: "caption",
|
|
53
68
|
fontWeight: "medium",
|
|
69
|
+
letterSpacing: "compact",
|
|
54
70
|
cursor: "pointer",
|
|
55
|
-
_hover: {
|
|
71
|
+
_hover: { background: "bg.raisedHover" },
|
|
56
72
|
_focusVisible: { focusRing: "outer" },
|
|
57
73
|
});
|
|
74
|
+
const actionSlot = css({ flexShrink: "0" });
|
|
58
75
|
// Sonner places its close button in the card's corner; the kit's sits at the end of the row.
|
|
59
76
|
const closeButton = css({
|
|
60
77
|
position: "static!",
|
|
@@ -62,8 +79,8 @@ const closeButton = css({
|
|
|
62
79
|
display: "grid",
|
|
63
80
|
placeItems: "center",
|
|
64
81
|
flexShrink: "0",
|
|
65
|
-
width: "control.
|
|
66
|
-
height: "control.
|
|
82
|
+
width: "control.compact",
|
|
83
|
+
height: "control.compact",
|
|
67
84
|
padding: "none",
|
|
68
85
|
border: "0",
|
|
69
86
|
borderRadius: "round",
|
|
@@ -74,12 +91,40 @@ const closeButton = css({
|
|
|
74
91
|
_hover: { color: "fg.primary", background: "bg.hover" },
|
|
75
92
|
_focusVisible: { focusRing: "outer" },
|
|
76
93
|
});
|
|
94
|
+
/* A progress toast's track and how far along it is, after its message. */
|
|
95
|
+
const progressMessage = css({ display: "flex", alignItems: "center", gap: "layout.contentGap" });
|
|
96
|
+
const progressMessageText = css({ flex: "1", minWidth: "0" });
|
|
97
|
+
const progress = css({
|
|
98
|
+
display: "inline-flex",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
gap: "layout.controlGap",
|
|
101
|
+
flexShrink: "0",
|
|
102
|
+
});
|
|
103
|
+
const progressTrack = css({
|
|
104
|
+
width: "toast.progressWidth",
|
|
105
|
+
height: "toast.progressHeight",
|
|
106
|
+
overflow: "hidden",
|
|
107
|
+
borderRadius: "round",
|
|
108
|
+
background: "border.control",
|
|
109
|
+
_light: { background: "token(gradients.canvas)" },
|
|
110
|
+
});
|
|
111
|
+
const progressFill = css({ height: "100%", borderRadius: "round", background: "fg.primary" });
|
|
112
|
+
const progressPercent = css({
|
|
113
|
+
color: "fg.muted",
|
|
114
|
+
_light: { color: "fg.secondary" },
|
|
115
|
+
fontSize: "code",
|
|
116
|
+
fontVariantNumeric: "tabular-nums",
|
|
117
|
+
});
|
|
77
118
|
const toneIcon = {
|
|
78
119
|
success: css({ color: "accent.green" }),
|
|
79
120
|
info: css({ color: "accent.blue" }),
|
|
80
|
-
warning: css({ color: "accent.
|
|
121
|
+
warning: css({ color: "accent.yellowIcon" }),
|
|
81
122
|
danger: css({ color: "accent.red" }),
|
|
82
123
|
};
|
|
124
|
+
// Sonner sizes its stack by `--width`; the kit's toast is the design's width.
|
|
125
|
+
const toasterStyle = {
|
|
126
|
+
"--width": "var(--sizes-toast\\.width)",
|
|
127
|
+
};
|
|
83
128
|
/** The toast stack, in the kit's look: mount one at the app's root. */
|
|
84
129
|
export function Toaster(props) {
|
|
85
130
|
const { labels, dir } = useCiridaeLocale();
|
|
@@ -92,8 +137,8 @@ export function Toaster(props) {
|
|
|
92
137
|
warning: _jsx(WarningCircleIcon, { className: cx(small, toneIcon.warning), weight: "fill" }),
|
|
93
138
|
error: _jsx(WarningIcon, { className: cx(small, toneIcon.danger), weight: "fill" }),
|
|
94
139
|
loading: _jsx(SpinnerGapIcon, { className: cx(small, toneIcon.info, spinning) }),
|
|
95
|
-
close: _jsx(XIcon, { className:
|
|
96
|
-
}, containerAriaLabel: labels.notifications.title, ...props, toastOptions: {
|
|
140
|
+
close: _jsx(XIcon, { className: small }),
|
|
141
|
+
}, containerAriaLabel: labels.notifications.title, ...props, style: { ...toasterStyle, ...props.style }, toastOptions: {
|
|
97
142
|
unstyled: true,
|
|
98
143
|
...props.toastOptions,
|
|
99
144
|
classNames: {
|
|
@@ -108,16 +153,20 @@ export function Toaster(props) {
|
|
|
108
153
|
},
|
|
109
154
|
} }));
|
|
110
155
|
}
|
|
156
|
+
let lastToastId = 0;
|
|
111
157
|
/** Raises a toast with the kit's options, returning its id. */
|
|
112
|
-
function addToast({ title:
|
|
158
|
+
function addToast({ title: ownMessage, description: detail, variant, timeout, actionProps, id = `ciridae-toast-${++lastToastId}`, percent, }) {
|
|
113
159
|
const duration = timeout ?? (variant === "error" ? 0 : undefined);
|
|
160
|
+
const message = percent === undefined ? (ownMessage) : (_jsxs("span", { className: progressMessage, children: [_jsx("span", { className: progressMessageText, children: ownMessage }), _jsxs("span", { className: progress, children: [_jsx("span", { className: progressTrack, children: _jsx("span", { className: progressFill, style: { width: `${percent}%` } }) }), _jsxs("span", { className: progressPercent, children: [Math.round(percent), "%"] })] })] }));
|
|
114
161
|
const options = {
|
|
162
|
+
id,
|
|
115
163
|
description: detail,
|
|
116
164
|
duration: duration === 0 ? Infinity : duration,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
165
|
+
closeButton: actionProps === undefined,
|
|
166
|
+
action: actionProps && (_jsx("span", { className: actionSlot, children: _jsx(Button, { variant: "secondary", size: "small", onClick: (event) => {
|
|
167
|
+
actionProps.onClick?.(event);
|
|
168
|
+
toast.dismiss(id);
|
|
169
|
+
}, children: actionProps.children }) })),
|
|
121
170
|
};
|
|
122
171
|
switch (variant) {
|
|
123
172
|
case "success":
|