@godxjp/ui 31.6.0 → 31.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/agent/START-HERE.md +4 -4
  2. package/agent/components/Breadcrumb.json +1 -1
  3. package/agent/components/PageContainer.json +1 -1
  4. package/agent/components/Progress.json +12 -1
  5. package/agent/components/Text.json +1 -1
  6. package/agent/components.json +15 -4
  7. package/agent/index.json +4 -4
  8. package/agent/llms.txt +5 -5
  9. package/agent/tokens.json +6 -0
  10. package/dist/components/data-display/progress.d.ts +16 -0
  11. package/dist/components/data-display/progress.js +17 -3
  12. package/dist/components/feedback/tooltip.d.ts +21 -0
  13. package/dist/components/feedback/tooltip.js +101 -0
  14. package/dist/components/general/typography.js +30 -11
  15. package/dist/components/layout/breadcrumb-ellipsis.d.ts +16 -0
  16. package/dist/components/layout/breadcrumb-ellipsis.js +28 -0
  17. package/dist/components/layout/breadcrumb.js +16 -7
  18. package/dist/components/layout/page-container.js +29 -20
  19. package/dist/contracts/measurement.json +1 -1
  20. package/dist/i18n/messages/en.json +2 -1
  21. package/dist/i18n/messages/ja.json +2 -1
  22. package/dist/i18n/messages/vi.json +2 -1
  23. package/dist/props/registry.d.ts +5 -0
  24. package/dist/props/registry.js +5 -0
  25. package/dist/props/vocabulary/index.d.ts +1 -1
  26. package/dist/props/vocabulary/navigation.prop.d.ts +14 -0
  27. package/dist/styles/layers.json +3 -2
  28. package/dist/styles/layout.css +22 -0
  29. package/dist/styles/table-layout.css +8 -0
  30. package/dist/tokens/components/navigation.css +2 -0
  31. package/docs/data-display/progress.tsx +24 -6
  32. package/docs/general/typography.tsx +84 -1
  33. package/docs/i18n/messages/en.json +29 -1
  34. package/docs/i18n/messages/ja.json +29 -1
  35. package/docs/i18n/messages/vi.json +29 -1
  36. package/docs/navigation/breadcrumb.tsx +24 -0
  37. package/package.json +2 -2
@@ -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.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.7.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` — 2099 design tokens, each tagged with its `tier`. **If you were handed a
59
+ 4. `tokens.json` — 2100 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
- 1784 `component` entries are per-part knobs; reach for one only when a role is
62
+ 1785 `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` | 1784 | 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` |
148
+ | `component` | 1785 | 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.
@@ -5,7 +5,7 @@
5
5
  "name": "Breadcrumb",
