@nexub/react 6.1.0 → 6.3.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/accessibility.json +49 -1
- package/dist/components/DataTable/DataTable.d.ts +11 -2
- package/dist/components/DataTable/DataTable.js +51 -4
- package/dist/components/DateTimePicker/DateTimePicker.d.ts +20 -0
- package/dist/components/DateTimePicker/DateTimePicker.js +67 -0
- package/dist/components/DateTimePicker/index.d.ts +2 -0
- package/dist/components/DateTimePicker/index.js +4 -0
- package/dist/components/EventCalendar/EventCalendar.d.ts +38 -0
- package/dist/components/EventCalendar/EventCalendar.js +204 -0
- package/dist/components/EventCalendar/index.d.ts +2 -0
- package/dist/components/EventCalendar/index.js +4 -0
- package/dist/components/TimePicker/TimePicker.d.ts +15 -0
- package/dist/components/TimePicker/TimePicker.js +26 -0
- package/dist/components/TimePicker/index.d.ts +2 -0
- package/dist/components/TimePicker/index.js +4 -0
- package/dist/components/TreeView/TreeView.d.ts +38 -0
- package/dist/components/TreeView/TreeView.js +253 -0
- package/dist/components/TreeView/index.d.ts +2 -0
- package/dist/components/TreeView/index.js +4 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/styles.css +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Check, ChevronRight, Minus } from "@nexub/icons";
|
|
5
|
+
import { cn } from "../../utils/cn.js";
|
|
6
|
+
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
7
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
8
|
+
const hasKids = (n) => Boolean(n.children?.length || n.hasChildren && !n.children);
|
|
9
|
+
const text = (n) => n.textValue ?? (typeof n.label === "string" ? n.label : "");
|
|
10
|
+
const descendants = (n) => (n.children ?? []).flatMap((c) => [c.id, ...descendants(c)]);
|
|
11
|
+
function TreeView({
|
|
12
|
+
nodes,
|
|
13
|
+
expanded: expandedProp,
|
|
14
|
+
defaultExpanded,
|
|
15
|
+
onExpandedChange,
|
|
16
|
+
selected: selectedProp,
|
|
17
|
+
defaultSelected,
|
|
18
|
+
onSelectedChange,
|
|
19
|
+
checkable = false,
|
|
20
|
+
checked: checkedProp,
|
|
21
|
+
defaultChecked,
|
|
22
|
+
onCheckedChange,
|
|
23
|
+
onLoadChildren,
|
|
24
|
+
className,
|
|
25
|
+
...aria
|
|
26
|
+
}) {
|
|
27
|
+
const [expanded = [], setExpanded] = useControllableState({
|
|
28
|
+
value: expandedProp,
|
|
29
|
+
defaultValue: defaultExpanded ?? [],
|
|
30
|
+
onChange: onExpandedChange
|
|
31
|
+
});
|
|
32
|
+
const [selected = null, setSelected] = useControllableState({
|
|
33
|
+
value: selectedProp,
|
|
34
|
+
defaultValue: defaultSelected ?? null,
|
|
35
|
+
onChange: onSelectedChange
|
|
36
|
+
});
|
|
37
|
+
const [checked = [], setChecked] = useControllableState({
|
|
38
|
+
value: checkedProp,
|
|
39
|
+
defaultValue: defaultChecked ?? [],
|
|
40
|
+
onChange: onCheckedChange
|
|
41
|
+
});
|
|
42
|
+
const [loading, setLoading] = React.useState(null);
|
|
43
|
+
const [focused, setFocused] = React.useState(null);
|
|
44
|
+
const refs = React.useRef(/* @__PURE__ */ new Map());
|
|
45
|
+
const typed = React.useRef({ text: "", at: 0 });
|
|
46
|
+
const open = React.useMemo(() => new Set(expanded), [expanded]);
|
|
47
|
+
const checkedSet = React.useMemo(() => new Set(checked), [checked]);
|
|
48
|
+
const { all, visible } = React.useMemo(() => {
|
|
49
|
+
const all2 = /* @__PURE__ */ new Map();
|
|
50
|
+
const visible2 = [];
|
|
51
|
+
const walk = (list, level, parent, shown) => {
|
|
52
|
+
for (const node of list) {
|
|
53
|
+
const flat = { node, level, parent };
|
|
54
|
+
all2.set(node.id, flat);
|
|
55
|
+
if (shown) visible2.push(flat);
|
|
56
|
+
if (node.children) walk(node.children, level + 1, node.id, shown && open.has(node.id));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
walk(nodes, 1, null, true);
|
|
60
|
+
return { all: all2, visible: visible2 };
|
|
61
|
+
}, [nodes, open]);
|
|
62
|
+
const tabStop = focused && visible.some((f) => f.node.id === focused) && focused || selected && visible.some((f) => f.node.id === selected) && selected || visible[0]?.node.id;
|
|
63
|
+
const focus = (id) => {
|
|
64
|
+
if (!id) return;
|
|
65
|
+
setFocused(id);
|
|
66
|
+
refs.current.get(id)?.focus();
|
|
67
|
+
};
|
|
68
|
+
const toggle = async (node, to = !open.has(node.id)) => {
|
|
69
|
+
if (!hasKids(node) || to === open.has(node.id)) return;
|
|
70
|
+
setExpanded(to ? [...expanded, node.id] : expanded.filter((id) => id !== node.id));
|
|
71
|
+
if (to && node.hasChildren && !node.children && onLoadChildren) {
|
|
72
|
+
setLoading(node.id);
|
|
73
|
+
try {
|
|
74
|
+
await onLoadChildren(node);
|
|
75
|
+
} finally {
|
|
76
|
+
setLoading((id) => id === node.id ? null : id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const toggleCheck = (node) => {
|
|
81
|
+
const next = new Set(checkedSet);
|
|
82
|
+
const on = !next.has(node.id);
|
|
83
|
+
for (const id of [node.id, ...descendants(node)]) {
|
|
84
|
+
if (on) next.add(id);
|
|
85
|
+
else next.delete(id);
|
|
86
|
+
}
|
|
87
|
+
for (let p = all.get(node.id)?.parent; p; p = all.get(p)?.parent ?? null) {
|
|
88
|
+
const kids = all.get(p).node.children ?? [];
|
|
89
|
+
if (kids.every((k) => next.has(k.id))) next.add(p);
|
|
90
|
+
else next.delete(p);
|
|
91
|
+
}
|
|
92
|
+
setChecked([...next]);
|
|
93
|
+
};
|
|
94
|
+
const activate = (node) => {
|
|
95
|
+
if (node.disabled) return;
|
|
96
|
+
if (checkable) toggleCheck(node);
|
|
97
|
+
else setSelected(node.id);
|
|
98
|
+
};
|
|
99
|
+
const onKeyDown = (e, flat) => {
|
|
100
|
+
const { node } = flat;
|
|
101
|
+
const i = visible.findIndex((f) => f.node.id === node.id);
|
|
102
|
+
const rtl = getComputedStyle(e.currentTarget).direction === "rtl";
|
|
103
|
+
const flip = { ArrowLeft: "ArrowRight", ArrowRight: "ArrowLeft" };
|
|
104
|
+
const key = rtl ? flip[e.key] ?? e.key : e.key;
|
|
105
|
+
let handled = true;
|
|
106
|
+
switch (key) {
|
|
107
|
+
case "ArrowDown":
|
|
108
|
+
focus(visible[i + 1]?.node.id);
|
|
109
|
+
break;
|
|
110
|
+
case "ArrowUp":
|
|
111
|
+
focus(visible[i - 1]?.node.id);
|
|
112
|
+
break;
|
|
113
|
+
case "Home":
|
|
114
|
+
focus(visible[0]?.node.id);
|
|
115
|
+
break;
|
|
116
|
+
case "End":
|
|
117
|
+
focus(visible.at(-1)?.node.id);
|
|
118
|
+
break;
|
|
119
|
+
case "ArrowRight":
|
|
120
|
+
if (hasKids(node) && !open.has(node.id)) void toggle(node, true);
|
|
121
|
+
else if (open.has(node.id)) focus(node.children?.[0]?.id);
|
|
122
|
+
break;
|
|
123
|
+
case "ArrowLeft":
|
|
124
|
+
if (open.has(node.id)) void toggle(node, false);
|
|
125
|
+
else focus(flat.parent ?? void 0);
|
|
126
|
+
break;
|
|
127
|
+
case "Enter":
|
|
128
|
+
case " ":
|
|
129
|
+
activate(node);
|
|
130
|
+
break;
|
|
131
|
+
default: {
|
|
132
|
+
handled = false;
|
|
133
|
+
if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey) break;
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const t = typed.current;
|
|
136
|
+
t.text = (now - t.at > 500 ? "" : t.text) + e.key.toLowerCase();
|
|
137
|
+
t.at = now;
|
|
138
|
+
const order = [...visible.slice(i + 1), ...visible.slice(0, i + 1)];
|
|
139
|
+
const hit = order.find((f) => text(f.node).toLowerCase().startsWith(t.text));
|
|
140
|
+
if (hit) {
|
|
141
|
+
focus(hit.node.id);
|
|
142
|
+
handled = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (handled) {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const render = (list, level, parent) => list.map((node, index) => {
|
|
152
|
+
const kids = hasKids(node);
|
|
153
|
+
const isOpen = open.has(node.id);
|
|
154
|
+
const isChecked = checkedSet.has(node.id);
|
|
155
|
+
const mixed = !isChecked && descendants(node).some((id) => checkedSet.has(id));
|
|
156
|
+
const flat = { node, level, parent };
|
|
157
|
+
return /* @__PURE__ */ jsxs(
|
|
158
|
+
"li",
|
|
159
|
+
{
|
|
160
|
+
ref: (el) => {
|
|
161
|
+
if (el) refs.current.set(node.id, el);
|
|
162
|
+
else refs.current.delete(node.id);
|
|
163
|
+
},
|
|
164
|
+
role: "treeitem",
|
|
165
|
+
"aria-level": level,
|
|
166
|
+
"aria-setsize": list.length,
|
|
167
|
+
"aria-posinset": index + 1,
|
|
168
|
+
"aria-expanded": kids ? isOpen : void 0,
|
|
169
|
+
"aria-selected": checkable ? void 0 : selected === node.id,
|
|
170
|
+
"aria-checked": checkable ? mixed ? "mixed" : isChecked : void 0,
|
|
171
|
+
"aria-disabled": node.disabled || void 0,
|
|
172
|
+
"aria-busy": loading === node.id || void 0,
|
|
173
|
+
tabIndex: tabStop === node.id ? 0 : -1,
|
|
174
|
+
onKeyDown: (e) => onKeyDown(e, flat),
|
|
175
|
+
onFocus: (e) => {
|
|
176
|
+
if (e.target === e.currentTarget) setFocused(node.id);
|
|
177
|
+
},
|
|
178
|
+
className: "outline-none [&:focus-visible>div]:outline [&:focus-visible>div]:outline-2 [&:focus-visible>div]:-outline-offset-2 [&:focus-visible>div]:outline-[var(--color-primary)]",
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ jsxs(
|
|
181
|
+
"div",
|
|
182
|
+
{
|
|
183
|
+
ref: (el) => {
|
|
184
|
+
if (el) el.style.paddingInlineStart = `${(level - 1) * 1.25 + 0.25}rem`;
|
|
185
|
+
},
|
|
186
|
+
onClick: () => {
|
|
187
|
+
focus(node.id);
|
|
188
|
+
if (kids && !checkable) void toggle(node);
|
|
189
|
+
activate(node);
|
|
190
|
+
},
|
|
191
|
+
className: cn(
|
|
192
|
+
"flex min-h-control-sm cursor-default items-center gap-1.5 rounded-shape-control pe-2 text-body-md text-[var(--color-on-surface)]",
|
|
193
|
+
"hover:bg-[var(--color-surface-container-high)]",
|
|
194
|
+
!checkable && selected === node.id && "bg-[var(--color-secondary-container)] text-[var(--color-on-secondary-container)] hover:bg-[var(--color-secondary-container)]",
|
|
195
|
+
node.disabled && "cursor-not-allowed text-[var(--color-text-disabled)] hover:bg-transparent"
|
|
196
|
+
),
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ jsx(
|
|
199
|
+
"span",
|
|
200
|
+
{
|
|
201
|
+
"aria-hidden": "true",
|
|
202
|
+
onClick: (e) => {
|
|
203
|
+
if (!kids) return;
|
|
204
|
+
e.stopPropagation();
|
|
205
|
+
focus(node.id);
|
|
206
|
+
void toggle(node);
|
|
207
|
+
},
|
|
208
|
+
className: "inline-flex size-5 shrink-0 items-center justify-center text-[var(--color-on-surface-variant)]",
|
|
209
|
+
children: loading === node.id ? /* @__PURE__ */ jsx(Spinner, { size: "xs" }) : kids ? /* @__PURE__ */ jsx(
|
|
210
|
+
ChevronRight,
|
|
211
|
+
{
|
|
212
|
+
size: "xs",
|
|
213
|
+
className: cn("transition-transform rtl:-scale-x-100", isOpen && "rotate-90 rtl:-rotate-90")
|
|
214
|
+
}
|
|
215
|
+
) : null
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
checkable && /* @__PURE__ */ jsx(
|
|
219
|
+
"span",
|
|
220
|
+
{
|
|
221
|
+
"aria-hidden": "true",
|
|
222
|
+
className: cn(
|
|
223
|
+
"inline-flex size-4 shrink-0 items-center justify-center rounded-[4px] border-2",
|
|
224
|
+
isChecked || mixed ? "border-[var(--color-primary)] bg-[var(--color-primary)] text-[var(--color-on-primary)]" : "border-[var(--color-outline)]"
|
|
225
|
+
),
|
|
226
|
+
children: isChecked ? /* @__PURE__ */ jsx(Check, { size: "xs" }) : mixed ? /* @__PURE__ */ jsx(Minus, { size: "xs" }) : null
|
|
227
|
+
}
|
|
228
|
+
),
|
|
229
|
+
node.icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0 text-[var(--color-on-surface-variant)]", children: node.icon }),
|
|
230
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: node.label })
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
kids && isOpen && node.children && /* @__PURE__ */ jsx("ul", { role: "group", className: "m-0 list-none p-0", children: render(node.children, level + 1, node.id) })
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
node.id
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
return /* @__PURE__ */ jsx(
|
|
241
|
+
"ul",
|
|
242
|
+
{
|
|
243
|
+
role: "tree",
|
|
244
|
+
"aria-multiselectable": checkable || void 0,
|
|
245
|
+
className: cn("m-0 flex list-none flex-col p-0", className),
|
|
246
|
+
...aria,
|
|
247
|
+
children: render(nodes, 1, null)
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
export {
|
|
252
|
+
TreeView
|
|
253
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -220,6 +220,14 @@ export { Fieldset } from './components/Fieldset/index.js';
|
|
|
220
220
|
export type { FieldsetProps } from './components/Fieldset/index.js';
|
|
221
221
|
export { InlineEdit } from './components/InlineEdit/index.js';
|
|
222
222
|
export type { InlineEditProps } from './components/InlineEdit/index.js';
|
|
223
|
+
export { TimePicker } from './components/TimePicker/index.js';
|
|
224
|
+
export type { TimePickerProps } from './components/TimePicker/index.js';
|
|
225
|
+
export { DateTimePicker } from './components/DateTimePicker/index.js';
|
|
226
|
+
export type { DateTimePickerProps } from './components/DateTimePicker/index.js';
|
|
227
|
+
export { EventCalendar } from './components/EventCalendar/index.js';
|
|
228
|
+
export type { EventCalendarProps, EventCalendarView, CalendarEvent, EventTone } from './components/EventCalendar/index.js';
|
|
229
|
+
export { TreeView } from './components/TreeView/index.js';
|
|
230
|
+
export type { TreeViewProps, TreeNode } from './components/TreeView/index.js';
|
|
223
231
|
export type { OTPInputProps } from './components/OTPInput/index.js';
|
|
224
232
|
export { DatePicker } from './components/DatePicker/index.js';
|
|
225
233
|
export type { DatePickerProps } from './components/DatePicker/index.js';
|
package/dist/index.js
CHANGED
|
@@ -124,6 +124,10 @@ import { OTPInput } from "./components/OTPInput/index.js";
|
|
|
124
124
|
import { Form, useForm } from "./components/Form/index.js";
|
|
125
125
|
import { Fieldset } from "./components/Fieldset/index.js";
|
|
126
126
|
import { InlineEdit } from "./components/InlineEdit/index.js";
|
|
127
|
+
import { TimePicker } from "./components/TimePicker/index.js";
|
|
128
|
+
import { DateTimePicker } from "./components/DateTimePicker/index.js";
|
|
129
|
+
import { EventCalendar } from "./components/EventCalendar/index.js";
|
|
130
|
+
import { TreeView } from "./components/TreeView/index.js";
|
|
127
131
|
import { DatePicker } from "./components/DatePicker/index.js";
|
|
128
132
|
import { DateRangePicker } from "./components/DateRangePicker/index.js";
|
|
129
133
|
import { todayIn } from "./components/DateRangePicker/index.js";
|
|
@@ -169,6 +173,7 @@ export {
|
|
|
169
173
|
DataTable,
|
|
170
174
|
DatePicker,
|
|
171
175
|
DateRangePicker,
|
|
176
|
+
DateTimePicker,
|
|
172
177
|
Dialog,
|
|
173
178
|
DiffView,
|
|
174
179
|
DonutChart,
|
|
@@ -177,6 +182,7 @@ export {
|
|
|
177
182
|
EmptyState,
|
|
178
183
|
EmptyStatePreset,
|
|
179
184
|
ErrorState,
|
|
185
|
+
EventCalendar,
|
|
180
186
|
EyebrowChip,
|
|
181
187
|
Fade,
|
|
182
188
|
Fieldset,
|
|
@@ -266,6 +272,7 @@ export {
|
|
|
266
272
|
ThemeProvider,
|
|
267
273
|
ThemeScript,
|
|
268
274
|
ThinkingIndicator,
|
|
275
|
+
TimePicker,
|
|
269
276
|
Toast,
|
|
270
277
|
Toaster,
|
|
271
278
|
Toggle,
|
|
@@ -275,6 +282,7 @@ export {
|
|
|
275
282
|
Tooltip,
|
|
276
283
|
Provider as TooltipProvider,
|
|
277
284
|
TopNav,
|
|
285
|
+
TreeView,
|
|
278
286
|
VisuallyHidden,
|
|
279
287
|
addonVariants,
|
|
280
288
|
alertVariants,
|