@godxjp/ui 20.2.0 → 21.0.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/dist/components/data-display/chat-bubble.d.ts +30 -0
- package/dist/components/data-display/chat-bubble.js +229 -0
- package/dist/components/data-display/descriptions.js +4 -1
- package/dist/components/data-display/index.d.ts +4 -2
- package/dist/components/data-display/index.js +5 -2
- package/dist/components/data-display/popover.js +3 -2
- package/dist/components/data-display/scroll-area.js +26 -1
- package/dist/components/data-display/tree.d.ts +8 -0
- package/dist/components/data-display/tree.js +426 -0
- package/dist/components/data-entry/chat-composer.d.ts +50 -0
- package/dist/components/data-entry/chat-composer.js +163 -0
- package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
- package/dist/components/data-entry/chat-suggestion.js +285 -0
- package/dist/components/data-entry/index.d.ts +4 -0
- package/dist/components/data-entry/index.js +4 -0
- package/dist/components/data-entry/textarea.js +3 -1
- package/dist/components/data-entry/tree-utils.d.ts +10 -48
- package/dist/components/data-entry/tree-utils.js +1 -154
- package/dist/components/layout/org-switcher.js +22 -2
- package/dist/i18n/messages/en.json +50 -0
- package/dist/i18n/messages/ja.json +48 -0
- package/dist/i18n/messages/vi.json +49 -0
- package/dist/lib/tree.d.ts +53 -0
- package/dist/lib/tree.js +155 -0
- package/dist/props/components/data-display.prop.d.ts +187 -1
- package/dist/props/components/data-entry.prop.d.ts +127 -0
- package/dist/props/registry.d.ts +140 -3
- package/dist/props/registry.js +202 -3
- package/dist/styles/control.css +7 -0
- package/dist/styles/data-display-layout.css +309 -55
- package/dist/styles/data-entry-layout.css +64 -0
- package/dist/styles/shell-layout.css +41 -11
- package/dist/tokens/base.css +3 -0
- package/dist/tokens/components/chat-bubble.css +36 -0
- package/dist/tokens/components/chat-composer.css +19 -0
- package/dist/tokens/components/data-display.css +0 -6
- package/dist/tokens/components/descriptions.css +4 -0
- package/dist/tokens/components/shell.css +13 -3
- package/dist/tokens/components/tree.css +27 -0
- package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
- package/docs/FRAME-COVERAGE-REPORT.md +8 -3
- package/docs/data-display/chat-bubble.tsx +397 -0
- package/docs/data-display/timeline.tsx +46 -0
- package/docs/data-display/tree.tsx +394 -0
- package/docs/data-entry/chat-composer.tsx +464 -0
- package/docs/data-entry/chat-suggestion.tsx +301 -0
- package/docs/roadmap/ai-chat-components.md +207 -0
- package/docs/roadmap/antd-parity.md +154 -0
- package/docs/roadmap/badge-tag-chip-count.md +172 -0
- package/docs/roadmap/list-masonry.md +159 -0
- package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
- package/docs/roadmap/parity-audit-data-entry.md +344 -0
- package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
- package/docs/roadmap/parity-backlog.md +79 -0
- package/docs/roadmap/tree-components.md +151 -0
- package/docs/showcase/table-tree-rows.tsx +4 -4
- package/package.json +5 -3
- package/dist/components/data-display/tree-list.d.ts +0 -13
- package/dist/components/data-display/tree-list.js +0 -26
- package/docs/data-display/tree-list.tsx +0 -107
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { VisuallyHidden } from "../general/visually-hidden.js";
|
|
7
|
+
import { Popover, PopoverAnchor, PopoverContent } from "../data-display/popover.js";
|
|
8
|
+
import { Command, CommandEmpty, CommandItem, CommandList } from "./command.js";
|
|
9
|
+
function matchTrigger(text, caret, trigger) {
|
|
10
|
+
if (!trigger) return null;
|
|
11
|
+
const before = text.slice(0, caret);
|
|
12
|
+
const start = before.lastIndexOf(trigger);
|
|
13
|
+
if (start === -1) return null;
|
|
14
|
+
const query = before.slice(start + trigger.length);
|
|
15
|
+
if (/\s/.test(query)) return null;
|
|
16
|
+
const preceding = start === 0 ? "" : before[start - 1];
|
|
17
|
+
if (preceding !== "" && !/\s/.test(preceding)) return null;
|
|
18
|
+
return { query };
|
|
19
|
+
}
|
|
20
|
+
function levelItems(items, path) {
|
|
21
|
+
let current = items;
|
|
22
|
+
for (const step of path) {
|
|
23
|
+
const next = current.find((item) => item.value === step)?.children;
|
|
24
|
+
if (!next) return current;
|
|
25
|
+
current = next;
|
|
26
|
+
}
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
function labelOf(item) {
|
|
30
|
+
return item.label ?? item.value;
|
|
31
|
+
}
|
|
32
|
+
function matches(item, query) {
|
|
33
|
+
if (!query) return true;
|
|
34
|
+
const needle = query.toLocaleLowerCase();
|
|
35
|
+
return labelOf(item).toLocaleLowerCase().includes(needle) || item.value.toLocaleLowerCase().includes(needle) || (item.description?.toLocaleLowerCase().includes(needle) ?? false);
|
|
36
|
+
}
|
|
37
|
+
function ChatSuggestion({
|
|
38
|
+
items,
|
|
39
|
+
onValueChange,
|
|
40
|
+
triggerCharacter = "/",
|
|
41
|
+
open,
|
|
42
|
+
defaultOpen,
|
|
43
|
+
onOpenChange,
|
|
44
|
+
children,
|
|
45
|
+
emptyMessage,
|
|
46
|
+
listLabel: listLabelProp,
|
|
47
|
+
id,
|
|
48
|
+
className
|
|
49
|
+
}) {
|
|
50
|
+
const { t } = useTranslation();
|
|
51
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false);
|
|
52
|
+
const isOpen = open ?? uncontrolledOpen;
|
|
53
|
+
const [query, setQuery] = React.useState("");
|
|
54
|
+
const [path, setPath] = React.useState([]);
|
|
55
|
+
const [activeValue, setActiveValue] = React.useState("");
|
|
56
|
+
const [activeId, setActiveId] = React.useState(void 0);
|
|
57
|
+
const anchorRef = React.useRef(null);
|
|
58
|
+
const contentRef = React.useRef(null);
|
|
59
|
+
const [listId, setListId] = React.useState(void 0);
|
|
60
|
+
const field = React.useCallback(() => anchorRef.current?.querySelector("textarea") ?? null, []);
|
|
61
|
+
const openRef = React.useRef(isOpen);
|
|
62
|
+
openRef.current = isOpen;
|
|
63
|
+
const setOpen = React.useCallback(
|
|
64
|
+
(next) => {
|
|
65
|
+
if (openRef.current === next) return;
|
|
66
|
+
openRef.current = next;
|
|
67
|
+
if (open === void 0) setUncontrolledOpen(next);
|
|
68
|
+
onOpenChange?.(next);
|
|
69
|
+
if (!next) {
|
|
70
|
+
setQuery("");
|
|
71
|
+
setPath([]);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[open, onOpenChange]
|
|
75
|
+
);
|
|
76
|
+
const visible = React.useMemo(() => {
|
|
77
|
+
return levelItems(items, path).filter((item) => matches(item, query));
|
|
78
|
+
}, [items, path, query]);
|
|
79
|
+
const enabled = React.useMemo(() => visible.filter((item) => !item.disabled), [visible]);
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
if (enabled.length === 0) {
|
|
82
|
+
setActiveValue("");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
setActiveValue(
|
|
86
|
+
(current) => enabled.some((item) => item.value === current) ? current : enabled[0].value
|
|
87
|
+
);
|
|
88
|
+
}, [enabled]);
|
|
89
|
+
React.useEffect(() => {
|
|
90
|
+
if (!isOpen) {
|
|
91
|
+
setListId(void 0);
|
|
92
|
+
setActiveId(void 0);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const content = contentRef.current;
|
|
96
|
+
setListId(content?.querySelector("[cmdk-list]")?.id || void 0);
|
|
97
|
+
setActiveId(content?.querySelector('[cmdk-item][aria-selected="true"]')?.id || void 0);
|
|
98
|
+
});
|
|
99
|
+
const close = React.useCallback(() => {
|
|
100
|
+
setOpen(false);
|
|
101
|
+
field()?.focus();
|
|
102
|
+
}, [field, setOpen]);
|
|
103
|
+
const evaluate = React.useCallback(() => {
|
|
104
|
+
const node = field();
|
|
105
|
+
if (!node) return;
|
|
106
|
+
const caret = node.selectionStart ?? node.value.length;
|
|
107
|
+
const hit = matchTrigger(node.value, caret, triggerCharacter);
|
|
108
|
+
if (!hit) {
|
|
109
|
+
setOpen(false);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setQuery(hit.query);
|
|
113
|
+
setOpen(true);
|
|
114
|
+
}, [field, setOpen, triggerCharacter]);
|
|
115
|
+
const onTrigger = React.useCallback(
|
|
116
|
+
(next) => {
|
|
117
|
+
if (next === false) {
|
|
118
|
+
setOpen(false);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (next === void 0) {
|
|
122
|
+
setQuery("");
|
|
123
|
+
setOpen(true);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
window.setTimeout(evaluate, 0);
|
|
127
|
+
},
|
|
128
|
+
[evaluate, setOpen]
|
|
129
|
+
);
|
|
130
|
+
const pick = React.useCallback(
|
|
131
|
+
(item) => {
|
|
132
|
+
if (item.disabled) return;
|
|
133
|
+
if (item.children && item.children.length > 0) {
|
|
134
|
+
setPath((current) => [...current, item.value]);
|
|
135
|
+
setQuery("");
|
|
136
|
+
field()?.focus();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
onValueChange?.(item.value);
|
|
140
|
+
setOpen(false);
|
|
141
|
+
field()?.focus();
|
|
142
|
+
},
|
|
143
|
+
[field, onValueChange, setOpen]
|
|
144
|
+
);
|
|
145
|
+
const move = React.useCallback(
|
|
146
|
+
(direction) => {
|
|
147
|
+
if (enabled.length === 0) return;
|
|
148
|
+
const index = enabled.findIndex((item) => item.value === activeValue);
|
|
149
|
+
const from = index === -1 ? direction === 1 ? -1 : 0 : index;
|
|
150
|
+
const next = (from + direction + enabled.length) % enabled.length;
|
|
151
|
+
setActiveValue(enabled[next].value);
|
|
152
|
+
},
|
|
153
|
+
[activeValue, enabled]
|
|
154
|
+
);
|
|
155
|
+
const onKeyDown = React.useCallback(
|
|
156
|
+
(event) => {
|
|
157
|
+
if (!openRef.current) {
|
|
158
|
+
if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End") {
|
|
159
|
+
window.setTimeout(evaluate, 0);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
switch (event.key) {
|
|
164
|
+
case "ArrowDown":
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
move(1);
|
|
167
|
+
return;
|
|
168
|
+
case "ArrowUp":
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
move(-1);
|
|
171
|
+
return;
|
|
172
|
+
case "Enter":
|
|
173
|
+
case "Tab": {
|
|
174
|
+
const item = enabled.find((candidate) => candidate.value === activeValue);
|
|
175
|
+
if (!item) return;
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
pick(item);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
case "Escape":
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
close();
|
|
184
|
+
return;
|
|
185
|
+
default:
|
|
186
|
+
if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End") {
|
|
187
|
+
window.setTimeout(evaluate, 0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
[activeValue, close, enabled, evaluate, move, pick]
|
|
192
|
+
);
|
|
193
|
+
const listLabel = listLabelProp ?? t("dataEntry.chatSuggestion.label");
|
|
194
|
+
return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: setOpen, children: [
|
|
195
|
+
/* @__PURE__ */ jsxs(
|
|
196
|
+
PopoverAnchor,
|
|
197
|
+
{
|
|
198
|
+
ref: anchorRef,
|
|
199
|
+
id,
|
|
200
|
+
"data-slot": "chat-suggestion",
|
|
201
|
+
className: cn("block w-full", className),
|
|
202
|
+
onPointerUp: () => window.setTimeout(evaluate, 0),
|
|
203
|
+
onBlurCapture: (event) => {
|
|
204
|
+
const next = event.relatedTarget;
|
|
205
|
+
if (next && anchorRef.current?.contains(next)) return;
|
|
206
|
+
if (openRef.current) setOpen(false);
|
|
207
|
+
},
|
|
208
|
+
children: [
|
|
209
|
+
children({
|
|
210
|
+
onTrigger,
|
|
211
|
+
onKeyDown
|
|
212
|
+
}),
|
|
213
|
+
/* @__PURE__ */ jsx(FieldPopupWiring, { field, open: isOpen, listId, activeId }),
|
|
214
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { role: "status", "aria-live": "polite", children: isOpen ? t("dataEntry.chatSuggestion.count", { count: visible.length }) : "" })
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
/* @__PURE__ */ jsx(
|
|
219
|
+
PopoverContent,
|
|
220
|
+
{
|
|
221
|
+
ref: contentRef,
|
|
222
|
+
flush: true,
|
|
223
|
+
align: "start",
|
|
224
|
+
"aria-label": listLabel,
|
|
225
|
+
className: "ui-chat-suggestion-panel",
|
|
226
|
+
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
227
|
+
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
228
|
+
children: /* @__PURE__ */ jsx(
|
|
229
|
+
Command,
|
|
230
|
+
{
|
|
231
|
+
value: activeValue,
|
|
232
|
+
onValueChange: setActiveValue,
|
|
233
|
+
shouldFilter: false,
|
|
234
|
+
label: listLabel,
|
|
235
|
+
children: /* @__PURE__ */ jsxs(CommandList, { label: listLabel, children: [
|
|
236
|
+
/* @__PURE__ */ jsx(CommandEmpty, { children: emptyMessage ?? t("dataEntry.chatSuggestion.empty") }),
|
|
237
|
+
visible.map((item) => /* @__PURE__ */ jsxs(
|
|
238
|
+
CommandItem,
|
|
239
|
+
{
|
|
240
|
+
value: item.value,
|
|
241
|
+
disabled: item.disabled,
|
|
242
|
+
onSelect: () => pick(item),
|
|
243
|
+
children: [
|
|
244
|
+
item.icon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: item.icon }) : null,
|
|
245
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-chat-suggestion-item-text", children: [
|
|
246
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: labelOf(item) }),
|
|
247
|
+
item.description ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate text-xs", children: item.description }) : null
|
|
248
|
+
] })
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
item.value
|
|
252
|
+
))
|
|
253
|
+
] })
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
] });
|
|
259
|
+
}
|
|
260
|
+
ChatSuggestion.displayName = "ChatSuggestion";
|
|
261
|
+
function FieldPopupWiring({
|
|
262
|
+
field,
|
|
263
|
+
open,
|
|
264
|
+
listId,
|
|
265
|
+
activeId
|
|
266
|
+
}) {
|
|
267
|
+
React.useEffect(() => {
|
|
268
|
+
const node = field();
|
|
269
|
+
if (!node) return;
|
|
270
|
+
node.setAttribute("aria-haspopup", "listbox");
|
|
271
|
+
node.setAttribute("aria-autocomplete", "list");
|
|
272
|
+
if (open && listId) {
|
|
273
|
+
node.setAttribute("aria-controls", listId);
|
|
274
|
+
if (activeId) node.setAttribute("aria-activedescendant", activeId);
|
|
275
|
+
else node.removeAttribute("aria-activedescendant");
|
|
276
|
+
} else {
|
|
277
|
+
node.removeAttribute("aria-controls");
|
|
278
|
+
node.removeAttribute("aria-activedescendant");
|
|
279
|
+
}
|
|
280
|
+
}, [activeId, field, listId, open]);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
export {
|
|
284
|
+
ChatSuggestion
|
|
285
|
+
};
|
|
@@ -7,6 +7,10 @@ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScro
|
|
|
7
7
|
export { Checkbox } from "./checkbox.js";
|
|
8
8
|
export { CheckboxGroup } from "./checkbox-group.js";
|
|
9
9
|
export { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
|
|
10
|
+
export { ChatComposer } from "./chat-composer.js";
|
|
11
|
+
export type { ChatComposerProp, ChatComposerProps, ChatComposerSubmitTypeProp, } from "./chat-composer.js";
|
|
12
|
+
export { ChatSuggestion } from "./chat-suggestion.js";
|
|
13
|
+
export type { ChatSuggestionProp, ChatSuggestionProps, ChatSuggestionItemProp, ChatSuggestionRenderProp, } from "./chat-suggestion.js";
|
|
10
14
|
export { Textarea } from "./textarea.js";
|
|
11
15
|
export type { TextareaProps } from "./textarea.js";
|
|
12
16
|
export { Form, useFormLayout, type FormLayoutContextValue } from "./form.js";
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
import { Checkbox } from "./checkbox.js";
|
|
17
17
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
18
18
|
import { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
|
|
19
|
+
import { ChatComposer } from "./chat-composer.js";
|
|
20
|
+
import { ChatSuggestion } from "./chat-suggestion.js";
|
|
19
21
|
import { Textarea } from "./textarea.js";
|
|
20
22
|
import { Form, useFormLayout } from "./form.js";
|
|
21
23
|
import { FormField } from "./form-field.js";
|
|
@@ -64,6 +66,8 @@ export {
|
|
|
64
66
|
BranchScopePicker,
|
|
65
67
|
Calendar,
|
|
66
68
|
Cascader,
|
|
69
|
+
ChatComposer,
|
|
70
|
+
ChatSuggestion,
|
|
67
71
|
Checkbox,
|
|
68
72
|
CheckboxGroup,
|
|
69
73
|
ColorPicker,
|
|
@@ -158,11 +158,13 @@ const Textarea = React.forwardRef(
|
|
|
158
158
|
}
|
|
159
159
|
);
|
|
160
160
|
if (!needsWrapper) return field;
|
|
161
|
+
const placeholderText = typeof props.placeholder === "string" ? props.placeholder : void 0;
|
|
162
|
+
const mirrorText = mirror.length > 0 ? mirror : placeholderText ?? "";
|
|
161
163
|
return /* @__PURE__ */ jsxs(
|
|
162
164
|
"span",
|
|
163
165
|
{
|
|
164
166
|
"data-slot": "textarea-affix-wrapper",
|
|
165
|
-
"data-autogrow-value": growing ?
|
|
167
|
+
"data-autogrow-value": growing ? mirrorText : void 0,
|
|
166
168
|
style: { ...autoGrowVars, ...mirrorInset },
|
|
167
169
|
className: cn(
|
|
168
170
|
"relative w-full",
|
|
@@ -1,48 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
export type TreeFieldNames = {
|
|
13
|
-
label?: string;
|
|
14
|
-
value?: string;
|
|
15
|
-
children?: string;
|
|
16
|
-
};
|
|
17
|
-
export type NormalizedTreeOption = TreeOption & {
|
|
18
|
-
children?: NormalizedTreeOption[];
|
|
19
|
-
};
|
|
20
|
-
type RawTreeNode = Record<string, unknown>;
|
|
21
|
-
export declare function reactNodeText(value: React.ReactNode): string;
|
|
22
|
-
export declare function normalizeTreeOptions(nodes: RawTreeNode[] | undefined, fieldNames?: TreeFieldNames): NormalizedTreeOption[];
|
|
23
|
-
export declare function getNodeByPath(options: NormalizedTreeOption[], path: string[]): NormalizedTreeOption[];
|
|
24
|
-
export declare function getOptionsAtPath(options: NormalizedTreeOption[], path: string[]): NormalizedTreeOption[];
|
|
25
|
-
export declare function formatPathLabels(chain: NormalizedTreeOption[], separator?: string): string;
|
|
26
|
-
export type TreePath = {
|
|
27
|
-
path: string[];
|
|
28
|
-
labels: string[];
|
|
29
|
-
};
|
|
30
|
-
export declare function collectLeafPaths(options: NormalizedTreeOption[], prefix?: string[], root?: NormalizedTreeOption[]): TreePath[];
|
|
31
|
-
export declare function collectAllPaths(options: NormalizedTreeOption[], prefix?: string[], root?: NormalizedTreeOption[]): TreePath[];
|
|
32
|
-
export declare function pathKey(path: string[]): string;
|
|
33
|
-
export declare function pathsEqual(a: string[], b: string[]): boolean;
|
|
34
|
-
export declare function filterTreeOptions(options: NormalizedTreeOption[], query: string, filter?: (query: string, path: NormalizedTreeOption[]) => boolean): TreePath[];
|
|
35
|
-
export declare function getDescendantValues(node: NormalizedTreeOption): string[];
|
|
36
|
-
export declare function flattenVisibleTree(options: NormalizedTreeOption[], expandedKeys: Set<string>, depth?: number): {
|
|
37
|
-
node: NormalizedTreeOption;
|
|
38
|
-
depth: number;
|
|
39
|
-
hasChildren: boolean;
|
|
40
|
-
}[];
|
|
41
|
-
export declare function filterVisibleTree(options: NormalizedTreeOption[], query: string): {
|
|
42
|
-
node: NormalizedTreeOption;
|
|
43
|
-
depth: number;
|
|
44
|
-
hasChildren: boolean;
|
|
45
|
-
}[];
|
|
46
|
-
export declare function collectAllExpandableKeys(options: NormalizedTreeOption[]): string[];
|
|
47
|
-
export declare function findNodeByValue(options: NormalizedTreeOption[], value: string): NormalizedTreeOption | undefined;
|
|
48
|
-
export {};
|
|
1
|
+
/**
|
|
2
|
+
* RE-EXPORT SHIM — the tree model moved to `src/lib/tree.ts`.
|
|
3
|
+
*
|
|
4
|
+
* `Tree` (data-display) and `TreeSelect` (data-entry) are two SURFACES over ONE model. Forking the
|
|
5
|
+
* traversal would let a page tree and a dropdown tree disagree about what "expanded", "a leaf" or
|
|
6
|
+
* "every descendant" means — so the normalizer, the visible-row flattener and the descendant walk
|
|
7
|
+
* live in `src/lib/`, which both groups may import, and this path stays valid so nothing that
|
|
8
|
+
* already imports `./tree-utils` has to change.
|
|
9
|
+
*/
|
|
10
|
+
export * from "../../lib/tree.js";
|
|
@@ -1,154 +1 @@
|
|
|
1
|
-
|
|
2
|
-
if (value == null || typeof value === "boolean") return "";
|
|
3
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
|
|
4
|
-
return String(value);
|
|
5
|
-
}
|
|
6
|
-
if (Array.isArray(value)) {
|
|
7
|
-
return value.map((item) => reactNodeText(item)).join("");
|
|
8
|
-
}
|
|
9
|
-
return "";
|
|
10
|
-
}
|
|
11
|
-
function unknownText(value) {
|
|
12
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
|
|
13
|
-
return String(value);
|
|
14
|
-
}
|
|
15
|
-
return "";
|
|
16
|
-
}
|
|
17
|
-
function normalizeTreeOptions(nodes, fieldNames) {
|
|
18
|
-
if (!nodes?.length) return [];
|
|
19
|
-
const labelKey = fieldNames?.label ?? "label";
|
|
20
|
-
const valueKey = fieldNames?.value ?? "value";
|
|
21
|
-
const childrenKey = fieldNames?.children ?? "children";
|
|
22
|
-
return nodes.map((node) => {
|
|
23
|
-
const children = node[childrenKey];
|
|
24
|
-
const value = unknownText(node[valueKey]);
|
|
25
|
-
const label = node[labelKey];
|
|
26
|
-
return {
|
|
27
|
-
value,
|
|
28
|
-
label: label ?? value,
|
|
29
|
-
disabled: Boolean(node.disabled),
|
|
30
|
-
disableCheckbox: Boolean(node.disableCheckbox),
|
|
31
|
-
isLeaf: node.isLeaf,
|
|
32
|
-
children: Array.isArray(children) ? normalizeTreeOptions(children, fieldNames) : void 0
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
function getNodeByPath(options, path) {
|
|
37
|
-
const chain = [];
|
|
38
|
-
let level = options;
|
|
39
|
-
for (const segment of path) {
|
|
40
|
-
const found = level.find((n) => n.value === segment);
|
|
41
|
-
if (!found) break;
|
|
42
|
-
chain.push(found);
|
|
43
|
-
level = found.children ?? [];
|
|
44
|
-
}
|
|
45
|
-
return chain;
|
|
46
|
-
}
|
|
47
|
-
function getOptionsAtPath(options, path) {
|
|
48
|
-
if (!path.length) return options;
|
|
49
|
-
const chain = getNodeByPath(options, path);
|
|
50
|
-
return chain.at(-1)?.children ?? [];
|
|
51
|
-
}
|
|
52
|
-
function formatPathLabels(chain, separator = " / ") {
|
|
53
|
-
return chain.map((n) => reactNodeText(n.label)).join(separator);
|
|
54
|
-
}
|
|
55
|
-
function collectLeafPaths(options, prefix = [], root = options) {
|
|
56
|
-
const out = [];
|
|
57
|
-
for (const node of options) {
|
|
58
|
-
const path = [...prefix, node.value];
|
|
59
|
-
const hasChildren = (node.children?.length ?? 0) > 0;
|
|
60
|
-
if (!hasChildren || node.isLeaf === true) {
|
|
61
|
-
out.push({ path, labels: getNodeByPath(root, path).map((n) => reactNodeText(n.label)) });
|
|
62
|
-
}
|
|
63
|
-
if (hasChildren) out.push(...collectLeafPaths(node.children, path, root));
|
|
64
|
-
}
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
function collectAllPaths(options, prefix = [], root = options) {
|
|
68
|
-
const out = [];
|
|
69
|
-
for (const node of options) {
|
|
70
|
-
const path = [...prefix, node.value];
|
|
71
|
-
out.push({ path, labels: getNodeByPath(root, path).map((n) => reactNodeText(n.label)) });
|
|
72
|
-
if (node.children?.length) out.push(...collectAllPaths(node.children, path, root));
|
|
73
|
-
}
|
|
74
|
-
return out;
|
|
75
|
-
}
|
|
76
|
-
function pathKey(path) {
|
|
77
|
-
return path.join("\0");
|
|
78
|
-
}
|
|
79
|
-
function pathsEqual(a, b) {
|
|
80
|
-
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
81
|
-
}
|
|
82
|
-
function filterTreeOptions(options, query, filter) {
|
|
83
|
-
const q = query.trim().toLowerCase();
|
|
84
|
-
if (!q) return [];
|
|
85
|
-
const paths = collectLeafPaths(options);
|
|
86
|
-
return paths.filter(({ path }) => {
|
|
87
|
-
const chain = getNodeByPath(options, path);
|
|
88
|
-
if (filter) return filter(query, chain);
|
|
89
|
-
return chain.some((n) => reactNodeText(n.label).toLowerCase().includes(q));
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
function getDescendantValues(node) {
|
|
93
|
-
const values = [node.value];
|
|
94
|
-
for (const child of node.children ?? []) values.push(...getDescendantValues(child));
|
|
95
|
-
return values;
|
|
96
|
-
}
|
|
97
|
-
function flattenVisibleTree(options, expandedKeys, depth = 0) {
|
|
98
|
-
const out = [];
|
|
99
|
-
for (const node of options) {
|
|
100
|
-
const hasChildren = (node.children?.length ?? 0) > 0 && node.isLeaf !== true;
|
|
101
|
-
out.push({ node, depth, hasChildren });
|
|
102
|
-
if (hasChildren && expandedKeys.has(node.value)) {
|
|
103
|
-
out.push(...flattenVisibleTree(node.children, expandedKeys, depth + 1));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return out;
|
|
107
|
-
}
|
|
108
|
-
function filterVisibleTree(options, query) {
|
|
109
|
-
const q = query.trim().toLowerCase();
|
|
110
|
-
if (!q) return flattenVisibleTree(options, new Set(collectAllExpandableKeys(options)));
|
|
111
|
-
function matches(nodes, depth) {
|
|
112
|
-
return nodes.flatMap((node) => {
|
|
113
|
-
const children = node.isLeaf ? [] : matches(node.children ?? [], depth + 1);
|
|
114
|
-
if (!reactNodeText(node.label).toLowerCase().includes(q) && children.length === 0) return [];
|
|
115
|
-
return [{ node, depth, hasChildren: children.length > 0 }, ...children];
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return matches(options, 0);
|
|
119
|
-
}
|
|
120
|
-
function collectAllExpandableKeys(options) {
|
|
121
|
-
const keys = [];
|
|
122
|
-
for (const node of options) {
|
|
123
|
-
if ((node.children?.length ?? 0) > 0 && node.isLeaf !== true) {
|
|
124
|
-
keys.push(node.value);
|
|
125
|
-
keys.push(...collectAllExpandableKeys(node.children));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return keys;
|
|
129
|
-
}
|
|
130
|
-
function findNodeByValue(options, value) {
|
|
131
|
-
for (const node of options) {
|
|
132
|
-
if (node.value === value) return node;
|
|
133
|
-
const nested = node.children ? findNodeByValue(node.children, value) : void 0;
|
|
134
|
-
if (nested) return nested;
|
|
135
|
-
}
|
|
136
|
-
return void 0;
|
|
137
|
-
}
|
|
138
|
-
export {
|
|
139
|
-
collectAllExpandableKeys,
|
|
140
|
-
collectAllPaths,
|
|
141
|
-
collectLeafPaths,
|
|
142
|
-
filterTreeOptions,
|
|
143
|
-
filterVisibleTree,
|
|
144
|
-
findNodeByValue,
|
|
145
|
-
flattenVisibleTree,
|
|
146
|
-
formatPathLabels,
|
|
147
|
-
getDescendantValues,
|
|
148
|
-
getNodeByPath,
|
|
149
|
-
getOptionsAtPath,
|
|
150
|
-
normalizeTreeOptions,
|
|
151
|
-
pathKey,
|
|
152
|
-
pathsEqual,
|
|
153
|
-
reactNodeText
|
|
154
|
-
};
|
|
1
|
+
export * from "../../lib/tree.js";
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Check, ChevronsUpDown, Loader2, RotateCcw } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
6
|
import { cn } from "../../lib/utils.js";
|
|
6
7
|
import {
|
|
7
8
|
Dialog,
|
|
8
9
|
DialogBody,
|
|
9
10
|
DialogContent,
|
|
11
|
+
DialogFooter,
|
|
10
12
|
DialogHeader,
|
|
11
13
|
DialogTitle,
|
|
12
14
|
DialogTrigger
|
|
@@ -23,6 +25,7 @@ import {
|
|
|
23
25
|
import {
|
|
24
26
|
Sheet,
|
|
25
27
|
SheetBody,
|
|
28
|
+
SheetFooter,
|
|
26
29
|
SheetContent,
|
|
27
30
|
SheetHeader,
|
|
28
31
|
SheetTrigger,
|
|
@@ -158,6 +161,7 @@ function OrgSwitcher({
|
|
|
158
161
|
className,
|
|
159
162
|
...rest
|
|
160
163
|
}) {
|
|
164
|
+
const { t } = useTranslation();
|
|
161
165
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
|
|
162
166
|
const controlled = open !== void 0;
|
|
163
167
|
const resolvedOpen = controlled ? open : uncontrolledOpen;
|
|
@@ -201,6 +205,20 @@ function OrgSwitcher({
|
|
|
201
205
|
close: () => setOpen(false)
|
|
202
206
|
}
|
|
203
207
|
);
|
|
208
|
+
const keyboardLegend = /* @__PURE__ */ jsxs("div", { className: "ui-org-switcher-legend", children: [
|
|
209
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-org-switcher-hint", children: [
|
|
210
|
+
/* @__PURE__ */ jsx("kbd", { className: "kbd", "aria-hidden": "true", children: "\u2191\u2193" }),
|
|
211
|
+
t("layout.orgSwitcher.hintMove")
|
|
212
|
+
] }),
|
|
213
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-org-switcher-hint", children: [
|
|
214
|
+
/* @__PURE__ */ jsx("kbd", { className: "kbd", "aria-hidden": "true", children: "\u21B5" }),
|
|
215
|
+
t("layout.orgSwitcher.hintSelect")
|
|
216
|
+
] }),
|
|
217
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-org-switcher-hint", children: [
|
|
218
|
+
/* @__PURE__ */ jsx("kbd", { className: "kbd", "aria-hidden": "true", children: "esc" }),
|
|
219
|
+
t("layout.orgSwitcher.hintClose")
|
|
220
|
+
] })
|
|
221
|
+
] });
|
|
204
222
|
if (sheet) {
|
|
205
223
|
return /* @__PURE__ */ jsx("div", { className: cn("ui-org-switcher", className), "data-collapsed": collapsed || void 0, children: /* @__PURE__ */ jsxs(Sheet, { open: resolvedOpen, onOpenChange: setOpen, children: [
|
|
206
224
|
/* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: trigger }),
|
|
@@ -214,7 +232,8 @@ function OrgSwitcher({
|
|
|
214
232
|
},
|
|
215
233
|
children: [
|
|
216
234
|
/* @__PURE__ */ jsx(SheetHeader, { title: labels.title }),
|
|
217
|
-
/* @__PURE__ */ jsx(SheetBody, { children: panel })
|
|
235
|
+
/* @__PURE__ */ jsx(SheetBody, { children: panel }),
|
|
236
|
+
/* @__PURE__ */ jsx(SheetFooter, { children: keyboardLegend })
|
|
218
237
|
]
|
|
219
238
|
}
|
|
220
239
|
)
|
|
@@ -225,7 +244,8 @@ function OrgSwitcher({
|
|
|
225
244
|
/* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: trigger }),
|
|
226
245
|
/* @__PURE__ */ jsxs(DialogContent, { className: "ui-org-switcher-dialog", children: [
|
|
227
246
|
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: labels.title }) }),
|
|
228
|
-
/* @__PURE__ */ jsx(DialogBody, { children: panel })
|
|
247
|
+
/* @__PURE__ */ jsx(DialogBody, { children: panel }),
|
|
248
|
+
/* @__PURE__ */ jsx(DialogFooter, { children: keyboardLegend })
|
|
229
249
|
] })
|
|
230
250
|
] }) });
|
|
231
251
|
}
|
|
@@ -162,6 +162,22 @@
|
|
|
162
162
|
"error": "Invalid",
|
|
163
163
|
"validating": "Validating",
|
|
164
164
|
"submitFailed": "Could not submit the form. Please try again."
|
|
165
|
+
},
|
|
166
|
+
"chatComposer": {
|
|
167
|
+
"label": "Message",
|
|
168
|
+
"placeholder": "Write a message",
|
|
169
|
+
"send": "Send message",
|
|
170
|
+
"cancel": "Stop generating",
|
|
171
|
+
"hintEnter": "Enter to send · Shift + Enter for a new line",
|
|
172
|
+
"hintShiftEnter": "Shift + Enter to send · Enter for a new line"
|
|
173
|
+
},
|
|
174
|
+
"chatSuggestion": {
|
|
175
|
+
"label": "Suggestions",
|
|
176
|
+
"empty": "No suggestions match",
|
|
177
|
+
"count": {
|
|
178
|
+
"one": "{count} suggestion",
|
|
179
|
+
"other": "{count} suggestions"
|
|
180
|
+
}
|
|
165
181
|
}
|
|
166
182
|
},
|
|
167
183
|
"feedback": {
|
|
@@ -242,6 +258,11 @@
|
|
|
242
258
|
"deniedDescription": "Ask an administrator to grant you access.",
|
|
243
259
|
"loading": "Loading roles…",
|
|
244
260
|
"noDetail": "Select a role to see its detail"
|
|
261
|
+
},
|
|
262
|
+
"orgSwitcher": {
|
|
263
|
+
"hintMove": "Move",
|
|
264
|
+
"hintSelect": "Select",
|
|
265
|
+
"hintClose": "Close"
|
|
245
266
|
}
|
|
246
267
|
},
|
|
247
268
|
"dataDisplay": {
|
|
@@ -284,6 +305,13 @@
|
|
|
284
305
|
"progress": {
|
|
285
306
|
"ariaLabel": "Progress",
|
|
286
307
|
"breakdownSeparator": ", "
|
|
308
|
+
},
|
|
309
|
+
"tree": {
|
|
310
|
+
"expand": "Expand",
|
|
311
|
+
"collapse": "Collapse",
|
|
312
|
+
"loading": "Loading child nodes…",
|
|
313
|
+
"empty": "Nothing to show",
|
|
314
|
+
"selected": "Selected"
|
|
287
315
|
}
|
|
288
316
|
},
|
|
289
317
|
"ui": {
|
|
@@ -531,5 +559,27 @@
|
|
|
531
559
|
"textExamples": {
|
|
532
560
|
"preservedText": "Plain text",
|
|
533
561
|
"sampleText": "First line\n Indented line\n\nLast line"
|
|
562
|
+
},
|
|
563
|
+
"chat": {
|
|
564
|
+
"bubble": {
|
|
565
|
+
"typing": "Typing…",
|
|
566
|
+
"loading": "Loading the message",
|
|
567
|
+
"you": "You",
|
|
568
|
+
"assistant": "Assistant",
|
|
569
|
+
"tone": {
|
|
570
|
+
"info": "Information",
|
|
571
|
+
"success": "Success",
|
|
572
|
+
"warning": "Warning",
|
|
573
|
+
"destructive": "Error"
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
"list": {
|
|
577
|
+
"label": "Conversation",
|
|
578
|
+
"jumpToLatest": "Jump to latest",
|
|
579
|
+
"newMessages": {
|
|
580
|
+
"one": "{count} new message",
|
|
581
|
+
"other": "{count} new messages"
|
|
582
|
+
}
|
|
583
|
+
}
|
|
534
584
|
}
|
|
535
585
|
}
|