@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.
Files changed (60) hide show
  1. package/dist/components/data-display/chat-bubble.d.ts +30 -0
  2. package/dist/components/data-display/chat-bubble.js +229 -0
  3. package/dist/components/data-display/descriptions.js +4 -1
  4. package/dist/components/data-display/index.d.ts +4 -2
  5. package/dist/components/data-display/index.js +5 -2
  6. package/dist/components/data-display/popover.js +3 -2
  7. package/dist/components/data-display/scroll-area.js +26 -1
  8. package/dist/components/data-display/tree.d.ts +8 -0
  9. package/dist/components/data-display/tree.js +426 -0
  10. package/dist/components/data-entry/chat-composer.d.ts +50 -0
  11. package/dist/components/data-entry/chat-composer.js +163 -0
  12. package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
  13. package/dist/components/data-entry/chat-suggestion.js +285 -0
  14. package/dist/components/data-entry/index.d.ts +4 -0
  15. package/dist/components/data-entry/index.js +4 -0
  16. package/dist/components/data-entry/textarea.js +3 -1
  17. package/dist/components/data-entry/tree-utils.d.ts +10 -48
  18. package/dist/components/data-entry/tree-utils.js +1 -154
  19. package/dist/components/layout/org-switcher.js +22 -2
  20. package/dist/i18n/messages/en.json +50 -0
  21. package/dist/i18n/messages/ja.json +48 -0
  22. package/dist/i18n/messages/vi.json +49 -0
  23. package/dist/lib/tree.d.ts +53 -0
  24. package/dist/lib/tree.js +155 -0
  25. package/dist/props/components/data-display.prop.d.ts +187 -1
  26. package/dist/props/components/data-entry.prop.d.ts +127 -0
  27. package/dist/props/registry.d.ts +140 -3
  28. package/dist/props/registry.js +202 -3
  29. package/dist/styles/control.css +7 -0
  30. package/dist/styles/data-display-layout.css +309 -55
  31. package/dist/styles/data-entry-layout.css +64 -0
  32. package/dist/styles/shell-layout.css +41 -11
  33. package/dist/tokens/base.css +3 -0
  34. package/dist/tokens/components/chat-bubble.css +36 -0
  35. package/dist/tokens/components/chat-composer.css +19 -0
  36. package/dist/tokens/components/data-display.css +0 -6
  37. package/dist/tokens/components/descriptions.css +4 -0
  38. package/dist/tokens/components/shell.css +13 -3
  39. package/dist/tokens/components/tree.css +27 -0
  40. package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
  41. package/docs/FRAME-COVERAGE-REPORT.md +8 -3
  42. package/docs/data-display/chat-bubble.tsx +397 -0
  43. package/docs/data-display/timeline.tsx +46 -0
  44. package/docs/data-display/tree.tsx +394 -0
  45. package/docs/data-entry/chat-composer.tsx +464 -0
  46. package/docs/data-entry/chat-suggestion.tsx +301 -0
  47. package/docs/roadmap/ai-chat-components.md +207 -0
  48. package/docs/roadmap/antd-parity.md +154 -0
  49. package/docs/roadmap/badge-tag-chip-count.md +172 -0
  50. package/docs/roadmap/list-masonry.md +159 -0
  51. package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
  52. package/docs/roadmap/parity-audit-data-entry.md +344 -0
  53. package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
  54. package/docs/roadmap/parity-backlog.md +79 -0
  55. package/docs/roadmap/tree-components.md +151 -0
  56. package/docs/showcase/table-tree-rows.tsx +4 -4
  57. package/package.json +5 -3
  58. package/dist/components/data-display/tree-list.d.ts +0 -13
  59. package/dist/components/data-display/tree-list.js +0 -26
  60. 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 ? mirror : void 0,
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
- import type * as React from "react";
2
- /** Normalized tree node — the conventional `treeData` / Cascader `options` shape. */
3
- export type TreeOption = {
4
- value: string;
5
- label: React.ReactNode;
6
- disabled?: boolean;
7
- disableCheckbox?: boolean;
8
- /** When false with `loadData`, shows expand affordance */
9
- isLeaf?: boolean;
10
- children?: TreeOption[];
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
- function reactNodeText(value) {
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
  }