@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
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { ChevronDown, ChevronRight } from "lucide-react";
4
4
  import { useTranslation } from "../../i18n/use-translation.js";
5
+ import { BreadcrumbCrumbText, trailEllipsis } from "./breadcrumb-ellipsis.js";
5
6
  import {
6
7
  DropdownMenu,
7
8
  DropdownMenuContent,
@@ -17,12 +18,20 @@ function Breadcrumb({
17
18
  }) {
18
19
  const { t } = useTranslation();
19
20
  const separatorNode = separator === void 0 ? /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" }) : separator;
20
- return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel ?? t("navigation.breadcrumb.ariaLabel"), className: "ui-breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: items.map((item, index) => {
21
+ return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel ?? t("navigation.breadcrumb.ariaLabel"), className: "ui-breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", "data-ellipsis": trailEllipsis(items), children: items.map((item, index) => {
21
22
  const isLast = index === items.length - 1;
22
- return /* @__PURE__ */ jsxs("li", { className: "ui-breadcrumb-item", children: [
23
- renderBreadcrumbSegment({ item, index, isLast, items, itemRender, LinkComponent }),
24
- !isLast ? /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-separator", "aria-hidden": "true", children: separatorNode }) : null
25
- ] }, item.to ?? index);
23
+ return /* @__PURE__ */ jsxs(
24
+ "li",
25
+ {
26
+ className: "ui-breadcrumb-item",
27
+ "data-ellipsis": item.ellipsis ? "" : void 0,
28
+ children: [
29
+ renderBreadcrumbSegment({ item, index, isLast, items, itemRender, LinkComponent }),
30
+ !isLast ? /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-separator", "aria-hidden": "true", children: separatorNode }) : null
31
+ ]
32
+ },
33
+ item.to ?? index
34
+ );
26
35
  }) }) });
27
36
  }
28
37
  function renderBreadcrumbSegment({
@@ -54,9 +63,9 @@ function renderBreadcrumbSegment({
54
63
  ] });
55
64
  }
56
65
  if (item.to && !isLast) {
57
- return /* @__PURE__ */ jsx(LinkComponent, { href: item.to, to: item.to, className: "ui-breadcrumb-link", children: item.label });
66
+ return /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(LinkComponent, { href: item.to, to: item.to, className: "ui-breadcrumb-link", children: item.label }) });
58
67
  }
59
- return /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-current", "aria-current": isLast ? "page" : void 0, children: item.label });
68
+ return /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-current", "aria-current": isLast ? "page" : void 0, children: item.label }) });
60
69
  }
61
70
  export {
62
71
  Breadcrumb
@@ -5,6 +5,7 @@ import { ChevronRight } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { scrollParent, useInView } from "../../lib/hooks.js";
7
7
  import { cn } from "../../lib/utils.js";
8
+ import { BreadcrumbCrumbText, trailEllipsis } from "./breadcrumb-ellipsis.js";
8
9
  import { densityClass, pageContainerVariantClass, padStyle } from "../../lib/variants.js";
9
10
  function useFooterReveal(enabled) {
10
11
  const headerRef = useRef(null);
@@ -92,7 +93,7 @@ function PageContainerRoot({
92
93
  {
93
94
  "aria-label": breadcrumbLabel ?? breadcrumbAriaLabel ?? t("navigation.breadcrumb.ariaLabel"),
94
95
  className: "ui-breadcrumb",
95
- children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
96
+ children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", "data-ellipsis": trailEllipsis(breadcrumb), children: breadcrumb.map((item, i) => {
96
97
  const isLast = i === breadcrumb.length - 1;
97
98
  return (
98
99
  // `--space-inline-xs` gap the previous `.ui-inline-xs` gave, and it brings
@@ -100,25 +101,33 @@ function PageContainerRoot({
100
101
  // longer carries a `size-3` literal. It also drops `.ui-inline-xs`'s
101
102
  // `flex-wrap: wrap` — a crumb must not break between its label and its
102
103
  // chevron; the LIST wraps instead, exactly as <Breadcrumb> already behaves.
103
- /* @__PURE__ */ jsxs("li", { className: "ui-breadcrumb-item", children: [
104
- item.to && !isLast ? /* @__PURE__ */ jsx(
105
- LinkComponent,
106
- {
107
- href: item.to,
108
- to: item.to,
109
- className: "hover:text-foreground hover:underline",
110
- children: item.label
111
- }
112
- ) : /* @__PURE__ */ jsx(
113
- "span",
114
- {
115
- className: isLast ? "text-foreground" : "",
116
- "aria-current": isLast ? "page" : void 0,
117
- children: item.label
118
- }
119
- ),
120
- !isLast && /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
121
- ] }, i)
104
+ /* @__PURE__ */ jsxs(
105
+ "li",
106
+ {
107
+ className: "ui-breadcrumb-item",
108
+ "data-ellipsis": item.ellipsis ? "" : void 0,
109
+ children: [
110
+ item.to && !isLast ? /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(
111
+ LinkComponent,
112
+ {
113
+ href: item.to,
114
+ to: item.to,
115
+ className: "hover:text-foreground hover:underline",
116
+ children: item.label
117
+ }
118
+ ) }) : /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(
119
+ "span",
120
+ {
121
+ className: isLast ? "text-foreground" : "",
122
+ "aria-current": isLast ? "page" : void 0,
123
+ children: item.label
124
+ }
125
+ ) }),
126
+ !isLast && /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
127
+ ]
128
+ },
129
+ i
130
+ )
122
131
  );
123
132
  }) })
