@momoi-labs/kiso-react 0.4.1 → 0.6.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 +13 -5
- package/dist/app-shell.d.ts +14 -4
- package/dist/app-shell.js +15 -7
- package/dist/chip-input.d.ts +43 -0
- package/dist/chip-input.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/sparkline.d.ts +13 -0
- package/dist/sparkline.js +19 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -46,6 +46,11 @@ Controls and forms
|
|
|
46
46
|
- Input, Textarea, Search, Label, Checkbox, Switch, and FormField.
|
|
47
47
|
- Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup,
|
|
48
48
|
SelectLabel, and SelectSeparator.
|
|
49
|
+
- ChipInput, ChipInputBox, ChipInputField, ChipInputList, ChipInputOption,
|
|
50
|
+
ChipInputEmpty, and the chip parts Chip, ChipScope, ChipName, ChipValue,
|
|
51
|
+
ChipOption, ChipOptionAdd, ChipRemove, for several structured values in one
|
|
52
|
+
field. Each chip reads as a call: scope, name, value, then one segment per
|
|
53
|
+
option as name=value or name=[a, b].
|
|
49
54
|
- ValidationMessage, for a field error referenced by `aria-describedby`.
|
|
50
55
|
|
|
51
56
|
Data and structure
|
|
@@ -57,7 +62,8 @@ Data and structure
|
|
|
57
62
|
- Sidebar, SidebarHeader, SidebarBody, SidebarFooter.
|
|
58
63
|
- AppShell and AppShellMain, the low-level columns of a console.
|
|
59
64
|
- ApplicationShell, the standard brand, navigation, header, footer, and page
|
|
60
|
-
arrangement.
|
|
65
|
+
arrangement. Pass `layout="topbar"` for a single-surface frame with brand and
|
|
66
|
+
primary action in the top bar and no sidebar rail.
|
|
61
67
|
- Split, Pane, and Splitter, a list-detail layout with a resizable divider.
|
|
62
68
|
- LogView, LogViewLine, LogViewTime, and LogViewLevel.
|
|
63
69
|
- Stat, StatHeader, StatLabel, StatValue, StatFoot, and StatDelta.
|
|
@@ -114,9 +120,11 @@ const notify = useToast();
|
|
|
114
120
|
notify('error', `Could not stop ${app.name}`, details);
|
|
115
121
|
```
|
|
116
122
|
|
|
117
|
-
ApplicationShell takes navigation groups as data
|
|
118
|
-
the product through `{ href, active, onClick? }`;
|
|
119
|
-
Sidebar, Navigation, Header, and main
|
|
123
|
+
ApplicationShell takes navigation groups as data in the default sidebar layout.
|
|
124
|
+
Destinations keep routing in the product through `{ href, active, onClick? }`;
|
|
125
|
+
the package owns the repeated Sidebar, Navigation, Header, and main
|
|
126
|
+
arrangement. With `layout="topbar"`, omit navigation and footer: brand and
|
|
127
|
+
primary action move into Header beside the existing header slot.
|
|
120
128
|
|
|
121
129
|
Navigation, Sidebar, and Link know nothing about routing. Mark the current
|
|
122
130
|
destination with `active`, and pass `asChild` to render a router's own link
|
|
@@ -176,7 +184,7 @@ import { BrandMark, TerminalIcon } from '@momoi-labs/kiso-react';
|
|
|
176
184
|
## Development
|
|
177
185
|
|
|
178
186
|
From the repository root, run `npm ci`, then `npm run prototype`. The component
|
|
179
|
-
gallery opens at <http://127.0.0.1:5173/#components> and covers all
|
|
187
|
+
gallery opens at <http://127.0.0.1:5173/#components> and covers all 44 Kiso
|
|
180
188
|
catalog entries, each rendering the component this package exports. Its sample
|
|
181
189
|
data is simulated in memory.
|
|
182
190
|
|
package/dist/app-shell.d.ts
CHANGED
|
@@ -14,15 +14,25 @@ type ApplicationShellGroup = {
|
|
|
14
14
|
destinations: readonly ApplicationShellDestination[];
|
|
15
15
|
empty?: React.ReactNode;
|
|
16
16
|
};
|
|
17
|
-
type
|
|
17
|
+
type ApplicationShellSharedProps = Omit<React.ComponentProps<typeof AppShell>, "children"> & {
|
|
18
18
|
brand: React.ReactNode;
|
|
19
19
|
primaryAction?: React.ReactNode;
|
|
20
|
+
header?: React.ReactNode;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
};
|
|
23
|
+
type ApplicationShellSidebarProps = ApplicationShellSharedProps & {
|
|
24
|
+
layout?: "sidebar";
|
|
20
25
|
navigation: readonly ApplicationShellGroup[];
|
|
21
26
|
navigationLabel?: string;
|
|
22
27
|
footer?: React.ReactNode;
|
|
23
|
-
header?: React.ReactNode;
|
|
24
|
-
children: React.ReactNode;
|
|
25
28
|
};
|
|
26
|
-
|
|
29
|
+
type ApplicationShellTopbarProps = ApplicationShellSharedProps & {
|
|
30
|
+
layout: "topbar";
|
|
31
|
+
navigation?: never;
|
|
32
|
+
navigationLabel?: never;
|
|
33
|
+
footer?: never;
|
|
34
|
+
};
|
|
35
|
+
type ApplicationShellProps = ApplicationShellSidebarProps | ApplicationShellTopbarProps;
|
|
36
|
+
declare function ApplicationShell(props: ApplicationShellProps): React.JSX.Element;
|
|
27
37
|
export { AppShell, AppShellMain, ApplicationShell };
|
|
28
38
|
export type { ApplicationShellDestination, ApplicationShellGroup, ApplicationShellProps, };
|
package/dist/app-shell.js
CHANGED
|
@@ -11,12 +11,20 @@ function AppShell({ className, ...props }) {
|
|
|
11
11
|
function AppShellMain({ className, ...props }) {
|
|
12
12
|
return (_jsx("main", { "data-slot": "app-shell-main", className: cn("grow", className), ...props }));
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
return (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
function ApplicationShellNavigation({ navigation, navigationLabel, }) {
|
|
15
|
+
return (_jsx(Navigation, { "aria-label": navigationLabel, children: navigation.map((group, groupIndex) => (_jsx(NavigationGroup, { label: group.label, children: group.destinations.length > 0 ? (_jsx(NavigationList, { children: group.destinations.map((destination, destinationIndex) => (_jsx(NavigationItem, { children: _jsxs(NavigationLink, { href: destination.href, active: destination.active, onClick: (event) => {
|
|
16
|
+
if (!destination.onClick)
|
|
17
|
+
return;
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
destination.onClick();
|
|
20
|
+
}, children: [destination.leading, _jsx("span", { className: "grow truncate", children: destination.label }), destination.trailing] }) }, `${destination.href}-${destinationIndex}`))) })) : (group.empty) }, group.label ?? groupIndex))) }));
|
|
21
|
+
}
|
|
22
|
+
function ApplicationShell(props) {
|
|
23
|
+
if (props.layout === "topbar") {
|
|
24
|
+
const { brand, primaryAction, header, children, layout: _layout, ...shellProps } = props;
|
|
25
|
+
return (_jsx(AppShell, { ...shellProps, "data-layout": "topbar", children: _jsxs(AppShellMain, { children: [_jsxs(Header, { children: [brand, primaryAction, header] }), children] }) }));
|
|
26
|
+
}
|
|
27
|
+
const { brand, primaryAction, navigation, navigationLabel = "Primary", footer, header, children, layout: _layout, ...shellProps } = props;
|
|
28
|
+
return (_jsxs(AppShell, { ...shellProps, children: [_jsxs(Sidebar, { children: [_jsxs(SidebarHeader, { children: [brand, primaryAction] }), _jsx(SidebarBody, { children: _jsx(ApplicationShellNavigation, { navigation: navigation, navigationLabel: navigationLabel }) }), footer && _jsx(SidebarFooter, { children: footer })] }), _jsxs(AppShellMain, { children: [header && _jsx(Header, { children: header }), children] })] }));
|
|
21
29
|
}
|
|
22
30
|
export { AppShell, AppShellMain, ApplicationShell };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
declare function ChipInput({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
3
|
+
declare function ChipInputBox({ className, disabled, onMouseDown, ...props }: React.ComponentProps<"div"> & {
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
}): React.JSX.Element;
|
|
6
|
+
declare function ChipInputField({ className, onKeyDown, onRemoveLast, ...props }: React.ComponentProps<"input"> & {
|
|
7
|
+
onRemoveLast?: () => void;
|
|
8
|
+
}): React.JSX.Element;
|
|
9
|
+
declare function ChipInputList({ className, "aria-label": ariaLabel, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
10
|
+
declare function ChipInputOption({ className, disabled, onSelect, onClick, ...props }: React.ComponentProps<"div"> & {
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
onSelect?: () => void;
|
|
13
|
+
}): React.JSX.Element;
|
|
14
|
+
declare function ChipInputEmpty({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element;
|
|
15
|
+
declare function Chip({ className, invalid, ...props }: React.ComponentProps<"span"> & {
|
|
16
|
+
invalid?: boolean;
|
|
17
|
+
}): React.JSX.Element;
|
|
18
|
+
declare function ChipScope({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
|
|
19
|
+
declare function ChipName({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
|
|
20
|
+
declare function EditableSegment({ slot, className, value, placeholder, display, onCommit, editable, editLabel, confirmLabel, maxWidth, ...props }: Omit<React.ComponentProps<"button">, "value"> & {
|
|
21
|
+
slot: string;
|
|
22
|
+
value: string;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
display?: React.ReactNode;
|
|
25
|
+
onCommit?: (value: string) => void;
|
|
26
|
+
editable?: boolean;
|
|
27
|
+
editLabel?: string;
|
|
28
|
+
confirmLabel?: string;
|
|
29
|
+
maxWidth?: number;
|
|
30
|
+
}): React.JSX.Element;
|
|
31
|
+
declare function ChipValue({ className, value, editLabel, confirmLabel, ...props }: Omit<React.ComponentProps<typeof EditableSegment>, "slot"> & {
|
|
32
|
+
value: string;
|
|
33
|
+
}): React.JSX.Element;
|
|
34
|
+
declare function ChipOption({ className, name, value, label, editLabel, confirmLabel, ...props }: Omit<React.ComponentProps<typeof EditableSegment>, "slot" | "value" | "display"> & {
|
|
35
|
+
name: string;
|
|
36
|
+
value: string | string[];
|
|
37
|
+
label?: string;
|
|
38
|
+
}): React.JSX.Element;
|
|
39
|
+
declare function ChipOptionAdd({ className, label, placeholder, editLabel, confirmLabel, ...props }: Omit<React.ComponentProps<typeof EditableSegment>, "slot" | "value" | "display"> & {
|
|
40
|
+
label: string;
|
|
41
|
+
}): React.JSX.Element;
|
|
42
|
+
declare function ChipRemove({ className, ...props }: React.ComponentProps<"button">): React.JSX.Element;
|
|
43
|
+
export { Chip, ChipInput, ChipInputBox, ChipInputEmpty, ChipInputField, ChipInputList, ChipInputOption, ChipName, ChipOption, ChipOptionAdd, ChipRemove, ChipScope, ChipValue, };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { clsx as cn } from "clsx";
|
|
5
|
+
// One field holding several structured values. The component owns the box, the
|
|
6
|
+
// chips, and the combobox keys; the product owns what a chip means, which
|
|
7
|
+
// suggestions match, and which options a chip accepts.
|
|
8
|
+
const ChipInputContext = React.createContext(null);
|
|
9
|
+
function useChipInput() {
|
|
10
|
+
const context = React.useContext(ChipInputContext);
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error("ChipInput parts must be used inside <ChipInput>");
|
|
13
|
+
}
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
16
|
+
function options(list) {
|
|
17
|
+
return Array.from(list?.querySelectorAll('[data-slot="chip-input-option"]:not([aria-disabled="true"])') ?? []);
|
|
18
|
+
}
|
|
19
|
+
function ChipInput({ className, ...props }) {
|
|
20
|
+
const [activeId, setActiveId] = React.useState("");
|
|
21
|
+
const [open, setOpen] = React.useState(false);
|
|
22
|
+
const listId = React.useId();
|
|
23
|
+
const listRef = React.useRef(null);
|
|
24
|
+
const fieldRef = React.useRef(null);
|
|
25
|
+
const value = React.useMemo(() => ({ activeId, setActiveId, listId, listRef, fieldRef, open, setOpen }), [activeId, listId, open]);
|
|
26
|
+
return (_jsx(ChipInputContext.Provider, { value: value, children: _jsx("div", { "data-slot": "chip-input", className: cn("chip-input", className), ...props }) }));
|
|
27
|
+
}
|
|
28
|
+
// The box is the field: clicking its empty space puts the caret back where
|
|
29
|
+
// typing continues, the way a single-line input behaves.
|
|
30
|
+
function ChipInputBox({ className, disabled = false, onMouseDown, ...props }) {
|
|
31
|
+
const { fieldRef } = useChipInput();
|
|
32
|
+
return (_jsx("div", { "data-slot": "chip-input-box", "data-disabled": disabled || undefined, className: cn("chip-input-box", className), onMouseDown: (event) => {
|
|
33
|
+
if (event.target === event.currentTarget && !disabled) {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
fieldRef.current?.focus();
|
|
36
|
+
}
|
|
37
|
+
onMouseDown?.(event);
|
|
38
|
+
}, ...props }));
|
|
39
|
+
}
|
|
40
|
+
function ChipInputField({ className, onKeyDown, onRemoveLast, ...props }) {
|
|
41
|
+
const { activeId, setActiveId, listId, listRef, fieldRef, open } = useChipInput();
|
|
42
|
+
// Typing changes the list under the highlight, so re-anchor it every render
|
|
43
|
+
// instead of tracking an index the filter invalidates.
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
const visible = options(listRef.current);
|
|
46
|
+
if (!visible.some((option) => option.id === activeId)) {
|
|
47
|
+
setActiveId(visible[0]?.id ?? "");
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
function keys(event) {
|
|
51
|
+
const visible = options(listRef.current);
|
|
52
|
+
const index = visible.findIndex((option) => option.id === activeId);
|
|
53
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
if (!visible.length)
|
|
56
|
+
return;
|
|
57
|
+
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
58
|
+
const next = visible[(index + step + visible.length) % visible.length];
|
|
59
|
+
setActiveId(next.id);
|
|
60
|
+
next.scrollIntoView({ block: "nearest" });
|
|
61
|
+
}
|
|
62
|
+
else if ((event.key === "Enter" || event.key === "Tab") && index >= 0) {
|
|
63
|
+
// Tab only commits a highlighted suggestion; with none it still leaves
|
|
64
|
+
// the field, so the box never traps the keyboard. Enter with no
|
|
65
|
+
// highlight falls through to the product, which may take the query.
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
visible[index]?.click();
|
|
68
|
+
}
|
|
69
|
+
else if (event.key === "Backspace" &&
|
|
70
|
+
event.currentTarget.value === "" &&
|
|
71
|
+
onRemoveLast) {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
onRemoveLast();
|
|
74
|
+
}
|
|
75
|
+
onKeyDown?.(event);
|
|
76
|
+
}
|
|
77
|
+
return (_jsx("input", { ref: fieldRef, "data-slot": "chip-input-field", role: "combobox", "aria-expanded": open, "aria-controls": listId, "aria-activedescendant": activeId || undefined, "aria-autocomplete": "list", autoComplete: "off", onKeyDown: keys, className: cn("chip-input-field", className), ...props }));
|
|
78
|
+
}
|
|
79
|
+
function ChipInputList({ className, "aria-label": ariaLabel = "Suggestions", ...props }) {
|
|
80
|
+
const { listId, listRef, setOpen } = useChipInput();
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
setOpen(true);
|
|
83
|
+
return () => setOpen(false);
|
|
84
|
+
}, [setOpen]);
|
|
85
|
+
return (_jsx("div", { id: listId, ref: listRef, role: "listbox", "aria-label": ariaLabel, "data-slot": "chip-input-list", className: cn("menu chip-input-list", className), ...props }));
|
|
86
|
+
}
|
|
87
|
+
function ChipInputOption({ className, disabled = false, onSelect, onClick, ...props }) {
|
|
88
|
+
const id = React.useId();
|
|
89
|
+
const { activeId, setActiveId } = useChipInput();
|
|
90
|
+
return (_jsx("div", { id: id, role: "option", "data-slot": "chip-input-option", "aria-selected": activeId === id, "aria-disabled": disabled || undefined, className: cn("menu-item", className), onPointerMove: () => !disabled && setActiveId(id), onMouseDown: (event) => event.preventDefault(), onClick: (event) => {
|
|
91
|
+
if (disabled)
|
|
92
|
+
return;
|
|
93
|
+
onSelect?.();
|
|
94
|
+
onClick?.(event);
|
|
95
|
+
}, ...props }));
|
|
96
|
+
}
|
|
97
|
+
function ChipInputEmpty({ className, ...props }) {
|
|
98
|
+
return (_jsx("p", { "data-slot": "chip-input-empty", className: cn("menu-item muted", className), ...props }));
|
|
99
|
+
}
|
|
100
|
+
function Chip({ className, invalid = false, ...props }) {
|
|
101
|
+
return (_jsx("span", { "data-slot": "chip", "data-state": invalid ? "invalid" : undefined, className: cn("chip", className), ...props }));
|
|
102
|
+
}
|
|
103
|
+
// The leading segment: what kind of value this is, when that is a separate
|
|
104
|
+
// fact from its name. Reads as the caller in `npm(t3, latest)`.
|
|
105
|
+
function ChipScope({ className, ...props }) {
|
|
106
|
+
return (_jsx("span", { "data-slot": "chip-scope", className: cn("chip-scope", className), ...props }));
|
|
107
|
+
}
|
|
108
|
+
function ChipName({ className, ...props }) {
|
|
109
|
+
return (_jsx("span", { "data-slot": "chip-name", className: cn("chip-name truncate", className), ...props }));
|
|
110
|
+
}
|
|
111
|
+
// Every editable segment behaves the same: press it, it becomes an input
|
|
112
|
+
// sized to its text, Enter or the check confirms, Escape restores.
|
|
113
|
+
function EditableSegment({ slot, className, value, placeholder, display, onCommit, editable = true, editLabel, confirmLabel, maxWidth = 48, ...props }) {
|
|
114
|
+
const [draft, setDraft] = React.useState(null);
|
|
115
|
+
if (!editable || !onCommit) {
|
|
116
|
+
return (_jsx("span", { "data-slot": slot, className: cn(className), children: value ? (display ?? value) : placeholder }));
|
|
117
|
+
}
|
|
118
|
+
function commit(next) {
|
|
119
|
+
if (next !== null)
|
|
120
|
+
onCommit?.(next.trim());
|
|
121
|
+
setDraft(null);
|
|
122
|
+
}
|
|
123
|
+
if (draft !== null) {
|
|
124
|
+
return (_jsxs(_Fragment, { children: [_jsx("input", { "data-slot": `${slot}-input`, className: cn(className, "is-editing"), "aria-label": editLabel, value: draft, autoFocus: true, size: 1, style: {
|
|
125
|
+
width: `${Math.min(Math.max(draft.length + 1, 6), maxWidth)}ch`,
|
|
126
|
+
}, onChange: (event) => setDraft(event.target.value), onBlur: () => commit(draft), onKeyDown: (event) => {
|
|
127
|
+
if (event.key === "Enter") {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
commit(draft);
|
|
130
|
+
}
|
|
131
|
+
else if (event.key === "Escape") {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
setDraft(null);
|
|
134
|
+
}
|
|
135
|
+
event.stopPropagation();
|
|
136
|
+
} }), _jsx("button", { type: "button", className: "chip-action", "aria-label": confirmLabel,
|
|
137
|
+
// Commit before the input's blur can unmount this button.
|
|
138
|
+
onMouseDown: (event) => {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
commit(draft);
|
|
141
|
+
}, children: _jsx(CheckIcon, {}) })] }));
|
|
142
|
+
}
|
|
143
|
+
return (_jsx("button", { type: "button", "data-slot": slot, "data-empty": value ? undefined : "true", className: cn(className), "aria-label": editLabel, onClick: () => setDraft(value), ...props, children: value ? (display ?? value) : placeholder }));
|
|
144
|
+
}
|
|
145
|
+
// The version is the part people revise most, so it edits in place instead of
|
|
146
|
+
// forcing a remove and a retype.
|
|
147
|
+
function ChipValue({ className, value, editLabel, confirmLabel = "Confirm value", ...props }) {
|
|
148
|
+
return (_jsx(EditableSegment, { slot: "chip-value", className: cn("chip-value", className), value: value, editLabel: editLabel ?? `Edit value, currently ${value}`, confirmLabel: confirmLabel, maxWidth: 16, ...props }));
|
|
149
|
+
}
|
|
150
|
+
// One segment per option, divided like the rest, written the way the config
|
|
151
|
+
// file writes it. A list keeps its brackets so `os=linux` and
|
|
152
|
+
// `allow_builds=[node-pty, esbuild]` read as the same kind of fact.
|
|
153
|
+
function ChipOption({ className, name, value, label, editLabel, confirmLabel, ...props }) {
|
|
154
|
+
const list = Array.isArray(value);
|
|
155
|
+
const shown = list ? `[${value.join(", ")}]` : value;
|
|
156
|
+
const text = `${name}=${shown}`;
|
|
157
|
+
const where = label ? ` on ${label}` : "";
|
|
158
|
+
return (_jsx(EditableSegment, { slot: "chip-option", className: cn("chip-option", className), value: text, display: _jsxs(_Fragment, { children: [_jsx("span", { className: "chip-option-name", children: name }), _jsx("span", { className: "chip-option-mark", children: "=" }), list ? _jsx("span", { className: "chip-option-mark", children: "[" }) : null, _jsx("span", { className: "chip-option-value", children: list ? value.join(", ") : value }), list ? _jsx("span", { className: "chip-option-mark", children: "]" }) : null] }), editLabel: editLabel ?? `Edit ${name}${where}, currently ${shown}`, confirmLabel: confirmLabel ?? `Confirm ${name}${where}`, ...props }));
|
|
159
|
+
}
|
|
160
|
+
// The segment that takes a new option. It stays at the end of the chip so the
|
|
161
|
+
// place to add one is where the last one ended.
|
|
162
|
+
function ChipOptionAdd({ className, label, placeholder = "+", editLabel, confirmLabel, ...props }) {
|
|
163
|
+
return (_jsx(EditableSegment, { slot: "chip-option-add", className: cn("chip-option", className), value: "", placeholder: placeholder, editLabel: editLabel ?? `Add an option to ${label}`, confirmLabel: confirmLabel ?? `Confirm the new option on ${label}`, ...props }));
|
|
164
|
+
}
|
|
165
|
+
function ChipRemove({ className, ...props }) {
|
|
166
|
+
return (_jsx("button", { type: "button", "data-slot": "chip-remove", className: cn("chip-action", className), ...props, children: _jsx(CloseIcon, {}) }));
|
|
167
|
+
}
|
|
168
|
+
function CheckIcon() {
|
|
169
|
+
return (_jsx("svg", { className: "icon", viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "m3.5 8.5 3 3 6-7" }) }));
|
|
170
|
+
}
|
|
171
|
+
function CloseIcon() {
|
|
172
|
+
return (_jsx("svg", { className: "icon", viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "m4 4 8 8M12 4l-8 8" }) }));
|
|
173
|
+
}
|
|
174
|
+
export { Chip, ChipInput, ChipInputBox, ChipInputEmpty, ChipInputField, ChipInputList, ChipInputOption, ChipName, ChipOption, ChipOptionAdd, ChipRemove, ChipScope, ChipValue, };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./breadcrumb.js";
|
|
|
7
7
|
export * from "./button.js";
|
|
8
8
|
export * from "./card.js";
|
|
9
9
|
export * from "./checkbox.js";
|
|
10
|
+
export * from "./chip-input.js";
|
|
10
11
|
export * from "./command-palette.js";
|
|
11
12
|
export * from "./dialog.js";
|
|
12
13
|
export * from "./dot.js";
|
|
@@ -29,6 +30,7 @@ export * from "./select.js";
|
|
|
29
30
|
export * from "./separator.js";
|
|
30
31
|
export * from "./sidebar.js";
|
|
31
32
|
export * from "./skeleton.js";
|
|
33
|
+
export * from "./sparkline.js";
|
|
32
34
|
export * from "./spinner.js";
|
|
33
35
|
export * from "./split.js";
|
|
34
36
|
export * from "./stat.js";
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./breadcrumb.js";
|
|
|
8
8
|
export * from "./button.js";
|
|
9
9
|
export * from "./card.js";
|
|
10
10
|
export * from "./checkbox.js";
|
|
11
|
+
export * from "./chip-input.js";
|
|
11
12
|
export * from "./command-palette.js";
|
|
12
13
|
export * from "./dialog.js";
|
|
13
14
|
export * from "./dot.js";
|
|
@@ -30,6 +31,7 @@ export * from "./select.js";
|
|
|
30
31
|
export * from "./separator.js";
|
|
31
32
|
export * from "./sidebar.js";
|
|
32
33
|
export * from "./skeleton.js";
|
|
34
|
+
export * from "./sparkline.js";
|
|
33
35
|
export * from "./spinner.js";
|
|
34
36
|
export * from "./split.js";
|
|
35
37
|
export * from "./stat.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type SparklineProps = Omit<React.ComponentProps<"div">, "ref"> & {
|
|
3
|
+
values: number[];
|
|
4
|
+
height?: number;
|
|
5
|
+
tone?: "neutral" | "primary";
|
|
6
|
+
fill?: boolean;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
label?: string;
|
|
10
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
11
|
+
};
|
|
12
|
+
declare function Sparkline({ values, height, tone, fill, min, max, label, className, style, ref, ...props }: SparklineProps): React.JSX.Element | null;
|
|
13
|
+
export { Sparkline };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Area, AreaChart, ResponsiveContainer, YAxis } from "recharts";
|
|
4
|
+
import { clsx as cn } from "clsx";
|
|
5
|
+
function Sparkline({ values, height = 24, tone = "neutral", fill = false, min, max, label, className, style, ref, ...props }) {
|
|
6
|
+
// Below two samples there is no shape to show, and a flat rule across a
|
|
7
|
+
// cell reads as a border rather than as a measurement.
|
|
8
|
+
if (values.length < 2)
|
|
9
|
+
return null;
|
|
10
|
+
// Default domain is the data's own extent; pass min and max to make
|
|
11
|
+
// sibling sparklines share one scale, so rows compare honestly.
|
|
12
|
+
let lo = min ?? Math.min(...values);
|
|
13
|
+
let hi = max ?? Math.max(...values);
|
|
14
|
+
const pad = lo === hi ? Math.max(1, Math.abs(lo) * 0.1) : 0;
|
|
15
|
+
lo -= pad;
|
|
16
|
+
hi += pad;
|
|
17
|
+
return (_jsx("div", { ref: ref, "data-slot": "sparkline", "data-tone": tone, role: label ? "img" : undefined, "aria-label": label, "aria-hidden": label ? undefined : true, className: cn("sparkline", tone === "primary" && "primary", className), style: { height, ...style }, ...props, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(AreaChart, { data: values.map((value) => ({ value })), margin: { top: 2, right: 0, bottom: 2, left: 0 }, children: [_jsx(YAxis, { hide: true, domain: [lo, hi] }), _jsx(Area, { type: "monotone", dataKey: "value", stroke: "currentColor", strokeWidth: 1.5, fill: fill ? "currentColor" : "none", fillOpacity: fill ? 0.12 : 0, isAnimationActive: false, dot: false, activeDot: false })] }) }) }));
|
|
18
|
+
}
|
|
19
|
+
export { Sparkline };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momoi-labs/kiso-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Kiso React components built with shadcn/ui and Radix",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
"prepack": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@momoi-labs/kiso": "^0.
|
|
35
|
+
"@momoi-labs/kiso": "^0.9.0",
|
|
36
36
|
"class-variance-authority": "0.7.1",
|
|
37
37
|
"clsx": "2.1.1",
|
|
38
|
-
"radix-ui": "1.6.7"
|
|
38
|
+
"radix-ui": "1.6.7",
|
|
39
|
+
"react-is": "^19.3.0",
|
|
40
|
+
"recharts": "^3.10.1"
|
|
39
41
|
},
|
|
40
42
|
"peerDependencies": {
|
|
41
43
|
"react": "^19.0.0",
|