@momoi-labs/kiso-react 0.7.0 → 0.8.0
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/README.md +6 -0
- package/dist/accent-selector.d.ts +9 -0
- package/dist/accent-selector.js +13 -0
- package/dist/form.d.ts +11 -0
- package/dist/form.js +9 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/step-list.d.ts +26 -0
- package/dist/step-list.js +33 -0
- package/dist/styles.css +63 -0
- package/dist/time-range-control.js +3 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -66,6 +66,8 @@ Data and structure
|
|
|
66
66
|
primary action in the top bar and no sidebar rail.
|
|
67
67
|
- Split, Pane, and Splitter, a list-detail layout with a resizable divider.
|
|
68
68
|
- LogView, LogViewLine, LogViewTime, and LogViewLevel.
|
|
69
|
+
- StepList, a run's steps with a state each, and StepBar, the same run as
|
|
70
|
+
one segment per step for a summary, a table cell, or a toast.
|
|
69
71
|
- Stat, StatHeader, StatLabel, StatValue, StatFoot, and StatDelta.
|
|
70
72
|
- KV, KVKey, and KVValue, a description list of fixed facts.
|
|
71
73
|
- Separator, horizontal or vertical, and Dot and `dotVariants`.
|
|
@@ -82,6 +84,10 @@ Navigation
|
|
|
82
84
|
- ThemeSelector, a controlled selector. The application owns theme persistence
|
|
83
85
|
and applies `data-theme="light"` or `data-theme="dark"` to the document root.
|
|
84
86
|
Remove the attribute for system mode.
|
|
87
|
+
- AccentSelector, a controlled selector, plus the `accents` list and `Accent`
|
|
88
|
+
type. The application owns accent persistence and applies
|
|
89
|
+
`data-accent="<name>"` to the document root, or to any container that should
|
|
90
|
+
carry its own accent. No attribute means violet.
|
|
85
91
|
|
|
86
92
|
Feedback
|
|
87
93
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const accents: readonly ["violet", "terracotta", "teal", "cobalt", "nocturne"];
|
|
2
|
+
export type Accent = (typeof accents)[number];
|
|
3
|
+
export declare function AccentSelector({ accent, onChange, options, preview, }: {
|
|
4
|
+
accent: string;
|
|
5
|
+
onChange: (accent: Accent) => void;
|
|
6
|
+
options?: readonly Accent[];
|
|
7
|
+
/** Render the live preview of the selected accent below the pills. */
|
|
8
|
+
preview?: boolean;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
export const accents = [
|
|
4
|
+
"violet",
|
|
5
|
+
"terracotta",
|
|
6
|
+
"teal",
|
|
7
|
+
"cobalt",
|
|
8
|
+
"nocturne",
|
|
9
|
+
];
|
|
10
|
+
const label = (accent) => `${accent[0].toUpperCase()}${accent.slice(1)}`;
|
|
11
|
+
export function AccentSelector({ accent, onChange, options = accents, preview = true, }) {
|
|
12
|
+
return (_jsxs("div", { className: "accent-row", children: [_jsxs("div", { className: "theme-row", children: [_jsx("span", { className: "t-label", children: "Accent" }), _jsx("div", { className: "accent-pills", role: "group", "aria-label": "Accent", children: options.map((option) => (_jsxs("button", { type: "button", className: "accent-pill", "aria-pressed": accent === option, "data-accent": option, onClick: () => onChange(option), children: [_jsx("span", { className: "accent-dot", "aria-hidden": "true" }), label(option)] }, option))) })] }), preview && (_jsxs("div", { className: "accent-preview", "data-accent": accent, "aria-hidden": "true", children: [_jsxs("span", { className: "accent-preview-sidebar", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {}), _jsx("i", {})] }), _jsxs("span", { className: "accent-preview-main", children: [_jsx("i", { className: "accent-preview-title" }), _jsx("i", { className: "accent-preview-text" }), _jsx("i", { className: "accent-preview-link" }), _jsxs("span", { className: "accent-preview-actions", children: [_jsx("i", { className: "accent-preview-primary" }), _jsx("i", { className: "accent-preview-secondary" })] }), _jsx("i", { className: "accent-preview-selected" })] })] }))] }));
|
|
13
|
+
}
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
type FormProps = ComponentProps<"form">;
|
|
3
|
+
declare function Form({ className, ...props }: FormProps): import("react").JSX.Element;
|
|
4
|
+
type FormActionsProps = ComponentProps<"div"> & {
|
|
5
|
+
sticky?: boolean;
|
|
6
|
+
tone?: "neutral" | "warning" | "danger";
|
|
7
|
+
message?: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
declare function FormActions({ className, sticky, tone, message, children, ...props }: FormActionsProps): import("react").JSX.Element;
|
|
10
|
+
export { Form, FormActions };
|
|
11
|
+
export type { FormProps, FormActionsProps };
|
package/dist/form.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
function Form({ className, ...props }) {
|
|
4
|
+
return _jsx("form", { "data-slot": "form", className: clsx("form", className), ...props });
|
|
5
|
+
}
|
|
6
|
+
function FormActions({ className, sticky = false, tone = "neutral", message, children, ...props }) {
|
|
7
|
+
return (_jsxs("div", { "data-slot": "form-actions", "data-sticky": sticky || undefined, "data-tone": tone, className: clsx("form-actions", className), ...props, children: [_jsx("div", { className: "form-actions-message", role: "status", "aria-atomic": "true", children: message }), _jsx("div", { className: "form-actions-buttons", children: children })] }));
|
|
8
|
+
}
|
|
9
|
+
export { Form, FormActions };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./drawer.js";
|
|
|
15
15
|
export * from "./dropdown-menu.js";
|
|
16
16
|
export * from "./empty-state.js";
|
|
17
17
|
export * from "./form-field.js";
|
|
18
|
+
export * from "./form.js";
|
|
18
19
|
export * from "./header.js";
|
|
19
20
|
export * from "./input.js";
|
|
20
21
|
export * from "./kv.js";
|
|
@@ -40,6 +41,7 @@ export * from "./tabs.js";
|
|
|
40
41
|
export * from "./terminal-icon.js";
|
|
41
42
|
export * from "./textarea.js";
|
|
42
43
|
export * from "./theme-selector.js";
|
|
44
|
+
export * from "./accent-selector.js";
|
|
43
45
|
export * from "./toast.js";
|
|
44
46
|
export * from "./tooltip.js";
|
|
45
47
|
export * from "./validation-message.js";
|
|
@@ -50,3 +52,4 @@ export * from "./bar-gauge.js";
|
|
|
50
52
|
export * from "./disclosure.js";
|
|
51
53
|
export * from "./time-range-control.js";
|
|
52
54
|
export * from "./dashboard-grid.js";
|
|
55
|
+
export * from "./step-list.js";
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./drawer.js";
|
|
|
16
16
|
export * from "./dropdown-menu.js";
|
|
17
17
|
export * from "./empty-state.js";
|
|
18
18
|
export * from "./form-field.js";
|
|
19
|
+
export * from "./form.js";
|
|
19
20
|
export * from "./header.js";
|
|
20
21
|
export * from "./input.js";
|
|
21
22
|
export * from "./kv.js";
|
|
@@ -41,6 +42,7 @@ export * from "./tabs.js";
|
|
|
41
42
|
export * from "./terminal-icon.js";
|
|
42
43
|
export * from "./textarea.js";
|
|
43
44
|
export * from "./theme-selector.js";
|
|
45
|
+
export * from "./accent-selector.js";
|
|
44
46
|
export * from "./toast.js";
|
|
45
47
|
export * from "./tooltip.js";
|
|
46
48
|
export * from "./validation-message.js";
|
|
@@ -51,3 +53,4 @@ export * from "./bar-gauge.js";
|
|
|
51
53
|
export * from "./disclosure.js";
|
|
52
54
|
export * from "./time-range-control.js";
|
|
53
55
|
export * from "./dashboard-grid.js";
|
|
56
|
+
export * from "./step-list.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type StepState = "pending" | "running" | "done" | "skipped" | "failed";
|
|
3
|
+
export type StepItem = {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
state: StepState;
|
|
7
|
+
meta?: string;
|
|
8
|
+
detail?: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export type StepListProps = Omit<React.ComponentProps<"ol">, "children" | "onSelect"> & {
|
|
11
|
+
label: string;
|
|
12
|
+
steps: readonly StepItem[];
|
|
13
|
+
selected?: string | null;
|
|
14
|
+
onSelect?: (key: string) => void;
|
|
15
|
+
};
|
|
16
|
+
export declare function StepList({ label, steps, selected, onSelect, className, ...props }: StepListProps): React.JSX.Element;
|
|
17
|
+
export type StepBarProps = Omit<React.ComponentProps<"div">, "children"> & {
|
|
18
|
+
label: string;
|
|
19
|
+
steps: readonly {
|
|
20
|
+
key: string;
|
|
21
|
+
label: string;
|
|
22
|
+
state: StepState;
|
|
23
|
+
progress?: number | null;
|
|
24
|
+
}[];
|
|
25
|
+
};
|
|
26
|
+
export declare function StepBar({ label, steps, className, ...props }: StepBarProps): React.JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { clsx as cn } from "clsx";
|
|
4
|
+
const STATE_TEXT = {
|
|
5
|
+
pending: "Pending", running: "Running", done: "Done", skipped: "Skipped", failed: "Failed",
|
|
6
|
+
};
|
|
7
|
+
const DEFAULT_META = { running: "Running", skipped: "Not needed" };
|
|
8
|
+
/* The rail is the progress: each connector takes the state of the step below
|
|
9
|
+
it, so the line fills as the run advances. Nothing else measures it. */
|
|
10
|
+
export function StepList({ label, steps, selected, onSelect, className, ...props }) {
|
|
11
|
+
return _jsx("ol", { ...props, "aria-label": label, "data-slot": "step-list", className: cn("step-list", className), children: steps.map(step => {
|
|
12
|
+
const row = _jsxs(_Fragment, { children: [_jsx("span", { className: "step-label", children: step.label }), _jsx("span", { className: "step-meta", children: step.meta ?? DEFAULT_META[step.state] ?? "" })] });
|
|
13
|
+
return _jsxs("li", { "data-slot": "step", "data-state": step.state, className: "step", "aria-current": step.state === "running" ? "step" : undefined, children: [_jsx("span", { className: "step-rail", "aria-hidden": "true", children: _jsx("span", { className: "step-mark" }) }), _jsxs("div", { className: "step-body", children: [onSelect
|
|
14
|
+
? _jsx("button", { type: "button", className: "step-row", "aria-pressed": selected === step.key, "aria-label": `${step.label}, ${STATE_TEXT[step.state].toLowerCase()}`, onClick: () => onSelect(step.key), children: row })
|
|
15
|
+
: _jsx("div", { className: "step-row", role: "img", "aria-label": `${step.label}, ${STATE_TEXT[step.state].toLowerCase()}`, children: row }), step.detail != null && _jsx("div", { className: "step-detail", children: step.detail })] })] }, step.key);
|
|
16
|
+
}) });
|
|
17
|
+
}
|
|
18
|
+
/* One segment per step, coloured by state. The product writes the count and
|
|
19
|
+
the current step beside it; the bar only names them for assistive tech. */
|
|
20
|
+
export function StepBar({ label, steps, className, ...props }) {
|
|
21
|
+
const failed = steps.findIndex(step => step.state === "failed");
|
|
22
|
+
const running = steps.findIndex(step => step.state === "running");
|
|
23
|
+
const at = failed >= 0 ? failed : running;
|
|
24
|
+
const finished = steps.length > 0 && steps.every(step => step.state === "done" || step.state === "skipped");
|
|
25
|
+
const name = at >= 0
|
|
26
|
+
? `${label}: ${failed >= 0 ? "failed at" : "running"} step ${at + 1} of ${steps.length}, ${steps[at].label}`
|
|
27
|
+
: finished ? `${label}: all ${steps.length} steps done` : `${label}: not started`;
|
|
28
|
+
return _jsx("div", { ...props, role: "img", "aria-label": name, "data-slot": "step-bar", className: cn("step-bar", className), children: steps.map(step => {
|
|
29
|
+
const progress = step.state === "running" && step.progress != null && Number.isFinite(step.progress)
|
|
30
|
+
? `${Math.min(1, Math.max(0, step.progress)) * 100}%` : undefined;
|
|
31
|
+
return _jsx("span", { "data-state": step.state, title: step.label, style: progress !== undefined ? { "--step-progress": progress } : undefined }, step.key);
|
|
32
|
+
}) });
|
|
33
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -21,6 +21,69 @@
|
|
|
21
21
|
color: var(--color-foreground);
|
|
22
22
|
box-shadow: var(--shadow-xs);
|
|
23
23
|
}
|
|
24
|
+
/* AccentSelector: a pill row that chooses, and one preview that answers.
|
|
25
|
+
Each pill and the preview carry their own data-accent, so their colours
|
|
26
|
+
resolve under that accent without any inline values. */
|
|
27
|
+
.accent-row { display: grid; gap: var(--spacing-sm); }
|
|
28
|
+
/* Only the variant with a preview is a block-level settings row and can be a
|
|
29
|
+
size container; inline-size containment would collapse the preview-less
|
|
30
|
+
variant to zero width inside a toolbar's flex row. */
|
|
31
|
+
.accent-row:has(.accent-preview) { container-type: inline-size; }
|
|
32
|
+
.accent-pills { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--spacing-xs); }
|
|
33
|
+
/* Narrow hosts (a sidebar footer) stack the label over a two-column grid of
|
|
34
|
+
pills instead of wrapping them ragged beside it. */
|
|
35
|
+
@container (width < 360px) {
|
|
36
|
+
.accent-row .theme-row { flex-direction: column; align-items: stretch; }
|
|
37
|
+
.accent-pills { display: grid; grid-template-columns: 1fr 1fr; }
|
|
38
|
+
.accent-pill { justify-content: center; }
|
|
39
|
+
}
|
|
40
|
+
.accent-pill {
|
|
41
|
+
display: inline-flex; align-items: center; gap: var(--spacing-xs);
|
|
42
|
+
height: var(--size-control-xs);
|
|
43
|
+
padding-inline: var(--spacing-xs) var(--spacing-sm);
|
|
44
|
+
border: 1px solid var(--color-border);
|
|
45
|
+
border-radius: var(--radius-full);
|
|
46
|
+
background: var(--color-card);
|
|
47
|
+
color: var(--color-muted-foreground);
|
|
48
|
+
font: inherit; font-size: var(--type-size-label);
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
transition: background var(--motion-duration-fast) var(--motion-easing-standard),
|
|
51
|
+
border-color var(--motion-duration-fast) var(--motion-easing-standard);
|
|
52
|
+
}
|
|
53
|
+
.accent-pill:hover { background: var(--color-accent-surface); color: var(--color-foreground); }
|
|
54
|
+
.accent-pill[aria-pressed="true"] {
|
|
55
|
+
border-color: var(--color-primary);
|
|
56
|
+
background: var(--color-accent-surface);
|
|
57
|
+
color: var(--color-foreground);
|
|
58
|
+
font-weight: var(--type-weight-medium);
|
|
59
|
+
}
|
|
60
|
+
.accent-dot { width: var(--spacing-md); height: var(--spacing-md); border-radius: var(--radius-full); background: var(--color-primary); }
|
|
61
|
+
|
|
62
|
+
.accent-preview {
|
|
63
|
+
display: grid; grid-template-columns: 1fr 3fr;
|
|
64
|
+
height: 120px;
|
|
65
|
+
border: 1px solid var(--color-border);
|
|
66
|
+
border-radius: var(--radius-lg);
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
background: var(--color-background);
|
|
69
|
+
}
|
|
70
|
+
.accent-preview i { display: block; border-radius: var(--radius-sm); }
|
|
71
|
+
.accent-preview-sidebar {
|
|
72
|
+
display: grid; align-content: start; gap: var(--spacing-sm);
|
|
73
|
+
padding: var(--spacing-md) var(--spacing-sm);
|
|
74
|
+
background: var(--color-sidebar);
|
|
75
|
+
border-inline-end: 1px solid var(--color-border);
|
|
76
|
+
}
|
|
77
|
+
.accent-preview-sidebar i { height: var(--spacing-xs); width: 70%; background: var(--color-border-strong); }
|
|
78
|
+
.accent-preview-sidebar i:nth-child(2) { width: 85%; background: var(--color-link); }
|
|
79
|
+
.accent-preview-main { display: grid; align-content: start; gap: var(--spacing-sm); padding: var(--spacing-md); }
|
|
80
|
+
.accent-preview-title { height: var(--spacing-sm); width: 55%; background: var(--color-foreground); opacity: .8; }
|
|
81
|
+
.accent-preview-text { height: var(--spacing-xs); width: 80%; background: var(--color-muted-foreground); }
|
|
82
|
+
.accent-preview-link { height: var(--spacing-xs); width: 40%; background: var(--color-link); }
|
|
83
|
+
.accent-preview-actions { display: flex; gap: var(--spacing-sm); }
|
|
84
|
+
.accent-preview-primary { height: var(--spacing-lg); width: 56px; background: var(--color-primary); }
|
|
85
|
+
.accent-preview-secondary { height: var(--spacing-lg); width: 44px; border: 1px solid var(--color-border-strong); background: var(--color-card); }
|
|
86
|
+
.accent-preview-selected { height: var(--spacing-md); background: var(--color-selected); }
|
|
24
87
|
|
|
25
88
|
.kiso-checkbox {
|
|
26
89
|
position: relative;
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { clsx as cn } from "clsx";
|
|
5
5
|
import { Button } from "./button.js";
|
|
6
|
+
import { Form, FormActions } from "./form.js";
|
|
6
7
|
import { Input } from "./input.js";
|
|
7
8
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
8
9
|
const defaultPresets = [
|
|
@@ -40,7 +41,7 @@ function TimeRangeForm({ value, bounds, onApply }) {
|
|
|
40
41
|
const [from, setFrom] = React.useState(dateInput(value.from));
|
|
41
42
|
const [to, setTo] = React.useState(dateInput(value.to));
|
|
42
43
|
const [error, setError] = React.useState("");
|
|
43
|
-
return _jsxs(
|
|
44
|
+
return _jsxs(Form, { className: "stack-sm", onSubmit: event => {
|
|
44
45
|
event.preventDefault();
|
|
45
46
|
const range = { from: Date.parse(`${from}Z`), to: Date.parse(`${to}Z`) };
|
|
46
47
|
if (!validRange(range) || range.from < bounds.from || range.to > bounds.to) {
|
|
@@ -48,5 +49,5 @@ function TimeRangeForm({ value, bounds, onApply }) {
|
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
onApply(range);
|
|
51
|
-
}, children: [_jsx("label", { htmlFor: `${id}-from`, children: "From (UTC)" }), _jsx(Input, { id: `${id}-from`, type: "datetime-local", step: "0.001", required: true, value: from, min: dateInput(bounds.from), max: dateInput(bounds.to), onChange: event => { setFrom(event.target.value); setError(""); }, "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : undefined }), _jsx("label", { htmlFor: `${id}-to`, children: "To (UTC)" }), _jsx(Input, { id: `${id}-to`, type: "datetime-local", step: "0.001", required: true, value: to, min: dateInput(bounds.from), max: dateInput(bounds.to), onChange: event => { setTo(event.target.value); setError(""); }, "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : undefined }), error && _jsx("p", { id: `${id}-error`, role: "alert", className: "field-error", children: error }), _jsx(Button, { type: "submit", variant: "primary", children: "Apply range" })] });
|
|
52
|
+
}, children: [_jsx("label", { htmlFor: `${id}-from`, children: "From (UTC)" }), _jsx(Input, { id: `${id}-from`, type: "datetime-local", step: "0.001", required: true, value: from, min: dateInput(bounds.from), max: dateInput(bounds.to), onChange: event => { setFrom(event.target.value); setError(""); }, "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : undefined }), _jsx("label", { htmlFor: `${id}-to`, children: "To (UTC)" }), _jsx(Input, { id: `${id}-to`, type: "datetime-local", step: "0.001", required: true, value: to, min: dateInput(bounds.from), max: dateInput(bounds.to), onChange: event => { setTo(event.target.value); setError(""); }, "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : undefined }), error && _jsx("p", { id: `${id}-error`, role: "alert", className: "field-error", children: error }), _jsx(FormActions, { children: _jsx(Button, { type: "submit", variant: "primary", children: "Apply range" }) })] });
|
|
52
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momoi-labs/kiso-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Kiso React components built with shadcn/ui and Radix",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"prepack": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@momoi-labs/kiso": "^0.
|
|
35
|
+
"@momoi-labs/kiso": "^0.11.0",
|
|
36
36
|
"class-variance-authority": "0.7.1",
|
|
37
37
|
"clsx": "2.1.1",
|
|
38
38
|
"radix-ui": "1.6.7",
|