@godxjp/ui 30.1.1 → 30.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/START-HERE.md +1 -1
- package/agent/components/Tree.json +1 -0
- package/agent/components.json +1 -0
- package/agent/index.json +2 -2
- package/agent/llms.txt +3 -3
- package/dist/components/data-display/tree.js +29 -1
- package/dist/components/layout/topbar.d.ts +2 -1
- package/dist/components/layout/topbar.js +48 -5
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +1 -0
- package/dist/i18n/messages/ja.json +1 -0
- package/dist/i18n/messages/vi.json +1 -0
- package/dist/props/components/layout.prop.d.ts +6 -1
- package/dist/styles/shell-layout.css +30 -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` 30.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 30.3.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.
|
|
@@ -167,6 +167,7 @@
|
|
|
167
167
|
"usage": [
|
|
168
168
|
"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.",
|
|
169
169
|
"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.",
|
|
170
|
+
"DO keep SELECTION and EXPANSION apart too — this is the THIRD axis and the one gh#910 tripped on. `onValueChange` says which node a detail pane should show; `onExpandedValuesChange` says which branches are unfolded. A sibling pane listens to the first and ignores the second, so unfolding a branch cannot reload it. If your pane reloads when a branch opens, it is being driven from the wrong event — not from a missing feature.",
|
|
170
171
|
"DO let the keyboard work: the tree ships the full APG contract (Up/Down through visible nodes, Right expands then descends, Left collapses then climbs, Home/End, Enter/Space, `*` to expand the current level, type-ahead). Do not add your own key handling on top.",
|
|
171
172
|
"DON'T nest a Button, Checkbox, Link or any focusable control inside a node label. A tree item owns exactly ONE tab stop; the disclosure triangle and the tick box are decorative glyphs for that reason. Put row actions in a sibling column outside the tree, or open a detail pane on selection.",
|
|
172
173
|
"DON'T hand-roll an indented `<ul>` (or a NavList / ListRow stack with a per-depth margin) for a hierarchy. A flat indented list only LOOKS like a tree: no expand/collapse, no `role=\"tree\"`, no keyboard model, no selection contract. `TreeList` was exactly that list and was REMOVED in 21.0.0 — Tree is what replaced it, and it is the one to reach for whenever nodes expand, collapse or are keyboard-navigated.",
|
package/agent/components.json
CHANGED
|
@@ -10877,6 +10877,7 @@
|
|
|
10877
10877
|
"usage": [
|
|
10878
10878
|
"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.",
|
|
10879
10879
|
"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.",
|
|
10880
|
+
"DO keep SELECTION and EXPANSION apart too — this is the THIRD axis and the one gh#910 tripped on. `onValueChange` says which node a detail pane should show; `onExpandedValuesChange` says which branches are unfolded. A sibling pane listens to the first and ignores the second, so unfolding a branch cannot reload it. If your pane reloads when a branch opens, it is being driven from the wrong event — not from a missing feature.",
|
|
10880
10881
|
"DO let the keyboard work: the tree ships the full APG contract (Up/Down through visible nodes, Right expands then descends, Left collapses then climbs, Home/End, Enter/Space, `*` to expand the current level, type-ahead). Do not add your own key handling on top.",
|
|
10881
10882
|
"DON'T nest a Button, Checkbox, Link or any focusable control inside a node label. A tree item owns exactly ONE tab stop; the disclosure triangle and the tick box are decorative glyphs for that reason. Put row actions in a sibling column outside the tree, or open a detail pane on selection.",
|
|
10882
10883
|
"DON'T hand-roll an indented `<ul>` (or a NavList / ListRow stack with a per-depth margin) for a hierarchy. A flat indented list only LOOKS like a tree: no expand/collapse, no `role=\"tree\"`, no keyboard model, no selection contract. `TreeList` was exactly that list and was REMOVED in 21.0.0 — Tree is what replaced it, and it is the one to reach for whenever nodes expand, collapse or are keyboard-navigated.",
|
package/agent/index.json
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
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/v30.
|
|
51
|
+
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v30.3.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",
|
|
@@ -62,5 +62,5 @@
|
|
|
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": "30.
|
|
65
|
+
"version": "30.3.0"
|
|
66
66
|
}
|
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
3
|
> A Japanese-enterprise React design system: 171 components, 2071 design tokens,
|
|
4
|
-
> 50 cardinal rules. This file is the entry point for AI agents. Catalog version 30.
|
|
4
|
+
> 50 cardinal rules. This file is the entry point for AI agents. Catalog version 30.3.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@30.
|
|
7
|
+
(`npx @godxjp/ui-mcp@30.3.0`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -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/v30.
|
|
29
|
+
the tag: `.../godx-jp/godxjp-ui/v30.3.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.
|
|
@@ -87,7 +87,11 @@ function TreeRoot({
|
|
|
87
87
|
});
|
|
88
88
|
const isExpandedControlled = expandedValues !== void 0;
|
|
89
89
|
const expanded = isExpandedControlled ? [...expandedValues] : internalExpanded;
|
|
90
|
-
const
|
|
90
|
+
const expandedKey = expanded.join("\0");
|
|
91
|
+
const expandedSet = React.useMemo(
|
|
92
|
+
() => new Set(expandedKey ? expandedKey.split("\0") : []),
|
|
93
|
+
[expandedKey]
|
|
94
|
+
);
|
|
91
95
|
const commitExpanded = (next) => {
|
|
92
96
|
if (!isExpandedControlled) setInternalExpanded(next);
|
|
93
97
|
onExpandedValuesChange?.(next);
|
|
@@ -126,6 +130,29 @@ function TreeRoot({
|
|
|
126
130
|
});
|
|
127
131
|
});
|
|
128
132
|
};
|
|
133
|
+
const requestLoadRef = React.useRef(requestLoad);
|
|
134
|
+
requestLoadRef.current = requestLoad;
|
|
135
|
+
const nodesByValue = React.useMemo(() => {
|
|
136
|
+
const map = /* @__PURE__ */ new Map();
|
|
137
|
+
const walk = (list) => {
|
|
138
|
+
for (const node of list) {
|
|
139
|
+
map.set(node.value, node);
|
|
140
|
+
if (node.children?.length) walk(node.children);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
walk(options);
|
|
144
|
+
return map;
|
|
145
|
+
}, [options]);
|
|
146
|
+
const loadedFor = React.useRef(/* @__PURE__ */ new Set());
|
|
147
|
+
React.useEffect(() => {
|
|
148
|
+
if (!loadData) return;
|
|
149
|
+
for (const value2 of expandedSet) {
|
|
150
|
+
if (loadedFor.current.has(value2)) continue;
|
|
151
|
+
loadedFor.current.add(value2);
|
|
152
|
+
const node = nodesByValue.get(value2);
|
|
153
|
+
if (node) requestLoadRef.current(node);
|
|
154
|
+
}
|
|
155
|
+
}, [expandedSet, loadData, nodesByValue, requestLoadRef]);
|
|
129
156
|
const visible = flattenVisibleTree(options, expandedSet);
|
|
130
157
|
const visibleIndex = new Map(visible.map((entry, index) => [entry.node.value, index]));
|
|
131
158
|
const [activeValue, setActiveValue] = React.useState(null);
|
|
@@ -327,6 +354,7 @@ function TreeRoot({
|
|
|
327
354
|
if (nodeDisabled) return;
|
|
328
355
|
select(node);
|
|
329
356
|
},
|
|
357
|
+
"data-value": node.value,
|
|
330
358
|
"data-selected": isSelected ? "true" : void 0,
|
|
331
359
|
"data-disabled": nodeDisabled ? "" : void 0,
|
|
332
360
|
className: "ui-tree-node ui-focus-ring",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import type { TopbarProp } from "../../props/components/layout.prop.js";
|
|
2
3
|
export type { TopbarProp, TopbarProp as TopbarProps } from "../../props/components/layout.prop.js";
|
|
3
4
|
/**
|
|
4
5
|
* Topbar — a PURE SLOT bar. It positions three clusters (`start` / `center` / `end`) and owns the
|
|
5
6
|
* bar's flex layout; it does NOT bake any chrome.
|
|
6
7
|
*/
|
|
7
|
-
export declare function Topbar({ start, center, end, className, children, height, overflow, pad, style, ...props }: TopbarProp):
|
|
8
|
+
export declare function Topbar({ start, center, end, className, children, height, overflow, pad, style, ...props }: TopbarProp): React.JSX.Element;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { MoreHorizontal } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
2
6
|
import { padStyle } from "../../lib/variants.js";
|
|
3
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
9
|
+
import { TopbarItem } from "./topbar-item.js";
|
|
4
10
|
function Topbar({
|
|
5
11
|
start,
|
|
6
12
|
center,
|
|
@@ -13,23 +19,60 @@ function Topbar({
|
|
|
13
19
|
style,
|
|
14
20
|
...props
|
|
15
21
|
}) {
|
|
22
|
+
const barRef = React.useRef(null);
|
|
23
|
+
const foldable = overflow === "menu" && children == null;
|
|
24
|
+
const collapsed = useCollapsesToMenu(barRef, foldable);
|
|
25
|
+
const clusters = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
26
|
+
start != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-start", className: "ui-topbar-start", children: start }) : null,
|
|
27
|
+
center != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-center", className: "ui-topbar-center", children: center }) : null,
|
|
28
|
+
end != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-end", className: "ui-topbar-end", children: end }) : null
|
|
29
|
+
] });
|
|
16
30
|
return /* @__PURE__ */ jsx(
|
|
17
31
|
"div",
|
|
18
32
|
{
|
|
33
|
+
ref: barRef,
|
|
19
34
|
"data-height": height,
|
|
20
35
|
"data-overflow": overflow,
|
|
36
|
+
"data-collapsed": foldable ? String(collapsed) : void 0,
|
|
21
37
|
style: { ...style, ...padStyle(pad, void 0) },
|
|
22
38
|
"data-slot": "topbar",
|
|
23
39
|
className: cn("ui-topbar", className),
|
|
24
40
|
...props,
|
|
25
|
-
children: children ?? /* @__PURE__ */
|
|
26
|
-
start != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-start", className: "ui-topbar-start", children: start }) : null,
|
|
27
|
-
center != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-center", className: "ui-topbar-center", children: center }) : null,
|
|
28
|
-
end != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-end", className: "ui-topbar-end", children: end }) : null
|
|
29
|
-
] })
|
|
41
|
+
children: children ?? (collapsed ? /* @__PURE__ */ jsx(TopbarOverflowMenu, { children: clusters }) : clusters)
|
|
30
42
|
}
|
|
31
43
|
);
|
|
32
44
|
}
|
|
45
|
+
function TopbarOverflowMenu({ children }) {
|
|
46
|
+
const { t } = useTranslation();
|
|
47
|
+
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
48
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(TopbarItem, { "data-topbar-overflow-trigger": "", "aria-label": t("layout.topbar.more"), children: /* @__PURE__ */ jsx(MoreHorizontal, { "aria-hidden": "true" }) }) }),
|
|
49
|
+
/* @__PURE__ */ jsx(PopoverContent, { align: "end", width: "auto", children: /* @__PURE__ */ jsx("div", { "data-slot": "topbar-overflow", className: "ui-topbar-overflow", children }) })
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
52
|
+
function useCollapsesToMenu(ref, enabled) {
|
|
53
|
+
const [collapsed, setCollapsed] = React.useState(false);
|
|
54
|
+
const needed = React.useRef(0);
|
|
55
|
+
React.useLayoutEffect(() => {
|
|
56
|
+
const bar = ref.current;
|
|
57
|
+
if (!enabled || !bar) return void 0;
|
|
58
|
+
const update = () => {
|
|
59
|
+
if (!collapsed) {
|
|
60
|
+
if (bar.clientWidth > 0 && bar.scrollWidth - bar.clientWidth > 1) {
|
|
61
|
+
needed.current = bar.scrollWidth;
|
|
62
|
+
setCollapsed(true);
|
|
63
|
+
}
|
|
64
|
+
} else if (bar.clientWidth >= needed.current) {
|
|
65
|
+
setCollapsed(false);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
update();
|
|
69
|
+
if (typeof ResizeObserver === "undefined") return void 0;
|
|
70
|
+
const observer = new ResizeObserver(update);
|
|
71
|
+
observer.observe(bar);
|
|
72
|
+
return () => observer.disconnect();
|
|
73
|
+
}, [ref, enabled, collapsed]);
|
|
74
|
+
return enabled && collapsed;
|
|
75
|
+
}
|
|
33
76
|
export {
|
|
34
77
|
Topbar
|
|
35
78
|
};
|
|
@@ -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": "30.
|
|
3
|
+
"version": "30.2.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,
|
|
@@ -1424,12 +1424,17 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
|
|
|
1424
1424
|
* - `"clip"` — the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted.
|
|
1425
1425
|
* Still focusable, still announced, invisible and unreachable by pointer. Choose it only for a
|
|
1426
1426
|
* bar you have measured as never overflowing.
|
|
1427
|
+
* - `"menu"` — when the clusters do not fit, the bar shows ONE "more" cell and the clusters
|
|
1428
|
+
* move into its popover, where they wrap (gh#914). Nothing is off-screen and nothing is
|
|
1429
|
+
* duplicated: each control is rendered once, in the bar or in the popover. Use it for a bar
|
|
1430
|
+
* that must survive a narrow viewport with enlarged text, where a scrolling bar can shrink to
|
|
1431
|
+
* a window narrower than one cell. Ignored with `children`.
|
|
1427
1432
|
*
|
|
1428
1433
|
* Independent of `TopbarItem hideBelow`, which REMOVES a cell at a step: dropping a cell is the
|
|
1429
1434
|
* consumer saying it does not belong on a phone, and that remains the way to spend the budget
|
|
1430
1435
|
* deliberately. This prop is only about what happens once there is no budget left.
|
|
1431
1436
|
*/
|
|
1432
|
-
overflow?: "scroll" | "clip";
|
|
1437
|
+
overflow?: "scroll" | "clip" | "menu";
|
|
1433
1438
|
/** Escape hatch — render fully custom bar content instead of the three slots. */
|
|
1434
1439
|
children?: ReactNode;
|
|
1435
1440
|
};
|
|
@@ -2084,6 +2084,36 @@
|
|
|
2084
2084
|
min-inline-size: auto;
|
|
2085
2085
|
}
|
|
2086
2086
|
|
|
2087
|
+
.ui-topbar[data-overflow="menu"] {
|
|
2088
|
+
overflow: hidden;
|
|
2089
|
+
min-inline-size: var(--topbar-item-min-width);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
.ui-topbar[data-overflow="menu"] > .ui-topbar-end {
|
|
2093
|
+
max-inline-size: none;
|
|
2094
|
+
min-inline-size: auto;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
.ui-topbar[data-overflow="menu"] > [data-topbar-overflow-trigger] {
|
|
2098
|
+
margin-inline-start: auto;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
.ui-topbar-overflow {
|
|
2102
|
+
display: flex;
|
|
2103
|
+
flex-wrap: wrap;
|
|
2104
|
+
align-items: center;
|
|
2105
|
+
gap: var(--topbar-gap);
|
|
2106
|
+
|
|
2107
|
+
max-inline-size: calc(100vi - 2 * var(--popover-space-inset) - var(--space-4));
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
.ui-topbar-overflow > :is(.ui-topbar-start, .ui-topbar-center, .ui-topbar-end) {
|
|
2111
|
+
flex-wrap: wrap;
|
|
2112
|
+
margin-inline-start: 0;
|
|
2113
|
+
min-block-size: var(--topbar-item-min-width);
|
|
2114
|
+
overflow: visible;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2087
2117
|
.ui-topbar-item {
|
|
2088
2118
|
display: inline-flex;
|
|
2089
2119
|
min-width: var(--topbar-item-min-width);
|