6
6
  "props": [
7
7
  {
8
- "description": "Array of { label, to?, menu? } — omit `to` on the last (current) segment. `menu` (Ant Design `BreadcrumbItemType.menu`) hangs a sibling picker off that segment: `{ items: { value, label, to?, disabled? }[], onSelect? }`. The segment then renders as a menu button, not a link — an entry with `to` becomes a real anchor inside the menu while keeping `role=\"menuitem\"`.",
8
+ "description": "Array of { label, to?, menu?, ellipsis? } — omit `to` on the last (current) segment. `ellipsis` (`boolean | { tooltip?: ReactNode }`, gh#1046; antd Breadcrumb has none, the shape is antd Typography's) keeps the trail on one line and cuts that segment's label with an ellipsis, showing the full label in a tooltip on hover and on keyboard focus of the crumb link; `{ tooltip: false }` drops the tooltip. Link and current segments only. `--breadcrumb-item-max-inline-size` (default `none`) caps such a segment. `menu` (Ant Design `BreadcrumbItemType.menu`) hangs a sibling picker off that segment: `{ items: { value, label, to?, disabled? }[], onSelect? }`. The segment then renders as a menu button, not a link — an entry with `to` becomes a real anchor inside the menu while keeping `role=\"menuitem\"`.",
9
9
  "name": "items",
10
10
  "required": true,
11
11
  "type": "BreadcrumbItemProp[]"
@@ -56,7 +56,7 @@
56
56
  "type": "ReactNode"
57
57
  },
58
58
  {
59
- "description": "Ordered trail of { label, to? } segments above the title.",
59
+ "description": "Ordered trail of { label, to?, ellipsis? } segments above the title. `ellipsis: true` on a segment keeps the trail on one line and cuts that label with an ellipsis, the full label in a tooltip on hover and on keyboard focus of the crumb link — same contract as `Breadcrumb` items.",
60
60
  "name": "breadcrumb",
61
61
  "type": "BreadcrumbItemProp[]"
62
62
  },
@@ -1,5 +1,5 @@
1
1
  {
2
- "example": "import { Progress } from \"@godxjp/ui/data-display\";\n\n<Progress value={pct} label={pct + \"% 使用中\"} tone={pct >= 80 ? \"warning\" : \"success\"} />\n<Progress value={252} over label=\"252% 積載\" />\n<Progress\n segments={[\n { value: 2, tone: \"destructive\", label: \"期限超過\" },\n { value: 3, tone: \"warning\", label: \"期限間近\" },\n { value: 12, tone: \"success\", label: \"対応済\" },\n ]}\n aria-labelledby={companyNameId}\n/>",
2
+ "example": "import { Progress } from \"@godxjp/ui/data-display\";\n\n<Progress value={pct} label={pct + \"% 使用中\"} tone={pct >= 80 ? \"warning\" : \"success\"} />\n<Progress value={252} over label=\"252% 積載\" />\n<Progress\n segments={[\n { value: 2, tone: \"destructive\", label: \"期限超過\" },\n { value: 3, tone: \"warning\", label: \"期限間近\" },\n { value: 12, tone: \"success\", label: \"対応済\" },\n ]}\n aria-labelledby={companyNameId}\n/>\n<Progress\n segments={[\n { value: 3, tone: \"success\", label: \"合格\" },\n { value: 1, tone: \"destructive\", label: \"失敗\" },\n ]}\n total={400}\n remainderLabel=\"未実施\"\n/>",
3
3
  "group": "data-display",
4
4
  "importPath": "@godxjp/ui/data-display",
5
5
  "name": "Progress",
@@ -15,6 +15,16 @@
15
15
  "name": "segments",
16
16
  "type": "{ value: number; tone: \"success\" | \"warning\" | \"destructive\"; label: string }[]"
17
17
  },
18
+ {
19
+ "description": "BREAKDOWN mode: the WHOLE the slices are drawn against, in the same unit as their values. Omit it and the whole is the sum of the slices (the bar is always full). Pass it when part of the whole has no state yet — 3 passed of 400 test cases — and the rest stays the neutral TRACK, the antd `percent` model where the unfilled track is the remainder. The remainder is spoken in the role=\"img\" name, Intl-formatted in the active locale. A total below the slice sum is ignored (the sum wins), so slices never overflow.",
20
+ "name": "total",
21
+ "type": "number"
22
+ },
23
+ {
24
+ "description": "BREAKDOWN mode, with `total`: what the remainder is called in the spoken breakdown (e.g. 未実施). Defaults to the catalogue's Remaining / 残り / Còn lại.",
25
+ "name": "remainderLabel",
26
+ "type": "string"
27
+ },
18
28
  {
19
29
  "description": "Text label beside/below the bar; it also becomes the accessible name. Pass `aria-labelledby` instead when the name is ALREADY on screen (a row's company name, a card heading) — the bar then borrows it rather than repeating it.",
20
30
  "name": "label",
@@ -64,6 +74,7 @@
64
74
  "DON'T pass children or sub-components — Progress is a single self-contained element (track + bar + label). The `label` prop is the only text injection point; don't wrap it in a custom parent div to add a label alongside it.",
65
75
  "DON'T hand-roll a stacked bar out of three divs to show a part-to-whole split — pass `segments`. Hand-rolled slices need a hex fill, an arbitrary height and an arbitrary radius, which ui-audit blocks three ways (no-arbitrary-hex, no-arbitrary-size, no-arbitrary-radius), and they leave the picture with no accessible name at all.",
66
76
  "DON'T convert segment amounts to percentages yourself — pass the raw counts. The component divides by the total, so the slices always sum to the whole; pre-rounded percentages do not.",
77
+ "DO pass `total` when part of the whole has no state yet (a test run: 合格 3 · 失敗 1 of 400) — the rest stays track and is read out as `remainderLabel` (e.g. \"未実施\"). Leaving it out makes 3 passed of 400 fill the whole bar green.",
67
78
  "DO pair a breakdown with `Legend` so each tone is spelled out in words once, instead of repeating the labels on every bar.",
68
79
  "DON'T use Progress for editable numeric input or range selection — it has no callbacks, no interactivity, and no form `name` prop. Use Slider (bounded range input) or Input (free-form number) for data-entry scenarios."
69
80
  ],
@@ -132,7 +132,7 @@
132
132
  "type": "boolean | { text, editing, icon, tooltip, onStart, onChange, onCancel, onEnd, maxLength, autoSize, triggerType, enterIcon, tabIndex }"
133
133
  },