124
133
  }
@@ -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": "31.6.0",
3
+ "version": "31.7.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,
@@ -350,7 +350,8 @@
350
350
  },
351
351
  "progress": {
352
352
  "ariaLabel": "Progress",
353
- "breakdownSeparator": ", "
353
+ "breakdownSeparator": ", ",
354
+ "remainder": "Remaining"
354
355
  },
355
356
  "scrollArea": {
356
357
  "region": "Scrollable region"
@@ -343,7 +343,8 @@
343
343
  },
344
344
  "progress": {
345
345
  "ariaLabel": "進捗",
346
- "breakdownSeparator": "、"
346
+ "breakdownSeparator": "、",
347
+ "remainder": "残り"
347
348
  },
348
349
  "scrollArea": {
349
350
  "region": "スクロール可能な領域"
@@ -344,7 +344,8 @@
344
344
  },
345
345
  "progress": {
346
346
  "ariaLabel": "Tiến độ",
347
- "breakdownSeparator": ", "
347
+ "breakdownSeparator": ", ",
348
+ "remainder": "Còn lại"
348
349
  },
349
350
  "scrollArea": {
350
351
  "region": "Vùng có thể cuộn"
@@ -604,6 +604,11 @@ export declare const VOCABULARY_REGISTRY: {
604
604
  readonly category: "navigation";
605
605
  readonly description: "Single breadcrumb segment";
606
606
  };
607
+ readonly BreadcrumbItemEllipsisProp: {
608
+ readonly file: "vocabulary/navigation.prop.ts";
609
+ readonly category: "navigation";
610
+ readonly description: "One-line truncation of a breadcrumb segment, with the full label in a tooltip";
611
+ };
607
612
  readonly BreadcrumbProp: {
608
613
  readonly file: "vocabulary/navigation.prop.ts";
609
614
  readonly category: "navigation";
@@ -604,6 +604,11 @@ const VOCABULARY_REGISTRY = {
604
604
  category: "navigation",
605
605
  description: "Single breadcrumb segment"
606
606
  },
607
+ BreadcrumbItemEllipsisProp: {
608
+ file: "vocabulary/navigation.prop.ts",
609
+ category: "navigation",
610
+ description: "One-line truncation of a breadcrumb segment, with the full label in a tooltip"
611
+ },
607
612
  BreadcrumbProp: {
608
613
  file: "vocabulary/navigation.prop.ts",
609
614
  category: "navigation",
@@ -3,5 +3,5 @@ export type { ClassNameProp, ChildrenProp, IdProp, OpenProp, DefaultOpenProp, On
3
3
  export type { TitleProp, SubtitleProp, StatusProp, DescriptionProp, ExtraProp, FooterProp, ActionProp, IconProp, ConfirmLabelProp, CancelLabelProp, ActionsProp, EmptyMessageProp, } from "./content.prop.js";
4
4
  export type { PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, MobileShellWidthProp, OrientationProp, TableDensityProp, DensityProp, GapNameProp, PadProp, PadRawProp, PadSides, GapProp, GapStepProp, } from "./layout.prop.js";
5
5
  export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, AppSettingToggleAppearanceProp, ShapeProp, AvatarShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, TextWhitespaceProp, TextBreakProp, TypographyTypeProp, TitleLevelProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyActionsConfigProp, SizeProp, ControlStatusProp, ControlVariantProp, FormLayoutProp, DescriptionsLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, CalloutKindProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, RevealTriggerProp, InViewAmountProp, ActivityVariantProp, ActivityAnnounceProp, IconSizeProp, } from "./interaction.prop.js";
6
- export type { BreadcrumbItemProp, BreadcrumbProp, BreadcrumbItemMenuProp, BreadcrumbItemMenuEntryProp, BreadcrumbSeparatorProp, BreadcrumbItemRenderProp, } from "./navigation.prop.js";
6
+ export type { BreadcrumbItemProp, BreadcrumbProp, BreadcrumbItemMenuProp, BreadcrumbItemEllipsisProp, BreadcrumbItemMenuEntryProp, BreadcrumbSeparatorProp, BreadcrumbItemRenderProp, } from "./navigation.prop.js";
7
7
  export type { GetRowIdProp, GetRowLabelProp, OnRowClickProp, ColumnDefProp, TablePresetProp, TableColumnPriorityProp, TableCellIndentProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, TableRowToneProp, RowToneProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, StickyProp, ColumnFixedProp, ColumnFilterValueProp, ColumnFilterItemProp, ColumnFilterStateProp, OnColumnFilterChangeProp, ColumnCompareProp, ColumnSorterProp, TableSelectionItemProp, TableSelectionBuiltInProp, TableSelectAllMatchingProp, TableRowSelectionProp, TableExpandableProp, TableSummaryProp, TableScrollProp, TableStickyProp, OnRowProp, TablePaginationProp, TablePaginationPositionProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, } from "./data.prop.js";
@@ -33,6 +33,20 @@ export type BreadcrumbItemProp = {
33
33
  to?: string;
34
34
  /** Ant Design `menu` — a sibling dropdown on this segment. @see BreadcrumbItemMenuProp */
35
35
  menu?: BreadcrumbItemMenuProp;
36
+ /**
37
+ * One line, a long label cut with "…" (gh#1046). antd's Breadcrumb has no per-item ellipsis;
38
+ * the name and shape are antd Typography's `ellipsis`. The full label shows in a tooltip on hover
39
+ * and on keyboard focus of the crumb link — `true` / `{}` use the label, `{ tooltip: node }`
40
+ * replaces it, `{ tooltip: false }` drops it. Applies to link and current crumbs, not to a
41
+ * `menu` segment or an `itemRender` body. The trail then stays on one line; a theme may cap
42
+ * each such crumb with `--breadcrumb-item-max-inline-size`.
43
+ * @see BreadcrumbItemEllipsisProp
44
+ */
45
+ ellipsis?: BreadcrumbItemEllipsisProp;
46
+ };
47
+ /** `BreadcrumbItemProp.ellipsis` — on, or on with an explicit tooltip (gh#1046). */
48
+ export type BreadcrumbItemEllipsisProp = boolean | {
49
+ tooltip?: React.ReactNode;
36
50
  };
37
51
  /** Ordered breadcrumb trail above page title. */
38
52
  export type BreadcrumbProp = BreadcrumbItemProp[];
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.6.0",
2
+ "version": "31.7.0",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -702,7 +702,8 @@
702
702
  },
703
703
  "PageContainer": {
704
704
  "layers": [
705
- "layout.css"
705
+ "layout.css",
706
+ "dialog-layout.css"
706
707
  ]
707
708
  },
708
709
  "Pagination": {
@@ -1036,6 +1036,28 @@
1036
1036
  color: hsl(var(--foreground));
1037
1037
  }
1038
1038
 
1039
+ .ui-breadcrumb-list[data-ellipsis] {
1040
+ flex-wrap: nowrap;
1041
+ }
1042
+ .ui-breadcrumb-list[data-ellipsis] > .ui-breadcrumb-item:not([data-ellipsis]) {
1043
+ flex-shrink: 0;
1044
+ white-space: nowrap;
1045
+ }
1046
+ .ui-breadcrumb-item[data-ellipsis] {
1047
+ min-inline-size: 0;
1048
+ max-inline-size: var(--breadcrumb-item-max-inline-size);
1049
+ }
1050
+ .ui-breadcrumb-item[data-ellipsis] > svg,
1051
+ .ui-breadcrumb-item[data-ellipsis] > .ui-breadcrumb-separator {
1052
+ flex-shrink: 0;
1053
+ }
1054
+ .ui-breadcrumb-ellipsis {
1055
+ min-inline-size: 0;
1056
+ overflow: hidden;
1057
+ text-overflow: ellipsis;
1058
+ white-space: nowrap;
1059
+ }
1060
+
1039
1061
  .ui-empty-state {
1040
1062
  display: flex;
1041
1063
  flex-direction: column;
@@ -212,6 +212,9 @@
212
212
  font: inherit;
213
213
  cursor: pointer;
214
214
  outline: none;
215
+
216
+ max-inline-size: 100%;
217
+ min-inline-size: 0;
215
218
  }
216
219
 
217
220
  .ui-data-table-sort-button::after {
@@ -334,6 +337,11 @@
334
337
  display: inline-flex;
335
338
  align-items: center;
336
339
  gap: var(--space-inline-xs);
340
+ min-inline-size: 0;
341
+ max-inline-size: 100%;
342
+ }
343
+ .ui-data-table-sort-label > .ui-data-table-sort-icon {
344
+ flex-shrink: 0;
337
345
  }
338
346
 
339
347
  :is(th, td).text-center > .ui-data-table-sort-button > .ui-data-table-sort-label {
@@ -46,6 +46,8 @@
46
46
  --breadcrumb-font-size: var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)));
