@godxjp/ui 33.22.2 → 33.25.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/DESIGN.md +1 -1
- package/README.md +16 -5
- package/agent/DESIGN.md +1 -1
- package/agent/START-HERE.md +8 -8
- package/agent/components/AgentBadge.json +43 -0
- package/agent/components/AppShell.json +6 -0
- package/agent/components/Avatar.json +7 -0
- package/agent/components/CatalogDetail.json +7 -3
- package/agent/components/DatePicker.json +1 -1
- package/agent/components/DropdownMenu.json +1 -0
- package/agent/components/EditorialCard.json +2 -1
- package/agent/components/Image.json +6 -1
- package/agent/components/PageContainer.json +5 -0
- package/agent/components/PageErrorBoundary.json +48 -0
- package/agent/components/Pagination.json +5 -0
- package/agent/components/Popover.json +1 -0
- package/agent/components/Select.json +2 -1
- package/agent/components/Toaster.json +2 -2
- package/agent/components/Toolbar.json +1 -1
- package/agent/components/Topbar.json +1 -1
- package/agent/components-index.json +10 -0
- package/agent/components.json +138 -11
- package/agent/index.json +8 -8
- package/agent/llms.txt +7 -7
- package/agent/patterns/notice-bar.json +16 -0
- package/agent/patterns-index.json +15 -0
- package/agent/patterns.json +16 -0
- package/agent/rules.json +2 -2
- package/agent/tokens.json +121 -7
- package/dist/components/charts/chart-cartesian.js +5 -1
- package/dist/components/charts/chart-summary.d.ts +1 -1
- package/dist/components/charts/chart-summary.js +17 -6
- package/dist/components/charts/compact-bar-trend.js +4 -3
- package/dist/components/charts/pie-chart.js +2 -1
- package/dist/components/data-display/avatar.d.ts +1 -1
- package/dist/components/data-display/avatar.js +7 -1
- package/dist/components/data-display/badge.d.ts +11 -0
- package/dist/components/data-display/badge.js +20 -0
- package/dist/components/data-display/image.d.ts +1 -0
- package/dist/components/data-display/image.js +10 -2
- package/dist/components/data-display/index.d.ts +2 -2
- package/dist/components/data-display/index.js +2 -1
- package/dist/components/data-display/popover.js +11 -0
- package/dist/components/data-entry/calendar.js +1 -1
- package/dist/components/data-entry/date-picker.js +22 -5
- package/dist/components/data-entry/record-picker.js +122 -98
- package/dist/components/data-entry/select.js +13 -3
- package/dist/components/general/button.js +2 -2
- package/dist/components/layout/affix.js +1 -1
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +3 -1
- package/dist/components/layout/editorial-catalog.d.ts +1 -1
- package/dist/components/layout/editorial-catalog.js +53 -15
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +6 -3
- package/dist/components/layout/page-error-boundary.d.ts +32 -0
- package/dist/components/layout/page-error-boundary.js +57 -0
- package/dist/components/layout/sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar.js +100 -49
- package/dist/components/layout/topbar.js +108 -21
- package/dist/components/navigation/dropdown-menu.d.ts +13 -3
- package/dist/components/navigation/dropdown-menu.js +36 -6
- package/dist/components/navigation/pagination.d.ts +1 -1
- package/dist/components/navigation/pagination.js +13 -1
- package/dist/components/navigation/toolbar.js +2 -1
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +7 -0
- package/dist/i18n/messages/ja.json +7 -0
- package/dist/i18n/messages/vi.json +7 -0
- package/dist/props/components/data-display.prop.d.ts +29 -0
- package/dist/props/components/layout.prop.d.ts +52 -2
- package/dist/props/components/navigation.prop.d.ts +15 -1
- package/dist/props/registry.d.ts +65 -5
- package/dist/props/registry.js +90 -4
- package/dist/styles/alert-layout.css +4 -0
- package/dist/styles/application.css +24 -0
- package/dist/styles/badge-layout.css +14 -0
- package/dist/styles/base.css +11 -0
- package/dist/styles/control.css +32 -19
- package/dist/styles/core.css +1 -0
- package/dist/styles/data-display-layout.css +51 -0
- package/dist/styles/dialog-layout.css +9 -0
- package/dist/styles/editorial-layout.css +2076 -0
- package/dist/styles/editorial.css +3 -0
- package/dist/styles/icon-layout.css +3 -0
- package/dist/styles/index.css +1 -0
- package/dist/styles/layers.json +60 -9
- package/dist/styles/layout.css +9 -1985
- package/dist/styles/precompiled/alert-layout.css +2 -0
- package/dist/styles/precompiled/badge-layout.css +2 -0
- package/dist/styles/precompiled/base.css +2 -0
- package/dist/styles/precompiled/card-layout.css +2 -0
- package/dist/styles/precompiled/chart-layout.css +2 -0
- package/dist/styles/precompiled/control.css +2 -0
- package/dist/styles/precompiled/data-display-layout.css +2 -0
- package/dist/styles/precompiled/data-entry-layout.css +2 -0
- package/dist/styles/precompiled/dialog-layout.css +2 -0
- package/dist/styles/precompiled/editorial-account-layout.css +2 -0
- package/dist/styles/precompiled/editorial-layout.css +2 -0
- package/dist/styles/precompiled/float-button-layout.css +2 -0
- package/dist/styles/precompiled/form-layout.css +2 -0
- package/dist/styles/precompiled/icon-layout.css +2 -0
- package/dist/styles/precompiled/layout.css +2 -0
- package/dist/styles/precompiled/logo-layout.css +2 -0
- package/dist/styles/precompiled/motion.css +2 -0
- package/dist/styles/precompiled/navigation-layout.css +2 -0
- package/dist/styles/precompiled/shell-layout.css +2 -0
- package/dist/styles/precompiled/table-layout.css +2 -0
- package/dist/styles/precompiled/text-layout.css +2 -0
- package/dist/styles/precompiled/toggle.css +2 -0
- package/dist/styles/precompiled/vendor-day-picker.css +2 -0
- package/dist/styles/precompiled/vendor-sonner.css +2 -0
- package/dist/styles/shell-layout.css +17 -1
- package/dist/styles/table-layout.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/tokens/components/badge.css +3 -0
- package/dist/tokens/components/control.css +3 -0
- package/dist/tokens/components/data-display.css +9 -0
- package/dist/tokens/components/editorial.css +9 -6
- package/dist/tokens/components/shell.css +2 -0
- package/dist/tokens/foundation.css +8 -0
- package/docs/CONSUMER-RULES.md +36 -1
- package/package.json +4 -2
- package/scripts/consumer-rule.md +1 -1
- package/scripts/prune-css.mjs +56 -8
- package/scripts/ui-audit.mjs +36 -0
- package/skills/godxjp-ui-navigation/SKILL.md +14 -9
- package/skills/godxjp-ui-navigation/references/principles.md +2 -2
|
@@ -33,6 +33,30 @@ function SidebarTrailingIcon({
|
|
|
33
33
|
}) {
|
|
34
34
|
return /* @__PURE__ */ jsx("span", { className: "sb-trailing-icon", children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" }) });
|
|
35
35
|
}
|
|
36
|
+
function SidebarLabel({ children }) {
|
|
37
|
+
const ref = React.useRef(null);
|
|
38
|
+
React.useLayoutEffect(() => {
|
|
39
|
+
const label = ref.current;
|
|
40
|
+
if (!label) return;
|
|
41
|
+
const sync = () => {
|
|
42
|
+
const cut = label.scrollWidth - label.clientWidth > 1;
|
|
43
|
+
if (cut) {
|
|
44
|
+
const text = (label.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
45
|
+
if (label.title !== text) label.title = text;
|
|
46
|
+
label.dataset.truncated = "";
|
|
47
|
+
} else if (label.dataset.truncated !== void 0) {
|
|
48
|
+
label.removeAttribute("title");
|
|
49
|
+
delete label.dataset.truncated;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
sync();
|
|
53
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
54
|
+
const observer = new ResizeObserver(sync);
|
|
55
|
+
observer.observe(label);
|
|
56
|
+
return () => observer.disconnect();
|
|
57
|
+
});
|
|
58
|
+
return /* @__PURE__ */ jsx("span", { ref, className: "sb-label", children });
|
|
59
|
+
}
|
|
36
60
|
function SidebarRowContent({
|
|
37
61
|
item,
|
|
38
62
|
sub = false,
|
|
@@ -42,7 +66,7 @@ function SidebarRowContent({
|
|
|
42
66
|
const showBadge = item.badge !== void 0 && item.badge !== "";
|
|
43
67
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
44
68
|
!sub ? /* @__PURE__ */ jsx(SidebarIcon, { icon: item.icon }) : null,
|
|
45
|
-
/* @__PURE__ */ jsx(
|
|
69
|
+
/* @__PURE__ */ jsx(SidebarLabel, { children: item.label }),
|
|
46
70
|
showBadge ? (
|
|
47
71
|
// absent-when-off), so a rail that never sets `badgeTone` renders the exact same node it
|
|
48
72
|
// always did and no consumer selector has to out-specify a marker meaning "unchanged".
|
|
@@ -147,20 +171,40 @@ function NavLeafsInGroup({
|
|
|
147
171
|
linkComponent,
|
|
148
172
|
renderItem
|
|
149
173
|
}) {
|
|
150
|
-
return children.map(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
return children.map(
|
|
175
|
+
(child) => child.children && child.children.length > 0 ? /* @__PURE__ */ jsx(
|
|
176
|
+
NavGroup,
|
|
177
|
+
{
|
|
178
|
+
item: child,
|
|
179
|
+
activeId,
|
|
180
|
+
onSelect,
|
|
181
|
+
sub: true,
|
|
182
|
+
linkComponent,
|
|
183
|
+
renderItem
|
|
184
|
+
},
|
|
185
|
+
child.id
|
|
186
|
+
) : /* @__PURE__ */ jsx(
|
|
187
|
+
NavLeaf,
|
|
188
|
+
{
|
|
189
|
+
item: child,
|
|
190
|
+
activeId,
|
|
191
|
+
onSelect,
|
|
192
|
+
sub: true,
|
|
193
|
+
linkComponent,
|
|
194
|
+
renderItem
|
|
195
|
+
},
|
|
196
|
+
child.id
|
|
197
|
+
)
|
|
198
|
+
);
|
|
162
199
|
}
|
|
163
|
-
function NavGroup({
|
|
200
|
+
function NavGroup({
|
|
201
|
+
item,
|
|
202
|
+
activeId,
|
|
203
|
+
onSelect,
|
|
204
|
+
sub = false,
|
|
205
|
+
linkComponent,
|
|
206
|
+
renderItem
|
|
207
|
+
}) {
|
|
164
208
|
const active = isItemActive(item, activeId);
|
|
165
209
|
const children = item.children ?? [];
|
|
166
210
|
const [open, setOpen] = React.useState(active);
|
|
@@ -171,10 +215,10 @@ function NavGroup({ item, activeId, onSelect, linkComponent, renderItem }) {
|
|
|
171
215
|
/* @__PURE__ */ jsxs(
|
|
172
216
|
CollapsibleTrigger,
|
|
173
217
|
{
|
|
174
|
-
className: "sb-nav-item sb-nav-group-trigger",
|
|
218
|
+
className: cn("sb-nav-item sb-nav-group-trigger", sub && "sb-nav-item--sub"),
|
|
175
219
|
"data-active": active ? "true" : void 0,
|
|
176
220
|
children: [
|
|
177
|
-
/* @__PURE__ */ jsx(SidebarRowContent, { item }),
|
|
221
|
+
/* @__PURE__ */ jsx(SidebarRowContent, { item, sub }),
|
|
178
222
|
/* @__PURE__ */ jsx(ChevronDown, { className: "sb-chevron", "aria-hidden": "true" })
|
|
179
223
|
]
|
|
180
224
|
}
|
|
@@ -232,6 +276,45 @@ function CollapsedRow({ item, activeId, onSelect, linkComponent: LinkComponent }
|
|
|
232
276
|
children: /* @__PURE__ */ jsx(SidebarRowContent, { item, iconOnly: true })
|
|
233
277
|
}
|
|
234
278
|
) : null;
|
|
279
|
+
const renderFlyoutEntries = (entries) => entries.map((child) => {
|
|
280
|
+
if (child.children && child.children.length > 0) {
|
|
281
|
+
return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": child.label, className: "sb-flyout-group", children: [
|
|
282
|
+
/* @__PURE__ */ jsx("div", { className: "sb-flyout-title", "aria-hidden": "true", children: child.label }),
|
|
283
|
+
renderFlyoutEntries(child.children)
|
|
284
|
+
] }, child.id);
|
|
285
|
+
}
|
|
286
|
+
const childActive = child.id === activeId;
|
|
287
|
+
const select = (event) => {
|
|
288
|
+
setMenuOpen(false);
|
|
289
|
+
if (child.disabled) {
|
|
290
|
+
event.preventDefault();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
onSelect?.(child.id);
|
|
294
|
+
};
|
|
295
|
+
const flyoutState = {
|
|
296
|
+
role: "menuitem",
|
|
297
|
+
className: "sb-nav-item",
|
|
298
|
+
"data-active": childActive ? "true" : void 0,
|
|
299
|
+
"aria-current": childActive ? "page" : void 0,
|
|
300
|
+
"aria-disabled": child.disabled || void 0
|
|
301
|
+
};
|
|
302
|
+
return LinkComponent && child.href ? /* @__PURE__ */ jsx(
|
|
303
|
+
LinkComponent,
|
|
304
|
+
{
|
|
305
|
+
...flyoutState,
|
|
306
|
+
href: child.disabled ? void 0 : child.href,
|
|
307
|
+
onClick: select,
|
|
308
|
+
children: /* @__PURE__ */ jsx(SidebarRowContent, { item: child, sub: true })
|
|
309
|
+
},
|
|
310
|
+
child.id
|
|
311
|
+
) : (
|
|
312
|
+
// A collapsed-rail flyout entry is a `menuitem`-styled nav row owning `.sb-nav-item`
|
|
313
|
+
// composition, not a <Button> — same contract as the leaf row above.
|
|
314
|
+
// ui-audit-disable-next-line no-raw-button
|
|
315
|
+
/* @__PURE__ */ jsx("button", { type: "button", ...flyoutState, onClick: select, children: /* @__PURE__ */ jsx(SidebarRowContent, { item: child, sub: true }) }, child.id)
|
|
316
|
+
);
|
|
317
|
+
});
|
|
235
318
|
if (!hasChildren) {
|
|
236
319
|
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
237
320
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: iconLink ?? iconButton }),
|
|
@@ -253,39 +336,7 @@ function CollapsedRow({ item, activeId, onSelect, linkComponent: LinkComponent }
|
|
|
253
336
|
className: "sb-flyout-pop",
|
|
254
337
|
children: [
|
|
255
338
|
/* @__PURE__ */ jsx("div", { className: "sb-flyout-title", children: item.label }),
|
|
256
|
-
children
|
|
257
|
-
const childActive = child.id === activeId;
|
|
258
|
-
const select = (event) => {
|
|
259
|
-
setMenuOpen(false);
|
|
260
|
-
if (child.disabled) {
|
|
261
|
-
event.preventDefault();
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
onSelect?.(child.id);
|
|
265
|
-
};
|
|
266
|
-
const flyoutState = {
|
|
267
|
-
role: "menuitem",
|
|
268
|
-
className: "sb-nav-item",
|
|
269
|
-
"data-active": childActive ? "true" : void 0,
|
|
270
|
-
"aria-current": childActive ? "page" : void 0,
|
|
271
|
-
"aria-disabled": child.disabled || void 0
|
|
272
|
-
};
|
|
273
|
-
return LinkComponent && child.href ? /* @__PURE__ */ jsx(
|
|
274
|
-
LinkComponent,
|
|
275
|
-
{
|
|
276
|
-
...flyoutState,
|
|
277
|
-
href: child.disabled ? void 0 : child.href,
|
|
278
|
-
onClick: select,
|
|
279
|
-
children: /* @__PURE__ */ jsx(SidebarRowContent, { item: child, sub: true })
|
|
280
|
-
},
|
|
281
|
-
child.id
|
|
282
|
-
) : (
|
|
283
|
-
// A collapsed-rail flyout entry is a `menuitem`-styled nav row owning `.sb-nav-item`
|
|
284
|
-
// composition, not a <Button> — same contract as the leaf row above.
|
|
285
|
-
// ui-audit-disable-next-line no-raw-button
|
|
286
|
-
/* @__PURE__ */ jsx("button", { type: "button", ...flyoutState, onClick: select, children: /* @__PURE__ */ jsx(SidebarRowContent, { item: child, sub: true }) }, child.id)
|
|
287
|
-
);
|
|
288
|
-
})
|
|
339
|
+
renderFlyoutEntries(children)
|
|
289
340
|
]
|
|
290
341
|
}
|
|
291
342
|
)
|
|
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
|
|
4
4
|
import { MoreHorizontal } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { createPortal } from "react-dom";
|
|
6
7
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
7
8
|
import { padStyle } from "../../lib/variants.js";
|
|
8
9
|
import { cn } from "../../lib/utils.js";
|
|
@@ -26,13 +27,54 @@ function Topbar({
|
|
|
26
27
|
const controlsRef = React.useRef(null);
|
|
27
28
|
const hasIdentity = identity != null && children == null;
|
|
28
29
|
const foldable = overflow === "menu" && children == null;
|
|
29
|
-
const
|
|
30
|
-
const
|
|
30
|
+
const hosts = useFoldHosts(foldable);
|
|
31
|
+
const present = { start: start != null, center: center != null, end: end != null };
|
|
32
|
+
const holderRef = React.useRef(null);
|
|
33
|
+
const overflowRef = React.useRef(null);
|
|
34
|
+
const placeClusters = React.useCallback(
|
|
35
|
+
(isCollapsed) => {
|
|
36
|
+
if (!hosts) return;
|
|
37
|
+
const target = !isCollapsed ? hasIdentity ? controlsRef.current : barRef.current : overflowRef.current ?? holderRef.current;
|
|
38
|
+
if (!target) return;
|
|
39
|
+
for (const key of CLUSTER_ORDER) {
|
|
40
|
+
const node = hosts[key];
|
|
41
|
+
if (!present[key]) node.remove();
|
|
42
|
+
else if (node.parentNode !== target || node !== target.lastElementChild) {
|
|
43
|
+
target.appendChild(node);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
// `present` is derived from the three slots on every render; its booleans are what matter.
|
|
48
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
49
|
+
[hosts, hasIdentity, present.start, present.center, present.end]
|
|
50
|
+
);
|
|
51
|
+
const collapsed = useCollapsesToMenu(
|
|
52
|
+
hasIdentity ? controlsRef : barRef,
|
|
53
|
+
foldable,
|
|
54
|
+
hosts ? placeClusters : void 0
|
|
55
|
+
);
|
|
56
|
+
const overflowNode = React.useCallback(
|
|
57
|
+
(node) => {
|
|
58
|
+
overflowRef.current = node;
|
|
59
|
+
placeClusters(true);
|
|
60
|
+
},
|
|
61
|
+
[placeClusters]
|
|
62
|
+
);
|
|
63
|
+
const clusters = hosts ? (
|
|
64
|
+
// Kept mounted in ONE place, whatever the bar is showing; `placeClusters` moves the elements.
|
|
65
|
+
/* @__PURE__ */ jsx(TopbarClusterHolder, { ref: holderRef, visible: collapsed })
|
|
66
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
31
67
|
start != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-start", className: "ui-topbar-start", children: start }) : null,
|
|
32
68
|
center != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-center", className: "ui-topbar-center", children: center }) : null,
|
|
33
69
|
end != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-end", className: "ui-topbar-end", children: end }) : null
|
|
34
70
|
] });
|
|
35
|
-
|
|
71
|
+
const portals = hosts ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
72
|
+
start != null ? createPortal(start, hosts.start) : null,
|
|
73
|
+
center != null ? createPortal(center, hosts.center) : null,
|
|
74
|
+
end != null ? createPortal(end, hosts.end) : null
|
|
75
|
+
] }) : null;
|
|
76
|
+
const overflowMenu = /* @__PURE__ */ jsx(TopbarOverflowMenu, { contentRef: hosts ? overflowNode : void 0, children: hosts ? null : clusters });
|
|
77
|
+
return /* @__PURE__ */ jsxs(
|
|
36
78
|
"div",
|
|
37
79
|
{
|
|
38
80
|
ref: barRef,
|
|
@@ -48,36 +90,81 @@ function Topbar({
|
|
|
48
90
|
"data-slot": "topbar",
|
|
49
91
|
className: cn("ui-topbar", className),
|
|
50
92
|
...props,
|
|
51
|
-
children:
|
|
52
|
-
/* @__PURE__ */
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
children: [
|
|
94
|
+
children ?? (hasIdentity ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
95
|
+
/* @__PURE__ */ jsxs(
|
|
96
|
+
"div",
|
|
97
|
+
{
|
|
98
|
+
ref: controlsRef,
|
|
99
|
+
onFocusCapture: revealKeyboardFocus,
|
|
100
|
+
"data-slot": "topbar-controls",
|
|
101
|
+
"data-overflow": overflow,
|
|
102
|
+
className: "ui-topbar ui-topbar-controls",
|
|
103
|
+
children: [
|
|
104
|
+
collapsed ? overflowMenu : null,
|
|
105
|
+
collapsed && !hosts ? null : clusters
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
/* @__PURE__ */ jsx("div", { "data-slot": "topbar-identity", className: "ui-topbar-identity", children: identity })
|
|
110
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
111
|
+
collapsed ? overflowMenu : null,
|
|
112
|
+
collapsed && !hosts ? null : clusters
|
|
113
|
+
] })),
|
|
114
|
+
portals
|
|
115
|
+
]
|
|
65
116
|
}
|
|
66
117
|
);
|
|
67
118
|
}
|
|
68
|
-
|
|
119
|
+
const CLUSTER_ORDER = ["start", "center", "end"];
|
|
120
|
+
const subscribeNothing = () => () => {
|
|
121
|
+
};
|
|
122
|
+
function useFoldHosts(enabled) {
|
|
123
|
+
const isClient = React.useSyncExternalStore(
|
|
124
|
+
subscribeNothing,
|
|
125
|
+
() => true,
|
|
126
|
+
() => false
|
|
127
|
+
);
|
|
128
|
+
const [hosts] = React.useState(() => {
|
|
129
|
+
if (typeof document === "undefined") return null;
|
|
130
|
+
const make = (key) => {
|
|
131
|
+
const node = document.createElement("div");
|
|
132
|
+
node.dataset.slot = `topbar-${key}`;
|
|
133
|
+
node.className = `ui-topbar-${key}`;
|
|
134
|
+
return node;
|
|
135
|
+
};
|
|
136
|
+
return { start: make("start"), center: make("center"), end: make("end") };
|
|
137
|
+
});
|
|
138
|
+
React.useEffect(
|
|
139
|
+
() => () => {
|
|
140
|
+
if (hosts) for (const key of CLUSTER_ORDER) hosts[key].remove();
|
|
141
|
+
},
|
|
142
|
+
[hosts]
|
|
143
|
+
);
|
|
144
|
+
return enabled && isClient ? hosts : null;
|
|
145
|
+
}
|
|
146
|
+
const TopbarClusterHolder = React.forwardRef(
|
|
147
|
+
function TopbarClusterHolder2({ visible }, ref) {
|
|
148
|
+
return visible ? /* @__PURE__ */ jsx("div", { ref, hidden: true, "data-slot": "topbar-fold-holder" }) : null;
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
function TopbarOverflowMenu({
|
|
152
|
+
children,
|
|
153
|
+
contentRef
|
|
154
|
+
}) {
|
|
69
155
|
const { t } = useTranslation();
|
|
70
156
|
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
71
157
|
/* @__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" }) }) }),
|
|
72
|
-
/* @__PURE__ */ jsx(PopoverContent, { align: "end", width: "auto", children: /* @__PURE__ */ jsx("div", { "data-slot": "topbar-overflow", className: "ui-topbar-overflow", children }) })
|
|
158
|
+
/* @__PURE__ */ jsx(PopoverContent, { align: "end", width: "auto", children: /* @__PURE__ */ jsx("div", { ref: contentRef, "data-slot": "topbar-overflow", className: "ui-topbar-overflow", children }) })
|
|
73
159
|
] });
|
|
74
160
|
}
|
|
75
|
-
function useCollapsesToMenu(ref, enabled) {
|
|
161
|
+
function useCollapsesToMenu(ref, enabled, place) {
|
|
76
162
|
const [collapsed, setCollapsed] = React.useState(false);
|
|
77
163
|
const needed = React.useRef(0);
|
|
78
164
|
React.useLayoutEffect(() => {
|
|
79
165
|
const bar = ref.current;
|
|
80
166
|
if (!enabled || !bar) return void 0;
|
|
167
|
+
place?.(collapsed);
|
|
81
168
|
const update = () => {
|
|
82
169
|
if (!collapsed) {
|
|
83
170
|
if (bar.clientWidth > 0 && bar.scrollWidth - bar.clientWidth > 1) {
|
|
@@ -100,7 +187,7 @@ function useCollapsesToMenu(ref, enabled) {
|
|
|
100
187
|
}
|
|
101
188
|
}
|
|
102
189
|
return () => observer.disconnect();
|
|
103
|
-
}, [ref, enabled, collapsed]);
|
|
190
|
+
}, [ref, enabled, collapsed, place]);
|
|
104
191
|
return enabled && collapsed;
|
|
105
192
|
}
|
|
106
193
|
export {
|
|
@@ -169,6 +169,14 @@ interface DropdownMenuItemPropsOwn {
|
|
|
169
169
|
description?: React.ReactNode;
|
|
170
170
|
/** Mượn thẻ của con thay vì dựng `<div>` riêng — chỗ để nhét một `<Link>` vào một mục menu. */
|
|
171
171
|
asChild?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* The item is a link to this URL. With `asChild` it is read from the borrowed child's own `href`
|
|
174
|
+
* when there is one; pass it here when the child is a router link that takes something else
|
|
175
|
+
* (TanStack/React Router `<Link to>`), so react-aria renders its LINK item — an `<a>` — instead of
|
|
176
|
+
* an action `<div>` (and stops warning "Expected <div>, got <a>"). Without a react-aria
|
|
177
|
+
* `RouterProvider` the click stays the child's own: navigation is whatever the child does.
|
|
178
|
+
*/
|
|
179
|
+
href?: string;
|
|
172
180
|
/**
|
|
173
181
|
* `id` và mọi `data-*` ĐI XUỐNG phần tử gốc của item (gh#631).
|
|
174
182
|
*
|
|
@@ -183,7 +191,7 @@ interface DropdownMenuItemPropsOwn {
|
|
|
183
191
|
id?: string;
|
|
184
192
|
}
|
|
185
193
|
type DropdownMenuItemProps = React.PropsWithChildren<DropdownMenuItemPropsOwn> & Record<`data-${string}`, unknown>;
|
|
186
|
-
export declare function DropdownMenuItem({ children, className, inset, variant, disabled, onSelect, textValue, description, asChild, ...forwarded }: DropdownMenuItemProps): React.JSX.Element;
|
|
194
|
+
export declare function DropdownMenuItem({ children, className, inset, variant, disabled, onSelect, textValue, description, asChild, href, ...forwarded }: DropdownMenuItemProps): React.JSX.Element;
|
|
187
195
|
interface DropdownMenuLabelPropsOwn {
|
|
188
196
|
className?: string;
|
|
189
197
|
inset?: boolean;
|
|
@@ -197,10 +205,12 @@ type DropdownMenuLabelProps = React.PropsWithChildren<DropdownMenuLabelPropsOwn>
|
|
|
197
205
|
* `Header` không đi qua `useRenderProps`, nên `props.children` trong `render` CHÍNH là children của
|
|
198
206
|
* consumer — không có `Provider` chen vào như ở `MenuItem`. `Slot` vì thế nhận thẳng gói props.
|
|
199
207
|
*
|
|
200
|
-
*
|
|
208
|
+
* `<header>` TRẦN thì không được: nó mang vai trò ngầm `banner`, và
|
|
201
209
|
* `banner` không nằm trong danh sách con hợp lệ của `role="menu"` (axe: aria-required-children,
|
|
202
210
|
* ARIA 1.2 — menu chỉ nhận menuitem / menuitemcheckbox / menuitemradio / group / separator). Radix
|
|
203
|
-
* dựng `<div>`, vai trò `generic`, nên nó không bao giờ vấp lỗi này.
|
|
211
|
+
* dựng `<div>`, vai trò `generic`, nên nó không bao giờ vấp lỗi này. Còn `<div>` thì RAC cảnh báo
|
|
212
|
+
* "Expected <header>, got <div>" (gh#1452). Nên: `<header role="presentation">` — đúng thẻ RAC chờ,
|
|
213
|
+
* không có landmark.
|
|
204
214
|
*
|
|
205
215
|
* `Header` vẫn được dùng chứ không thay bằng `<div>` trần: bộ dựng collection của RAC chỉ nhận các
|
|
206
216
|
* nút nó biết, một phần tử lạ đặt thẳng trong `Menu` sẽ bị nó bỏ. Nên giữ nút, đổi thẻ.
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { Check, ChevronRight } from "lucide-react";
|
|
23
23
|
import { ariaKeyShortcuts, parseShortcut, shouldHandleShortcut } from "../../lib/shortcut.js";
|
|
24
24
|
import { cn } from "../../lib/utils.js";
|
|
25
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
25
26
|
import { Slot } from "../../lib/slot.js";
|
|
26
27
|
const DROPDOWN_MENU_PLACEMENT = {
|
|
27
28
|
top: { side: "top", align: "center" },
|
|
@@ -40,7 +41,7 @@ class MenuCollectionBoundary extends React.Component {
|
|
|
40
41
|
// of gh#637 that cost the reporter a night of instrumenting `Object.prototype`.
|
|
41
42
|
componentDidCatch(error) {
|
|
42
43
|
console.error(
|
|
43
|
-
"[@godxjp/ui] DropdownMenu: the menu could not be built and was left empty; the rest of the application is unaffected. A menu's children must be collection nodes (DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuSub) or components returning one \u2014 an icon or a text label belongs INSIDE an item, not beside it. Underlying error:",
|
|
44
|
+
"[@godxjp/ui] DropdownMenu: the menu could not be built and was left empty; the rest of the application is unaffected. A menu's children must be collection nodes (DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuSub) or components returning one \u2014 an icon or a text label belongs INSIDE an item, not beside it. Use Popover + ListRow for arbitrary panel content. Underlying error:",
|
|
44
45
|
error
|
|
45
46
|
);
|
|
46
47
|
}
|
|
@@ -51,6 +52,10 @@ class MenuCollectionBoundary extends React.Component {
|
|
|
51
52
|
function borrowedTag(children, asChild) {
|
|
52
53
|
return asChild ? React.Children.only(children) : null;
|
|
53
54
|
}
|
|
55
|
+
function borrowedHref(tag) {
|
|
56
|
+
const href = tag?.props?.href;
|
|
57
|
+
return typeof href === "string" ? href : void 0;
|
|
58
|
+
}
|
|
54
59
|
function renderItemTag(tag, domProps, content) {
|
|
55
60
|
if (!tag) {
|
|
56
61
|
return /* @__PURE__ */ jsx("div", { ...domProps, children: content });
|
|
@@ -331,6 +336,16 @@ function DropdownMenuSub({ children }) {
|
|
|
331
336
|
const parts = React.Children.toArray(children).filter(React.isValidElement);
|
|
332
337
|
return /* @__PURE__ */ jsx(SubmenuTrigger, { children: parts });
|
|
333
338
|
}
|
|
339
|
+
function hasNonMenuChildren(children) {
|
|
340
|
+
return React.Children.toArray(children).some((child) => {
|
|
341
|
+
if (!React.isValidElement(child)) return true;
|
|
342
|
+
if (typeof child.type === "string") return true;
|
|
343
|
+
if (child.type === React.Fragment || child.type === DropdownMenuGroup || child.type === DropdownMenuRadioGroup) {
|
|
344
|
+
return hasNonMenuChildren(child.props.children);
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
});
|
|
348
|
+
}
|
|
334
349
|
function DropdownMenuContent({
|
|
335
350
|
children,
|
|
336
351
|
className,
|
|
@@ -348,6 +363,13 @@ function DropdownMenuContent({
|
|
|
348
363
|
sticky,
|
|
349
364
|
forceMount
|
|
350
365
|
}) {
|
|
366
|
+
React.useEffect(() => {
|
|
367
|
+
if (isDevelopment() && hasNonMenuChildren(children)) {
|
|
368
|
+
console.error(
|
|
369
|
+
"[@godxjp/ui] DropdownMenuContent accepts menu collection children only. Use DropdownMenuItem for actions; use Popover + ListRow for notifications, headers, lists and links."
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}, [children]);
|
|
351
373
|
void hideWhenDetached;
|
|
352
374
|
void sticky;
|
|
353
375
|
void forceMount;
|
|
@@ -417,6 +439,7 @@ function DropdownMenuItem({
|
|
|
417
439
|
textValue,
|
|
418
440
|
description,
|
|
419
441
|
asChild,
|
|
442
|
+
href,
|
|
420
443
|
...forwarded
|
|
421
444
|
}) {
|
|
422
445
|
const tag = borrowedTag(children, asChild);
|
|
@@ -426,6 +449,7 @@ function DropdownMenuItem({
|
|
|
426
449
|
MenuItem,
|
|
427
450
|
{
|
|
428
451
|
isDisabled: disabled,
|
|
452
|
+
href: href ?? borrowedHref(tag),
|
|
429
453
|
textValue: textValue ?? (typeof label === "string" ? label : void 0),
|
|
430
454
|
onAction: onSelect ? () => onSelect(selectEvent("dropdownmenu.itemSelect")) : void 0,
|
|
431
455
|
className: cn(
|
|
@@ -477,11 +501,17 @@ function DropdownMenuLabel({ children, className, inset, asChild }) {
|
|
|
477
501
|
{
|
|
478
502
|
...props
|
|
479
503
|
}
|
|
480
|
-
) : (props) =>
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
504
|
+
) : (props) => (
|
|
505
|
+
// RAC's `Header` element (it warns in dev when `render` returns another one, gh#1452),
|
|
506
|
+
// with the landmark dropped: `role="presentation"` is allowed on `header` and keeps
|
|
507
|
+
// the menu owning no `banner`.
|
|
508
|
+
/* @__PURE__ */ jsx(
|
|
509
|
+
"header",
|
|
510
|
+
{
|
|
511
|
+
role: "presentation",
|
|
512
|
+
...props
|
|
513
|
+
}
|
|
514
|
+
)
|
|
485
515
|
),
|
|
486
516
|
children
|
|
487
517
|
}
|
|
@@ -22,5 +22,5 @@ declare const PaginationNext: React.ForwardRefExoticComponent<Omit<Omit<React.De
|
|
|
22
22
|
disabled?: boolean;
|
|
23
23
|
href?: string;
|
|
24
24
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
25
|
-
export declare function Pagination({ ariaLabel, value, total, pageSize, pageSizeOptions, showSizeChanger, showTotal, hideOnSinglePage, simple, showQuickJumper, size, align, responsive, disabled, className, onValueChange, }: PaginationProp): React.JSX.Element | null;
|
|
25
|
+
export declare function Pagination({ ariaLabel, value, total, pageSize, pageSizeOptions, showSizeChanger, showTotal, hideOnSinglePage, simple, showQuickJumper, size, align, responsive, disabled, className, onValueChange, onPageIntent, }: PaginationProp): React.JSX.Element | null;
|
|
26
26
|
export { PaginationContent, PaginationItem, PaginationLink, PaginationEllipsis, PaginationPrevious, PaginationNext, };
|
|
@@ -122,7 +122,8 @@ function Pagination({
|
|
|
122
122
|
responsive = true,
|
|
123
123
|
disabled,
|
|
124
124
|
className,
|
|
125
|
-
onValueChange
|
|
125
|
+
onValueChange,
|
|
126
|
+
onPageIntent
|
|
126
127
|
}) {
|
|
127
128
|
const { t } = useTranslation();
|
|
128
129
|
const jumperId = React.useId();
|
|
@@ -138,6 +139,12 @@ function Pagination({
|
|
|
138
139
|
const nextPage = Math.min(Math.max(1, page), Math.max(1, Math.ceil(total / size2)));
|
|
139
140
|
onValueChange?.(nextPage, size2);
|
|
140
141
|
};
|
|
142
|
+
const intent = (page) => {
|
|
143
|
+
const notify = () => {
|
|
144
|
+
if (!disabled && page >= 1 && page <= totalPages) onPageIntent?.(page);
|
|
145
|
+
};
|
|
146
|
+
return { onPointerEnter: notify, onFocus: notify, onTouchStart: notify };
|
|
147
|
+
};
|
|
141
148
|
const commitJump = () => {
|
|
142
149
|
const parsed = Number.parseInt(jumperDraft, 10);
|
|
143
150
|
setJumperDraft("");
|
|
@@ -204,6 +211,7 @@ function Pagination({
|
|
|
204
211
|
size: "sm",
|
|
205
212
|
disabled: Boolean(disabled) || safeCurrent <= 1,
|
|
206
213
|
"aria-label": t("navigation.pagination.prev"),
|
|
214
|
+
...intent(safeCurrent - 1),
|
|
207
215
|
onClick: () => go(safeCurrent - 1),
|
|
208
216
|
children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true" })
|
|
209
217
|
}
|
|
@@ -221,6 +229,7 @@ function Pagination({
|
|
|
221
229
|
size: "sm",
|
|
222
230
|
disabled: Boolean(disabled) || safeCurrent >= totalPages,
|
|
223
231
|
"aria-label": t("navigation.pagination.next"),
|
|
232
|
+
...intent(safeCurrent + 1),
|
|
224
233
|
onClick: () => go(safeCurrent + 1),
|
|
225
234
|
children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
|
|
226
235
|
}
|
|
@@ -264,6 +273,7 @@ function Pagination({
|
|
|
264
273
|
PaginationPrevious,
|
|
265
274
|
{
|
|
266
275
|
"aria-label": t("navigation.pagination.prev"),
|
|
276
|
+
...intent(safeCurrent - 1),
|
|
267
277
|
onClick: () => go(safeCurrent - 1),
|
|
268
278
|
disabled: Boolean(disabled) || safeCurrent <= 1,
|
|
269
279
|
children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true" })
|
|
@@ -274,6 +284,7 @@ function Pagination({
|
|
|
274
284
|
PaginationLink,
|
|
275
285
|
{
|
|
276
286
|
isActive: page === safeCurrent,
|
|
287
|
+
...intent(page),
|
|
277
288
|
"aria-label": t("navigation.pagination.page", { page }),
|
|
278
289
|
onClick: () => {
|
|
279
290
|
if (!disabled) go(page);
|
|
@@ -287,6 +298,7 @@ function Pagination({
|
|
|
287
298
|
PaginationNext,
|
|
288
299
|
{
|
|
289
300
|
"aria-label": t("navigation.pagination.next"),
|
|
301
|
+
...intent(safeCurrent + 1),
|
|
290
302
|
onClick: () => go(safeCurrent + 1),
|
|
291
303
|
disabled: Boolean(disabled) || safeCurrent >= totalPages,
|
|
292
304
|
children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
|
|
@@ -132,7 +132,8 @@ function Toolbar({
|
|
|
132
132
|
onValueChange: (selected) => filter.onSelectedChange?.(selected),
|
|
133
133
|
placeholder: filter.placeholder,
|
|
134
134
|
disabled: disabled || filter.disabled,
|
|
135
|
-
allowClear: true,
|
|
135
|
+
allowClear: filter.required ? false : filter.allowClear ?? true,
|
|
136
|
+
"aria-required": filter.required || void 0,
|
|
136
137
|
clearLabel: typeof filter.label === "string" ? t("navigation.filterBar.clearFilter", { label: filter.label }) : void 0
|
|
137
138
|
}
|
|
138
139
|
)
|
|
@@ -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": "33.
|
|
3
|
+
"version": "33.25.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,
|
|
@@ -382,10 +382,16 @@
|
|
|
382
382
|
"position": "Cover position",
|
|
383
383
|
"hint": "Drag the image, or use the arrow keys, to reposition it",
|
|
384
384
|
"valueText": "{value}% from the top"
|
|
385
|
+
},
|
|
386
|
+
"pageErrorBoundary": {
|
|
387
|
+
"title": "This page could not be displayed",
|
|
388
|
+
"description": "Something went wrong while showing this page. The rest of the app still works: reload the page, or go to another page from the menu.",
|
|
389
|
+
"reload": "Reload page"
|
|
385
390
|
}
|
|
386
391
|
},
|
|
387
392
|
"dataDisplay": {
|
|
388
393
|
"avatar": {
|
|
394
|
+
"agent": "AI agent",
|
|
389
395
|
"presence": {
|
|
390
396
|
"online": "Online",
|
|
391
397
|
"away": "Away",
|
|
@@ -805,6 +811,7 @@
|
|
|
805
811
|
"trendCurrent": "current",
|
|
806
812
|
"heatmapSummary": "Heatmap, {rows} × {columns}",
|
|
807
813
|
"heatmapHighest": "Highest: {value} at {row}, {column}",
|
|
814
|
+
"noData": "No data",
|
|
808
815
|
"heatmapNoData": "No data",
|
|
809
816
|
"heatmapTop": "Top {rank}",
|
|
810
817
|
"heatmapLegend": "Color scale",
|
|
@@ -374,10 +374,16 @@
|
|
|
374
374
|
"position": "カバー画像の位置",
|
|
375
375
|
"hint": "画像をドラッグするか矢印キーで位置を調整",
|
|
376
376
|
"valueText": "上から {value}%"
|
|
377
|
+
},
|
|
378
|
+
"pageErrorBoundary": {
|
|
379
|
+
"title": "このページを表示できませんでした",
|
|
380
|
+
"description": "ページの表示中に問題が発生しました。アプリの他の部分は使えます。ページを再読み込みするか、メニューから別のページに移動してください。",
|
|
381
|
+
"reload": "ページを再読み込み"
|
|
377
382
|
}
|
|
378
383
|
},
|
|
379
384
|
"dataDisplay": {
|
|
380
385
|
"avatar": {
|
|
386
|
+
"agent": "AIエージェント",
|
|
381
387
|
"presence": {
|
|
382
388
|
"online": "オンライン",
|
|
383
389
|
"away": "離席中",
|
|
@@ -779,6 +785,7 @@
|
|
|
779
785
|
"trendCurrent": "現在",
|
|
780
786
|
"heatmapSummary": "ヒートマップ({rows}行×{columns}列)",
|
|
781
787
|
"heatmapHighest": "最大 {value}({row}・{column})",
|
|
788
|
+
"noData": "データなし",
|
|
782
789
|
"heatmapNoData": "データなし",
|
|
783
790
|
"heatmapTop": "上位{rank}位",
|
|
784
791
|
"heatmapLegend": "色の濃さの凡例",
|