134
134
  {
135
- "description": "Single-line truncation, antd's spelling. antd drops `rows` / `expandable` / `onExpand` on `Text` — an inline run has no second line to expand into — and that omission is ported; reach for `Paragraph` when you want them. It is the SAME axis as `truncate` / `clamp` and OUTRANKS both (dev builds warn), because it is the only spelling that can also carry a suffix or a tooltip.",
135
+ "description": "Single-line truncation, antd's spelling. antd drops `rows` / `expandable` / `onExpand` on `Text` — an inline run has no second line to expand into — and that omission is ported; reach for `Paragraph` when you want them. It is the SAME axis as `truncate` / `clamp` and OUTRANKS both (dev builds warn), because it is the only spelling that can also carry a suffix or a tooltip. `tooltip: true` (the children) or a node shows the full text in a Tooltip ONLY while the run is actually clipped — on pointer hover, and on keyboard focus of the Text itself (`asChild` link) or of the nearest focusable control around it (a Tree `treeitem`, a DataTable sort button, a link); the Text never becomes a tab stop of its own and its accessible name stays the full text. With `asChild` the child element is the truncated box (one `<a>`, not `<a><a>`). A `<Text ellipsis>` in `ColumnDef.header` truncates inside its column like a cell.",
136
136
  "name": "ellipsis",
137
137
  "type": "boolean | { suffix, symbol, defaultExpanded, expanded, onEllipsis, tooltip }"
138
138
  },
@@ -668,7 +668,7 @@
668
668
  "type": "ReactNode"
669
669
  },
670
670
  {
671
- "description": "Ordered trail of { label, to? } segments above the title.",
671
+ "description": "Ordered trail of { label, to?, ellipsis? } segments above the title. `ellipsis: true` on a segment keeps the trail on one line and cuts that label with an ellipsis, the full label in a tooltip on hover and on keyboard focus of the crumb link — same contract as `Breadcrumb` items.",
672
672
  "name": "breadcrumb",
673
673
  "type": "BreadcrumbItemProp[]"
674
674
  },
@@ -2339,7 +2339,7 @@
2339
2339
  "name": "Breadcrumb",
2340
2340
  "props": [
2341
2341
  {
2342
- "description": "Array of { label, to?, menu? } — omit `to` on the last (current) segment. `menu` (Ant Design `BreadcrumbItemType.menu`) hangs a sibling picker off that segment: `{ items: { value, label, to?, disabled? }[], onSelect? }`. The segment then renders as a menu button, not a link — an entry with `to` becomes a real anchor inside the menu while keeping `role=\"menuitem\"`.",
2342
+ "description": "Array of { label, to?, menu?, ellipsis? } — omit `to` on the last (current) segment. `ellipsis` (`boolean | { tooltip?: ReactNode }`, gh#1046; antd Breadcrumb has none, the shape is antd Typography's) keeps the trail on one line and cuts that segment's label with an ellipsis, showing the full label in a tooltip on hover and on keyboard focus of the crumb link; `{ tooltip: false }` drops the tooltip. Link and current segments only. `--breadcrumb-item-max-inline-size` (default `none`) caps such a segment. `menu` (Ant Design `BreadcrumbItemType.menu`) hangs a sibling picker off that segment: `{ items: { value, label, to?, disabled? }[], onSelect? }`. The segment then renders as a menu button, not a link — an entry with `to` becomes a real anchor inside the menu while keeping `role=\"menuitem\"`.",
2343
2343
  "name": "items",
2344
2344
  "required": true,
2345
2345
  "type": "BreadcrumbItemProp[]"
@@ -2744,7 +2744,7 @@
2744
2744
  "type": "boolean | { text, editing, icon, tooltip, onStart, onChange, onCancel, onEnd, maxLength, autoSize, triggerType, enterIcon, tabIndex }"
2745
2745
  },
2746
2746
  {
2747
- "description": "Single-line truncation, antd's spelling. antd drops `rows` / `expandable` / `onExpand` on `Text` — an inline run has no second line to expand into — and that omission is ported; reach for `Paragraph` when you want them. It is the SAME axis as `truncate` / `clamp` and OUTRANKS both (dev builds warn), because it is the only spelling that can also carry a suffix or a tooltip.",
2747
+ "description": "Single-line truncation, antd's spelling. antd drops `rows` / `expandable` / `onExpand` on `Text` — an inline run has no second line to expand into — and that omission is ported; reach for `Paragraph` when you want them. It is the SAME axis as `truncate` / `clamp` and OUTRANKS both (dev builds warn), because it is the only spelling that can also carry a suffix or a tooltip. `tooltip: true` (the children) or a node shows the full text in a Tooltip ONLY while the run is actually clipped — on pointer hover, and on keyboard focus of the Text itself (`asChild` link) or of the nearest focusable control around it (a Tree `treeitem`, a DataTable sort button, a link); the Text never becomes a tab stop of its own and its accessible name stays the full text. With `asChild` the child element is the truncated box (one `<a>`, not `<a><a>`). A `<Text ellipsis>` in `ColumnDef.header` truncates inside its column like a cell.",
2748
2748
  "name": "ellipsis",
2749
2749
  "type": "boolean | { suffix, symbol, defaultExpanded, expanded, onEllipsis, tooltip }"
2750
2750
  },
@@ -4735,7 +4735,7 @@
4735
4735
  ]