47
47
 
48
48
  --breadcrumb-menu-trigger-gap: var(--space-inline-xs);
49
+
50
+ --breadcrumb-item-max-inline-size: none;
49
51
  --menu-shortcut-font-size: var(
50
52
  --font-size-xs,
51
53
  calc(var(--font-size-base) / var(--font-size-ratio))
@@ -6,6 +6,7 @@ import {
6
6
  CardTitle,
7
7
  Progress,
8
8
  } from "@godxjp/ui/data-display";
9
+ import { useTranslation } from "@godxjp/ui/i18n";
9
10
  import { Flex, PageContainer } from "@godxjp/ui/layout";
10
11
 
11
12
  /**
@@ -13,6 +14,7 @@ import { Flex, PageContainer } from "@godxjp/ui/layout";
13
14
  * Composed only from real @godxjp/ui components.
14
15
  */
15
16
  export default function Demo() {
17
+ const { t, locale } = useTranslation();
16
18
  return (
17
19
  <PageContainer title="Progress" subtitle="0–100 bar · optional label + semantic tone">
18
20
  <Flex direction="col" gap="lg">
@@ -37,11 +39,10 @@ export default function Demo() {
37
39
  <CardHeader>
38
40
  <CardTitle level={2}>shape=&quot;ring&quot;</CardTitle>
39
41
  <CardDescription>
40
- 同じメーターを弧で描きます。バーと同じ value / tone / size / ARIA
41
- のまま、label がリングの内側に入ります。狭いアプリバーで「18 / 42
42
- 件」を見出しの横に出したいとき、バー+キャプションの 2
43
- 段ではなく 1 マスで済むのが理由です。数値が重要なときではなく、
44
- 場所が正方形のときに選びます。
42
+ 同じメーターを弧で描きます。バーと同じ value / tone / size / ARIA のまま、label
43
+ がリングの内側に入ります。狭いアプリバーで「18 / 42
44
+ 件」を見出しの横に出したいとき、バー+キャプションの 2 段ではなく 1
45
+ マスで済むのが理由です。数値が重要なときではなく、 場所が正方形のときに選びます。
45
46
  </CardDescription>
46
47
  </CardHeader>
47
48
  <CardContent>
@@ -68,7 +69,10 @@ export default function Demo() {
68
69
  1 つの合計を状態ごとに分割します。割合ではなく実数を渡すと、各スライスの比率は
69
70
  コンポーネントが計算します。メーターより背の高いトラックになるのは、0.5rem の pill
70
71
  上に 3 色を並べると比率が読めなくなるためです。role=&quot;img&quot;
71
- として、全スライスを 1 つの名前で読み上げます。
72
+ として、全スライスを 1 つの名前で読み上げます。total を渡すと全体は合計ではなく total
73
+ になり、残りはトラックのまま「残り」として読み上げます(antd の percent +
74
+ success.percent と同じく、未塗りのトラックが残り。antd の全体は常に 100
75
+ ですが、ここは実数の単位で total を指定します)。
72
76
  </CardDescription>
73
77
  </CardHeader>
74
78
  <CardContent>
@@ -89,6 +93,20 @@ export default function Demo() {
89
93
  ]}
90
94
  label="みどり農産株式会社(0 件のスライスは幅 0)"
91
95
  />
96
+ {/* total を渡すと、スライスは合計ではなく total に対して描かれ、残りはトラックのまま。
97
+ 読み上げには残り(remainderLabel)が Intl 形式の数値で入ります。 */}
98
+ <Progress
99
+ segments={[
100
+ { value: 3, tone: "success", label: t("progressDocs.testRun.passed") },
101
+ { value: 1, tone: "destructive", label: t("progressDocs.testRun.failed") },
102
+ { value: 2, tone: "warning", label: t("progressDocs.testRun.blocked") },
103
+ ]}
104
+ total={400}
105
+ remainderLabel={t("progressDocs.testRun.notRun")}
106
+ label={t("progressDocs.testRun.caption", {
107
+ total: new Intl.NumberFormat(locale).format(400),
108
+ })}
109
+ />
92
110
  </Flex>
93
111
  </CardContent>
94
112
  </Card>
@@ -21,8 +21,10 @@ import {
21
21
  TableHead,
22
22
  TableHeader,
23
23
  TableRow,
24
+ DataTable,
25
+ Tree,
24
26
  } from "@godxjp/ui/data-display";
25
- import { Flex, PageContainer } from "@godxjp/ui/layout";
27
+ import { Breadcrumb, Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
26
28
 
27
29
  /**
28
30
  * Text + Heading — the typographic primitives. Use these INSTEAD of a hand-rolled
@@ -46,8 +48,15 @@ const headingTones = [
46
48
  { tone: "info", title: "システムメンテナンスのお知らせ" },
47
49
  ] as const;
48
50
 
51
+ type SuiteRow = { id: string; title: string };
52
+
49
53
  export default function Demo() {
50
54
  const { t } = useTranslation();
55
+ const longTitle = t("textExamples.ellipsisLongTitle");
56
+ const suiteRows: SuiteRow[] = [
57
+ { id: "TS-0412", title: longTitle },
58
+ { id: "TS-0413", title: t("textExamples.ellipsisShortTitle") },
59
+ ];
51
60
  return (
52
61
  <PageContainer
53
62
  title="Typography"
@@ -388,6 +397,80 @@ export default function Demo() {
388
397
  </Flex>
389
398
  </CardContent>
390
399
  </Card>
400
+ <Card>
401
+ <CardHeader>
402
+ <CardTitle level={2}>{t("textExamples.ellipsisTitle")}</CardTitle>
403
+ <CardDescription>{t("textExamples.ellipsisDescription")}</CardDescription>
404
+ </CardHeader>
405
+ <CardContent>
406
+ <Flex direction="col" gap="md">
407
+ <ResponsiveGrid columns={{ sm: 2, lg: 3 }}>
408
+ <Flex direction="col" gap="xs">
409
+ <Text size="xs" tone="muted">
410
+ {t("textExamples.ellipsisPlain")}
411
+ </Text>
412
+ <Text ellipsis={{ tooltip: true }}>{longTitle}</Text>
413
+ </Flex>
414
+ <Flex direction="col" gap="xs">
415
+ <Text size="xs" tone="muted">
416
+ {t("textExamples.ellipsisLink")}
417
+ </Text>
418
+ <Text asChild link ellipsis={{ tooltip: true }}>
419
+ <a href="#typography">{longTitle}</a>
420
+ </Text>
421
+ </Flex>
422
+ <Flex direction="col" gap="xs">
423
+ <Text size="xs" tone="muted">
424
+ {t("textExamples.ellipsisTree")}
425
+ </Text>
426
+ <Tree
427
+ aria-label={t("textExamples.ellipsisTree")}
428
+ treeData={[
429
+ {
430
+ value: "suite",
431
+ label: longTitle,
432
+ children: [
433
+ { value: "case-1", label: t("textExamples.ellipsisShortTitle") },
434
+ ],
435
+ },
436
+ ]}
437
+ defaultExpandedValues={["suite"]}
438
+ titleRender={(node) => <Text ellipsis={{ tooltip: true }}>{node.label}</Text>}
439
+ />
440
+ </Flex>
441
+ </ResponsiveGrid>
442
+ <DataTable<SuiteRow>
443
+ label={t("textExamples.ellipsisTable")}
444
+ data={suiteRows}
445
+ getRowId={(row) => row.id}
446
+ columns={[
447
+ { key: "id", header: "ID", width: "7rem", render: (row) => row.id },
448
+ {
449
+ key: "title",
450
+ header: (
451
+ <Text ellipsis={{ tooltip: true }}>
452
+ {t("textExamples.ellipsisLongHeader")}
453
+ </Text>
454
+ ),
455
+ width: "12rem",
456
+ ellipsis: true,
457
+ sortable: true,
458
+ sorter: (a, b) => a.title.localeCompare(b.title),
459
+ render: (row) => <Text ellipsis={{ tooltip: true }}>{row.title}</Text>,
460
+ },
461
+ ]}
462
+ />
463
+ <Breadcrumb
464
+ ariaLabel={t("textExamples.ellipsisBreadcrumb")}
465
+ items={[
466
+ { label: t("textExamples.ellipsisCrumbHome"), to: "#typography" },
467
+ { label: longTitle, to: "#typography", ellipsis: true },
468
+ { label: longTitle, ellipsis: true },
469
+ ]}
470
+ />
471
+ </Flex>
472
+ </CardContent>
473
+ </Card>
391
474
  <Card>
392
475
  <CardHeader>
393
476
  <CardTitle level={2}>copyable · editable · 装飾</CardTitle>
@@ -6,7 +6,18 @@
6
6
  "breakDescription": "An identifier with nowhere to break — an email address, a coupon code, a login ID — takes break=\"anywhere\", and the column stays inside the viewport even in a table cell. whitespace=\"pre-wrap\" is for newlines a person typed; it cannot wrap an identifier in a table cell.",
7
7
  "breakColumnOwner": "Owner",
8
8
  "breakColumnEmail": "Email address",
9
- "breakSampleName": "Sato"
9
+ "breakSampleName": "Sato",
10
+ "ellipsisTitle": "ellipsis · the full text in a tooltip while it is cut",
11
+ "ellipsisDescription": "ellipsis={{ tooltip: true }} keeps one line and shows the whole text on hover and on keyboard focus of the control it sits in (the link, the tree item, the sort button). A title that fits shows no tooltip, and the Text never becomes a tab stop of its own.",
12
+ "ellipsisPlain": "Plain Text",
13
+ "ellipsisLink": "Text asChild · link",
14
+ "ellipsisTree": "Tree titleRender",
15
+ "ellipsisTable": "Test suites",
16
+ "ellipsisBreadcrumb": "Truncated breadcrumb",
17
+ "ellipsisCrumbHome": "Home",
18
+ "ellipsisLongTitle": "Q3 integration test · payment module · invoice re-issue scenarios and acceptance criteria review",
19
+ "ellipsisShortTitle": "Login smoke test",
20
+ "ellipsisLongHeader": "Latest run name (release candidate 2026-09)"
10
21
  },
11
22
  "themeShowcase": {
12
23
  "page": {
@@ -1469,5 +1480,22 @@
1469
1480
  "other": "Load {count} more"
1470
1481
  }
1471
1482
  }
1483
+ },
1484
+ "progressDocs": {
1485
+ "testRun": {
1486
+ "passed": "Passed",
1487
+ "failed": "Failed",
1488
+ "blocked": "Blocked",
1489
+ "notRun": "Not run",
1490
+ "caption": "Regression suite — {total} cases"
1491
+ }
1492
+ },
1493
+ "breadcrumbExamples": {
1494
+ "ellipsisTitle": "ellipsis · a long segment on one line",
1495
+ "ellipsisDescription": "ellipsis on a segment keeps the trail on one line and cuts that label with \"…\"; the full label shows in a tooltip on hover and when the crumb link takes keyboard focus. antd Breadcrumb has no per-item ellipsis — the shape is Typography’s. PageContainer breadcrumb takes the same item field.",
1496
+ "ellipsisAria": "Truncated breadcrumb",
1497
+ "home": "Home",
1498
+ "longSuite": "Q3 integration test · payment module · invoice re-issue scenarios",
1499
+ "longSection": "Acceptance criteria review for the release candidate of September 2026"
1472
1500
  }
1473
1501
  }
@@ -6,7 +6,18 @@
6
6
  "breakDescription": "メールアドレス・クーポンコード・ログイン ID のような区切りのない識別子は break=\"anywhere\"。表のセルでも列が画面幅に収まります。whitespace=\"pre-wrap\" は人が入力した改行のためのもので、表のセルでは識別子を折り返せません。",
7
7
  "breakColumnOwner": "担当者",
8
8
  "breakColumnEmail": "メールアドレス",
9
- "breakSampleName": "佐藤"
9
+ "breakSampleName": "佐藤",
10
+ "ellipsisTitle": "ellipsis · 省略中は全文をツールチップで",
11
+ "ellipsisDescription": "ellipsis={{ tooltip: true }} は 1 行に収め、ホバーと、置かれたコントロール(リンク・ツリー項目・並べ替えボタン)へのキーボードフォーカスで全文を表示します。収まるタイトルにはツールチップを出さず、Text 自体はタブ移動先になりません。",
12
+ "ellipsisPlain": "通常の Text",
13
+ "ellipsisLink": "Text asChild · リンク",
14
+ "ellipsisTree": "Tree の titleRender",
15
+ "ellipsisTable": "テストスイート",
16
+ "ellipsisBreadcrumb": "省略付きパンくずリスト",
17
+ "ellipsisCrumbHome": "ホーム",
18
+ "ellipsisLongTitle": "第3四半期 結合テスト 決済モジュール 請求書再発行シナリオと受入基準のレビュー",
19
+ "ellipsisShortTitle": "ログインのスモークテスト",
20
+ "ellipsisLongHeader": "最新の実行名(2026年9月 リリース候補)"
10
21
  },
11
22
  "themeShowcase": {
12
23
  "page": {
@@ -1462,5 +1473,22 @@
1462
1473
  "other": "さらに {count} 件を読み込む"
1463
1474
  }
1464
1475
  }
1476
+ },
1477
+ "progressDocs": {
1478
+ "testRun": {
1479
+ "passed": "合格",
1480
+ "failed": "失敗",
1481
+ "blocked": "ブロック",
1482
+ "notRun": "未実施",
1483
+ "caption": "回帰テスト — 全 {total} 件"
1484
+ }
1485
+ },
1486
+ "breadcrumbExamples": {
1487
+ "ellipsisTitle": "ellipsis · 長いセグメントを 1 行に",
1488
+ "ellipsisDescription": "セグメントに ellipsis を付けるとパンくずは 1 行のまま、そのラベルを「…」で省略します。全文はホバーと、パンくずのリンクへのキーボードフォーカスでツールチップに出ます。antd の Breadcrumb に項目単位の省略はなく、形は Typography に合わせています。PageContainer の breadcrumb も同じ項目フィールドを受け付けます。",
1489
+ "ellipsisAria": "省略付きパンくずリスト",
1490
+ "home": "ホーム",
1491
+ "longSuite": "第3四半期 結合テスト 決済モジュール 請求書再発行シナリオ",
1492
+ "longSection": "2026年9月 リリース候補の受入基準レビュー"
1465
1493
  }
1466
1494
  }
@@ -6,7 +6,18 @@
6
6
  "breakDescription": "Định danh không có chỗ ngắt — địa chỉ email, mã giảm giá, ID đăng nhập — dùng break=\"anywhere\", và cột vẫn nằm gọn trong bề ngang màn hình kể cả trong ô của bảng. whitespace=\"pre-wrap\" dành cho ký tự xuống dòng do người nhập; nó không ngắt được một định danh trong ô bảng.",
7
7
  "breakColumnOwner": "Phụ trách",
8
8
  "breakColumnEmail": "Địa chỉ email",
9
- "breakSampleName": "Sato"
9
+ "breakSampleName": "Sato",
10
+ "ellipsisTitle": "ellipsis · toàn văn trong tooltip khi bị cắt",
11
+ "ellipsisDescription": "ellipsis={{ tooltip: true }} giữ một dòng và hiện toàn văn khi rê chuột và khi bàn phím đưa tiêu điểm tới điều khiển chứa nó (liên kết, mục cây, nút sắp xếp). Tiêu đề vừa chỗ thì không có tooltip, và Text không bao giờ tự thành một điểm dừng Tab.",
12
+ "ellipsisPlain": "Text thường",
13
+ "ellipsisLink": "Text asChild · liên kết",
14
+ "ellipsisTree": "titleRender của Tree",
15
+ "ellipsisTable": "Bộ kiểm thử",
16
+ "ellipsisBreadcrumb": "Breadcrumb có cắt chữ",
17
+ "ellipsisCrumbHome": "Trang chủ",
18
+ "ellipsisLongTitle": "Kiểm thử tích hợp quý 3 · mô-đun thanh toán · kịch bản xuất lại hoá đơn và rà soát tiêu chí nghiệm thu",
19
+ "ellipsisShortTitle": "Kiểm thử nhanh đăng nhập",
20
+ "ellipsisLongHeader": "Tên lần chạy gần nhất (bản ứng viên 2026-09)"
10
21
  },
11
22
  "themeShowcase": {
12
23
  "page": {
@@ -1462,5 +1473,22 @@
1462
1473
  "other": "Tải thêm {count} ca"
1463
1474
  }
1464
1475
  }
1476
+ },
1477
+ "progressDocs": {
1478
+ "testRun": {
1479
+ "passed": "Đạt",
1480
+ "failed": "Lỗi",
1481
+ "blocked": "Bị chặn",
1482
+ "notRun": "Chưa chạy",
1483
+ "caption": "Bộ kiểm thử hồi quy — {total} ca"
1484
+ }
1485
+ },
1486
+ "breadcrumbExamples": {
1487
+ "ellipsisTitle": "ellipsis · một đoạn dài trên một dòng",
1488
+ "ellipsisDescription": "ellipsis trên một đoạn giữ breadcrumb trên một dòng và cắt nhãn đó bằng \"…\"; toàn văn hiện trong tooltip khi rê chuột và khi liên kết của đoạn nhận tiêu điểm bàn phím. Breadcrumb của antd không có ellipsis theo từng mục — hình dạng lấy theo Typography. breadcrumb của PageContainer nhận cùng trường này.",
1489
+ "ellipsisAria": "Breadcrumb có cắt chữ",
1490
+ "home": "Trang chủ",
1491
+ "longSuite": "Kiểm thử tích hợp quý 3 · mô-đun thanh toán · kịch bản xuất lại hoá đơn",
1492
+ "longSection": "Rà soát tiêu chí nghiệm thu cho bản ứng viên tháng 9/2026"
1465
1493
  }
1466
1494
  }
@@ -9,6 +9,7 @@ import {
9
9
  CodeBlock,
10
10
  } from "@godxjp/ui/data-display";
11
11
  import { Text } from "@godxjp/ui/general";
12
+ import { useTranslation } from "@godxjp/ui/i18n";
12
13
  import { Breadcrumb } from "@godxjp/ui/layout";
13
14
  import { Flex, PageContainer } from "@godxjp/ui/layout";
14
15
 
@@ -25,6 +26,9 @@ const RouterLink = forwardRef<
25
26
  });
26
27
 
27
28
  export default function Demo() {
29
+ const { t } = useTranslation();
30
+ const suite = t("breadcrumbExamples.longSuite");
31
+ const section = t("breadcrumbExamples.longSection");
28
32
  return (
29
33
  <PageContainer
30
34
  title="Breadcrumb"
@@ -177,6 +181,26 @@ export default function Demo() {
177
181
  </Flex>
178
182
  </CardContent>
179
183
  </Card>
184
+
185
+ {/* gh#1046 — a long segment cut to one line, the full label in a tooltip. */}
186
+ <Card>
187
+ <CardHeader>
188
+ <CardTitle level={2}>{t("breadcrumbExamples.ellipsisTitle")}</CardTitle>
189
+ <CardDescription>{t("breadcrumbExamples.ellipsisDescription")}</CardDescription>
190
+ </CardHeader>
191
+ <CardContent>
192
+ <div id="breadcrumb-ellipsis">
193
+ <Breadcrumb
194
+ ariaLabel={t("breadcrumbExamples.ellipsisAria")}
195
+ items={[
196
+ { label: t("breadcrumbExamples.home"), to: "/" },
197
+ { label: suite, to: "/suites/412", ellipsis: true },
198
+ { label: section, ellipsis: true },
199
+ ]}
200
+ />
201
+ </div>
202
+ </CardContent>
203
+ </Card>
180
204
  </Flex>
181
205
  </PageContainer>
182
206
  );