@zvk/ui 0.1.11 → 0.1.12
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/CHANGELOG.md +7 -0
- package/dist/components/grid-list/grid-list.d.ts +29 -0
- package/dist/components/grid-list/grid-list.js +125 -0
- package/dist/components/grid-list/index.d.ts +2 -0
- package/dist/components/grid-list/index.js +2 -0
- package/dist/components/index.d.ts +16 -0
- package/dist/components/index.js +8 -0
- package/dist/components/list-row/index.d.ts +2 -0
- package/dist/components/list-row/index.js +2 -0
- package/dist/components/list-row/list-row.d.ts +54 -0
- package/dist/components/list-row/list-row.js +60 -0
- package/dist/components/multi-select/index.d.ts +2 -0
- package/dist/components/multi-select/index.js +2 -0
- package/dist/components/multi-select/multi-select.d.ts +32 -0
- package/dist/components/multi-select/multi-select.js +212 -0
- package/dist/components/splitter/index.d.ts +2 -0
- package/dist/components/splitter/index.js +2 -0
- package/dist/components/splitter/splitter.d.ts +63 -0
- package/dist/components/splitter/splitter.js +163 -0
- package/dist/components/stepper/index.d.ts +2 -0
- package/dist/components/stepper/index.js +2 -0
- package/dist/components/stepper/stepper.d.ts +30 -0
- package/dist/components/stepper/stepper.js +61 -0
- package/dist/components/tags-input/index.d.ts +2 -0
- package/dist/components/tags-input/index.js +2 -0
- package/dist/components/tags-input/tags-input.d.ts +21 -0
- package/dist/components/tags-input/tags-input.js +132 -0
- package/dist/components/toolbar/index.d.ts +2 -0
- package/dist/components/toolbar/index.js +2 -0
- package/dist/components/toolbar/toolbar.d.ts +33 -0
- package/dist/components/toolbar/toolbar.js +106 -0
- package/dist/components/tree-view/index.d.ts +2 -0
- package/dist/components/tree-view/index.js +2 -0
- package/dist/components/tree-view/tree-view.d.ts +33 -0
- package/dist/components/tree-view/tree-view.js +190 -0
- package/dist/internal/collection/index.d.ts +2 -0
- package/dist/internal/collection/index.js +1 -0
- package/dist/internal/collection/selection.d.ts +11 -0
- package/dist/internal/collection/selection.js +60 -0
- package/dist/styles.css +887 -64
- package/package.json +41 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { commandItemMatches } from "../command/command-filter.js";
|
|
5
|
+
import { Field } from "../field/field.js";
|
|
6
|
+
import { useControllableState } from "../../hooks/use-controllable-state.js";
|
|
7
|
+
import { DismissableLayer } from "../../internal/dismissable-layer/index.js";
|
|
8
|
+
import { useFloatingPosition } from "../../internal/floating/index.js";
|
|
9
|
+
import { placementParts } from "../../internal/floating/placement-aliases.js";
|
|
10
|
+
import { Portal } from "../../internal/portal/index.js";
|
|
11
|
+
import { moveCollectionKey, normalizeSelectionKeys } from "../../internal/collection/index.js";
|
|
12
|
+
import { composeEventHandlers } from "../../utils/compose-event-handlers.js";
|
|
13
|
+
import { cn } from "../../utils/cn.js";
|
|
14
|
+
function hasRenderableNode(value) {
|
|
15
|
+
return value !== undefined && value !== null && value !== false;
|
|
16
|
+
}
|
|
17
|
+
function joinIds(...ids) {
|
|
18
|
+
const value = ids.filter(Boolean).join(" ");
|
|
19
|
+
return value.length > 0 ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
function composeRefs(...refs) {
|
|
22
|
+
return (node) => {
|
|
23
|
+
for (const ref of refs) {
|
|
24
|
+
if (typeof ref === "function") {
|
|
25
|
+
ref(node);
|
|
26
|
+
}
|
|
27
|
+
else if (ref && "current" in ref) {
|
|
28
|
+
ref.current = node;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function optionId(baseId, index) {
|
|
34
|
+
return `${baseId}-option-${index}`;
|
|
35
|
+
}
|
|
36
|
+
const nativeSelectStyle = {
|
|
37
|
+
position: "absolute",
|
|
38
|
+
width: 1,
|
|
39
|
+
height: 1,
|
|
40
|
+
margin: -1,
|
|
41
|
+
opacity: 0,
|
|
42
|
+
pointerEvents: "none"
|
|
43
|
+
};
|
|
44
|
+
function orderSelectedValues(values, options) {
|
|
45
|
+
const normalizedValues = normalizeSelectionKeys(values);
|
|
46
|
+
const optionOrder = new Map(options.map((option, index) => [option.value, index]));
|
|
47
|
+
return normalizedValues.sort((left, right) => {
|
|
48
|
+
const leftOrder = optionOrder.get(left) ?? Number.MAX_SAFE_INTEGER;
|
|
49
|
+
const rightOrder = optionOrder.get(right) ?? Number.MAX_SAFE_INTEGER;
|
|
50
|
+
if (leftOrder === rightOrder) {
|
|
51
|
+
return left.localeCompare(right);
|
|
52
|
+
}
|
|
53
|
+
return leftOrder - rightOrder;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function firstEnabledOption(options, selectedValues, maxSelected) {
|
|
57
|
+
return options.find((option) => !isOptionUnavailable(option, selectedValues, maxSelected));
|
|
58
|
+
}
|
|
59
|
+
function isOptionUnavailable(option, selectedValues, maxSelected) {
|
|
60
|
+
if (option.disabled === true) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return maxSelected !== undefined && selectedValues.length >= maxSelected && !selectedValues.includes(option.value);
|
|
64
|
+
}
|
|
65
|
+
function selectedOptionLabel(options, value) {
|
|
66
|
+
return options.find((option) => option.value === value)?.label ?? value;
|
|
67
|
+
}
|
|
68
|
+
export function MultiSelect({ "aria-describedby": ariaDescribedBy, className, clearable = false, collisionPadding = 0, container, defaultValue = [], description, disabled, emptyState = "No options found", error, id, invalid, label, matchTriggerWidth = true, maxSelected, name, onBlur, onFocus, onKeyDown, onValueChange, options, placeholder, placement = "bottom-start", ref, required, sideOffset = 4, size = "md", value, ...props }) {
|
|
69
|
+
const generatedId = React.useId();
|
|
70
|
+
const inputId = id ?? generatedId;
|
|
71
|
+
const listboxId = `${inputId}-listbox`;
|
|
72
|
+
const hasLabel = hasRenderableNode(label);
|
|
73
|
+
const hasDescription = hasRenderableNode(description);
|
|
74
|
+
const hasError = hasRenderableNode(error);
|
|
75
|
+
const invalidState = invalid || hasError;
|
|
76
|
+
const descriptionId = hasDescription ? `${inputId}-description` : undefined;
|
|
77
|
+
const errorId = hasError ? `${inputId}-error` : undefined;
|
|
78
|
+
const describedBy = joinIds(ariaDescribedBy, descriptionId, errorId);
|
|
79
|
+
const inputRef = React.useRef(null);
|
|
80
|
+
const [nativeInvalid, setNativeInvalid] = React.useState(false);
|
|
81
|
+
const [selectedValues, setSelectedValues] = useControllableState({
|
|
82
|
+
...(value !== undefined ? { value: orderSelectedValues(value, options) } : {}),
|
|
83
|
+
defaultValue: orderSelectedValues(defaultValue, options),
|
|
84
|
+
...(onValueChange ? { onChange: onValueChange } : {})
|
|
85
|
+
});
|
|
86
|
+
const resolvedInvalid = invalidState || nativeInvalid;
|
|
87
|
+
const [query, setQuery] = React.useState("");
|
|
88
|
+
const [open, setOpen] = React.useState(false);
|
|
89
|
+
const filteredOptions = React.useMemo(() => options.filter((option) => commandItemMatches(option, query)), [options, query]);
|
|
90
|
+
const navigationItems = filteredOptions.map((option) => ({
|
|
91
|
+
disabled: isOptionUnavailable(option, selectedValues, maxSelected),
|
|
92
|
+
id: option.value,
|
|
93
|
+
textValue: option.label
|
|
94
|
+
}));
|
|
95
|
+
const firstEnabledValue = moveCollectionKey(navigationItems, undefined, "first");
|
|
96
|
+
const [activeValue, setActiveValue] = React.useState(firstEnabledValue);
|
|
97
|
+
const activeOptionIndex = filteredOptions.findIndex((option) => option.value === activeValue);
|
|
98
|
+
const floating = useFloatingPosition({
|
|
99
|
+
open,
|
|
100
|
+
placement,
|
|
101
|
+
strategy: "absolute",
|
|
102
|
+
offset: sideOffset,
|
|
103
|
+
collisionPadding,
|
|
104
|
+
matchReferenceWidth: matchTriggerWidth
|
|
105
|
+
});
|
|
106
|
+
const resolvedPlacement = placementParts(floating.placement);
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
if (!open) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const activeOption = filteredOptions.find((option) => option.value === activeValue);
|
|
112
|
+
if (!activeOption || isOptionUnavailable(activeOption, selectedValues, maxSelected)) {
|
|
113
|
+
setActiveValue(firstEnabledValue);
|
|
114
|
+
}
|
|
115
|
+
}, [activeValue, filteredOptions, firstEnabledValue, maxSelected, open, selectedValues]);
|
|
116
|
+
React.useEffect(() => {
|
|
117
|
+
if (!required || selectedValues.length > 0) {
|
|
118
|
+
setNativeInvalid(false);
|
|
119
|
+
}
|
|
120
|
+
}, [required, selectedValues.length]);
|
|
121
|
+
function openListbox() {
|
|
122
|
+
if (disabled) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
setOpen(true);
|
|
126
|
+
setActiveValue(firstEnabledValue);
|
|
127
|
+
}
|
|
128
|
+
function toggleOption(option) {
|
|
129
|
+
if (!option || isOptionUnavailable(option, selectedValues, maxSelected)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
setSelectedValues((currentValues) => {
|
|
133
|
+
const normalizedValues = normalizeSelectionKeys(currentValues);
|
|
134
|
+
const nextValues = normalizedValues.includes(option.value)
|
|
135
|
+
? normalizedValues.filter((selectedValue) => selectedValue !== option.value)
|
|
136
|
+
: [...normalizedValues, option.value];
|
|
137
|
+
return orderSelectedValues(nextValues, options);
|
|
138
|
+
});
|
|
139
|
+
setQuery("");
|
|
140
|
+
inputRef.current?.focus();
|
|
141
|
+
}
|
|
142
|
+
function removeValue(selectedValue) {
|
|
143
|
+
setSelectedValues((currentValues) => orderSelectedValues(normalizeSelectionKeys(currentValues).filter((value) => value !== selectedValue), options));
|
|
144
|
+
inputRef.current?.focus();
|
|
145
|
+
}
|
|
146
|
+
function clearValues() {
|
|
147
|
+
setSelectedValues([]);
|
|
148
|
+
setQuery("");
|
|
149
|
+
setOpen(false);
|
|
150
|
+
inputRef.current?.focus();
|
|
151
|
+
}
|
|
152
|
+
function moveActive(movement) {
|
|
153
|
+
setActiveValue(moveCollectionKey(navigationItems, activeValue, movement));
|
|
154
|
+
}
|
|
155
|
+
const input = (_jsxs("div", { className: "zvk-ui-multi-select__control", "data-disabled": disabled ? "true" : undefined, "data-invalid": resolvedInvalid ? "true" : undefined, "data-size": size, onClick: () => inputRef.current?.focus(), children: [_jsxs("div", { className: "zvk-ui-multi-select__values", children: [selectedValues.map((selectedValue) => (_jsxs("span", { className: "zvk-ui-multi-select__token", children: [_jsx("span", { className: "zvk-ui-multi-select__token-label", children: selectedOptionLabel(options, selectedValue) }), _jsx("button", { "aria-label": `Remove ${selectedOptionLabel(options, selectedValue)}`, className: "zvk-ui-multi-select__token-remove", disabled: disabled, onClick: () => removeValue(selectedValue), onMouseDown: (event) => event.preventDefault(), type: "button", children: "x" })] }, selectedValue))), _jsx("input", { ...props, ref: composeRefs(ref, inputRef, floating.referenceRef), "aria-activedescendant": open && activeOptionIndex >= 0 ? optionId(inputId, activeOptionIndex) : undefined, "aria-autocomplete": "list", "aria-controls": listboxId, "aria-describedby": describedBy, "aria-expanded": open ? "true" : "false", "aria-invalid": resolvedInvalid ? "true" : undefined, className: cn("zvk-ui-multi-select__input", className), disabled: disabled, id: inputId, onBlur: onBlur, onChange: (event) => {
|
|
156
|
+
setQuery(event.currentTarget.value);
|
|
157
|
+
setOpen(true);
|
|
158
|
+
}, onFocus: composeEventHandlers(onFocus, openListbox), onKeyDown: composeEventHandlers(onKeyDown, (event) => {
|
|
159
|
+
if (event.key === "ArrowDown") {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
if (!open) {
|
|
162
|
+
openListbox();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
moveActive("next");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (event.key === "ArrowUp") {
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
moveActive("previous");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (event.key === "Home" && open) {
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
moveActive("first");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (event.key === "End" && open) {
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
moveActive("last");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
184
|
+
if (open) {
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
toggleOption(filteredOptions[activeOptionIndex]);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (event.key === "Escape") {
|
|
191
|
+
setOpen(false);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (event.key === "Backspace" && query.length === 0) {
|
|
195
|
+
const lastValue = selectedValues.at(-1);
|
|
196
|
+
if (lastValue !== undefined) {
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
removeValue(lastValue);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}), placeholder: selectedValues.length === 0 ? placeholder : undefined, required: false, role: "combobox", value: query })] }), clearable && selectedValues.length > 0 ? (_jsx("button", { "aria-label": "Clear selections", className: "zvk-ui-multi-select__clear", disabled: disabled, onClick: clearValues, onMouseDown: (event) => event.preventDefault(), type: "button", children: "Clear" })) : null, name || required ? (_jsx("select", { "aria-hidden": "true", disabled: disabled, multiple: true, name: name, onChange: () => undefined, onInvalid: (event) => {
|
|
202
|
+
event.preventDefault();
|
|
203
|
+
setNativeInvalid(true);
|
|
204
|
+
inputRef.current?.focus();
|
|
205
|
+
}, required: required, style: nativeSelectStyle, tabIndex: -1, value: selectedValues, children: options.map((option) => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value))) })) : null] }));
|
|
206
|
+
return (_jsxs(Field, { disabled: Boolean(disabled), invalid: resolvedInvalid, required: Boolean(required), children: [hasLabel ? _jsx(Field.Label, { htmlFor: inputId, children: label }) : null, input, hasDescription ? _jsx(Field.Description, { id: descriptionId, children: description }) : null, hasError ? _jsx(Field.Error, { id: errorId, children: error }) : null, open ? (_jsx(Portal, { ...(container === undefined ? {} : { container }), children: _jsx(DismissableLayer, { open: open, onDismiss: () => setOpen(false), children: _jsx("div", { ref: floating.floatingRef, className: "zvk-ui-multi-select__popup", "data-align": resolvedPlacement.align, "data-side": resolvedPlacement.side, "data-state": "open", id: listboxId, role: "listbox", "aria-multiselectable": "true", style: floating.floatingStyle, children: filteredOptions.length === 0 ? (_jsx("div", { className: "zvk-ui-multi-select__empty", children: emptyState })) : filteredOptions.map((option, index) => {
|
|
207
|
+
const selected = selectedValues.includes(option.value);
|
|
208
|
+
const unavailable = isOptionUnavailable(option, selectedValues, maxSelected);
|
|
209
|
+
const active = activeValue === option.value && !unavailable;
|
|
210
|
+
return (_jsxs("div", { "aria-disabled": unavailable ? "true" : undefined, "aria-selected": selected ? "true" : "false", className: "zvk-ui-multi-select__option", "data-disabled": unavailable ? "true" : undefined, "data-highlighted": active ? "true" : undefined, "data-selected": selected ? "true" : undefined, id: optionId(inputId, index), onClick: () => toggleOption(option), onMouseDown: (event) => event.preventDefault(), role: "option", children: [_jsx("span", { className: "zvk-ui-multi-select__option-check", "aria-hidden": "true", children: selected ? "*" : "" }), _jsx("span", { children: option.label })] }, option.value));
|
|
211
|
+
}) }) }) })) : null] }));
|
|
212
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type SplitterOrientation = "horizontal" | "vertical";
|
|
3
|
+
export type SplitterValue = readonly [number, number];
|
|
4
|
+
export interface SplitterProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue"> {
|
|
5
|
+
defaultValue?: SplitterValue;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
onResizeEnd?: (value: SplitterValue) => void;
|
|
8
|
+
onResizeStart?: (value: SplitterValue) => void;
|
|
9
|
+
onValueChange?: (value: SplitterValue) => void;
|
|
10
|
+
orientation?: SplitterOrientation;
|
|
11
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
12
|
+
value?: SplitterValue;
|
|
13
|
+
}
|
|
14
|
+
export interface SplitterPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
|
+
collapsedSize?: number;
|
|
16
|
+
collapsible?: boolean;
|
|
17
|
+
maxSize?: number;
|
|
18
|
+
minSize?: number;
|
|
19
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
20
|
+
}
|
|
21
|
+
export interface SplitterHandleProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
24
|
+
step?: number;
|
|
25
|
+
}
|
|
26
|
+
interface SplitterLimits {
|
|
27
|
+
collapsedSize: number;
|
|
28
|
+
collapsible: boolean;
|
|
29
|
+
maxSize: number;
|
|
30
|
+
minSize: number;
|
|
31
|
+
}
|
|
32
|
+
interface SplitterPanelInternalProps {
|
|
33
|
+
__zvkSplitterPanelIndex?: number | undefined;
|
|
34
|
+
__zvkSplitterPanelOrientation?: SplitterOrientation | undefined;
|
|
35
|
+
__zvkSplitterPanelSize?: number | undefined;
|
|
36
|
+
}
|
|
37
|
+
interface SplitterHandleInternalProps {
|
|
38
|
+
__zvkSplitterDisabled?: boolean | undefined;
|
|
39
|
+
__zvkSplitterLimits?: SplitterLimits | undefined;
|
|
40
|
+
__zvkSplitterOnChange?: ((nextPrimarySize: number, options?: SplitterResizeOptions) => SplitterValue) | undefined;
|
|
41
|
+
__zvkSplitterOnResizeEnd?: ((value: SplitterValue) => void) | undefined;
|
|
42
|
+
__zvkSplitterOnResizeStart?: ((value: SplitterValue) => void) | undefined;
|
|
43
|
+
__zvkSplitterOrientation?: SplitterOrientation | undefined;
|
|
44
|
+
__zvkSplitterRestoreRef?: React.MutableRefObject<number> | undefined;
|
|
45
|
+
__zvkSplitterValue?: SplitterValue | undefined;
|
|
46
|
+
}
|
|
47
|
+
interface SplitterResizeOptions {
|
|
48
|
+
allowCollapsed?: boolean;
|
|
49
|
+
}
|
|
50
|
+
declare function SplitterRoot({ children, className, defaultValue: defaultValueProp, disabled, onResizeEnd, onResizeStart, onValueChange, orientation, ref, value, ...props }: SplitterProps): React.JSX.Element;
|
|
51
|
+
declare function SplitterPanel({ __zvkSplitterPanelIndex, __zvkSplitterPanelOrientation, __zvkSplitterPanelSize, className, collapsedSize, collapsible, maxSize, minSize, ref, style, ...props }: SplitterPanelProps & SplitterPanelInternalProps): React.JSX.Element;
|
|
52
|
+
declare namespace SplitterPanel {
|
|
53
|
+
var displayName: string;
|
|
54
|
+
}
|
|
55
|
+
declare function SplitterHandle({ __zvkSplitterDisabled, __zvkSplitterLimits, __zvkSplitterOnChange, __zvkSplitterOnResizeEnd, __zvkSplitterOnResizeStart, __zvkSplitterOrientation, __zvkSplitterRestoreRef, __zvkSplitterValue, className, disabled, onKeyDown, onPointerDown, ref, role, step, tabIndex, ...props }: SplitterHandleProps & SplitterHandleInternalProps): React.JSX.Element;
|
|
56
|
+
declare namespace SplitterHandle {
|
|
57
|
+
var displayName: string;
|
|
58
|
+
}
|
|
59
|
+
export declare const Splitter: typeof SplitterRoot & {
|
|
60
|
+
Handle: typeof SplitterHandle;
|
|
61
|
+
Panel: typeof SplitterPanel;
|
|
62
|
+
};
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../utils/cn.js";
|
|
5
|
+
import { composeEventHandlers } from "../../utils/compose-event-handlers.js";
|
|
6
|
+
const defaultValue = [50, 50];
|
|
7
|
+
function normalizeValue(value) {
|
|
8
|
+
const primary = Number.isFinite(value[0]) ? value[0] : defaultValue[0];
|
|
9
|
+
const clampedPrimary = Math.min(100, Math.max(0, primary));
|
|
10
|
+
return [clampedPrimary, 100 - clampedPrimary];
|
|
11
|
+
}
|
|
12
|
+
function getPanelLimits(panel) {
|
|
13
|
+
return {
|
|
14
|
+
collapsedSize: panel?.props.collapsedSize ?? 0,
|
|
15
|
+
collapsible: panel?.props.collapsible ?? false,
|
|
16
|
+
maxSize: panel?.props.maxSize ?? 100,
|
|
17
|
+
minSize: panel?.props.minSize ?? 0
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function clampPrimarySize(nextPrimarySize, limits, options = {}) {
|
|
21
|
+
const minSize = options.allowCollapsed && limits.collapsible ? Math.min(limits.minSize, limits.collapsedSize) : limits.minSize;
|
|
22
|
+
return Math.min(limits.maxSize, Math.max(minSize, nextPrimarySize));
|
|
23
|
+
}
|
|
24
|
+
function isPanelElement(child) {
|
|
25
|
+
return React.isValidElement(child) && child.type === SplitterPanel;
|
|
26
|
+
}
|
|
27
|
+
function isHandleElement(child) {
|
|
28
|
+
return React.isValidElement(child) && child.type === SplitterHandle;
|
|
29
|
+
}
|
|
30
|
+
function separatorOrientation(orientation) {
|
|
31
|
+
return orientation === "horizontal" ? "vertical" : "horizontal";
|
|
32
|
+
}
|
|
33
|
+
function SplitterRoot({ children, className, defaultValue: defaultValueProp = defaultValue, disabled, onResizeEnd, onResizeStart, onValueChange, orientation = "horizontal", ref, value, ...props }) {
|
|
34
|
+
const [internalValue, setInternalValue] = React.useState(() => normalizeValue(defaultValueProp));
|
|
35
|
+
const currentValue = normalizeValue(value ?? internalValue);
|
|
36
|
+
const isControlled = value !== undefined;
|
|
37
|
+
const panelElements = React.Children.toArray(children).filter(isPanelElement);
|
|
38
|
+
const primaryPanel = panelElements[0];
|
|
39
|
+
const limits = getPanelLimits(primaryPanel);
|
|
40
|
+
const restoreValueRef = React.useRef(currentValue[0]);
|
|
41
|
+
React.useLayoutEffect(() => {
|
|
42
|
+
if (currentValue[0] > limits.collapsedSize) {
|
|
43
|
+
restoreValueRef.current = currentValue[0];
|
|
44
|
+
}
|
|
45
|
+
}, [currentValue, limits.collapsedSize]);
|
|
46
|
+
const updateValue = React.useCallback((nextPrimarySize, options = {}) => {
|
|
47
|
+
const nextValue = normalizeValue([clampPrimarySize(nextPrimarySize, limits, options), 0]);
|
|
48
|
+
if (!isControlled) {
|
|
49
|
+
setInternalValue(nextValue);
|
|
50
|
+
}
|
|
51
|
+
onValueChange?.(nextValue);
|
|
52
|
+
return nextValue;
|
|
53
|
+
}, [isControlled, limits, onValueChange]);
|
|
54
|
+
let panelIndex = 0;
|
|
55
|
+
const renderedChildren = React.Children.map(children, (child) => {
|
|
56
|
+
if (isPanelElement(child)) {
|
|
57
|
+
const nextPanelIndex = panelIndex;
|
|
58
|
+
panelIndex += 1;
|
|
59
|
+
return React.cloneElement(child, {
|
|
60
|
+
__zvkSplitterPanelIndex: nextPanelIndex,
|
|
61
|
+
__zvkSplitterPanelOrientation: orientation,
|
|
62
|
+
__zvkSplitterPanelSize: currentValue[nextPanelIndex] ?? 0
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (isHandleElement(child)) {
|
|
66
|
+
return React.cloneElement(child, {
|
|
67
|
+
__zvkSplitterDisabled: disabled,
|
|
68
|
+
__zvkSplitterLimits: limits,
|
|
69
|
+
__zvkSplitterOnChange: updateValue,
|
|
70
|
+
__zvkSplitterOnResizeEnd: onResizeEnd,
|
|
71
|
+
__zvkSplitterOnResizeStart: onResizeStart,
|
|
72
|
+
__zvkSplitterOrientation: orientation,
|
|
73
|
+
__zvkSplitterRestoreRef: restoreValueRef,
|
|
74
|
+
__zvkSplitterValue: currentValue
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return child;
|
|
78
|
+
});
|
|
79
|
+
return (_jsx("div", { ...props, ref: ref, className: cn("zvk-ui-splitter", className), "data-disabled": disabled ? "true" : undefined, "data-orientation": orientation, children: renderedChildren }));
|
|
80
|
+
}
|
|
81
|
+
function SplitterPanel({ __zvkSplitterPanelIndex, __zvkSplitterPanelOrientation, __zvkSplitterPanelSize, className, collapsedSize, collapsible, maxSize, minSize, ref, style, ...props }) {
|
|
82
|
+
const panelStyle = {
|
|
83
|
+
...style,
|
|
84
|
+
flexBasis: __zvkSplitterPanelSize === undefined ? style?.flexBasis : `${__zvkSplitterPanelSize}%`
|
|
85
|
+
};
|
|
86
|
+
return (_jsx("div", { ...props, ref: ref, className: cn("zvk-ui-splitter__panel", className), "data-orientation": __zvkSplitterPanelOrientation, "data-panel-index": __zvkSplitterPanelIndex, style: panelStyle }));
|
|
87
|
+
}
|
|
88
|
+
function SplitterHandle({ __zvkSplitterDisabled, __zvkSplitterLimits, __zvkSplitterOnChange, __zvkSplitterOnResizeEnd, __zvkSplitterOnResizeStart, __zvkSplitterOrientation = "horizontal", __zvkSplitterRestoreRef, __zvkSplitterValue = defaultValue, className, disabled, onKeyDown, onPointerDown, ref, role = "separator", step = 10, tabIndex, ...props }) {
|
|
89
|
+
const isDisabled = disabled || __zvkSplitterDisabled;
|
|
90
|
+
const limits = __zvkSplitterLimits ?? getPanelLimits(undefined);
|
|
91
|
+
const currentPrimarySize = __zvkSplitterValue[0];
|
|
92
|
+
const ariaOrientation = separatorOrientation(__zvkSplitterOrientation);
|
|
93
|
+
return (_jsx("div", { ...props, ref: ref, "aria-disabled": isDisabled ? true : undefined, "aria-orientation": ariaOrientation, "aria-valuemax": limits.maxSize, "aria-valuemin": limits.minSize, "aria-valuenow": currentPrimarySize, className: cn("zvk-ui-splitter__handle", className), "data-disabled": isDisabled ? "true" : undefined, "data-orientation": ariaOrientation, onKeyDown: composeEventHandlers(onKeyDown, (event) => {
|
|
94
|
+
if (isDisabled || !__zvkSplitterOnChange) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let nextPrimarySize;
|
|
98
|
+
let allowCollapsed = false;
|
|
99
|
+
if (event.key === "ArrowRight" ||
|
|
100
|
+
event.key === "ArrowDown") {
|
|
101
|
+
nextPrimarySize = currentPrimarySize + step;
|
|
102
|
+
}
|
|
103
|
+
else if (event.key === "ArrowLeft" ||
|
|
104
|
+
event.key === "ArrowUp") {
|
|
105
|
+
nextPrimarySize = currentPrimarySize - step;
|
|
106
|
+
}
|
|
107
|
+
else if (event.key === "Home") {
|
|
108
|
+
nextPrimarySize = limits.minSize;
|
|
109
|
+
}
|
|
110
|
+
else if (event.key === "End") {
|
|
111
|
+
nextPrimarySize = limits.maxSize;
|
|
112
|
+
}
|
|
113
|
+
else if (event.key === "Enter" && limits.collapsible) {
|
|
114
|
+
const restoreValue = __zvkSplitterRestoreRef?.current ?? limits.minSize;
|
|
115
|
+
nextPrimarySize = currentPrimarySize <= limits.collapsedSize ? restoreValue : limits.collapsedSize;
|
|
116
|
+
allowCollapsed = true;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
__zvkSplitterOnResizeStart?.(__zvkSplitterValue);
|
|
123
|
+
const nextValue = __zvkSplitterOnChange(nextPrimarySize, { allowCollapsed });
|
|
124
|
+
__zvkSplitterOnResizeEnd?.(nextValue);
|
|
125
|
+
}), onPointerDown: composeEventHandlers(onPointerDown, (event) => {
|
|
126
|
+
if (isDisabled || !__zvkSplitterOnChange) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const splitter = event.currentTarget.parentElement;
|
|
130
|
+
const rect = splitter?.getBoundingClientRect();
|
|
131
|
+
const totalSize = __zvkSplitterOrientation === "horizontal" ? rect?.width : rect?.height;
|
|
132
|
+
if (!totalSize) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
const handle = event.currentTarget;
|
|
137
|
+
const pointerId = event.pointerId;
|
|
138
|
+
const startCoordinate = __zvkSplitterOrientation === "horizontal" ? event.clientX : event.clientY;
|
|
139
|
+
const startPrimarySize = currentPrimarySize;
|
|
140
|
+
let latestValue = __zvkSplitterValue;
|
|
141
|
+
handle.setPointerCapture?.(pointerId);
|
|
142
|
+
__zvkSplitterOnResizeStart?.(__zvkSplitterValue);
|
|
143
|
+
const handlePointerMove = (moveEvent) => {
|
|
144
|
+
const currentCoordinate = __zvkSplitterOrientation === "horizontal" ? moveEvent.clientX : moveEvent.clientY;
|
|
145
|
+
const delta = ((currentCoordinate - startCoordinate) / totalSize) * 100;
|
|
146
|
+
latestValue = __zvkSplitterOnChange(startPrimarySize + delta);
|
|
147
|
+
};
|
|
148
|
+
const handlePointerUp = () => {
|
|
149
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
150
|
+
window.removeEventListener("pointerup", handlePointerUp);
|
|
151
|
+
handle.releasePointerCapture?.(pointerId);
|
|
152
|
+
__zvkSplitterOnResizeEnd?.(latestValue);
|
|
153
|
+
};
|
|
154
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
155
|
+
window.addEventListener("pointerup", handlePointerUp, { once: true });
|
|
156
|
+
}), role: role, tabIndex: isDisabled ? -1 : tabIndex ?? 0 }));
|
|
157
|
+
}
|
|
158
|
+
SplitterPanel.displayName = "Splitter.Panel";
|
|
159
|
+
SplitterHandle.displayName = "Splitter.Handle";
|
|
160
|
+
export const Splitter = Object.assign(SplitterRoot, {
|
|
161
|
+
Handle: SplitterHandle,
|
|
162
|
+
Panel: SplitterPanel
|
|
163
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type StepperOrientation = "horizontal" | "vertical";
|
|
3
|
+
export type StepperStatus = "complete" | "current" | "upcoming" | "disabled";
|
|
4
|
+
export interface StepperItem {
|
|
5
|
+
id: string;
|
|
6
|
+
label: React.ReactNode;
|
|
7
|
+
description?: React.ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
optional?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface StepperItemState {
|
|
12
|
+
clickable: boolean;
|
|
13
|
+
complete: boolean;
|
|
14
|
+
current: boolean;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
index: number;
|
|
17
|
+
status: StepperStatus;
|
|
18
|
+
}
|
|
19
|
+
export interface StepperProps extends Omit<React.HTMLAttributes<HTMLElement>, "children" | "onChange"> {
|
|
20
|
+
defaultValue?: string;
|
|
21
|
+
interactive?: boolean;
|
|
22
|
+
items: readonly StepperItem[];
|
|
23
|
+
linear?: boolean;
|
|
24
|
+
onValueChange?: (value: string) => void;
|
|
25
|
+
orientation?: StepperOrientation;
|
|
26
|
+
ref?: React.Ref<HTMLElement>;
|
|
27
|
+
renderItem?: (item: StepperItem, state: StepperItemState) => React.ReactNode;
|
|
28
|
+
value?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function Stepper({ "aria-label": ariaLabel, className, defaultValue, interactive, items, linear, onValueChange, orientation, ref, renderItem, value, ...props }: StepperProps): React.JSX.Element;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useControllableState } from "../../hooks/use-controllable-state.js";
|
|
5
|
+
import { cn } from "../../utils/cn.js";
|
|
6
|
+
function firstEnabledItem(items) {
|
|
7
|
+
return items.find((item) => item.disabled !== true) ?? items[0];
|
|
8
|
+
}
|
|
9
|
+
function nextEnabledIndex(items, currentIndex) {
|
|
10
|
+
for (let index = currentIndex + 1; index < items.length; index += 1) {
|
|
11
|
+
if (items[index]?.disabled !== true) {
|
|
12
|
+
return index;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return currentIndex;
|
|
16
|
+
}
|
|
17
|
+
function getItemStatus(item, index, currentIndex) {
|
|
18
|
+
if (item.disabled) {
|
|
19
|
+
return "disabled";
|
|
20
|
+
}
|
|
21
|
+
if (index === currentIndex) {
|
|
22
|
+
return "current";
|
|
23
|
+
}
|
|
24
|
+
if (index < currentIndex) {
|
|
25
|
+
return "complete";
|
|
26
|
+
}
|
|
27
|
+
return "upcoming";
|
|
28
|
+
}
|
|
29
|
+
function defaultRenderItem(item, state) {
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { className: "zvk-ui-stepper__indicator", "aria-hidden": "true", children: state.complete ? "done" : state.index + 1 }), _jsxs("span", { className: "zvk-ui-stepper__content", children: [_jsx("span", { className: "zvk-ui-stepper__label", children: item.label }), item.description ? _jsx("span", { className: "zvk-ui-stepper__description", children: item.description }) : null, item.optional ? _jsx("span", { className: "zvk-ui-stepper__optional", children: "Optional" }) : null] })] }));
|
|
31
|
+
}
|
|
32
|
+
export function Stepper({ "aria-label": ariaLabel = "Progress", className, defaultValue, interactive = false, items, linear = true, onValueChange, orientation = "horizontal", ref, renderItem = defaultRenderItem, value, ...props }) {
|
|
33
|
+
const fallbackValue = defaultValue ?? firstEnabledItem(items)?.id ?? "";
|
|
34
|
+
const [currentValue, setCurrentValue] = useControllableState({
|
|
35
|
+
...(value !== undefined ? { value } : {}),
|
|
36
|
+
defaultValue: fallbackValue,
|
|
37
|
+
...(onValueChange ? { onChange: onValueChange } : {})
|
|
38
|
+
});
|
|
39
|
+
const currentIndex = Math.max(0, items.findIndex((item) => item.id === currentValue));
|
|
40
|
+
const nextIndex = nextEnabledIndex(items, currentIndex);
|
|
41
|
+
function canActivate(item, index) {
|
|
42
|
+
if (!interactive || item.disabled) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return !linear || index <= nextIndex;
|
|
46
|
+
}
|
|
47
|
+
return (_jsx("nav", { ...props, ref: ref, "aria-label": ariaLabel, className: cn("zvk-ui-stepper", className), "data-interactive": interactive ? "true" : undefined, "data-linear": linear ? "true" : undefined, "data-orientation": orientation, children: _jsx("ol", { className: "zvk-ui-stepper__list", children: items.map((item, index) => {
|
|
48
|
+
const status = getItemStatus(item, index, currentIndex);
|
|
49
|
+
const clickable = canActivate(item, index);
|
|
50
|
+
const state = {
|
|
51
|
+
clickable,
|
|
52
|
+
complete: status === "complete",
|
|
53
|
+
current: status === "current",
|
|
54
|
+
disabled: status === "disabled",
|
|
55
|
+
index,
|
|
56
|
+
status
|
|
57
|
+
};
|
|
58
|
+
const content = renderItem(item, state);
|
|
59
|
+
return (_jsx("li", { "aria-current": state.current ? "step" : undefined, className: "zvk-ui-stepper__item", "data-clickable": clickable ? "true" : undefined, "data-state": status, children: interactive ? (_jsx("button", { "aria-disabled": !clickable ? "true" : undefined, className: "zvk-ui-stepper__trigger", disabled: !clickable, onClick: () => setCurrentValue(item.id), type: "button", children: content })) : (_jsx("span", { className: "zvk-ui-stepper__step", children: content })) }, item.id));
|
|
60
|
+
}) }) }));
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type TagsInputSize = "sm" | "md" | "lg";
|
|
3
|
+
export type TagsInputValidationResult = string | null | undefined;
|
|
4
|
+
export interface TagsInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "defaultValue" | "name" | "onChange" | "size" | "value"> {
|
|
5
|
+
allowDuplicates?: boolean;
|
|
6
|
+
defaultValue?: readonly string[];
|
|
7
|
+
delimiterKeys?: readonly string[];
|
|
8
|
+
description?: React.ReactNode;
|
|
9
|
+
error?: React.ReactNode;
|
|
10
|
+
invalid?: boolean;
|
|
11
|
+
label?: React.ReactNode;
|
|
12
|
+
maxItems?: number;
|
|
13
|
+
name?: string;
|
|
14
|
+
normalizeTag?: (value: string) => string;
|
|
15
|
+
onValueChange?: (value: string[]) => void;
|
|
16
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
17
|
+
size?: TagsInputSize;
|
|
18
|
+
validateTag?: (value: string, values: readonly string[]) => TagsInputValidationResult;
|
|
19
|
+
value?: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function TagsInput({ "aria-describedby": ariaDescribedBy, allowDuplicates, className, defaultValue, delimiterKeys, description, disabled, error, id, invalid, label, maxItems, name, normalizeTag, onBlur, onFocus, onKeyDown, onValueChange, placeholder, ref, required, size, validateTag, value, ...props }: TagsInputProps): React.JSX.Element;
|