4736
4736
  },
4737
4737
  {
4738
- "example": "import { Progress } from \"@godxjp/ui/data-display\";\n\n<Progress value={pct} label={pct + \"% 使用中\"} tone={pct >= 80 ? \"warning\" : \"success\"} />\n<Progress value={252} over label=\"252% 積載\" />\n<Progress\n segments={[\n { value: 2, tone: \"destructive\", label: \"期限超過\" },\n { value: 3, tone: \"warning\", label: \"期限間近\" },\n { value: 12, tone: \"success\", label: \"対応済\" },\n ]}\n aria-labelledby={companyNameId}\n/>",
4738
+ "example": "import { Progress } from \"@godxjp/ui/data-display\";\n\n<Progress value={pct} label={pct + \"% 使用中\"} tone={pct >= 80 ? \"warning\" : \"success\"} />\n<Progress value={252} over label=\"252% 積載\" />\n<Progress\n segments={[\n { value: 2, tone: \"destructive\", label: \"期限超過\" },\n { value: 3, tone: \"warning\", label: \"期限間近\" },\n { value: 12, tone: \"success\", label: \"対応済\" },\n ]}\n aria-labelledby={companyNameId}\n/>\n<Progress\n segments={[\n { value: 3, tone: \"success\", label: \"合格\" },\n { value: 1, tone: \"destructive\", label: \"失敗\" },\n ]}\n total={400}\n remainderLabel=\"未実施\"\n/>",
4739
4739
  "group": "data-display",
4740
4740
  "importPath": "@godxjp/ui/data-display",
4741
4741
  "name": "Progress",
@@ -4751,6 +4751,16 @@
4751
4751
  "name": "segments",
4752
4752
  "type": "{ value: number; tone: \"success\" | \"warning\" | \"destructive\"; label: string }[]"
4753
4753
  },
4754
+ {
4755
+ "description": "BREAKDOWN mode: the WHOLE the slices are drawn against, in the same unit as their values. Omit it and the whole is the sum of the slices (the bar is always full). Pass it when part of the whole has no state yet — 3 passed of 400 test cases — and the rest stays the neutral TRACK, the antd `percent` model where the unfilled track is the remainder. The remainder is spoken in the role=\"img\" name, Intl-formatted in the active locale. A total below the slice sum is ignored (the sum wins), so slices never overflow.",
4756
+ "name": "total",
4757
+ "type": "number"
4758
+ },
4759
+ {
4760
+ "description": "BREAKDOWN mode, with `total`: what the remainder is called in the spoken breakdown (e.g. 未実施). Defaults to the catalogue's Remaining / 残り / Còn lại.",
4761
+ "name": "remainderLabel",
4762
+ "type": "string"
4763
+ },
4754
4764
  {
4755
4765
  "description": "Text label beside/below the bar; it also becomes the accessible name. Pass `aria-labelledby` instead when the name is ALREADY on screen (a row's company name, a card heading) — the bar then borrows it rather than repeating it.",
4756
4766
  "name": "label",
@@ -4800,6 +4810,7 @@
4800
4810
  "DON'T pass children or sub-components — Progress is a single self-contained element (track + bar + label). The `label` prop is the only text injection point; don't wrap it in a custom parent div to add a label alongside it.",
4801
4811
  "DON'T hand-roll a stacked bar out of three divs to show a part-to-whole split — pass `segments`. Hand-rolled slices need a hex fill, an arbitrary height and an arbitrary radius, which ui-audit blocks three ways (no-arbitrary-hex, no-arbitrary-size, no-arbitrary-radius), and they leave the picture with no accessible name at all.",
4802
4812
  "DON'T convert segment amounts to percentages yourself — pass the raw counts. The component divides by the total, so the slices always sum to the whole; pre-rounded percentages do not.",
4813
+ "DO pass `total` when part of the whole has no state yet (a test run: 合格 3 · 失敗 1 of 400) — the rest stays track and is read out as `remainderLabel` (e.g. \"未実施\"). Leaving it out makes 3 passed of 400 fill the whole bar green.",
4803
4814
  "DO pair a breakdown with `Legend` so each tone is spelled out in words once, instead of repeating the labels on every bar.",
4804
4815
  "DON'T use Progress for editable numeric input or range selection — it has no callbacks, no interactivity, and no form `name` prop. Use Slider (bounded range input) or Input (free-form number) for data-entry scenarios."
4805
4816
  ],
