@godxjp/ui 31.14.0 → 31.15.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/agent/START-HERE.md +4 -4
- package/agent/components/Timeline.json +4 -3
- package/agent/components/Tree.json +22 -1
- package/agent/components-index.json +2 -2
- package/agent/components.json +26 -4
- package/agent/index.json +4 -4
- package/agent/llms.txt +5 -5
- package/agent/tokens.json +12 -0
- package/dist/components/data-display/index.d.ts +1 -1
- package/dist/components/data-display/timeline.d.ts +12 -2
- package/dist/components/data-display/timeline.js +6 -2
- package/dist/components/data-display/tree.d.ts +1 -1
- package/dist/components/data-display/tree.js +147 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +6 -1
- package/dist/i18n/messages/ja.json +6 -1
- package/dist/i18n/messages/vi.json +6 -1
- package/dist/props/components/data-display.prop.d.ts +40 -1
- package/dist/props/registry.d.ts +43 -0
- package/dist/props/registry.js +52 -0
- package/dist/styles/data-display-layout.css +40 -1
- package/dist/styles/layers.json +1 -1
- package/dist/tokens/components/tree.css +3 -0
- package/docs/data-display/timeline.tsx +28 -0
- package/docs/data-display/tree.tsx +82 -0
- package/docs/i18n/messages/en.json +24 -0
- package/docs/i18n/messages/ja.json +24 -0
- package/docs/i18n/messages/vi.json +24 -0
- package/package.json +2 -2
package/agent/START-HERE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
You are about to write code against a design system you did not author. This file is the whole
|
|
4
4
|
contract. Read it before you write JSX.
|
|
5
5
|
|
|
6
|
-
**This catalog describes `@godxjp/ui` 31.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 31.15.0.** If the project you are editing has a different
|
|
7
7
|
version in its `package.json`, read the pinned catalog for THAT version instead
|
|
8
8
|
(`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
|
|
9
9
|
not exist yet; older, and it hides props that do. Neither failure announces itself.
|
|
@@ -56,10 +56,10 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
|
|
|
56
56
|
its `importPath`, and its examples. Fetch only the handful you picked in step 1.
|
|
57
57
|
3. `rules.json` — 50 cardinal rules. The ones about raw HTML and hardcoded colour are not
|
|
58
58
|
style advice.
|
|
59
|
-
4. `tokens.json` —
|
|
59
|
+
4. `tokens.json` — 2121 design tokens, each tagged with its `tier`. **If you were handed a
|
|
60
60
|
brand, read the 211 `foundation` entries first** — `--primary`, `--background`,
|
|
61
61
|
`--radius`, `--font-size-base` are the handful everything else derives from. The
|
|
62
|
-
|
|
62
|
+
1806 `component` entries are per-part knobs; reach for one only when a role is
|
|
63
63
|
right everywhere except one component.
|
|
64
64
|
5. `anti-ai-tells.json` — 26 shapes that make generated UI look generated, each with the
|
|
65
65
|
fix. Read before you reach for a gradient hero or a wall of coloured chips.
|
|
@@ -145,7 +145,7 @@ has stopped following the brand.
|
|
|
145
145
|
|---|---|---|---|
|
|
146
146
|
| `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** Handed a brand colour, this is where it goes: `:root { --primary: <H> <S>% <L>%; }` (HSL components, no `hsl()` wrapper) |
|
|
147
147
|
| `semantic` | 104 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
|
|
148
|
-
| `component` |
|
|
148
|
+
| `component` | 1806 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
|
|
149
149
|
|
|
150
150
|
A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
|
|
151
151
|
value, so the real default is computed where the element paints it. Set it and yours wins.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { Timeline } from \"@godxjp/ui/data-display\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
|
|
2
|
+
"example": "import { Timeline } from \"@godxjp/ui/data-display\";\nimport { ArrowRightLeft, MessageSquare } from \"lucide-react\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Activity log / issue history: neutral dots, no status prefix (gh#1092)\n<Timeline density=\"compact\" items={[\n { title: \"Aki がステータスを変更 未対応 → 処理中\", time: \"10/01 08:12\", status: \"log\", icon: ArrowRightLeft },\n { title: \"Ken がコメント\", time: \"10/01 09:30\", status: \"log\", icon: MessageSquare },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
|
|
3
3
|
"group": "data-display",
|
|
4
4
|
"importPath": "@godxjp/ui/data-display",
|
|
5
5
|
"name": "Timeline",
|
|
6
6
|
"props": [
|
|
7
7
|
{
|
|
8
|
-
"description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is
|
|
8
|
+
"description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is 'done' | 'current' | 'pending' | 'log'; `current: true` is shorthand for `status: 'current'`. 'log' (gh#1092) is an activity-log / history row: the neutral dot + hairline rail `pending` draws, and NO sr-only status prefix (antd's `color=\"gray\"` item). `icon` (a LucideIcon) overrides the auto-glyph for that item.",
|
|
9
9
|
"name": "items",
|
|
10
10
|
"required": true,
|
|
11
11
|
"type": "TimelineItem[]"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"rules": [],
|
|
31
31
|
"storyPath": "data-display/Timeline.stories.tsx",
|
|
32
|
-
"tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a
|
|
32
|
+
"tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a `status` (done/current/pending, or `log` for an activity-log entry with no status semantics) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
|
|
33
33
|
"usage": [
|
|
34
34
|
"DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other props are `variant` and `density`.",
|
|
35
35
|
"DO mark the in-progress event with `current: true` (or `status: 'current'`). In the default `variant='icon'`, the current item renders a `Plane` icon and every other item a `CheckCircle2`. Use `variant='ordinal'` for a numbered route stepper (1,2,3…) or `variant='status'` for a done→check / current→dot / pending→number tracker. Pass `status: 'done' | 'current' | 'pending'` per item to drive colour and (in the status variant) the glyph; pass `icon` (a LucideIcon) to override the glyph for a single item.",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"DO NOT use Timeline for user-facing wizard progress (steps the user must complete in order) — use `Steps` for that. Timeline is read-only historical/status display; it has no interactive state, no `onClick`, and no concept of 'go to step'.",
|
|
40
40
|
"DO wrap Timeline in `<CardContent>` when placing it inside a `Card` — bare `Card` has no inner padding, so the rail will render flush against the card edge without `CardContent`.",
|
|
41
41
|
"THE PROGRESS COLUMN IS ONE HUE (gh#731). The done dot, the current dot and the travelled line all default to `hsl(var(--primary))` — the same contract `Steps` uses for `finish` + `process`, and antd's (only `error` leaves the primary hue). The current item is told apart by a ring (`--timeline-dot-current-ring-width`, `--steps-dot-process-ring-width`'s value), by `aria-current=\"step\"` + a localized sr-only prefix, and by a different glyph — never by a second colour role. Re-theme `--primary` and the whole rail follows. Measured light: done 2.18:1 → 6.31:1 against the card, glyph 6.31:1 on the fill; dark 7.22:1 → 9.84:1, glyph 10.72:1.",
|
|
42
|
+
"DO use `status: 'log'` for every row of an activity log / issue history / audit trail (gh#1092). Those rows already HAPPENED and are not steps: `done` paints them primary and prefixes 「Completed:」, `pending` reads 「Upcoming:」 to a screen reader. `log` draws the quiet neutral dot (a plain pip, or the item's `icon`) and hairline rail, with no prefix — antd's gray item.",
|
|
42
43
|
"DON'T expect `--success` on a `status: 'done'` item — `done | current | pending` is POSITION IN A SEQUENCE, not status, and Timeline has no `tone` axis. Put a real status on the item's `title` or `note` with a `<Badge tone=\"success\">`, which is the surface that owns the status green.",
|
|
43
44
|
"TO RESTORE the pre-27.9 green/violet pairing, set the done dot's fill AND its ink together in your theme (global or `[data-tenant]`) — the two roles have opposite ink polarity, so the fill alone leaves a near-white glyph on 若竹 green at 2.18:1 (gh#643): `--timeline-dot-done-background: hsl(var(--success)); --timeline-dot-done-foreground: hsl(var(--success-foreground));`. `--timeline-dot-current-background` and `--timeline-line-completed-background` retint the other two surfaces the same way."
|
|
44
45
|
],
|
|
@@ -90,6 +90,27 @@
|
|
|
90
90
|
"name": "loadData",
|
|
91
91
|
"type": "(node: TreeNodeProp) => void | Promise<void>"
|
|
92
92
|
},
|
|
93
|
+
{
|
|
94
|
+
"defaultValue": "false",
|
|
95
|
+
"description": "Move nodes by dragging (antd `draggable`, gh#1093). Dropping on a row's upper / lower quarter lands in the gap before / after it, the middle files it inside. The tree never edits `treeData` — it reports the move via `onDrop` and you persist it. Keyboard equivalent (WCAG 2.5.7): Alt+↑ / Alt+↓ move past the previous / next sibling, Alt+← outdents (after the parent), Alt+→ indents (into the previous sibling, as its last child); every move is announced in a live region and described on the tree. A node never drops into itself or its own subtree.",
|
|
96
|
+
"name": "draggable",
|
|
97
|
+
"type": "boolean"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"description": "Per-node drag gate (antd `draggable.nodeDraggable`). Disabled nodes never drag.",
|
|
101
|
+
"name": "allowDrag",
|
|
102
|
+
"type": "(node: TreeNodeProp) => boolean"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"description": "Per-target drop gate (antd `allowDrop`), asked for pointer AND keyboard moves. Use it for your own rules (max depth, no leaf parents); the self/subtree cycle is already refused.",
|
|
106
|
+
"name": "allowDrop",
|
|
107
|
+
"type": "(info: { dragNode: TreeNodeProp; dropNode: TreeNodeProp; dropPosition: -1 | 0 | 1 }) => boolean"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"description": "A committed move (antd `onDrop`). `dropPosition` is RELATIVE to `node` — -1 before, 0 inside, 1 after (antd reports an absolute index that every antd demo converts back to this). `dropToGap` is true for before/after (sibling of `node`), false for inside (child of `node`). Update `treeData` here.",
|
|
111
|
+
"name": "onDrop",
|
|
112
|
+
"type": "(info: { dragNode: TreeNodeProp; node: TreeNodeProp; dropPosition: -1 | 0 | 1; dropToGap: boolean }) => void"
|
|
113
|
+
},
|
|
93
114
|
{
|
|
94
115
|
"description": "Render a node's title yourself (antd `titleRender`).",
|
|
95
116
|
"name": "titleRender",
|
|
@@ -179,7 +200,7 @@
|
|
|
179
200
|
45
|
|
180
201
|
],
|
|
181
202
|
"storyPath": "data-display/Tree.stories.tsx",
|
|
182
|
-
"tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes
|
|
203
|
+
"tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
|
|
183
204
|
"usage": [
|
|
184
205
|
"DO name the tree — pass `aria-label` (through your own `t()`) or `aria-labelledby`. A tree with no name is an unlabelled landmark for every screen reader user.",
|
|
185
206
|
"DO keep SELECTION and CHECKS apart: `value`/`onValueChange` is which node is open in the detail pane; `checkedValues`/`onCheckedValuesChange` is which nodes are ticked. They are two axes, exactly as in antd — never drive one from the other.",
|
|
@@ -304,7 +304,7 @@
|
|
|
304
304
|
{
|
|
305
305
|
"group": "data-display",
|
|
306
306
|
"name": "Timeline",
|
|
307
|
-
"tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a
|
|
307
|
+
"tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a `status` (done/current/pending, or `log` for an activity-log entry with no status semantics) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`."
|
|
308
308
|
},
|
|
309
309
|
{
|
|
310
310
|
"group": "data-display",
|
|
@@ -585,7 +585,7 @@
|
|
|
585
585
|
{
|
|
586
586
|
"group": "data-display",
|
|
587
587
|
"name": "Tree",
|
|
588
|
-
"tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes
|
|
588
|
+
"tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover."
|
|
589
589
|
},
|
|
590
590
|
{
|
|
591
591
|
"group": "feedback",
|
package/agent/components.json
CHANGED
|
@@ -4959,13 +4959,13 @@
|
|
|
4959
4959
|
]
|
|
4960
4960
|
},
|
|
4961
4961
|
{
|
|
4962
|
-
"example": "import { Timeline } from \"@godxjp/ui/data-display\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
|
|
4962
|
+
"example": "import { Timeline } from \"@godxjp/ui/data-display\";\nimport { ArrowRightLeft, MessageSquare } from \"lucide-react\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Activity log / issue history: neutral dots, no status prefix (gh#1092)\n<Timeline density=\"compact\" items={[\n { title: \"Aki がステータスを変更 未対応 → 処理中\", time: \"10/01 08:12\", status: \"log\", icon: ArrowRightLeft },\n { title: \"Ken がコメント\", time: \"10/01 09:30\", status: \"log\", icon: MessageSquare },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
|
|
4963
4963
|
"group": "data-display",
|
|
4964
4964
|
"importPath": "@godxjp/ui/data-display",
|
|
4965
4965
|
"name": "Timeline",
|
|
4966
4966
|
"props": [
|
|
4967
4967
|
{
|
|
4968
|
-
"description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is
|
|
4968
|
+
"description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is 'done' | 'current' | 'pending' | 'log'; `current: true` is shorthand for `status: 'current'`. 'log' (gh#1092) is an activity-log / history row: the neutral dot + hairline rail `pending` draws, and NO sr-only status prefix (antd's `color=\"gray\"` item). `icon` (a LucideIcon) overrides the auto-glyph for that item.",
|
|
4969
4969
|
"name": "items",
|
|
4970
4970
|
"required": true,
|
|
4971
4971
|
"type": "TimelineItem[]"
|
|
@@ -4989,7 +4989,7 @@
|
|
|
4989
4989
|
],
|
|
4990
4990
|
"rules": [],
|
|
4991
4991
|
"storyPath": "data-display/Timeline.stories.tsx",
|
|
4992
|
-
"tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a
|
|
4992
|
+
"tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a `status` (done/current/pending, or `log` for an activity-log entry with no status semantics) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
|
|
4993
4993
|
"usage": [
|
|
4994
4994
|
"DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other props are `variant` and `density`.",
|
|
4995
4995
|
"DO mark the in-progress event with `current: true` (or `status: 'current'`). In the default `variant='icon'`, the current item renders a `Plane` icon and every other item a `CheckCircle2`. Use `variant='ordinal'` for a numbered route stepper (1,2,3…) or `variant='status'` for a done→check / current→dot / pending→number tracker. Pass `status: 'done' | 'current' | 'pending'` per item to drive colour and (in the status variant) the glyph; pass `icon` (a LucideIcon) to override the glyph for a single item.",
|
|
@@ -4999,6 +4999,7 @@
|
|
|
4999
4999
|
"DO NOT use Timeline for user-facing wizard progress (steps the user must complete in order) — use `Steps` for that. Timeline is read-only historical/status display; it has no interactive state, no `onClick`, and no concept of 'go to step'.",
|
|
5000
5000
|
"DO wrap Timeline in `<CardContent>` when placing it inside a `Card` — bare `Card` has no inner padding, so the rail will render flush against the card edge without `CardContent`.",
|
|
5001
5001
|
"THE PROGRESS COLUMN IS ONE HUE (gh#731). The done dot, the current dot and the travelled line all default to `hsl(var(--primary))` — the same contract `Steps` uses for `finish` + `process`, and antd's (only `error` leaves the primary hue). The current item is told apart by a ring (`--timeline-dot-current-ring-width`, `--steps-dot-process-ring-width`'s value), by `aria-current=\"step\"` + a localized sr-only prefix, and by a different glyph — never by a second colour role. Re-theme `--primary` and the whole rail follows. Measured light: done 2.18:1 → 6.31:1 against the card, glyph 6.31:1 on the fill; dark 7.22:1 → 9.84:1, glyph 10.72:1.",
|
|
5002
|
+
"DO use `status: 'log'` for every row of an activity log / issue history / audit trail (gh#1092). Those rows already HAPPENED and are not steps: `done` paints them primary and prefixes 「Completed:」, `pending` reads 「Upcoming:」 to a screen reader. `log` draws the quiet neutral dot (a plain pip, or the item's `icon`) and hairline rail, with no prefix — antd's gray item.",
|
|
5002
5003
|
"DON'T expect `--success` on a `status: 'done'` item — `done | current | pending` is POSITION IN A SEQUENCE, not status, and Timeline has no `tone` axis. Put a real status on the item's `title` or `note` with a `<Badge tone=\"success\">`, which is the surface that owns the status green.",
|
|
5003
5004
|
"TO RESTORE the pre-27.9 green/violet pairing, set the done dot's fill AND its ink together in your theme (global or `[data-tenant]`) — the two roles have opposite ink polarity, so the fill alone leaves a near-white glyph on 若竹 green at 2.18:1 (gh#643): `--timeline-dot-done-background: hsl(var(--success)); --timeline-dot-done-foreground: hsl(var(--success-foreground));`. `--timeline-dot-current-background` and `--timeline-line-completed-background` retint the other two surfaces the same way."
|
|
5004
5005
|
],
|
|
@@ -11130,6 +11131,27 @@
|
|
|
11130
11131
|
"name": "loadData",
|
|
11131
11132
|
"type": "(node: TreeNodeProp) => void | Promise<void>"
|
|
11132
11133
|
},
|
|
11134
|
+
{
|
|
11135
|
+
"defaultValue": "false",
|
|
11136
|
+
"description": "Move nodes by dragging (antd `draggable`, gh#1093). Dropping on a row's upper / lower quarter lands in the gap before / after it, the middle files it inside. The tree never edits `treeData` — it reports the move via `onDrop` and you persist it. Keyboard equivalent (WCAG 2.5.7): Alt+↑ / Alt+↓ move past the previous / next sibling, Alt+← outdents (after the parent), Alt+→ indents (into the previous sibling, as its last child); every move is announced in a live region and described on the tree. A node never drops into itself or its own subtree.",
|
|
11137
|
+
"name": "draggable",
|
|
11138
|
+
"type": "boolean"
|
|
11139
|
+
},
|
|
11140
|
+
{
|
|
11141
|
+
"description": "Per-node drag gate (antd `draggable.nodeDraggable`). Disabled nodes never drag.",
|
|
11142
|
+
"name": "allowDrag",
|
|
11143
|
+
"type": "(node: TreeNodeProp) => boolean"
|
|
11144
|
+
},
|
|
11145
|
+
{
|
|
11146
|
+
"description": "Per-target drop gate (antd `allowDrop`), asked for pointer AND keyboard moves. Use it for your own rules (max depth, no leaf parents); the self/subtree cycle is already refused.",
|
|
11147
|
+
"name": "allowDrop",
|
|
11148
|
+
"type": "(info: { dragNode: TreeNodeProp; dropNode: TreeNodeProp; dropPosition: -1 | 0 | 1 }) => boolean"
|
|
11149
|
+
},
|
|
11150
|
+
{
|
|
11151
|
+
"description": "A committed move (antd `onDrop`). `dropPosition` is RELATIVE to `node` — -1 before, 0 inside, 1 after (antd reports an absolute index that every antd demo converts back to this). `dropToGap` is true for before/after (sibling of `node`), false for inside (child of `node`). Update `treeData` here.",
|
|
11152
|
+
"name": "onDrop",
|
|
11153
|
+
"type": "(info: { dragNode: TreeNodeProp; node: TreeNodeProp; dropPosition: -1 | 0 | 1; dropToGap: boolean }) => void"
|
|
11154
|
+
},
|
|
11133
11155
|
{
|
|
11134
11156
|
"description": "Render a node's title yourself (antd `titleRender`).",
|
|
11135
11157
|
"name": "titleRender",
|
|
@@ -11219,7 +11241,7 @@
|
|
|
11219
11241
|
45
|
|
11220
11242
|
],
|
|
11221
11243
|
"storyPath": "data-display/Tree.stories.tsx",
|
|
11222
|
-
"tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes
|
|
11244
|
+
"tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
|
|
11223
11245
|
"usage": [
|
|
11224
11246
|
"DO name the tree — pass `aria-label` (through your own `t()`) or `aria-labelledby`. A tree with no name is an unlabelled landmark for every screen reader user.",
|
|
11225
11247
|
"DO keep SELECTION and CHECKS apart: `value`/`onValueChange` is which node is open in the detail pane; `checkedValues`/`onCheckedValuesChange` is which nodes are ticked. They are two axes, exactly as in antd — never drive one from the other.",
|
package/agent/index.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"components": 178,
|
|
5
5
|
"patterns": 21,
|
|
6
6
|
"rules": 50,
|
|
7
|
-
"tokens":
|
|
7
|
+
"tokens": 2121,
|
|
8
8
|
"vocabulary": 14
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
@@ -48,19 +48,19 @@
|
|
|
48
48
|
"note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
|
|
49
49
|
"read": {
|
|
50
50
|
"live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
|
|
51
|
-
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.
|
|
51
|
+
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.15.0/agent/index.json"
|
|
52
52
|
},
|
|
53
53
|
"source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
|
|
54
54
|
"start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
|
|
55
55
|
"tokenTiers": {
|
|
56
56
|
"component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
|
|
57
57
|
"counts": {
|
|
58
|
-
"component":
|
|
58
|
+
"component": 1806,
|
|
59
59
|
"foundation": 211,
|
|
60
60
|
"semantic": 104
|
|
61
61
|
},
|
|
62
62
|
"foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
|
|
63
63
|
"semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
|
|
64
64
|
},
|
|
65
|
-
"version": "31.
|
|
65
|
+
"version": "31.15.0"
|
|
66
66
|
}
|
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> A Japanese-enterprise React design system: 178 components,
|
|
4
|
-
> 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.
|
|
3
|
+
> A Japanese-enterprise React design system: 178 components, 2121 design tokens,
|
|
4
|
+
> 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.15.0.
|
|
5
5
|
|
|
6
6
|
If your client can run a process, do not read these files — run the MCP server instead
|
|
7
|
-
(`npx @godxjp/ui-mcp@31.
|
|
7
|
+
(`npx @godxjp/ui-mcp@31.15.0`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -18,7 +18,7 @@ that can only fetch URLs.
|
|
|
18
18
|
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 48 KB — all 178 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
|
|
19
19
|
- [components/<Name>.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–36 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
|
|
20
20
|
- [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
|
|
21
|
-
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles,
|
|
21
|
+
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles, 1806 `component` knobs.
|
|
22
22
|
- [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
|
|
23
23
|
- [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 50 cardinal rules.
|
|
24
24
|
- [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 26 shapes that make generated UI look generated, each with its fix.
|
|
@@ -26,7 +26,7 @@ that can only fetch URLs.
|
|
|
26
26
|
## Pinning
|
|
27
27
|
|
|
28
28
|
Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
|
|
29
|
-
the tag: `.../godx-jp/godxjp-ui/v31.
|
|
29
|
+
the tag: `.../godx-jp/godxjp-ui/v31.15.0/agent/...`. A catalog that does not match the installed
|
|
30
30
|
package describes props that are absent, or hides props that are present, and says nothing either way.
|
|
31
31
|
|
|
32
32
|
Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
|
package/agent/tokens.json
CHANGED
|
@@ -12317,6 +12317,18 @@
|
|
|
12317
12317
|
"tier": "component",
|
|
12318
12318
|
"value": "initial"
|
|
12319
12319
|
},
|
|
12320
|
+
{
|
|
12321
|
+
"description": "Drop indicator of a `draggable` tree (gh#1093): the gap line / inside frame. A role mirror, so `initial` with the role at the call site. Documented defaults: --tree-drop-indicator-color = hsl(var(--primary)); --tree-drop-indicator-width = var(--stroke-md).",
|
|
12322
|
+
"name": "--tree-drop-indicator-color",
|
|
12323
|
+
"tier": "component",
|
|
12324
|
+
"value": "initial"
|
|
12325
|
+
},
|
|
12326
|
+
{
|
|
12327
|
+
"description": "TREE — the standalone, on-page WAI-ARIA \"Tree View\" (`src/components/data-display/tree.tsx`). Every value the tree paints is a knob (cardinal rule #45), and the defaults are the QUIETEST reading (#44): no rails, a muted disclosure glyph, a selected band that tints rather than inverts. A service retunes the whole outline in one place instead of forking a row. ROLE-MIRROR KNOBS ARE `initial`, WITH THE DEFAULT AT THE CALL SITE. A knob whose default is a role token or a RE-SCOPED tier may not be bound at `:root` — CSS substitutes the `var()` where it is DECLARED, so the value would freeze at `:root` and a scoped `[data-tenant]` / `.dark` / a shell that re-scopes `--control-height` would never reach it. @see docs/TOKENS.md · \"Role-mirror knobs MUST be `initial`\".",
|
|
12328
|
+
"name": "--tree-drop-indicator-width",
|
|
12329
|
+
"tier": "component",
|
|
12330
|
+
"value": "initial"
|
|
12331
|
+
},
|
|
12320
12332
|
{
|
|
12321
12333
|
"description": "Width of the indeterminate bar shown while `loadData` resolves a branch. A fraction, not a length: it has to read as \"part of a row\" at every row width and every density.",
|
|
12322
12334
|
"name": "--tree-loading-bar-inline-size",
|
|
@@ -52,7 +52,7 @@ export type { RangeTimelineProps, RangeTimelineRow } from "./range-timeline.js";
|
|
|
52
52
|
export { ChatBubble, ChatBubbleList } from "./chat-bubble.js";
|
|
53
53
|
export type { ChatBubbleListProp, ChatBubbleListProps, ChatBubblePlacementProp, ChatBubbleProp, ChatBubbleProps, ChatBubbleToneProp, ChatBubbleTypingProp, ChatBubbleVariantProp, ChatMessageProp, } from "./chat-bubble.js";
|
|
54
54
|
export { Tree } from "./tree.js";
|
|
55
|
-
export type { TreeNodeProp, TreeProp, TreeProps } from "./tree.js";
|
|
55
|
+
export type { TreeAllowDropInfoProp, TreeDropInfoProp, TreeDropPositionProp, TreeNodeProp, TreeProp, TreeProps, } from "./tree.js";
|
|
56
56
|
export { OrgChart } from "./org-chart.js";
|
|
57
57
|
export type { OrgChartNodeProp, OrgChartNodeVariantProp, OrgChartProp, OrgChartProps, } from "./org-chart.js";
|
|
58
58
|
export { Welcome } from "./welcome.js";
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { type LucideIcon } from "lucide-react";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* `done` / `current` / `pending` are a PROCESS: steps behind, the one you are on, steps ahead.
|
|
5
|
+
* `log` is not a step at all — it is an entry in an activity log or history (antd's
|
|
6
|
+
* `<Timeline.Item color="gray">`, GitHub's conversation rail): a neutral dot, a hairline rail and
|
|
7
|
+
* NO screen-reader status prefix, because a thing that merely happened is neither "completed" nor
|
|
8
|
+
* "upcoming" (gh#1092).
|
|
9
|
+
*/
|
|
10
|
+
export type TimelineStatus = "done" | "current" | "pending" | "log";
|
|
4
11
|
export type TimelineVariant = "icon" | "ordinal" | "status";
|
|
5
12
|
/**
|
|
6
13
|
* A Timeline-local subset of the density vocabulary — it has no `comfortable` step, the same shape
|
|
@@ -14,7 +21,10 @@ export type TimelineItem = {
|
|
|
14
21
|
note?: ReactNode;
|
|
15
22
|
/** Shorthand for `status: "current"`. `status` wins when both are set. */
|
|
16
23
|
current?: boolean;
|
|
17
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Explicit status. Resolves as `status ?? (current ? "current" : "done")`. Use `log` for an
|
|
26
|
+
* activity-log / history row: neutral look, no "Completed:"/"Upcoming:" prefix.
|
|
27
|
+
*/
|
|
18
28
|
status?: TimelineStatus;
|
|
19
29
|
/** Per-item glyph override; wins over the variant/status auto-glyph. */
|
|
20
30
|
icon?: LucideIcon;
|
|
@@ -5,7 +5,9 @@ import { Check, CheckCircle2, Plane } from "lucide-react";
|
|
|
5
5
|
const SR_PREFIX_KEY = {
|
|
6
6
|
done: "dataDisplay.timeline.statusDone",
|
|
7
7
|
current: "dataDisplay.timeline.statusCurrent",
|
|
8
|
-
pending: "dataDisplay.timeline.statusPending"
|
|
8
|
+
pending: "dataDisplay.timeline.statusPending",
|
|
9
|
+
// A log entry carries no status semantics (antd's items have no prefix at all), gh#1092.
|
|
10
|
+
log: null
|
|
9
11
|
};
|
|
10
12
|
function resolveStatus(item) {
|
|
11
13
|
if (item.status) {
|
|
@@ -27,6 +29,8 @@ function Timeline({ items, variant = "icon", density = "default" }) {
|
|
|
27
29
|
if (item.icon) {
|
|
28
30
|
const Icon = item.icon;
|
|
29
31
|
glyph = /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" });
|
|
32
|
+
} else if (status === "log" && variant !== "ordinal") {
|
|
33
|
+
glyph = /* @__PURE__ */ jsx("span", { className: "ui-timeline-pip", "aria-hidden": "true" });
|
|
30
34
|
} else if (variant === "ordinal") {
|
|
31
35
|
glyph = /* @__PURE__ */ jsx("span", { className: "ui-timeline-ordinal", children: ordinal });
|
|
32
36
|
} else if (variant === "status") {
|
|
@@ -69,7 +73,7 @@ function Timeline({ items, variant = "icon", density = "default" }) {
|
|
|
69
73
|
/* @__PURE__ */ jsxs("div", { className: "ui-timeline-body", children: [
|
|
70
74
|
/* @__PURE__ */ jsxs("div", { className: "ui-timeline-head", children: [
|
|
71
75
|
/* @__PURE__ */ jsxs("span", { className: "ui-timeline-title", "data-current": isCurrent ? "true" : void 0, children: [
|
|
72
|
-
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t(SR_PREFIX_KEY[status]) }),
|
|
76
|
+
SR_PREFIX_KEY[status] ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: t(SR_PREFIX_KEY[status]) }) : null,
|
|
73
77
|
item.title
|
|
74
78
|
] }),
|
|
75
79
|
item.time ? /* @__PURE__ */ jsx("span", { className: "ui-timeline-time", children: item.time }) : null
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { TreeProp } from "../../props/components/data-display.prop.js";
|
|
3
|
-
export type { TreeNodeProp, TreeProp, TreeProp as TreeProps, } from "../../props/components/data-display.prop.js";
|
|
3
|
+
export type { TreeAllowDropInfoProp, TreeDropInfoProp, TreeDropPositionProp, TreeNodeProp, TreeProp, TreeProp as TreeProps, } from "../../props/components/data-display.prop.js";
|
|
4
4
|
/**
|
|
5
5
|
* `ref` reaches the `role="tree"` container — the element a page scrolls to, measures, or
|
|
6
6
|
* focus-manages. Every unknown prop (`aria-*`, `data-*`) rides `...ariaProps` onto the same node.
|
|
@@ -71,6 +71,10 @@ function TreeRoot({
|
|
|
71
71
|
showLine = false,
|
|
72
72
|
showIcon = false,
|
|
73
73
|
divided = false,
|
|
74
|
+
draggable = false,
|
|
75
|
+
allowDrag,
|
|
76
|
+
allowDrop,
|
|
77
|
+
onDrop,
|
|
74
78
|
variant = "default",
|
|
75
79
|
size = "md",
|
|
76
80
|
disabled = false,
|
|
@@ -82,6 +86,7 @@ function TreeRoot({
|
|
|
82
86
|
const { t } = useTranslation();
|
|
83
87
|
const reactId = React.useId();
|
|
84
88
|
const treeId = id ?? `${reactId}-tree`;
|
|
89
|
+
const dragHintId = `${treeId}-drag-hint`;
|
|
85
90
|
const options = React.useMemo(
|
|
86
91
|
() => normalizeTreeOptions(treeData, fieldNames),
|
|
87
92
|
[treeData, fieldNames]
|
|
@@ -166,6 +171,92 @@ function TreeRoot({
|
|
|
166
171
|
if (node) requestLoadRef.current(node);
|
|
167
172
|
}
|
|
168
173
|
}, [expandedSet, loadData, nodesByValue, requestLoadRef]);
|
|
174
|
+
const parentOf = React.useMemo(() => {
|
|
175
|
+
const map = /* @__PURE__ */ new Map();
|
|
176
|
+
const walk = (list, parent) => {
|
|
177
|
+
for (const node of list) {
|
|
178
|
+
map.set(node.value, parent);
|
|
179
|
+
if (node.children?.length) walk(node.children, node);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
walk(options, null);
|
|
183
|
+
return map;
|
|
184
|
+
}, [options]);
|
|
185
|
+
const isDraggable = draggable && !disabled;
|
|
186
|
+
const canDrag = (node) => isDraggable && !node.disabled && (allowDrag ? allowDrag(node) : true);
|
|
187
|
+
const canDrop = (dragNode, dropNode, dropPosition) => {
|
|
188
|
+
for (let cursor = dropNode; cursor; ) {
|
|
189
|
+
if (cursor.value === dragNode.value) return false;
|
|
190
|
+
cursor = parentOf.get(cursor.value);
|
|
191
|
+
}
|
|
192
|
+
return allowDrop ? allowDrop({
|
|
193
|
+
dragNode,
|
|
194
|
+
dropNode,
|
|
195
|
+
dropPosition
|
|
196
|
+
}) : true;
|
|
197
|
+
};
|
|
198
|
+
const dragRef = React.useRef(null);
|
|
199
|
+
const [dragValue, setDragValue] = React.useState(null);
|
|
200
|
+
const [dropTarget, setDropTarget] = React.useState(null);
|
|
201
|
+
const [moveAnnouncement, setMoveAnnouncement] = React.useState("");
|
|
202
|
+
const commitDrop = (dragNode, dropNode, dropPosition) => {
|
|
203
|
+
onDrop?.({
|
|
204
|
+
dragNode,
|
|
205
|
+
node: dropNode,
|
|
206
|
+
dropPosition,
|
|
207
|
+
dropToGap: dropPosition !== 0
|
|
208
|
+
});
|
|
209
|
+
const key = dropPosition === -1 ? "dataDisplay.tree.movedBefore" : dropPosition === 1 ? "dataDisplay.tree.movedAfter" : "dataDisplay.tree.movedInside";
|
|
210
|
+
setMoveAnnouncement(
|
|
211
|
+
t(key, { node: reactNodeText(dragNode.label), target: reactNodeText(dropNode.label) })
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
const dropPositionAt = (event) => {
|
|
215
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
216
|
+
if (rect.height <= 0) return 0;
|
|
217
|
+
const offset = (event.clientY - rect.top) / rect.height;
|
|
218
|
+
if (offset < 0.25) return -1;
|
|
219
|
+
if (offset > 0.75) return 1;
|
|
220
|
+
return 0;
|
|
221
|
+
};
|
|
222
|
+
const clearDrag = () => {
|
|
223
|
+
dragRef.current = null;
|
|
224
|
+
setDragValue(null);
|
|
225
|
+
setDropTarget(null);
|
|
226
|
+
};
|
|
227
|
+
const keyboardMove = (node, direction) => {
|
|
228
|
+
const parent = parentOf.get(node.value) ?? null;
|
|
229
|
+
const siblings = parent ? parent.children ?? [] : options;
|
|
230
|
+
const position = siblings.findIndex((entry) => entry.value === node.value);
|
|
231
|
+
let target;
|
|
232
|
+
let dropPosition;
|
|
233
|
+
if (direction === "up") {
|
|
234
|
+
target = siblings[position - 1];
|
|
235
|
+
dropPosition = -1;
|
|
236
|
+
} else if (direction === "down") {
|
|
237
|
+
target = siblings[position + 1];
|
|
238
|
+
dropPosition = 1;
|
|
239
|
+
} else if (direction === "out") {
|
|
240
|
+
target = parent;
|
|
241
|
+
dropPosition = 1;
|
|
242
|
+
} else {
|
|
243
|
+
const previous = siblings[position - 1];
|
|
244
|
+
const lastChild = previous?.children?.[previous.children.length - 1];
|
|
245
|
+
target = lastChild ?? previous;
|
|
246
|
+
dropPosition = lastChild ? 1 : 0;
|
|
247
|
+
}
|
|
248
|
+
if (!target || !canDrag(node) || !canDrop(node, target, dropPosition)) {
|
|
249
|
+
setMoveAnnouncement(t("dataDisplay.tree.moveRefused", { node: reactNodeText(node.label) }));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (direction === "in") {
|
|
253
|
+
const host = dropPosition === 0 ? target : siblings[position - 1];
|
|
254
|
+
if (host && !expandedSet.has(host.value)) commitExpanded([...expanded, host.value]);
|
|
255
|
+
}
|
|
256
|
+
commitDrop(node, target, dropPosition);
|
|
257
|
+
setActiveValue(node.value);
|
|
258
|
+
pendingFocus.current = node.value;
|
|
259
|
+
};
|
|
169
260
|
const visible = flattenVisibleTree(options, expandedSet);
|
|
170
261
|
const visibleIndex = new Map(visible.map((entry, index) => [entry.node.value, index]));
|
|
171
262
|
const [activeValue, setActiveValue] = React.useState(null);
|
|
@@ -300,6 +391,14 @@ function TreeRoot({
|
|
|
300
391
|
const rtl = isRtl(event.currentTarget);
|
|
301
392
|
const inward = rtl ? "ArrowLeft" : "ArrowRight";
|
|
302
393
|
const outward = rtl ? "ArrowRight" : "ArrowLeft";
|
|
394
|
+
if (isDraggable && event.altKey && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === inward || event.key === outward)) {
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
keyboardMove(
|
|
397
|
+
node,
|
|
398
|
+
event.key === "ArrowUp" ? "up" : event.key === "ArrowDown" ? "down" : event.key === inward ? "in" : "out"
|
|
399
|
+
);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
303
402
|
if (event.key === "ArrowDown") {
|
|
304
403
|
event.preventDefault();
|
|
305
404
|
moveTo(index + 1);
|
|
@@ -366,6 +465,8 @@ function TreeRoot({
|
|
|
366
465
|
const checkState = checkableProp ? checkStateOf(node) : "unchecked";
|
|
367
466
|
const nodeDisabled = disabled || Boolean(node.disabled);
|
|
368
467
|
const labelId = `${treeId}-${node.value}-label`;
|
|
468
|
+
const nodeDraggable = canDrag(node);
|
|
469
|
+
const dropHere = dropTarget?.value === node.value ? dropTarget.position : void 0;
|
|
369
470
|
const glyph = node.icon ?? (variant === "directory" ? hasChildren ? isOpen ? /* @__PURE__ */ jsx(FolderOpen, {}) : /* @__PURE__ */ jsx(Folder, {}) : /* @__PURE__ */ jsx(FileIcon, {}) : null);
|
|
370
471
|
return /* @__PURE__ */ jsxs(
|
|
371
472
|
"div",
|
|
@@ -395,6 +496,47 @@ function TreeRoot({
|
|
|
395
496
|
"data-selected": isSelected ? "true" : void 0,
|
|
396
497
|
"data-disabled": nodeDisabled ? "" : void 0,
|
|
397
498
|
"data-filter-node": isFilterMatch ? "true" : void 0,
|
|
499
|
+
draggable: nodeDraggable || void 0,
|
|
500
|
+
"data-dragging": dragValue === node.value ? "true" : void 0,
|
|
501
|
+
"data-drop-position": dropHere === void 0 ? void 0 : dropHere === -1 ? "before" : dropHere === 1 ? "after" : "inside",
|
|
502
|
+
onDragStart: isDraggable ? (event) => {
|
|
503
|
+
if (!nodeDraggable) {
|
|
504
|
+
event.preventDefault();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
event.dataTransfer.effectAllowed = "move";
|
|
508
|
+
event.dataTransfer.setData("text/plain", node.value);
|
|
509
|
+
dragRef.current = node.value;
|
|
510
|
+
setDragValue(node.value);
|
|
511
|
+
} : void 0,
|
|
512
|
+
onDragOver: isDraggable ? (event) => {
|
|
513
|
+
const dragged = dragRef.current === null ? void 0 : nodesByValue.get(dragRef.current);
|
|
514
|
+
if (!dragged) return;
|
|
515
|
+
const position = dropPositionAt(event);
|
|
516
|
+
if (!canDrop(dragged, node, position)) {
|
|
517
|
+
if (dropTarget) setDropTarget(null);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
event.preventDefault();
|
|
521
|
+
event.dataTransfer.dropEffect = "move";
|
|
522
|
+
if (dropTarget?.value !== node.value || dropTarget.position !== position) {
|
|
523
|
+
setDropTarget({ value: node.value, position });
|
|
524
|
+
}
|
|
525
|
+
} : void 0,
|
|
526
|
+
onDragLeave: isDraggable ? (event) => {
|
|
527
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
528
|
+
if (dropTarget?.value === node.value) setDropTarget(null);
|
|
529
|
+
} : void 0,
|
|
530
|
+
onDrop: isDraggable ? (event) => {
|
|
531
|
+
event.preventDefault();
|
|
532
|
+
const dragged = dragRef.current === null ? void 0 : nodesByValue.get(dragRef.current);
|
|
533
|
+
const position = dropPositionAt(event);
|
|
534
|
+
if (dragged && canDrop(dragged, node, position)) {
|
|
535
|
+
commitDrop(dragged, node, position);
|
|
536
|
+
}
|
|
537
|
+
clearDrag();
|
|
538
|
+
} : void 0,
|
|
539
|
+
onDragEnd: isDraggable ? clearDrag : void 0,
|
|
398
540
|
className: "ui-tree-node ui-focus-ring",
|
|
399
541
|
style: { "--tree-node-level": depth },
|
|
400
542
|
children: [
|
|
@@ -549,6 +691,7 @@ function TreeRoot({
|
|
|
549
691
|
"div",
|
|
550
692
|
{
|
|
551
693
|
...ariaProps,
|
|
694
|
+
"aria-describedby": isDraggable ? [ariaProps["aria-describedby"], dragHintId].filter(Boolean).join(" ") : ariaProps["aria-describedby"],
|
|
552
695
|
ref: mergeRefs(forwardedRef, treeRef),
|
|
553
696
|
id: treeId,
|
|
554
697
|
role: "tree",
|
|
@@ -602,6 +745,10 @@ function TreeRoot({
|
|
|
602
745
|
]
|
|
603
746
|
}
|
|
604
747
|
),
|
|
748
|
+
isDraggable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
749
|
+
/* @__PURE__ */ jsx("span", { id: dragHintId, className: "sr-only", children: t("dataDisplay.tree.dragHint") }),
|
|
750
|
+
/* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: moveAnnouncement })
|
|
751
|
+
] }) : null,
|
|
605
752
|
isEmpty ? /* @__PURE__ */ jsx("p", { role: "status", className: "ui-tree-empty", children: t("dataDisplay.tree.empty") }) : null
|
|
606
753
|
] });
|
|
607
754
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.15.0",
|
|
4
4
|
"targetSize": {
|
|
5
5
|
"standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
|
|
6
6
|
"min": 24,
|
|
@@ -364,7 +364,12 @@
|
|
|
364
364
|
"loading": "Loading child nodes…",
|
|
365
365
|
"empty": "Nothing to show",
|
|
366
366
|
"selected": "Selected",
|
|
367
|
-
"filterMatch": "Matches the filter"
|
|
367
|
+
"filterMatch": "Matches the filter",
|
|
368
|
+
"dragHint": "Alt plus arrow keys move the focused item: up and down past a sibling, left to outdent, right to indent.",
|
|
369
|
+
"movedBefore": "{node} moved before {target}",
|
|
370
|
+
"movedAfter": "{node} moved after {target}",
|
|
371
|
+
"movedInside": "{node} moved into {target}",
|
|
372
|
+
"moveRefused": "{node} cannot move there"
|
|
368
373
|
},
|
|
369
374
|
"thoughtChain": {
|
|
370
375
|
"label": "Reasoning steps",
|
|
@@ -357,7 +357,12 @@
|
|
|
357
357
|
"loading": "子ノードを読み込み中…",
|
|
358
358
|
"empty": "表示する項目がありません",
|
|
359
359
|
"selected": "選択中",
|
|
360
|
-
"filterMatch": "フィルターに一致"
|
|
360
|
+
"filterMatch": "フィルターに一致",
|
|
361
|
+
"dragHint": "Alt キーと矢印キーでフォーカス中の項目を移動します。上下で兄弟の前後へ、左で階層を上げ、右で階層を下げます。",
|
|
362
|
+
"movedBefore": "{node} を {target} の前に移動しました",
|
|
363
|
+
"movedAfter": "{node} を {target} の後に移動しました",
|
|
364
|
+
"movedInside": "{node} を {target} の中に移動しました",
|
|
365
|
+
"moveRefused": "{node} はそこへ移動できません"
|
|
361
366
|
},
|
|
362
367
|
"thoughtChain": {
|
|
363
368
|
"label": "思考の手順",
|
|
@@ -358,7 +358,12 @@
|
|
|
358
358
|
"loading": "Đang tải nút con…",
|
|
359
359
|
"empty": "Không có gì để hiển thị",
|
|
360
360
|
"selected": "Đã chọn",
|
|
361
|
-
"filterMatch": "Khớp bộ lọc"
|
|
361
|
+
"filterMatch": "Khớp bộ lọc",
|
|
362
|
+
"dragHint": "Alt cùng phím mũi tên di chuyển mục đang chọn: lên và xuống qua mục ngang hàng, trái để lùi cấp, phải để thụt cấp.",
|
|
363
|
+
"movedBefore": "Đã chuyển {node} lên trước {target}",
|
|
364
|
+
"movedAfter": "Đã chuyển {node} xuống sau {target}",
|
|
365
|
+
"movedInside": "Đã chuyển {node} vào trong {target}",
|
|
366
|
+
"moveRefused": "Không thể chuyển {node} tới đó"
|
|
362
367
|
},
|
|
363
368
|
"thoughtChain": {
|
|
364
369
|
"label": "Các bước suy luận",
|
|
@@ -711,6 +711,29 @@ export type TreeNodeProp = TreeOptionProp & {
|
|
|
711
711
|
* `checkedValues`/`defaultCheckedValues`/`onCheckedValuesChange`.
|
|
712
712
|
* @see docs/roadmap/tree-components.md §3
|
|
713
713
|
*/
|
|
714
|
+
/**
|
|
715
|
+
* Where a drop lands relative to `node` (gh#1093): `-1` in the gap BEFORE it, `0` INSIDE it (the
|
|
716
|
+
* dragged node becomes its child), `1` in the gap AFTER it. antd's `onDrop` reports an absolute
|
|
717
|
+
* index and every antd demo subtracts the node's position back out to get exactly this number, so
|
|
718
|
+
* the relative form is what a consumer needs.
|
|
719
|
+
*/
|
|
720
|
+
export type TreeDropPositionProp = -1 | 0 | 1;
|
|
721
|
+
/** `allowDrop` argument (antd `allowDrop({ dragNode, dropNode, dropPosition })`). */
|
|
722
|
+
export type TreeAllowDropInfoProp = {
|
|
723
|
+
dragNode: TreeNodeProp;
|
|
724
|
+
dropNode: TreeNodeProp;
|
|
725
|
+
dropPosition: TreeDropPositionProp;
|
|
726
|
+
};
|
|
727
|
+
/** `onDrop` argument (antd `onDrop({ dragNode, node, dropPosition, dropToGap })`). */
|
|
728
|
+
export type TreeDropInfoProp = {
|
|
729
|
+
/** The node being moved. */
|
|
730
|
+
dragNode: TreeNodeProp;
|
|
731
|
+
/** The node it was dropped on or beside. */
|
|
732
|
+
node: TreeNodeProp;
|
|
733
|
+
dropPosition: TreeDropPositionProp;
|
|
734
|
+
/** `true` when it lands in a gap (becomes a SIBLING of `node`), `false` when it lands inside. */
|
|
735
|
+
dropToGap: boolean;
|
|
736
|
+
};
|
|
714
737
|
export type TreeProp = {
|
|
715
738
|
/** The hierarchy (antd `treeData`). Use `fieldNames` to remap keys off an API response. */
|
|
716
739
|
treeData: readonly TreeNodeProp[];
|
|
@@ -781,6 +804,21 @@ export type TreeProp = {
|
|
|
781
804
|
* capability; see docs/DESIGN-AUTHORITY.md.
|
|
782
805
|
*/
|
|
783
806
|
divided?: boolean;
|
|
807
|
+
/**
|
|
808
|
+
* Let the reader move nodes by dragging (antd `draggable`, gh#1093): drop on the upper or lower
|
|
809
|
+
* quarter of a row to land in the gap before / after it, on the middle to file it inside. The
|
|
810
|
+
* tree never mutates `treeData` — it reports the move through `onDrop` and the consumer persists
|
|
811
|
+
* it. Keyboard equivalent (WCAG 2.5.7): Alt+↑ / Alt+↓ move past the previous / next sibling,
|
|
812
|
+
* Alt+← outdents (after its parent), Alt+→ indents (into the previous sibling); each move is
|
|
813
|
+
* announced through a live region. A node is never dropped into itself or its own subtree.
|
|
814
|
+
*/
|
|
815
|
+
draggable?: boolean;
|
|
816
|
+
/** Per-node drag gate (antd `draggable.nodeDraggable`). Disabled nodes never drag. */
|
|
817
|
+
allowDrag?: (node: TreeNodeProp) => boolean;
|
|
818
|
+
/** Per-target drop gate (antd `allowDrop`). Asked for pointer AND keyboard moves. */
|
|
819
|
+
allowDrop?: (info: TreeAllowDropInfoProp) => boolean;
|
|
820
|
+
/** A move the reader committed (antd `onDrop`). Update `treeData` here. */
|
|
821
|
+
onDrop?: (info: TreeDropInfoProp) => void;
|
|
784
822
|
/** Draw each node's `icon` (antd `showIcon`). `variant="directory"` supplies folder/file glyphs. */
|
|
785
823
|
showIcon?: boolean;
|
|
786
824
|
/** `directory` is antd's `<DirectoryTree>`: folder/file glyphs and a full-row selected band. */
|
|
@@ -793,7 +831,8 @@ export type TreeProp = {
|
|
|
793
831
|
id?: IdProp;
|
|
794
832
|
/** Accessible name of the `role="tree"` container. Required unless `aria-labelledby` is given. */
|
|
795
833
|
"aria-label"?: string;
|
|
796
|
-
"aria-labelledby"?: string;
|
|
834
|
+
"aria-labelledby"?: string; /** Extra description ids; a `draggable` tree appends its own keyboard-move hint. */
|
|
835
|
+
"aria-describedby"?: string;
|
|
797
836
|
};
|
|
798
837
|
/**
|
|
799
838
|
* @see ChatBubble — which side of the conversation the message sits on. LOGICAL, not physical:
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -3165,6 +3165,49 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
3165
3165
|
readonly reason: "The node's own child nodes — the hierarchy itself, not a slot.";
|
|
3166
3166
|
}];
|
|
3167
3167
|
};
|
|
3168
|
+
readonly TreeDropPositionProp: {
|
|
3169
|
+
readonly group: "data-display";
|
|
3170
|
+
readonly file: "components/data-display.prop.ts";
|
|
3171
|
+
readonly vocabulary: readonly [];
|
|
3172
|
+
};
|
|
3173
|
+
readonly TreeAllowDropInfoProp: {
|
|
3174
|
+
readonly group: "data-display";
|
|
3175
|
+
readonly file: "components/data-display.prop.ts";
|
|
3176
|
+
readonly vocabulary: readonly [{
|
|
3177
|
+
readonly field: "dragNode";
|
|
3178
|
+
readonly local: true;
|
|
3179
|
+
readonly reason: "antd `allowDrop` argument: the node being moved (gh#1093).";
|
|
3180
|
+
}, {
|
|
3181
|
+
readonly field: "dropNode";
|
|
3182
|
+
readonly local: true;
|
|
3183
|
+
readonly reason: "antd `allowDrop` argument: the node it would land on or beside.";
|
|
3184
|
+
}, {
|
|
3185
|
+
readonly field: "dropPosition";
|
|
3186
|
+
readonly local: true;
|
|
3187
|
+
readonly reason: "antd `allowDrop` argument: -1 before, 0 inside, 1 after.";
|
|
3188
|
+
}];
|
|
3189
|
+
};
|
|
3190
|
+
readonly TreeDropInfoProp: {
|
|
3191
|
+
readonly group: "data-display";
|
|
3192
|
+
readonly file: "components/data-display.prop.ts";
|
|
3193
|
+
readonly vocabulary: readonly [{
|
|
3194
|
+
readonly field: "dragNode";
|
|
3195
|
+
readonly local: true;
|
|
3196
|
+
readonly reason: "antd `onDrop` info: the node being moved (gh#1093).";
|
|
3197
|
+
}, {
|
|
3198
|
+
readonly field: "node";
|
|
3199
|
+
readonly local: true;
|
|
3200
|
+
readonly reason: "antd `onDrop` info: the node it landed on or beside.";
|
|
3201
|
+
}, {
|
|
3202
|
+
readonly field: "dropPosition";
|
|
3203
|
+
readonly local: true;
|
|
3204
|
+
readonly reason: "antd `onDrop` info, relative: -1 before, 0 inside, 1 after.";
|
|
3205
|
+
}, {
|
|
3206
|
+
readonly field: "dropToGap";
|
|
3207
|
+
readonly local: true;
|
|
3208
|
+
readonly reason: "antd `onDrop` info: true when it lands as a sibling, false inside.";
|
|
3209
|
+
}];
|
|
3210
|
+
};
|
|
3168
3211
|
readonly TreeProp: {
|
|
3169
3212
|
readonly group: "data-display";
|
|
3170
3213
|
readonly file: "components/data-display.prop.ts";
|
package/dist/props/registry.js
CHANGED
|
@@ -3751,6 +3751,58 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
3751
3751
|
}
|
|
3752
3752
|
]
|
|
3753
3753
|
},
|
|
3754
|
+
TreeDropPositionProp: {
|
|
3755
|
+
group: "data-display",
|
|
3756
|
+
file: "components/data-display.prop.ts",
|
|
3757
|
+
vocabulary: []
|
|
3758
|
+
},
|
|
3759
|
+
TreeAllowDropInfoProp: {
|
|
3760
|
+
group: "data-display",
|
|
3761
|
+
file: "components/data-display.prop.ts",
|
|
3762
|
+
vocabulary: [
|
|
3763
|
+
{
|
|
3764
|
+
field: "dragNode",
|
|
3765
|
+
local: true,
|
|
3766
|
+
reason: "antd `allowDrop` argument: the node being moved (gh#1093)."
|
|
3767
|
+
},
|
|
3768
|
+
{
|
|
3769
|
+
field: "dropNode",
|
|
3770
|
+
local: true,
|
|
3771
|
+
reason: "antd `allowDrop` argument: the node it would land on or beside."
|
|
3772
|
+
},
|
|
3773
|
+
{
|
|
3774
|
+
field: "dropPosition",
|
|
3775
|
+
local: true,
|
|
3776
|
+
reason: "antd `allowDrop` argument: -1 before, 0 inside, 1 after."
|
|
3777
|
+
}
|
|
3778
|
+
]
|
|
3779
|
+
},
|
|
3780
|
+
TreeDropInfoProp: {
|
|
3781
|
+
group: "data-display",
|
|
3782
|
+
file: "components/data-display.prop.ts",
|
|
3783
|
+
vocabulary: [
|
|
3784
|
+
{
|
|
3785
|
+
field: "dragNode",
|
|
3786
|
+
local: true,
|
|
3787
|
+
reason: "antd `onDrop` info: the node being moved (gh#1093)."
|
|
3788
|
+
},
|
|
3789
|
+
{
|
|
3790
|
+
field: "node",
|
|
3791
|
+
local: true,
|
|
3792
|
+
reason: "antd `onDrop` info: the node it landed on or beside."
|
|
3793
|
+
},
|
|
3794
|
+
{
|
|
3795
|
+
field: "dropPosition",
|
|
3796
|
+
local: true,
|
|
3797
|
+
reason: "antd `onDrop` info, relative: -1 before, 0 inside, 1 after."
|
|
3798
|
+
},
|
|
3799
|
+
{
|
|
3800
|
+
field: "dropToGap",
|
|
3801
|
+
local: true,
|
|
3802
|
+
reason: "antd `onDrop` info: true when it lands as a sibling, false inside."
|
|
3803
|
+
}
|
|
3804
|
+
]
|
|
3805
|
+
},
|
|
3754
3806
|
TreeProp: {
|
|
3755
3807
|
group: "data-display",
|
|
3756
3808
|
file: "components/data-display.prop.ts",
|
|
@@ -523,7 +523,8 @@
|
|
|
523
523
|
hsl(var(--primary) / 0.2);
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
.ui-timeline-dot[data-status="pending"]
|
|
526
|
+
.ui-timeline-dot[data-status="pending"],
|
|
527
|
+
.ui-timeline-dot[data-status="log"] {
|
|
527
528
|
border-color: hsl(var(--border));
|
|
528
529
|
background: hsl(var(--background));
|
|
529
530
|
color: hsl(var(--muted-foreground));
|
|
@@ -2126,6 +2127,44 @@
|
|
|
2126
2127
|
opacity: var(--disabled-opacity);
|
|
2127
2128
|
}
|
|
2128
2129
|
|
|
2130
|
+
.ui-tree-node[draggable="true"] {
|
|
2131
|
+
cursor: grab;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
.ui-tree-node[data-dragging="true"] {
|
|
2135
|
+
opacity: var(--disabled-opacity);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
.ui-tree-node[data-drop-position] {
|
|
2139
|
+
position: relative;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
.ui-tree-node[data-drop-position="before"]::after,
|
|
2143
|
+
.ui-tree-node[data-drop-position="after"]::after {
|
|
2144
|
+
content: "";
|
|
2145
|
+
position: absolute;
|
|
2146
|
+
inset-inline: calc(
|
|
2147
|
+
var(--tree-node-padding-inline) + var(--tree-node-level, 0) * var(--tree-indent-width)
|
|
2148
|
+
)
|
|
2149
|
+
0;
|
|
2150
|
+
block-size: var(--tree-drop-indicator-width, var(--stroke-md));
|
|
2151
|
+
background: var(--tree-drop-indicator-color, hsl(var(--primary)));
|
|
2152
|
+
pointer-events: none;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
.ui-tree-node[data-drop-position="before"]::after {
|
|
2156
|
+
inset-block-start: 0;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
.ui-tree-node[data-drop-position="after"]::after {
|
|
2160
|
+
inset-block-end: 0;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
.ui-tree-node[data-drop-position="inside"] {
|
|
2164
|
+
box-shadow: inset 0 0 0 var(--tree-drop-indicator-width, var(--stroke-md))
|
|
2165
|
+
var(--tree-drop-indicator-color, hsl(var(--primary)));
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2129
2168
|
.ui-tree-switcher {
|
|
2130
2169
|
display: inline-flex;
|
|
2131
2170
|
flex: none;
|
package/dist/styles/layers.json
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "@godxjp/ui/data-display";
|
|
14
14
|
import { Text } from "@godxjp/ui/general";
|
|
15
15
|
import { Flex, PageContainer } from "@godxjp/ui/layout";
|
|
16
|
+
import { ArrowRightLeft, MessageSquare } from "lucide-react";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Timeline — vertical event list with an icon rail; the current item gets a
|
|
@@ -89,6 +90,23 @@ const richItems: TimelineItem[] = [
|
|
|
89
90
|
|
|
90
91
|
export default function Demo() {
|
|
91
92
|
const { t, locale } = useTranslation();
|
|
93
|
+
// An activity log (gh#1092): every row already happened, so none is "done" or "upcoming" —
|
|
94
|
+
// `status: "log"` keeps the neutral dot and adds no screen-reader status prefix.
|
|
95
|
+
const logItems: TimelineItem[] = [
|
|
96
|
+
{
|
|
97
|
+
title: t("timelineDocs.log.status"),
|
|
98
|
+
time: "10/01 08:12",
|
|
99
|
+
status: "log",
|
|
100
|
+
icon: ArrowRightLeft,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
title: t("timelineDocs.log.comment"),
|
|
104
|
+
time: "10/01 09:30",
|
|
105
|
+
status: "log",
|
|
106
|
+
icon: MessageSquare,
|
|
107
|
+
},
|
|
108
|
+
{ title: t("timelineDocs.log.assignee"), time: "10/01 10:05", status: "log" },
|
|
109
|
+
];
|
|
92
110
|
const dayLabel = (day: number) =>
|
|
93
111
|
new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", timeZone: "UTC" }).format(
|
|
94
112
|
Date.UTC(2026, 8, day),
|
|
@@ -139,6 +157,16 @@ export default function Demo() {
|
|
|
139
157
|
</CardContent>
|
|
140
158
|
</Card>
|
|
141
159
|
|
|
160
|
+
<Card>
|
|
161
|
+
<CardHeader>
|
|
162
|
+
<CardTitle level={2}>{t("timelineDocs.log.title")}</CardTitle>
|
|
163
|
+
<CardDescription>{t("timelineDocs.log.description")}</CardDescription>
|
|
164
|
+
</CardHeader>
|
|
165
|
+
<CardContent>
|
|
166
|
+
<Timeline density="compact" items={logItems} />
|
|
167
|
+
</CardContent>
|
|
168
|
+
</Card>
|
|
169
|
+
|
|
142
170
|
<Card>
|
|
143
171
|
<CardHeader>
|
|
144
172
|
<CardTitle level={2}>配送ルート進捗</CardTitle>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
EmptyState,
|
|
11
11
|
ScrollArea,
|
|
12
12
|
Tree,
|
|
13
|
+
type TreeDropInfoProp,
|
|
13
14
|
type TreeNodeProp,
|
|
14
15
|
} from "@godxjp/ui/data-display";
|
|
15
16
|
import { SearchInput } from "@godxjp/ui/data-entry";
|
|
@@ -246,6 +247,52 @@ function labelSuiteTree(
|
|
|
246
247
|
});
|
|
247
248
|
}
|
|
248
249
|
|
|
250
|
+
// ── card · draggable (gh#1093) ─────────────────────────────────────────────────────────────
|
|
251
|
+
const movablePagesSeed: TreeNodeProp[] = [
|
|
252
|
+
{
|
|
253
|
+
value: "guide",
|
|
254
|
+
label: "guide",
|
|
255
|
+
children: [
|
|
256
|
+
{ value: "guide/start", label: "start" },
|
|
257
|
+
{ value: "guide/setup", label: "setup" },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
{ value: "faq", label: "faq" },
|
|
261
|
+
{ value: "notes", label: "notes" },
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
/** The seed's labels are catalogue keys (`treeDocs.drag.pages.*`), read in the reader's locale. */
|
|
265
|
+
function labelPages(
|
|
266
|
+
data: TreeNodeProp[],
|
|
267
|
+
t: (key: string) => string,
|
|
268
|
+
): TreeNodeProp[] {
|
|
269
|
+
return data.map((node) => ({
|
|
270
|
+
...node,
|
|
271
|
+
label: t(`treeDocs.drag.pages.${String(node.label)}`),
|
|
272
|
+
children: node.children ? labelPages(node.children, t) : undefined,
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** What a consumer does in `onDrop`: take the node out, put it back where the drop says. */
|
|
277
|
+
function moveNode(data: TreeNodeProp[], info: TreeDropInfoProp): TreeNodeProp[] {
|
|
278
|
+
let moved: TreeNodeProp | undefined;
|
|
279
|
+
const without = (list: TreeNodeProp[]): TreeNodeProp[] =>
|
|
280
|
+
list
|
|
281
|
+
.filter((node) => (node.value === info.dragNode.value ? ((moved = node), false) : true))
|
|
282
|
+
.map((node) => (node.children ? { ...node, children: without(node.children) } : node));
|
|
283
|
+
const place = (list: TreeNodeProp[]): TreeNodeProp[] =>
|
|
284
|
+
list.flatMap((node) => {
|
|
285
|
+
if (node.value !== info.node.value) {
|
|
286
|
+
return node.children ? [{ ...node, children: place(node.children) }] : [node];
|
|
287
|
+
}
|
|
288
|
+
if (info.dropPosition === 0)
|
|
289
|
+
return [{ ...node, children: [...(node.children ?? []), moved!] }];
|
|
290
|
+
return info.dropPosition === -1 ? [moved!, node] : [node, moved!];
|
|
291
|
+
});
|
|
292
|
+
const rest = without(data);
|
|
293
|
+
return moved ? place(rest) : data;
|
|
294
|
+
}
|
|
295
|
+
|
|
249
296
|
export default function Demo() {
|
|
250
297
|
const { t } = useTranslation();
|
|
251
298
|
// Card 1 — checks are controlled so the readout beside the tree can never disagree with it.
|
|
@@ -253,6 +300,8 @@ export default function Demo() {
|
|
|
253
300
|
// Card 2 — selection is controlled and drives the detail pane.
|
|
254
301
|
const [openFile, setOpenFile] = React.useState<string | undefined>("src/components/tree.tsx");
|
|
255
302
|
// The page index — the tree IS the navigation, so selection is what the reading pane follows.
|
|
303
|
+
const [movablePages, setMovablePages] = React.useState(movablePagesSeed);
|
|
304
|
+
const [lastMove, setLastMove] = React.useState("");
|
|
256
305
|
const [openPage, setOpenPage] = React.useState<string | undefined>("handbook/security/passwords");
|
|
257
306
|
// Card 3 — async children land in state, exactly as a real fetch would.
|
|
258
307
|
const [departments, setDepartments] = React.useState<Department[]>(initialDepartments);
|
|
@@ -452,6 +501,35 @@ export default function Demo() {
|
|
|
452
501
|
</CardContent>
|
|
453
502
|
</Card>
|
|
454
503
|
|
|
504
|
+
<Card>
|
|
505
|
+
<CardHeader>
|
|
506
|
+
<CardTitle level={2}>{t("treeDocs.drag.title")}</CardTitle>
|
|
507
|
+
<CardDescription>{t("treeDocs.drag.description")}</CardDescription>
|
|
508
|
+
</CardHeader>
|
|
509
|
+
<CardContent>
|
|
510
|
+
<Flex direction="col" gap="sm">
|
|
511
|
+
<Tree
|
|
512
|
+
aria-label={t("treeDocs.drag.treeLabel")}
|
|
513
|
+
treeData={labelPages(movablePages, t)}
|
|
514
|
+
defaultExpandAll
|
|
515
|
+
draggable
|
|
516
|
+
allowDrop={({ dropNode, dropPosition }) =>
|
|
517
|
+
!(dropNode.value === "notes" && dropPosition === 0)
|
|
518
|
+
}
|
|
519
|
+
onDrop={(info) => {
|
|
520
|
+
setMovablePages((current) => moveNode(current, info));
|
|
521
|
+
setLastMove(
|
|
522
|
+
`${String(info.dragNode.label)} → ${String(info.node.label)}(dropPosition ${info.dropPosition}, dropToGap ${info.dropToGap})`,
|
|
523
|
+
);
|
|
524
|
+
}}
|
|
525
|
+
/>
|
|
526
|
+
<Text size="sm" tone="muted">
|
|
527
|
+
{t("treeDocs.drag.lastMove", { move: lastMove || t("treeDocs.drag.none") })}
|
|
528
|
+
</Text>
|
|
529
|
+
</Flex>
|
|
530
|
+
</CardContent>
|
|
531
|
+
</Card>
|
|
532
|
+
|
|
455
533
|
<Card>
|
|
456
534
|
<CardHeader>
|
|
457
535
|
<CardTitle level={2}>組織ツリー(loadData · fieldNames · titleRender)</CardTitle>
|
|
@@ -632,6 +710,10 @@ export default function Demo() {
|
|
|
632
710
|
},
|
|
633
711
|
{ label: "*", children: "同じ階層の兄弟をすべて展開" },
|
|
634
712
|
{ label: "文字キー", children: "先頭一致で次のノードへジャンプ(type-ahead)" },
|
|
713
|
+
{
|
|
714
|
+
label: "Alt + ↑ / ↓ / ← / →",
|
|
715
|
+
children: t("treeDocs.drag.keys"),
|
|
716
|
+
},
|
|
635
717
|
]}
|
|
636
718
|
/>
|
|
637
719
|
</CardContent>
|
|
@@ -1507,6 +1507,21 @@
|
|
|
1507
1507
|
"one": "Load {count} more",
|
|
1508
1508
|
"other": "Load {count} more"
|
|
1509
1509
|
}
|
|
1510
|
+
},
|
|
1511
|
+
"drag": {
|
|
1512
|
+
"title": "Drag to reorder or reparent (draggable · allowDrop · onDrop)",
|
|
1513
|
+
"description": "Drop on the upper quarter of a row to land before it, the lower quarter after it, the middle inside it. Tree never edits treeData; onDrop reports the move. From the keyboard: Alt+↑ / Alt+↓ move past a sibling, Alt+← outdents, Alt+→ indents into the previous sibling. Every move is announced in a live region. Here allowDrop refuses dropping inside “Release notes”.",
|
|
1514
|
+
"treeLabel": "Reorderable pages",
|
|
1515
|
+
"lastMove": "Last move: {move}",
|
|
1516
|
+
"none": "none",
|
|
1517
|
+
"keys": "When draggable: past a sibling / outdent / indent",
|
|
1518
|
+
"pages": {
|
|
1519
|
+
"guide": "User guide",
|
|
1520
|
+
"start": "Getting started",
|
|
1521
|
+
"setup": "Setup",
|
|
1522
|
+
"faq": "FAQ",
|
|
1523
|
+
"notes": "Release notes"
|
|
1524
|
+
}
|
|
1510
1525
|
}
|
|
1511
1526
|
},
|
|
1512
1527
|
"progressDocs": {
|
|
@@ -1525,5 +1540,14 @@
|
|
|
1525
1540
|
"home": "Home",
|
|
1526
1541
|
"longSuite": "Q3 integration test · payment module · invoice re-issue scenarios",
|
|
1527
1542
|
"longSection": "Acceptance criteria review for the release candidate of September 2026"
|
|
1543
|
+
},
|
|
1544
|
+
"timelineDocs": {
|
|
1545
|
+
"log": {
|
|
1546
|
+
"title": "status=\"log\" (activity log)",
|
|
1547
|
+
"description": "For a record of things that already happened, such as an issue history, use status=\"log\": a neutral dot and hairline rail, and no “Completed:” / “Upcoming:” prefix read out (antd's gray item).",
|
|
1548
|
+
"status": "Tanaka changed the status Open → In progress",
|
|
1549
|
+
"comment": "Nguyen commented",
|
|
1550
|
+
"assignee": "Tanaka changed the assignee"
|
|
1551
|
+
}
|
|
1528
1552
|
}
|
|
1529
1553
|
}
|
|
@@ -1500,6 +1500,21 @@
|
|
|
1500
1500
|
"loadMore": {
|
|
1501
1501
|
"other": "さらに {count} 件を読み込む"
|
|
1502
1502
|
}
|
|
1503
|
+
},
|
|
1504
|
+
"drag": {
|
|
1505
|
+
"title": "ドラッグで並べ替え・親を変更(draggable · allowDrop · onDrop)",
|
|
1506
|
+
"description": "行の上 1/4 に落とすと前へ、下 1/4 で後ろへ、中央でその中(子)へ移動します。Tree は treeData を書き換えず、onDrop で移動を知らせるだけです。キーボードでは Alt+↑ / Alt+↓ で兄弟の前後へ、Alt+← で階層を上げ、Alt+→ で直前の兄弟の中へ。移動は毎回ライブリージョンで読み上げられます。ここでは allowDrop で「リリースノート」の中へは落とせません。",
|
|
1507
|
+
"treeLabel": "並べ替えできるページ一覧",
|
|
1508
|
+
"lastMove": "最後の移動: {move}",
|
|
1509
|
+
"none": "なし",
|
|
1510
|
+
"keys": "draggable のとき:兄弟の前後へ/階層を上げる/階層を下げる",
|
|
1511
|
+
"pages": {
|
|
1512
|
+
"guide": "利用ガイド",
|
|
1513
|
+
"start": "はじめに",
|
|
1514
|
+
"setup": "初期設定",
|
|
1515
|
+
"faq": "よくある質問",
|
|
1516
|
+
"notes": "リリースノート"
|
|
1517
|
+
}
|
|
1503
1518
|
}
|
|
1504
1519
|
},
|
|
1505
1520
|
"progressDocs": {
|
|
@@ -1518,5 +1533,14 @@
|
|
|
1518
1533
|
"home": "ホーム",
|
|
1519
1534
|
"longSuite": "第3四半期 結合テスト 決済モジュール 請求書再発行シナリオ",
|
|
1520
1535
|
"longSection": "2026年9月 リリース候補の受入基準レビュー"
|
|
1536
|
+
},
|
|
1537
|
+
"timelineDocs": {
|
|
1538
|
+
"log": {
|
|
1539
|
+
"title": "status=\"log\"(アクティビティログ)",
|
|
1540
|
+
"description": "課題の履歴のように、すべて起きたことの記録には status=\"log\"。中立の点と細い線で描き、「完了:」「予定:」のような読み上げ接頭辞を付けません(antd の gray item と同じ)。",
|
|
1541
|
+
"status": "田中 がステータスを変更 未対応 → 処理中",
|
|
1542
|
+
"comment": "グエン がコメント",
|
|
1543
|
+
"assignee": "田中 が担当者を変更"
|
|
1544
|
+
}
|
|
1521
1545
|
}
|
|
1522
1546
|
}
|
|
@@ -1500,6 +1500,21 @@
|
|
|
1500
1500
|
"loadMore": {
|
|
1501
1501
|
"other": "Tải thêm {count} ca"
|
|
1502
1502
|
}
|
|
1503
|
+
},
|
|
1504
|
+
"drag": {
|
|
1505
|
+
"title": "Kéo để sắp xếp hoặc đổi cha (draggable · allowDrop · onDrop)",
|
|
1506
|
+
"description": "Thả vào 1/4 trên của dòng để đặt trước nó, 1/4 dưới để đặt sau, phần giữa để đặt vào trong. Tree không sửa treeData; onDrop báo nước đi. Bàn phím: Alt+↑ / Alt+↓ qua mục ngang hàng, Alt+← lùi cấp, Alt+→ thụt vào mục liền trước. Mỗi lần di chuyển được đọc qua live region. Ở đây allowDrop không cho thả vào trong “Ghi chú phát hành”.",
|
|
1507
|
+
"treeLabel": "Danh sách trang sắp xếp được",
|
|
1508
|
+
"lastMove": "Lần di chuyển cuối: {move}",
|
|
1509
|
+
"none": "chưa có",
|
|
1510
|
+
"keys": "Khi draggable: qua mục ngang hàng / lùi cấp / thụt cấp",
|
|
1511
|
+
"pages": {
|
|
1512
|
+
"guide": "Hướng dẫn sử dụng",
|
|
1513
|
+
"start": "Bắt đầu",
|
|
1514
|
+
"setup": "Cài đặt",
|
|
1515
|
+
"faq": "Câu hỏi thường gặp",
|
|
1516
|
+
"notes": "Ghi chú phát hành"
|
|
1517
|
+
}
|
|
1503
1518
|
}
|
|
1504
1519
|
},
|
|
1505
1520
|
"progressDocs": {
|
|
@@ -1518,5 +1533,14 @@
|
|
|
1518
1533
|
"home": "Trang chủ",
|
|
1519
1534
|
"longSuite": "Kiểm thử tích hợp quý 3 · mô-đun thanh toán · kịch bản xuất lại hoá đơn",
|
|
1520
1535
|
"longSection": "Rà soát tiêu chí nghiệm thu cho bản ứng viên tháng 9/2026"
|
|
1536
|
+
},
|
|
1537
|
+
"timelineDocs": {
|
|
1538
|
+
"log": {
|
|
1539
|
+
"title": "status=\"log\" (nhật ký hoạt động)",
|
|
1540
|
+
"description": "Với bản ghi những việc đã xảy ra như lịch sử issue, dùng status=\"log\": chấm trung tính, đường mảnh, không đọc tiền tố “Hoàn thành:” / “Sắp tới:” (giống item gray của antd).",
|
|
1541
|
+
"status": "Tanaka đổi trạng thái Mở → Đang xử lý",
|
|
1542
|
+
"comment": "Nguyễn đã bình luận",
|
|
1543
|
+
"assignee": "Tanaka đổi người phụ trách"
|
|
1544
|
+
}
|
|
1521
1545
|
}
|
|
1522
1546
|
}
|