@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.
- package/agent/START-HERE.md +4 -4
- package/agent/components/Breadcrumb.json +1 -1
- package/agent/components/PageContainer.json +1 -1
- package/agent/components/Progress.json +12 -1
- package/agent/components/Text.json +1 -1
- package/agent/components.json +15 -4
- package/agent/index.json +4 -4
- package/agent/llms.txt +5 -5
- package/agent/tokens.json +6 -0
- package/dist/components/data-display/progress.d.ts +16 -0
- package/dist/components/data-display/progress.js +17 -3
- package/dist/components/feedback/tooltip.d.ts +21 -0
- package/dist/components/feedback/tooltip.js +101 -0
- package/dist/components/general/typography.js +30 -11
- package/dist/components/layout/breadcrumb-ellipsis.d.ts +16 -0
- package/dist/components/layout/breadcrumb-ellipsis.js +28 -0
- package/dist/components/layout/breadcrumb.js +16 -7
- package/dist/components/layout/page-container.js +29 -20
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +2 -1
- package/dist/i18n/messages/ja.json +2 -1
- package/dist/i18n/messages/vi.json +2 -1
- package/dist/props/registry.d.ts +5 -0
- package/dist/props/registry.js +5 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/navigation.prop.d.ts +14 -0
- package/dist/styles/layers.json +3 -2
- package/dist/styles/layout.css +22 -0
- package/dist/styles/table-layout.css +8 -0
- package/dist/tokens/components/navigation.css +2 -0
- package/docs/data-display/progress.tsx +24 -6
- package/docs/general/typography.tsx +84 -1
- package/docs/i18n/messages/en.json +29 -1
- package/docs/i18n/messages/ja.json +29 -1
- package/docs/i18n/messages/vi.json +29 -1
- package/docs/navigation/breadcrumb.tsx +24 -0
- package/package.json +2 -2
package/agent/START-HERE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
You are about to write code against a design system you did not author. This file is the whole
|
|
4
4
|
contract. Read it before you write JSX.
|
|
5
5
|
|
|
6
|
-
**This catalog describes `@godxjp/ui` 31.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 31.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` —
|
|
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
|
-
|
|
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` |
|
|
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
|
},
|
package/agent/components.json
CHANGED
|
@@ -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":
|
|
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.
|
|
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":
|
|
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.
|
|
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,
|
|
4
|
-
> 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.
|
|
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.
|
|
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/<Name>.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–36 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
|
|
20
20
|
- [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
|
|
21
|
-
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles,
|
|
21
|
+
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles, 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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
+
};
|