package/agent/index.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "components": 177,
5
5
  "patterns": 21,
6
6
  "rules": 50,
7
- "tokens": 2099,
7
+ "tokens": 2100,
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.6.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.7.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": 1784,
58
+ "component": 1785,
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.6.0"
65
+ "version": "31.7.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: 177 components, 2099 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.6.0.
3
+ > A Japanese-enterprise React design system: 177 components, 2100 design tokens,
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.7.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.6.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.7.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): 47 KB — all 177 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
19
  - [components/&lt;Name&gt;.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, 1784 `component` knobs.
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, 1785 `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.6.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.7.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
@@ -8417,6 +8417,12 @@
8417
8417
  "tier": "component",
8418
8418
  "value": "var(--space-inline-xs)"
8419
8419
  },
8420
+ {
8421
+ "description": "`BreadcrumbItemProp.ellipsis` (gh#1046) — the widest a truncating crumb may grow. `none` = it only cuts when the one-line trail runs out of room; a theme may cap it (e.g. `16em`).",
8422
+ "name": "--breadcrumb-item-max-inline-size",
8423
+ "tier": "component",
8424
+ "value": "none"
8425
+ },
8420
8426
  {
8421
8427
  "description": "Navigation primitive tokens: pagination, filters, compact pickers.",
8422
8428
  "name": "--menu-shortcut-font-size",
@@ -89,6 +89,22 @@ type ProgressMeterProps = ProgressBase & {
89
89
  */
90
90
  type ProgressBreakdownProps = ProgressBase & {
91
91
  segments: ProgressSegment[];
92
+ /**
93
+ * The WHOLE the slices are drawn against, in the same unit as their `value`s. Omit it and the
94
+ * whole is the sum of the slices (the bar is always full). Pass it when part of the whole has
95
+ * no state yet — 3 passed of 400 test cases — and the rest stays the TRACK, exactly as antd's
96
+ * `percent` leaves the unfilled track as the remainder of 100. A `total` below the sum is
97
+ * ignored (the sum wins), so the slices never overflow the bar.
98
+ *
99
+ * The remainder is spoken as part of the `role="img"` name, Intl-formatted in the active
100
+ * locale, under `remainderLabel`.
101
+ */
102
+ total?: number;
103
+ /**
104
+ * What the remainder is called in the spoken breakdown (e.g. `未実施`). Defaults to the
105
+ * catalogue's "Remaining" / 残り / Còn lại. Only read when `total` is set.
106
+ */
107
+ remainderLabel?: string;
92
108
  value?: never;
93
109
  tone?: never;
94
110
  over?: never;
@@ -2,18 +2,21 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useTranslation } from "../../i18n/use-translation.js";
5
+ import { numberFormat } from "../../lib/intl-cache.js";
5
6
  import { cn } from "../../lib/utils.js";
6
7
  const RING_BOX = 40;
7
8
  const RING_CENTRE = RING_BOX / 2;
8
9
  const RING_RADIUS = 16;
9
10
  const RING_ROTATION = `rotate(-90 ${RING_CENTRE} ${RING_CENTRE})`;
10
11
  function Progress(props) {
11
- const { t } = useTranslation();
12
+ const { t, locale } = useTranslation();
12
13
  const labelId = React.useId();
13
14
  const {
14
15
  label,
15
16
  className,
16
17
  segments,
18
+ total: wholeTotal,
19
+ remainderLabel,
17
20
  value,
18
21
  tone,
19
22
  size,
@@ -28,9 +31,20 @@ function Progress(props) {
28
31
  const caption = label !== void 0 ? /* @__PURE__ */ jsx("div", { className: "ui-progress-label", id: labelId, children: label }) : null;
29
32
  if (segments !== void 0) {
30
33
  const amounts = segments.map((segment) => Math.max(0, segment.value));
31
- const total = amounts.reduce((sum, amount) => sum + amount, 0);
34
+ const sliceSum = amounts.reduce((sum, amount) => sum + amount, 0);
35
+ const hasWhole = wholeTotal !== void 0;
36
+ const total = hasWhole ? Math.max(sliceSum, wholeTotal) : sliceSum;
37
+ const formatAmount = hasWhole ? (amount) => numberFormat(locale).format(amount) : (amount) => String(amount);
32
38
  const separator = t("dataDisplay.progress.breakdownSeparator");
33
- const spoken = segments.map((segment, index) => `${segment.label} ${amounts[index]}`).join(separator);
39
+ const parts = segments.map(
40
+ (segment, index) => `${segment.label} ${formatAmount(amounts[index])}`
41
+ );
42
+ if (hasWhole) {
43
+ parts.push(
44
+ `${remainderLabel ?? t("dataDisplay.progress.remainder")} ${formatAmount(total - sliceSum)}`
45
+ );
46
+ }
47
+ const spoken = parts.join(separator);
34
48
  return /* @__PURE__ */ jsxs(
35
49
  "div",
36
50
  {
@@ -51,4 +51,25 @@ interface TooltipContentProps extends React.ComponentPropsWithRef<"div"> {
51
51
  forceMount?: true;
52
52
  }
53
53
  export declare function TooltipContent({ className, style, children, ref, side, align, sideOffset, alignOffset, avoidCollisions, collisionPadding, sticky: _sticky, hideWhenDetached: _hideWhenDetached, forceMount: _forceMount, ...props }: TooltipContentProps): React.JSX.Element;
54
+ /**
55
+ * antd's ellipsis tooltip (`Base/Ellipsis` + `useTooltipProps`), ported for gh#1045 / gh#1046.
56
+ * Module-internal (not in the public index): `Text`/`Paragraph`/`Title` `ellipsis.tooltip` and
57
+ * `BreadcrumbItemProp.ellipsis` render it. It lives here, not in typography, so Breadcrumb does not
58
+ * pull the Typography module (and its Textarea) into its bundle and stylesheet layers.
59
+ *
60
+ * Open while the anchor is ACTUALLY clipping — measured at the moment of the pointer or the focus,
61
+ * so a box that fits never shows one — on pointer hover of the anchor, and on keyboard focus of the
62
+ * anchor itself (`Text asChild` link, a breadcrumb link) or of the nearest focusable ANCESTOR that is
63
+ * a control (`treeitem`, a sort `button`, a link). The anchor never becomes focusable, so a Text in
64
+ * a treeitem stays inside that item's single tab stop, and the accessible name stays the full text
65
+ * node — the truncation is paint only. Escape dismisses (WCAG 1.4.13); hovering the tooltip keeps
66
+ * it open.
67
+ *
68
+ * When the focus host is ALREADY a tooltip trigger (a DataTable sort button carries the sort hint on
69
+ * top), this one opens underneath so the two never cover each other.
70
+ */
71
+ export declare function EllipsisTooltip({ anchorRef, title, }: {
72
+ anchorRef: React.RefObject<HTMLElement | null>;
73
+ title: React.ReactNode;
74
+ }): React.JSX.Element;
54
75
  export {};
@@ -12,6 +12,7 @@ import {
12
12
  import { cn } from "../../lib/utils.js";
13
13
  import { Slot } from "../../lib/slot.js";
14
14
  const DEFAULT_DELAY_DURATION = 200;
15
+ const TooltipAnchorContext = React.createContext(null);
15
16
  function toPlacement(side, align) {
16
17
  if (align === "center") return side;
17
18
  if (side === "top" || side === "bottom") return `${side} ${align}`;
@@ -76,9 +77,11 @@ function TooltipContent({
76
77
  ...props
77
78
  }) {
78
79
  const overlayPortalContainer = useOverlayPortalContainer();
80
+ const anchorRef = React.useContext(TooltipAnchorContext);
79
81
  return /* @__PURE__ */ jsx(
80
82
  AriaTooltip,
81
83
  {
84
+ ...anchorRef ? { triggerRef: anchorRef } : null,
82
85
  UNSTABLE_portalContainer: overlayPortalContainer,
83
86
  placement: toPlacement(side, align),
84
87
  offset: sideOffset,
@@ -119,7 +122,105 @@ function TooltipContent({
119
122
  }
120
123
  );
121
124
  }
125
+ const FOCUS_HOST = 'a[href], button, input, select, textarea, summary, [tabindex]:is([role="treeitem"], [role="gridcell"], [role="row"], [role="option"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="tab"], [role="link"], [role="button"], [role="checkbox"], [role="radio"], [role="switch"])';
126
+ function isClipping(node) {
127
+ const box = node.querySelector(':scope > [data-slot="typography-content"]') ?? node;
128
+ return box.scrollWidth > box.clientWidth || box.scrollHeight > box.clientHeight;
129
+ }
130
+ function isFocusVisible(node) {
131
+ try {
132
+ return node.matches(":focus-visible");
133
+ } catch {
134
+ return true;
135
+ }
136
+ }
137
+ function EllipsisTooltip({
138
+ anchorRef,
139
+ title
140
+ }) {
141
+ const [open, setOpen] = React.useState(false);
142
+ const [side, setSide] = React.useState("top");
143
+ const timer = React.useRef(void 0);
144
+ React.useEffect(() => {
145
+ const node = anchorRef.current;
146
+ if (!node) return void 0;
147
+ const host = node.matches(FOCUS_HOST) ? node : node.parentElement?.closest(FOCUS_HOST);
148
+ const schedule = (next, delay) => {
149
+ clearTimeout(timer.current);
150
+ timer.current = setTimeout(() => {
151
+ setOpen(next);
152
+ }, delay);
153
+ };
154
+ const show = (delay) => {
155
+ if (!isClipping(node)) return;
156
+ setSide(host && host !== node && host.hasAttribute("data-state") ? "bottom" : "top");
157
+ schedule(true, delay);
158
+ };
159
+ const hide = () => {
160
+ schedule(false, 100);
161
+ };
162
+ const onPointerEnter = () => {
163
+ show(DEFAULT_DELAY_DURATION);
164
+ };
165
+ const onFocusIn = () => {
166
+ const active = document.activeElement;
167
+ if (active && (active === host || node.contains(active)) && isFocusVisible(active)) show(0);
168
+ };
169
+ node.addEventListener("pointerenter", onPointerEnter);
170
+ node.addEventListener("pointerleave", hide);
171
+ node.addEventListener("focusin", onFocusIn);
172
+ node.addEventListener("focusout", hide);
173
+ host?.addEventListener("focusin", onFocusIn);
174
+ host?.addEventListener("focusout", hide);
175
+ return () => {
176
+ clearTimeout(timer.current);
177
+ node.removeEventListener("pointerenter", onPointerEnter);
178
+ node.removeEventListener("pointerleave", hide);
179
+ node.removeEventListener("focusin", onFocusIn);
180
+ node.removeEventListener("focusout", hide);
181
+ host?.removeEventListener("focusin", onFocusIn);
182
+ host?.removeEventListener("focusout", hide);
183
+ };
184
+ }, [anchorRef]);
185
+ React.useEffect(() => {
186
+ if (!open) return void 0;
187
+ const onKeyDown = (event) => {
188
+ if (event.key === "Escape") {
189
+ clearTimeout(timer.current);
190
+ setOpen(false);
191
+ }
192
+ };
193
+ document.addEventListener("keydown", onKeyDown);
194
+ return () => {
195
+ document.removeEventListener("keydown", onKeyDown);
196
+ };
197
+ }, [open]);
198
+ return /* @__PURE__ */ jsx(
199
+ Tooltip,
200
+ {
201
+ open,
202
+ onOpenChange: (next) => {
203
+ if (!next) setOpen(false);
204
+ },
205
+ children: /* @__PURE__ */ jsx(TooltipAnchorContext.Provider, { value: anchorRef, children: /* @__PURE__ */ jsx(
206
+ TooltipContent,
207
+ {
208
+ side,
209
+ "data-ellipsis-tooltip": "",
210
+ onPointerEnter: () => {
211
+ clearTimeout(timer.current);
212
+ },
213
+ onPointerLeave: () => {
214
+ setOpen(false);
215
+ },
216
+ children: title
217
+ }
218
+ ) })
219
+ }
220
+ );
221
+ }
122
222
  export {
223
+ EllipsisTooltip,
123
224
  Tooltip,
124
225
  TooltipContent,
125
226
  TooltipProvider,
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { Check, Copy, LoaderCircle, Pencil } from "lucide-react";
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { useTranslation } from "../../i18n/use-translation.js";
7
- import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
7
+ import { EllipsisTooltip, Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
8
8
  import { Textarea } from "../data-entry/textarea.js";
9
9
  import { isDevelopment } from "../../lib/dev.js";
10
10
  const COPIED_RESET_MS = 3e3;
@@ -347,7 +347,9 @@ function useTypographyBlock(props, allowRows) {
347
347
  wrapsContent,
348
348
  onTextClick: enableEdit && triggerType.includes("text") ? startEditing : void 0,
349
349
  ellipsisTooltip,
350
- isEllipsis
350
+ isEllipsis,
351
+ anchorRef: elementRef,
352
+ showsEllipsisTooltip: clamping && ellipsisTooltip !== void 0 && ellipsisTooltip !== null && ellipsisTooltip !== false && ellipsisTooltip !== ""
351
353
  };
352
354
  }
353
355
  function usesBlockBehaviour(props) {
@@ -409,7 +411,11 @@ const TextBase = React.forwardRef((props, ref) => {
409
411
  italic: _italic,
410
412
  ...rest
411
413
  } = props;
412
- const block = useTypographyBlock(props, allowRows);
414
+ const asChildElement = asChild && React.isValidElement(children) ? children : null;
415
+ const block = useTypographyBlock(
416
+ asChildElement ? { ...props, children: asChildElement.props.children } : props,
417
+ allowRows
418
+ );
413
419
  const element = as ?? component ?? "span";
414
420
  const clampLines = typeof clamp === "number" && Number.isFinite(clamp) && clamp >= 1 ? Math.floor(clamp) : void 0;
415
421
  const ellipsisRowCount = ellipsis ? ellipsisRows(ellipsis) : 0;
@@ -492,21 +498,29 @@ const TextBase = React.forwardRef((props, ref) => {
492
498
  ) : block.text,
493
499
  block.trailing
494
500
  ] });
501
+ let rendered;
495
502
  if (asChild) {
496
503
  const child = React.Children.only(children);
497
504
  const cloned = {
498
505
  ...typography,
499
506
  ...child.props,
500
- ref,
507
+ // The measuring ref too, or an `asChild` ellipsis never learns it is clipping (gh#1045).
508
+ ref: composeRefs(block.measureRef, ref),
501
509
  className: cn(typography.className, child.props.className)
502
510
  };
503
- return usesBlockBehaviour(props) ? React.cloneElement(child, cloned, body) : React.cloneElement(child, cloned);
511
+ rendered = usesBlockBehaviour(props) ? React.cloneElement(child, cloned, body) : React.cloneElement(child, cloned);
512
+ } else {
513
+ rendered = React.createElement(
514
+ element,
515
+ { ref: composeRefs(block.measureRef, ref), ...typography },
516
+ body
517
+ );
504
518
  }
505
- return React.createElement(
506
- element,
507
- { ref: composeRefs(block.measureRef, ref), ...typography },
508
- body
509
- );
519
+ if (!block.showsEllipsisTooltip) return rendered;
520
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
521
+ rendered,
522
+ /* @__PURE__ */ jsx(EllipsisTooltip, { anchorRef: block.anchorRef, title: block.ellipsisTooltip })
523
+ ] });
510
524
  });
511
525
  TextBase.displayName = "TextBase";
512
526
  const Text = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(TextBase, { ref, ...props }));
@@ -615,7 +629,7 @@ const Title = React.forwardRef((props, ref) => {
615
629
  ) : block.text,
616
630
  block.trailing
617
631
  ] });
618
- return React.createElement(
632
+ const rendered = React.createElement(
619
633
  element,
620
634
  {
621
635
  ref: composeRefs(block.measureRef, ref),
@@ -635,6 +649,11 @@ const Title = React.forwardRef((props, ref) => {
635
649
  },
636
650
  body
637
651
  );
652
+ if (!block.showsEllipsisTooltip) return rendered;
653
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
654
+ rendered,
655
+ /* @__PURE__ */ jsx(EllipsisTooltip, { anchorRef: block.anchorRef, title: block.ellipsisTooltip })
656
+ ] });
638
657
  });
639
658
  Title.displayName = "Title";
640
659
  const TypographyRoot = React.forwardRef(
@@ -0,0 +1,16 @@
1
+ import { type ReactElement } from "react";
2
+ import type { BreadcrumbItemProp, BreadcrumbProp } from "../../props/vocabulary/navigation.prop.js";
3
+ /** `data-ellipsis` for the `<ol>`: present when any crumb truncates, so the trail stays one line. */
4
+ export declare function trailEllipsis(items: BreadcrumbProp): "" | undefined;
5
+ /**
6
+ * gh#1046 — one crumb's text box, bounded to one line with "…", and the full label in a tooltip on
7
+ * hover and on keyboard focus of the link. The crumb element itself is the clipped box (never a
8
+ * wrapper around it), so the link's own focus ring is not clipped. Shared with PageContainer's
9
+ * trail, which renders its own markup.
10
+ */
11
+ export declare function BreadcrumbCrumbText({ item, children, }: {
12
+ item: BreadcrumbItemProp;
13
+ children: ReactElement<{
14
+ className?: string;
15
+ }>;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { cloneElement, useRef } from "react";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { EllipsisTooltip } from "../feedback/tooltip.js";
6
+ function trailEllipsis(items) {
7
+ return items.some((item) => item.ellipsis) ? "" : void 0;
8
+ }
9
+ function BreadcrumbCrumbText({
10
+ item,
11
+ children
12
+ }) {
13
+ const anchorRef = useRef(null);
14
+ if (!item.ellipsis) return children;
15
+ const tooltip = typeof item.ellipsis === "object" && "tooltip" in item.ellipsis ? item.ellipsis.tooltip === true ? item.label : item.ellipsis.tooltip : item.label;
16
+ const hasTooltip = tooltip !== void 0 && tooltip !== null && tooltip !== false && tooltip !== "";
17
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
18
+ cloneElement(children, {
19
+ ref: anchorRef,
20
+ className: cn(children.props.className, "ui-breadcrumb-ellipsis")
21
+ }),
22
+ hasTooltip ? /* @__PURE__ */ jsx(EllipsisTooltip, { anchorRef, title: tooltip }) : null
23
+ ] });
24
+ }
25
+ export {
26
+ BreadcrumbCrumbText,
27
+ trailEllipsis
28
+ };