@godxjp/ui 33.12.8 → 33.14.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/README.md +8 -4
- package/agent/START-HERE.md +5 -5
- package/agent/components/BranchScopePicker.json +21 -1
- package/agent/components/Breadcrumb.json +8 -2
- package/agent/components/DataTable.json +2 -1
- package/agent/components/MobileShell.json +1 -0
- package/agent/components/PageContainer.json +6 -0
- package/agent/components/Persona.json +66 -0
- package/agent/components/Sidebar.json +1 -0
- package/agent/components/Table.json +1 -1
- package/agent/components-index.json +5 -0
- package/agent/components.json +106 -5
- package/agent/index.json +7 -7
- package/agent/llms.txt +6 -6
- package/agent/tokens.json +62 -2
- package/agent/vocabulary.json +13 -0
- package/dist/components/data-display/data-table.js +51 -10
- package/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/persona.d.ts +11 -0
- package/dist/components/data-display/persona.js +34 -0
- package/dist/components/data-entry/branch-scope-picker.d.ts +3 -0
- package/dist/components/data-entry/branch-scope-picker.js +65 -11
- package/dist/components/layout/breadcrumb-ellipsis.js +33 -1
- package/dist/components/layout/breadcrumb.d.ts +4 -2
- package/dist/components/layout/breadcrumb.js +13 -3
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +5 -3
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +2 -1
- package/dist/i18n/messages/ja.json +13 -12
- package/dist/i18n/messages/vi.json +2 -1
- package/dist/props/components/data-display.prop.d.ts +10 -0
- package/dist/props/components/data-entry.prop.d.ts +6 -0
- package/dist/props/components/layout.prop.d.ts +5 -1
- package/dist/props/registry.d.ts +24 -2
- package/dist/props/registry.js +26 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/navigation.prop.d.ts +2 -0
- package/dist/styles/control.css +29 -3
- package/dist/styles/data-display-layout.css +58 -0
- package/dist/styles/layers.json +8 -2
- package/dist/styles/table-layout.css +32 -12
- package/dist/styles.css +1 -1
- package/dist/tokens/base.css +2 -0
- package/dist/tokens/components/persona.css +13 -0
- package/dist/tokens/components/table.css +1 -1
- package/docs/TOKENS.md +11 -0
- package/package.json +2 -2
- package/scripts/cli.mjs +8 -2
- package/scripts/visual-audit.mjs +119 -27
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> A Japanese-enterprise React design system:
|
|
4
|
-
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.
|
|
3
|
+
> A Japanese-enterprise React design system: 170 components, 2251 design tokens,
|
|
4
|
+
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.14.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@33.
|
|
7
|
+
(`npx @godxjp/ui-mcp@33.14.0`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -16,10 +16,10 @@ that can only fetch URLs.
|
|
|
16
16
|
## Catalog
|
|
17
17
|
|
|
18
18
|
- [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 28 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
|
|
19
|
-
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 53 KB — all
|
|
19
|
+
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 53 KB — all 170 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
|
|
20
20
|
- [components/<Name>.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–37 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.
|
|
21
21
|
- [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.3 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
|
|
22
|
-
- [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` — 218 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles,
|
|
22
|
+
- [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` — 218 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1920 `component` knobs.
|
|
23
23
|
- [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.
|
|
24
24
|
- [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 44 cardinal rules.
|
|
25
25
|
- [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 35 shapes that make generated UI look generated, each with its fix.
|
|
@@ -27,7 +27,7 @@ that can only fetch URLs.
|
|
|
27
27
|
## Pinning
|
|
28
28
|
|
|
29
29
|
Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
|
|
30
|
-
the tag: `.../godx-jp/godxjp-ui/v33.
|
|
30
|
+
the tag: `.../godx-jp/godxjp-ui/v33.14.0/agent/...`. A catalog that does not match the installed
|
|
31
31
|
package describes props that are absent, or hides props that are present, and says nothing either way.
|
|
32
32
|
|
|
33
33
|
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
|
@@ -10073,6 +10073,66 @@
|
|
|
10073
10073
|
"tier": "component",
|
|
10074
10074
|
"value": "var(--space-2)"
|
|
10075
10075
|
},
|
|
10076
|
+
{
|
|
10077
|
+
"description": "Default var(--space-2).",
|
|
10078
|
+
"name": "--persona-avatar-gap",
|
|
10079
|
+
"tier": "component",
|
|
10080
|
+
"value": "initial"
|
|
10081
|
+
},
|
|
10082
|
+
{
|
|
10083
|
+
"description": "Default zero, set at the call site.",
|
|
10084
|
+
"name": "--persona-label-gap",
|
|
10085
|
+
"tier": "component",
|
|
10086
|
+
"value": "initial"
|
|
10087
|
+
},
|
|
10088
|
+
{
|
|
10089
|
+
"description": "Default var(--space-1).",
|
|
10090
|
+
"name": "--persona-trailing-gap",
|
|
10091
|
+
"tier": "component",
|
|
10092
|
+
"value": "initial"
|
|
10093
|
+
},
|
|
10094
|
+
{
|
|
10095
|
+
"description": "Identity geometry. Initial spacing/type defaults resolve against the local density.",
|
|
10096
|
+
"name": "--persona-label-line-height",
|
|
10097
|
+
"tier": "component",
|
|
10098
|
+
"value": "1.5"
|
|
10099
|
+
},
|
|
10100
|
+
{
|
|
10101
|
+
"description": "Default var(--font-size-sm).",
|
|
10102
|
+
"name": "--persona-name-font-size",
|
|
10103
|
+
"tier": "component",
|
|
10104
|
+
"value": "initial"
|
|
10105
|
+
},
|
|
10106
|
+
{
|
|
10107
|
+
"description": "Default var(--font-size-xs).",
|
|
10108
|
+
"name": "--persona-name-font-size-sm",
|
|
10109
|
+
"tier": "component",
|
|
10110
|
+
"value": "initial"
|
|
10111
|
+
},
|
|
10112
|
+
{
|
|
10113
|
+
"description": "Default var(--font-weight-medium).",
|
|
10114
|
+
"name": "--persona-name-font-weight",
|
|
10115
|
+
"tier": "component",
|
|
10116
|
+
"value": "initial"
|
|
10117
|
+
},
|
|
10118
|
+
{
|
|
10119
|
+
"description": "Default var(--font-size-xs).",
|
|
10120
|
+
"name": "--persona-description-font-size",
|
|
10121
|
+
"tier": "component",
|
|
10122
|
+
"value": "initial"
|
|
10123
|
+
},
|
|
10124
|
+
{
|
|
10125
|
+
"description": "Role mirror: hsl(var(--foreground)).",
|
|
10126
|
+
"name": "--persona-name-color",
|
|
10127
|
+
"tier": "component",
|
|
10128
|
+
"value": "initial"
|
|
10129
|
+
},
|
|
10130
|
+
{
|
|
10131
|
+
"description": "Role mirror: hsl(var(--muted-foreground)).",
|
|
10132
|
+
"name": "--persona-description-color",
|
|
10133
|
+
"tier": "component",
|
|
10134
|
+
"value": "initial"
|
|
10135
|
+
},
|
|
10076
10136
|
{
|
|
10077
10137
|
"description": "Separator layout thickness; focus paint must not reserve extra space.",
|
|
10078
10138
|
"name": "--resizable-handle-size",
|
|
@@ -12534,10 +12594,10 @@
|
|
|
12534
12594
|
"value": "var(--space-stack-sm)"
|
|
12535
12595
|
},
|
|
12536
12596
|
{
|
|
12537
|
-
"description": "
|
|
12597
|
+
"description": "default = Card inset, resolved at the row",
|
|
12538
12598
|
"name": "--table-stacked-collection-card-padding-x",
|
|
12539
12599
|
"tier": "component",
|
|
12540
|
-
"value": "
|
|
12600
|
+
"value": "initial"
|
|
12541
12601
|
},
|
|
12542
12602
|
{
|
|
12543
12603
|
"description": "Table component tokens: row height, cell padding.",
|
package/agent/vocabulary.json
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"concept": "Which breadcrumb item represents the current page.",
|
|
4
|
+
"name": "BreadcrumbCurrentItemProp",
|
|
5
|
+
"notes": "Defaults to last. none retains ancestor links without aria-current; supply to for each ancestor.",
|
|
6
|
+
"usedBy": [
|
|
7
|
+
"Breadcrumb",
|
|
8
|
+
"PageContainer"
|
|
9
|
+
],
|
|
10
|
+
"values": [
|
|
11
|
+
"last",
|
|
12
|
+
"none"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
2
15
|
{
|
|
3
16
|
"concept": "Abstract controlled value.",
|
|
4
17
|
"name": "ValueProp<T = string>",
|
|
@@ -80,6 +80,33 @@ import {
|
|
|
80
80
|
} from "../../lib/control-styles.js";
|
|
81
81
|
import { isDevelopment } from "../../lib/dev.js";
|
|
82
82
|
import { pageSizeOptionsWith } from "../navigation/pagination-utils.js";
|
|
83
|
+
function recoverCellText(cell) {
|
|
84
|
+
if (!cell) return;
|
|
85
|
+
const part = truncatedPart(cell);
|
|
86
|
+
const value = part && part !== cell ? part : cell.querySelector(".ui-table-stacked-collection-value") ?? cell;
|
|
87
|
+
const selector = '[title]:not([title=""]), [aria-describedby]';
|
|
88
|
+
const carrier = value.closest(selector);
|
|
89
|
+
const recovery = carrier && carrier !== cell ? carrier : value.querySelector(selector);
|
|
90
|
+
if (recovery) {
|
|
91
|
+
if (cell.dataset.cellTitle !== void 0) {
|
|
92
|
+
cell.removeAttribute("title");
|
|
93
|
+
delete cell.dataset.cellTitle;
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!cell.title || cell.dataset.cellTitle !== void 0) {
|
|
98
|
+
const content = value.cloneNode(true);
|
|
99
|
+
content.querySelectorAll('[aria-hidden="true"], [hidden], .sr-only, [role="tooltip"]').forEach((node) => node.remove());
|
|
100
|
+
const text = (content.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
101
|
+
if (text) {
|
|
102
|
+
if (cell.title !== text) cell.title = text;
|
|
103
|
+
cell.dataset.cellTitle = "";
|
|
104
|
+
} else {
|
|
105
|
+
cell.removeAttribute("title");
|
|
106
|
+
delete cell.dataset.cellTitle;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
83
110
|
function resolveColumnHideBelow(col) {
|
|
84
111
|
if (col.hideBelow) return col.hideBelow;
|
|
85
112
|
if (col.hiddenOnMobile) return "md";
|
|
@@ -913,27 +940,40 @@ DataTable.Content = function DataTableContent() {
|
|
|
913
940
|
observer.disconnect();
|
|
914
941
|
};
|
|
915
942
|
}, [fixedSignature]);
|
|
916
|
-
const
|
|
943
|
+
const ellipsisEnabled = visibleColumns.some((col) => col.ellipsis);
|
|
944
|
+
const hasEllipsis = !stacked && ellipsisEnabled;
|
|
945
|
+
React.useLayoutEffect(() => {
|
|
946
|
+
const root = scrollRef.current;
|
|
947
|
+
if (!root || !ellipsisEnabled) return;
|
|
948
|
+
const sync = () => {
|
|
949
|
+
for (const cell of root.querySelectorAll(
|
|
950
|
+
"tbody td.ui-data-table-ellipsis"
|
|
951
|
+
))
|
|
952
|
+
recoverCellText(cell);
|
|
953
|
+
};
|
|
954
|
+
sync();
|
|
955
|
+
const observer = new MutationObserver(sync);
|
|
956
|
+
observer.observe(root, {
|
|
957
|
+
subtree: true,
|
|
958
|
+
childList: true,
|
|
959
|
+
characterData: true,
|
|
960
|
+
attributes: true,
|
|
961
|
+
attributeFilter: ["title", "aria-describedby"]
|
|
962
|
+
});
|
|
963
|
+
return () => observer.disconnect();
|
|
964
|
+
});
|
|
917
965
|
const markTruncated = React.useCallback(() => {
|
|
918
966
|
const el = scrollRef.current;
|
|
919
967
|
if (!el) return;
|
|
920
968
|
for (const cell of el.querySelectorAll("tbody td.ui-data-table-ellipsis")) {
|
|
921
969
|
if (cell.contains(document.activeElement)) continue;
|
|
922
|
-
|
|
970
|
+
recoverCellText(cell);
|
|
923
971
|
const part = truncatedPart(cell);
|
|
924
972
|
if (part) {
|
|
925
973
|
cell.dataset.truncated = "";
|
|
926
|
-
if ((!cell.title || ownTitle) && (part === cell || !part.title)) {
|
|
927
|
-
cell.title = (part.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
928
|
-
cell.dataset.truncatedTitle = "";
|
|
929
|
-
}
|
|
930
974
|
if (!cell.querySelector(TRUNCATION_FOCUSABLE)) cell.tabIndex = 0;
|
|
931
975
|
} else if (cell.dataset.truncated !== void 0) {
|
|
932
976
|
delete cell.dataset.truncated;
|
|
933
|
-
if (ownTitle) {
|
|
934
|
-
cell.removeAttribute("title");
|
|
935
|
-
delete cell.dataset.truncatedTitle;
|
|
936
|
-
}
|
|
937
977
|
cell.removeAttribute("tabindex");
|
|
938
978
|
}
|
|
939
979
|
}
|
|
@@ -1380,6 +1420,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1380
1420
|
return /* @__PURE__ */ jsx(
|
|
1381
1421
|
TableCell,
|
|
1382
1422
|
{
|
|
1423
|
+
ref: col.ellipsis ? recoverCellText : void 0,
|
|
1383
1424
|
priority: col.priority,
|
|
1384
1425
|
"data-align": col.align,
|
|
1385
1426
|
title,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Persona } from "./persona.js";
|
|
1
2
|
import { Badge, StatusBadge } from "./badge.js";
|
|
2
3
|
import { Legend } from "./legend.js";
|
|
3
4
|
import { Swatch } from "./swatch.js";
|
|
@@ -78,6 +79,7 @@ export {
|
|
|
78
79
|
Legend,
|
|
79
80
|
ListRow,
|
|
80
81
|
PermissionMatrix,
|
|
82
|
+
Persona,
|
|
81
83
|
Popover,
|
|
82
84
|
PopoverAnchor,
|
|
83
85
|
PopoverContent,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type { PersonaProp, PersonaProp as PersonaProps, } from "../../props/components/data-display.prop.js";
|
|
3
|
+
/** A person's identity, independent of row chrome, navigation and account actions. */
|
|
4
|
+
export declare const Persona: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> & {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
avatarSrc?: string;
|
|
8
|
+
avatarFallback?: React.ReactNode;
|
|
9
|
+
trailing?: React.ReactNode;
|
|
10
|
+
size?: Extract<import("../../props/index.js").SizeProp, "sm" | "md">;
|
|
11
|
+
} & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
|
|
6
|
+
const Persona = React.forwardRef(
|
|
7
|
+
({ name, description, avatarSrc, avatarFallback, trailing, size = "md", className, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
8
|
+
"span",
|
|
9
|
+
{
|
|
10
|
+
ref,
|
|
11
|
+
"data-slot": "persona",
|
|
12
|
+
"data-size": size,
|
|
13
|
+
className: cn("ui-persona", className),
|
|
14
|
+
...props,
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ jsxs(Avatar, { size, "aria-hidden": "true", children: [
|
|
17
|
+
avatarSrc ? /* @__PURE__ */ jsx(AvatarImage, { src: avatarSrc, alt: "" }) : null,
|
|
18
|
+
/* @__PURE__ */ jsx(AvatarFallback, { children: avatarFallback ?? Array.from(name)[0] })
|
|
19
|
+
] }),
|
|
20
|
+
/* @__PURE__ */ jsxs("span", { "data-slot": "persona-body", children: [
|
|
21
|
+
/* @__PURE__ */ jsxs("span", { "data-slot": "persona-name-line", children: [
|
|
22
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "persona-name", dir: "auto", title: name, children: name }),
|
|
23
|
+
trailing != null ? /* @__PURE__ */ jsx("span", { "data-slot": "persona-trailing", children: trailing }) : null
|
|
24
|
+
] }),
|
|
25
|
+
description != null ? /* @__PURE__ */ jsx("span", { "data-slot": "persona-description", dir: "auto", title: description, children: description }) : null
|
|
26
|
+
] })
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
Persona.displayName = "Persona";
|
|
32
|
+
export {
|
|
33
|
+
Persona
|
|
34
|
+
};
|
|
@@ -10,6 +10,9 @@ export declare const BranchScopePicker: React.ForwardRefExoticComponent<import("
|
|
|
10
10
|
value?: BranchScopeValueProp;
|
|
11
11
|
defaultValue?: BranchScopeValueProp;
|
|
12
12
|
onValueChange?: (value: BranchScopeValueProp) => void;
|
|
13
|
+
selectionMode?: "single" | "multiple";
|
|
14
|
+
allowAll?: boolean;
|
|
15
|
+
hideWhenSingle?: boolean;
|
|
13
16
|
disabled?: import("../../props/index.js").DisabledProp;
|
|
14
17
|
readOnly?: boolean;
|
|
15
18
|
searchable?: boolean;
|
|
@@ -11,17 +11,21 @@ import { SkeletonRows } from "../feedback/skeleton.js";
|
|
|
11
11
|
import { Text } from "../general/typography.js";
|
|
12
12
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
13
13
|
import { RadioGroup } from "./radio.js";
|
|
14
|
+
import { Select } from "./select.js";
|
|
14
15
|
import { SearchInput } from "./search-input.js";
|
|
15
16
|
const DEFAULT_VALUE = { mode: "all" };
|
|
16
17
|
const BranchScopePicker = React.forwardRef(
|
|
17
18
|
function BranchScopePicker2({
|
|
18
19
|
branches,
|
|
19
20
|
value: controlledValue,
|
|
20
|
-
defaultValue
|
|
21
|
+
defaultValue,
|
|
21
22
|
onValueChange,
|
|
22
23
|
disabled = false,
|
|
23
24
|
readOnly = false,
|
|
24
25
|
searchable = true,
|
|
26
|
+
selectionMode = "multiple",
|
|
27
|
+
allowAll = true,
|
|
28
|
+
hideWhenSingle = false,
|
|
25
29
|
error,
|
|
26
30
|
loading = false,
|
|
27
31
|
empty,
|
|
@@ -39,7 +43,9 @@ const BranchScopePicker = React.forwardRef(
|
|
|
39
43
|
const reactId = React.useId();
|
|
40
44
|
const rootId = id ?? `branch-scope-${reactId}`;
|
|
41
45
|
const errorId = `${rootId}-error`;
|
|
42
|
-
const [uncontrolled, setUncontrolled] = React.useState(
|
|
46
|
+
const [uncontrolled, setUncontrolled] = React.useState(
|
|
47
|
+
defaultValue ?? (allowAll ? DEFAULT_VALUE : { mode: "selected", branchIds: [] })
|
|
48
|
+
);
|
|
43
49
|
const value = controlledValue ?? uncontrolled;
|
|
44
50
|
const branchIds = React.useMemo(() => value.branchIds ?? [], [value.branchIds]);
|
|
45
51
|
const commit = (next) => {
|
|
@@ -109,12 +115,58 @@ const BranchScopePicker = React.forwardRef(
|
|
|
109
115
|
/* @__PURE__ */ jsx("div", { "aria-live": "polite", children: empty ?? /* @__PURE__ */ jsx(EmptyState, { variant: "compact", title: t("dataEntry.branchScope.empty"), titleAs: "p" }) })
|
|
110
116
|
);
|
|
111
117
|
}
|
|
118
|
+
if (hideWhenSingle && branches.length === 1 && !branches[0].disabled && !invalid) return null;
|
|
119
|
+
if (selectionMode === "single") {
|
|
120
|
+
const selected = value.mode === "selected" && branchIds.length === 1 && branches.some((branch) => branch.id === branchIds[0]) ? `branch:${branchIds[0]}` : value.mode === "all" && allowAll ? "all" : "";
|
|
121
|
+
return surface(
|
|
122
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
Select,
|
|
125
|
+
{
|
|
126
|
+
value: selected,
|
|
127
|
+
onValueChange: (next) => {
|
|
128
|
+
if (next === "all" && allowAll) commit({ mode: "all" });
|
|
129
|
+
else if (next.startsWith("branch:"))
|
|
130
|
+
commit({ mode: "selected", branchIds: [next.slice(7)] });
|
|
131
|
+
},
|
|
132
|
+
options: [
|
|
133
|
+
...allowAll ? [
|
|
134
|
+
{
|
|
135
|
+
value: "all",
|
|
136
|
+
label: typeof resolvedAllLabel === "string" ? resolvedAllLabel : t("dataEntry.branchScope.all")
|
|
137
|
+
}
|
|
138
|
+
] : [],
|
|
139
|
+
...branches.map((branch) => ({
|
|
140
|
+
value: `branch:${branch.id}`,
|
|
141
|
+
label: branch.name,
|
|
142
|
+
disabled: branch.disabled
|
|
143
|
+
}))
|
|
144
|
+
],
|
|
145
|
+
optionRender: (option) => option.value === "all" ? resolvedAllLabel : option.label,
|
|
146
|
+
labelRender: (option) => option.value === "all" ? resolvedAllLabel : option.label,
|
|
147
|
+
showSearch: searchable,
|
|
148
|
+
searchPlaceholder: t("dataEntry.branchScope.searchPlaceholder"),
|
|
149
|
+
placeholder: t("dataEntry.branchScope.choose"),
|
|
150
|
+
notFoundContent: notFoundContent ?? t("dataEntry.branchScope.noMatches"),
|
|
151
|
+
disabled,
|
|
152
|
+
readOnly,
|
|
153
|
+
name,
|
|
154
|
+
...fieldA11y,
|
|
155
|
+
"aria-label": fieldA11y["aria-label"] ?? t("dataEntry.branchScope.listLabel"),
|
|
156
|
+
"aria-invalid": invalid ? true : fieldA11y["aria-invalid"],
|
|
157
|
+
"aria-errormessage": invalid ? mergeAriaIds(errorId, fieldA11y["aria-errormessage"]) : void 0
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
invalid && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-error-strong text-xs", children: error })
|
|
161
|
+
] })
|
|
162
|
+
);
|
|
163
|
+
}
|
|
112
164
|
if (readOnly) {
|
|
113
165
|
const selectedBranches = branches.filter((branch) => branchIds.includes(branch.id));
|
|
114
166
|
return surface(
|
|
115
167
|
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
116
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", weight: "medium", children: value.mode === "all" ? resolvedAllLabel : resolvedSelectedLabel }),
|
|
117
|
-
value.mode === "selected" && /* @__PURE__ */ jsx("div", { className: "ui-branch-scope-picker-badges", children: selectedBranches.map((branch) => /* @__PURE__ */ jsx(Badge, { variant: "outline", children: branch.name }, branch.id)) })
|
|
168
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", weight: "medium", children: value.mode === "all" && allowAll ? resolvedAllLabel : resolvedSelectedLabel }),
|
|
169
|
+
(value.mode === "selected" || !allowAll) && /* @__PURE__ */ jsx("div", { className: "ui-branch-scope-picker-badges", children: selectedBranches.map((branch) => /* @__PURE__ */ jsx(Badge, { variant: "outline", children: branch.name }, branch.id)) })
|
|
118
170
|
] })
|
|
119
171
|
);
|
|
120
172
|
}
|
|
@@ -123,16 +175,18 @@ const BranchScopePicker = React.forwardRef(
|
|
|
123
175
|
/* @__PURE__ */ jsx(
|
|
124
176
|
RadioGroup,
|
|
125
177
|
{
|
|
126
|
-
value: value.mode,
|
|
178
|
+
value: allowAll ? value.mode : "selected",
|
|
127
179
|
onValueChange: (mode) => {
|
|
128
180
|
commit({ ...value, mode });
|
|
129
181
|
},
|
|
130
182
|
options: [
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
183
|
+
...allowAll ? [
|
|
184
|
+
{
|
|
185
|
+
value: "all",
|
|
186
|
+
label: resolvedAllLabel,
|
|
187
|
+
description: allLabel === void 0 ? t("dataEntry.branchScope.allDescription") : void 0
|
|
188
|
+
}
|
|
189
|
+
] : [],
|
|
136
190
|
{
|
|
137
191
|
value: "selected",
|
|
138
192
|
label: resolvedSelectedLabel,
|
|
@@ -146,7 +200,7 @@ const BranchScopePicker = React.forwardRef(
|
|
|
146
200
|
"aria-errormessage": invalid ? mergeAriaIds(errorId, fieldA11y["aria-errormessage"]) : void 0
|
|
147
201
|
}
|
|
148
202
|
),
|
|
149
|
-
value.mode === "selected" && /* @__PURE__ */ jsxs("div", { className: "ui-branch-scope-picker-subset", children: [
|
|
203
|
+
(value.mode === "selected" || !allowAll) && /* @__PURE__ */ jsxs("div", { className: "ui-branch-scope-picker-subset", children: [
|
|
150
204
|
searchable && branches.length > 0 && /* @__PURE__ */ jsx(
|
|
151
205
|
SearchInput,
|
|
152
206
|
{
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Children,
|
|
5
|
+
cloneElement,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef
|
|
9
|
+
} from "react";
|
|
4
10
|
import { cn } from "../../lib/utils.js";
|
|
5
11
|
import { EllipsisTooltip } from "../feedback/tooltip.js";
|
|
12
|
+
function labelText(label) {
|
|
13
|
+
return Children.toArray(label).map((node) => {
|
|
14
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
15
|
+
if (!isValidElement(node))
|
|
16
|
+
return "";
|
|
17
|
+
if (node.props["aria-hidden"] === true || node.props["aria-hidden"] === "true" || node.props.hidden)
|
|
18
|
+
return "";
|
|
19
|
+
return labelText(node.props.children);
|
|
20
|
+
}).join("");
|
|
21
|
+
}
|
|
6
22
|
function trailEllipsis(items) {
|
|
7
23
|
return items.some((item) => item.ellipsis) ? "" : void 0;
|
|
8
24
|
}
|
|
@@ -11,12 +27,28 @@ function BreadcrumbCrumbText({
|
|
|
11
27
|
children
|
|
12
28
|
}) {
|
|
13
29
|
const anchorRef = useRef(null);
|
|
30
|
+
const fullLabel = labelText(item.label);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const node = anchorRef.current;
|
|
33
|
+
if (!node || !item.ellipsis) return;
|
|
34
|
+
const rendered = node.cloneNode(true);
|
|
35
|
+
rendered.querySelectorAll('[aria-hidden="true"], [hidden]').forEach((child) => child.remove());
|
|
36
|
+
const text = rendered.textContent || fullLabel;
|
|
37
|
+
if (text) {
|
|
38
|
+
node.setAttribute("aria-label", text);
|
|
39
|
+
node.setAttribute("title", text);
|
|
40
|
+
}
|
|
41
|
+
}, [fullLabel, item.label, item.ellipsis]);
|
|
14
42
|
if (!item.ellipsis) return children;
|
|
15
43
|
const tooltip = typeof item.ellipsis === "object" && "tooltip" in item.ellipsis ? item.ellipsis.tooltip === true ? item.label : item.ellipsis.tooltip : item.label;
|
|
16
44
|
const hasTooltip = tooltip !== void 0 && tooltip !== null && tooltip !== false && tooltip !== "";
|
|
17
45
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18
46
|
cloneElement(children, {
|
|
47
|
+
// Keep the label's own start visible regardless of the page direction (#1370).
|
|
48
|
+
dir: "auto",
|
|
19
49
|
ref: anchorRef,
|
|
50
|
+
"aria-label": fullLabel || void 0,
|
|
51
|
+
title: fullLabel || void 0,
|
|
20
52
|
className: cn(children.props.className, "ui-breadcrumb-ellipsis")
|
|
21
53
|
}),
|
|
22
54
|
hasTooltip ? /* @__PURE__ */ jsx(EllipsisTooltip, { anchorRef, title: tooltip }) : null
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ElementType } from "react";
|
|
2
|
-
import type { BreadcrumbItemRenderProp, BreadcrumbProp, BreadcrumbSeparatorProp } from "../../props/vocabulary/navigation.prop.js";
|
|
2
|
+
import type { BreadcrumbCurrentItemProp, BreadcrumbItemRenderProp, BreadcrumbProp, BreadcrumbSeparatorProp } from "../../props/vocabulary/navigation.prop.js";
|
|
3
3
|
export type BreadcrumbProps = {
|
|
4
4
|
items: BreadcrumbProp;
|
|
5
|
+
/** Last item is current by default; none keeps an ancestor-only trail linked. */
|
|
6
|
+
currentItem?: BreadcrumbCurrentItemProp;
|
|
5
7
|
linkComponent?: ElementType;
|
|
6
8
|
/** Override the `<nav>` landmark's accessible name. Defaults to a localized "Breadcrumb". */
|
|
7
9
|
ariaLabel?: string;
|
|
@@ -18,4 +20,4 @@ export type BreadcrumbProps = {
|
|
|
18
20
|
*/
|
|
19
21
|
itemRender?: BreadcrumbItemRenderProp;
|
|
20
22
|
};
|
|
21
|
-
export declare function Breadcrumb({ items, linkComponent: LinkComponent, ariaLabel, separator, itemRender, }: BreadcrumbProps): import("react").JSX.Element;
|
|
23
|
+
export declare function Breadcrumb({ items, currentItem, linkComponent: LinkComponent, ariaLabel, separator, itemRender, }: BreadcrumbProps): import("react").JSX.Element;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "../navigation/dropdown-menu.js";
|
|
12
12
|
function Breadcrumb({
|
|
13
13
|
items,
|
|
14
|
+
currentItem = "last",
|
|
14
15
|
linkComponent: LinkComponent = "a",
|
|
15
16
|
ariaLabel,
|
|
16
17
|
separator,
|
|
@@ -27,7 +28,15 @@ function Breadcrumb({
|
|
|
27
28
|
"data-ellipsis": item.ellipsis ? "" : void 0,
|
|
28
29
|
children: [
|
|
29
30
|
index > 0 ? /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-separator", "aria-hidden": "true", children: separatorNode }) : null,
|
|
30
|
-
renderBreadcrumbSegment({
|
|
31
|
+
renderBreadcrumbSegment({
|
|
32
|
+
item,
|
|
33
|
+
index,
|
|
34
|
+
isLast,
|
|
35
|
+
isCurrent: isLast && currentItem === "last",
|
|
36
|
+
items,
|
|
37
|
+
itemRender,
|
|
38
|
+
LinkComponent
|
|
39
|
+
})
|
|
31
40
|
]
|
|
32
41
|
},
|
|
33
42
|
item.to ?? index
|
|
@@ -38,6 +47,7 @@ function renderBreadcrumbSegment({
|
|
|
38
47
|
item,
|
|
39
48
|
index,
|
|
40
49
|
isLast,
|
|
50
|
+
isCurrent,
|
|
41
51
|
items,
|
|
42
52
|
itemRender,
|
|
43
53
|
LinkComponent
|
|
@@ -62,10 +72,10 @@ function renderBreadcrumbSegment({
|
|
|
62
72
|
)) })
|
|
63
73
|
] });
|
|
64
74
|
}
|
|
65
|
-
if (item.to && !
|
|
75
|
+
if (item.to && !isCurrent) {
|
|
66
76
|
return /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(LinkComponent, { href: item.to, to: item.to, className: "ui-breadcrumb-link", children: item.label }) });
|
|
67
77
|
}
|
|
68
|
-
return /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-current", "aria-current":
|
|
78
|
+
return /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-current", "aria-current": isCurrent ? "page" : void 0, children: item.label }) });
|
|
69
79
|
}
|
|
70
80
|
export {
|
|
71
81
|
Breadcrumb
|
|
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
|
|
|
2
2
|
export type { PageContainerProp, PageContainerProp as PageContainerProps, PageContainerExtraProp, } from "../../props/components/layout.prop.js";
|
|
3
3
|
export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop.js";
|
|
4
4
|
export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react").JSX.Element;
|
|
5
|
-
declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, titleRef, density, variant, preset, headerLayout, headerScale, icon, cover, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
5
|
+
declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbCurrentItem, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, titleRef, density, variant, preset, headerLayout, headerScale, icon, cover, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
6
6
|
export declare const PageContainer: typeof PageContainerRoot & {
|
|
7
7
|
Inset: typeof PageContainerInset;
|
|
8
8
|
};
|
|
@@ -42,6 +42,7 @@ function PageContainerRoot({
|
|
|
42
42
|
toolbarPad,
|
|
43
43
|
footerPad,
|
|
44
44
|
breadcrumb,
|
|
45
|
+
breadcrumbCurrentItem = "last",
|
|
45
46
|
breadcrumbLabel,
|
|
46
47
|
breadcrumbAriaLabel,
|
|
47
48
|
linkComponent: LinkComponent = "a",
|
|
@@ -111,6 +112,7 @@ function PageContainerRoot({
|
|
|
111
112
|
className: "ui-breadcrumb",
|
|
112
113
|
children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", "data-ellipsis": trailEllipsis(breadcrumb), children: breadcrumb.map((item, i) => {
|
|
113
114
|
const isLast = i === breadcrumb.length - 1;
|
|
115
|
+
const isCurrent = isLast && breadcrumbCurrentItem === "last";
|
|
114
116
|
return (
|
|
115
117
|
// `--space-inline-xs` gap the previous `.ui-inline-xs` gave, and it brings
|
|
116
118
|
// `.ui-breadcrumb-item svg { 0.75rem }` with it, so the separator glyph no
|
|
@@ -124,7 +126,7 @@ function PageContainerRoot({
|
|
|
124
126
|
"data-ellipsis": item.ellipsis ? "" : void 0,
|
|
125
127
|
children: [
|
|
126
128
|
i > 0 ? /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-separator", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" }) }) : null,
|
|
127
|
-
item.to && !
|
|
129
|
+
item.to && !isCurrent ? /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(
|
|
128
130
|
LinkComponent,
|
|
129
131
|
{
|
|
130
132
|
href: item.to,
|
|
@@ -135,8 +137,8 @@ function PageContainerRoot({
|
|
|
135
137
|
) }) : /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(
|
|
136
138
|
"span",
|
|
137
139
|
{
|
|
138
|
-
className:
|
|
139
|
-
"aria-current":
|
|
140
|
+
className: isCurrent ? "text-foreground" : "",
|
|
141
|
+
"aria-current": isCurrent ? "page" : void 0,
|
|
140
142
|
children: item.label
|
|
141
143
|
}
|
|
142
144
|
) })
|
|
@@ -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.14.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,
|
|
@@ -171,7 +171,8 @@
|
|
|
171
171
|
"error": "Could not load branches",
|
|
172
172
|
"denied": "You do not have permission to view branches",
|
|
173
173
|
"loading": "Loading branches…",
|
|
174
|
-
"noMatches": "No branches match your search"
|
|
174
|
+
"noMatches": "No branches match your search",
|
|
175
|
+
"choose": "Choose a branch"
|
|
175
176
|
},
|
|
176
177
|
"formErrors": {
|
|
177
178
|
"title": "Please review the following errors"
|