@squadbase/vantage 0.6.2-dev1.54bde93 → 0.6.2-dev1.56da87f

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 (89) hide show
  1. package/dist/{build-A23M4GCH.js → build-U7GO26HR.js} +3 -3
  2. package/dist/{build-A23M4GCH.js.map → build-U7GO26HR.js.map} +1 -1
  3. package/dist/{check-FBHT6W6J.js → check-MRVAIHG5.js} +3 -3
  4. package/dist/{check-FBHT6W6J.js.map → check-MRVAIHG5.js.map} +1 -1
  5. package/dist/{chunk-57GHR5LN.js → chunk-3RJEOP3J.js} +29 -18
  6. package/dist/chunk-3RJEOP3J.js.map +1 -0
  7. package/dist/{chunk-QOXXOFGL.js → chunk-77CISBMG.js} +994 -464
  8. package/dist/chunk-77CISBMG.js.map +1 -0
  9. package/dist/{chunk-JXFCSF3K.js → chunk-EG4PX726.js} +12 -7
  10. package/dist/chunk-EG4PX726.js.map +1 -0
  11. package/dist/{chunk-2N72FKLB.js → chunk-NZ5QIIG4.js} +33 -11
  12. package/dist/chunk-NZ5QIIG4.js.map +1 -0
  13. package/dist/{chunk-5C2SEDPH.js → chunk-PJ4SPXHS.js} +4 -4
  14. package/dist/{chunk-5C2SEDPH.js.map → chunk-PJ4SPXHS.js.map} +1 -1
  15. package/dist/{chunk-7S52M2SL.js → chunk-QNOX7WLZ.js} +3 -3
  16. package/dist/{chunk-7S52M2SL.js.map → chunk-QNOX7WLZ.js.map} +1 -1
  17. package/dist/{chunk-PDOBHMUQ.js → chunk-ROHNXP7J.js} +72 -3
  18. package/dist/chunk-ROHNXP7J.js.map +1 -0
  19. package/dist/{chunk-6OTTGAPO.js → chunk-TRMHNMMZ.js} +24 -7
  20. package/dist/chunk-TRMHNMMZ.js.map +1 -0
  21. package/dist/cli.js +4 -4
  22. package/dist/components/index.d.ts +49 -29
  23. package/dist/components/index.js +5 -5
  24. package/dist/{delta-text-CvwBGJ95.d.ts → delta-text-CbrOaW83.d.ts} +4 -2
  25. package/dist/{dev-H4I2JL63.js → dev-O42JFQ64.js} +3 -3
  26. package/dist/{dev-H4I2JL63.js.map → dev-O42JFQ64.js.map} +1 -1
  27. package/dist/doc/index.d.ts +14 -14
  28. package/dist/doc/index.js +198 -84
  29. package/dist/doc/index.js.map +1 -1
  30. package/dist/{doctor-B2O3GJDU.js → doctor-YD3XAAOT.js} +3 -3
  31. package/dist/{doctor-B2O3GJDU.js.map → doctor-YD3XAAOT.js.map} +1 -1
  32. package/dist/{format-U0cQl069.d.ts → format-DihQkL1N.d.ts} +17 -11
  33. package/dist/slide/index.d.ts +2 -2
  34. package/dist/slide/index.js +95 -44
  35. package/dist/slide/index.js.map +1 -1
  36. package/dist/table/index.js +6 -6
  37. package/dist/ui/index.d.ts +1 -1
  38. package/dist/ui/index.js +5 -5
  39. package/docs/en/agent-skills.md +1 -1
  40. package/docs/ja/agent-skills.md +1 -1
  41. package/package.json +2 -2
  42. package/registry/ui/app-shell.tsx +138 -1
  43. package/registry/ui/area-chart.tsx +344 -85
  44. package/registry/ui/attention-list.tsx +177 -15
  45. package/registry/ui/bar-chart.tsx +341 -85
  46. package/registry/ui/bar-list.tsx +332 -77
  47. package/registry/ui/box-plot-chart.tsx +405 -97
  48. package/registry/ui/category-bar.tsx +248 -40
  49. package/registry/ui/chart-frame.tsx +257 -46
  50. package/registry/ui/data-table.tsx +229 -37
  51. package/registry/ui/date-range-picker.tsx +190 -36
  52. package/registry/ui/delta.tsx +171 -12
  53. package/registry/ui/donut-chart.tsx +339 -84
  54. package/registry/ui/echart.tsx +142 -4
  55. package/registry/ui/filter-bar.tsx +179 -20
  56. package/registry/ui/funnel-steps.tsx +354 -80
  57. package/registry/ui/heatmap.tsx +293 -52
  58. package/registry/ui/histogram-chart.tsx +395 -97
  59. package/registry/ui/kpi-card.tsx +245 -44
  60. package/registry/ui/legend.tsx +330 -76
  61. package/registry/ui/line-chart.tsx +344 -85
  62. package/registry/ui/multi-select.tsx +159 -9
  63. package/registry/ui/page-shell.tsx +141 -3
  64. package/registry/ui/paired-chart.tsx +349 -90
  65. package/registry/ui/progress-circle.tsx +205 -39
  66. package/registry/ui/sample-data.tsx +153 -9
  67. package/registry/ui/scatter-chart.tsx +391 -100
  68. package/registry/ui/searchable-select.tsx +145 -7
  69. package/registry/ui/states.tsx +170 -13
  70. package/registry/ui/status-badge.tsx +193 -28
  71. package/registry/ui/tracker.tsx +205 -39
  72. package/registry/ui/trend-indicator.tsx +149 -6
  73. package/registry/ui/updated-at.tsx +170 -16
  74. package/registry/ui/waterfall-chart.tsx +415 -102
  75. package/skills/vantage-add-feature/SKILL.md +5 -0
  76. package/skills/vantage-design/SKILL.md +25 -27
  77. package/skills/vantage-design/references/extract.md +10 -13
  78. package/skills/vantage-design/references/format.md +12 -173
  79. package/skills/vantage-design/references/presets.md +0 -10
  80. package/templates/AGENTS.md +29 -15
  81. package/templates/agents/api.md +7 -0
  82. package/templates/agents/page.md +37 -1
  83. package/templates/agents/slide.md +7 -1
  84. package/dist/chunk-2N72FKLB.js.map +0 -1
  85. package/dist/chunk-57GHR5LN.js.map +0 -1
  86. package/dist/chunk-6OTTGAPO.js.map +0 -1
  87. package/dist/chunk-JXFCSF3K.js.map +0 -1
  88. package/dist/chunk-PDOBHMUQ.js.map +0 -1
  89. package/dist/chunk-QOXXOFGL.js.map +0 -1
@@ -75,6 +75,140 @@ import { Input } from "@squadbase/vantage/ui"
75
75
  import { Skeleton } from "@squadbase/vantage/ui"
76
76
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@squadbase/vantage/ui"
77
77
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@squadbase/vantage/ui"
78
+ import { useSyncExternalStore } from "react"
79
+
80
+ /* ---------- inlined from @squadbase/squadkit registry/lib/locale.ts (internal to @squadbase/vantage) ---------- */
81
+
82
+ /**
83
+ * The display language of the parts and the defaults that follow it.
84
+ *
85
+ * The language comes from `<html lang>`: a tag starting with `ja` reads Japanese, anything else English.
86
+ * Without a usable `lang` (missing, invalid, or no `document` — SSR, Node) the locale is `FALLBACK_LOCALE`
87
+ * (`en-US`). A `locale` passed explicitly (a prop, an argument) always wins.
88
+ *
89
+ * Parts read the locale through `useLocale` (`hooks/use-locale`) and pass it down; the pure helpers here
90
+ * and in `format` / `comparison` / `chart-*` take it as an argument and fall back to `documentLocale()`.
91
+ */
92
+
93
+ /** The language of the built-in wording: Japanese or English. */
94
+ type UiLanguage = "ja" | "en"
95
+
96
+ /** The locale used when `<html lang>` gives none (and on the server). */
97
+ const FALLBACK_LOCALE = "en-US"
98
+
99
+ /** `"ja"` when `locale` starts with `ja` (`ja`, `ja-JP`), else `"en"`. A missing locale is `"en"`. */
100
+ function uiLanguage(locale: string | null | undefined): UiLanguage {
101
+ return typeof locale === "string" && locale.trim().toLowerCase().startsWith("ja") ? "ja" : "en"
102
+ }
103
+
104
+ /** A BCP 47 tag in its canonical form (`en-gb` → `en-GB`), or `FALLBACK_LOCALE` when it is empty or invalid. */
105
+ function normalizeLocale(tag: string | null | undefined): string {
106
+ const value = tag?.trim()
107
+ if (!value) return FALLBACK_LOCALE
108
+ try {
109
+ return Intl.getCanonicalLocales(value)[0] ?? FALLBACK_LOCALE
110
+ } catch {
111
+ return FALLBACK_LOCALE
112
+ }
113
+ }
114
+
115
+ /**
116
+ * The locale of the page: `document.documentElement.lang`, canonicalized. `FALLBACK_LOCALE` when it is empty or
117
+ * invalid, or there is no `document`. Parts read it through `useLocale`, never while rendering.
118
+ */
119
+ function documentLocale(): string {
120
+ if (typeof document === "undefined") return FALLBACK_LOCALE
121
+ return normalizeLocale(document.documentElement?.getAttribute("lang"))
122
+ }
123
+
124
+ /** `locale` when given, else the page's locale (`documentLocale()`). */
125
+ function resolveLocale(locale?: string | null): string {
126
+ return locale || documentLocale()
127
+ }
128
+
129
+ /** Picks the wording table of the locale's language. */
130
+ function pickLocale<T>(table: { ja: T; en: T }, locale?: string | null): T {
131
+ return table[uiLanguage(resolveLocale(locale))]
132
+ }
133
+
134
+ /** The currency `format="currency"` uses without a `currency`: JPY for Japanese, USD otherwise. */
135
+ function defaultCurrency(locale?: string | null): string {
136
+ return uiLanguage(resolveLocale(locale)) === "ja" ? "JPY" : "USD"
137
+ }
138
+
139
+ /** The month a fiscal year starts in (1–12) when none is given: April for Japanese, January otherwise. */
140
+ function defaultFiscalYearStartMonth(locale?: string | null): number {
141
+ return uiLanguage(resolveLocale(locale)) === "ja" ? 4 : 1
142
+ }
143
+
144
+ /** A day of the week, 0 = Sunday … 6 = Saturday (the `Date#getDay` / date-fns numbering). */
145
+ type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6
146
+
147
+ /** Locales whose week starts on Sunday when the runtime has no week data. */
148
+ const SUNDAY_FIRST = new Set(["en", "en-US", "en-CA"])
149
+
150
+ /**
151
+ * The first day of the week: Monday for Japanese; otherwise the locale's convention from `Intl.Locale`
152
+ * (`en-US` Sunday, `en-GB` Monday). Without week data in the runtime: Sunday for `en` / `en-US` / `en-CA`,
153
+ * Monday for the rest.
154
+ */
155
+ function weekStartsOn(locale?: string | null): WeekDay {
156
+ const tag = normalizeLocale(resolveLocale(locale))
157
+ if (uiLanguage(tag) === "ja") return 1
158
+ try {
159
+ const info = new Intl.Locale(tag) as Intl.Locale & {
160
+ getWeekInfo?: () => { firstDay: number }
161
+ weekInfo?: { firstDay: number }
162
+ }
163
+ const firstDay = info.getWeekInfo?.().firstDay ?? info.weekInfo?.firstDay
164
+ if (typeof firstDay === "number" && firstDay >= 1 && firstDay <= 7) return (firstDay % 7) as WeekDay
165
+ } catch {
166
+ // Fall through to the table below.
167
+ }
168
+ return SUNDAY_FIRST.has(tag) ? 0 : 1
169
+ }
170
+
171
+ /* ---------- inlined from @squadbase/squadkit registry/hooks/use-locale.ts (internal to @squadbase/vantage) ---------- */
172
+
173
+ const listeners = new Set<() => void>()
174
+ let observer: MutationObserver | null = null
175
+
176
+ /** One observer on `<html lang>` for every subscribed part. */
177
+ function subscribe(onChange: () => void): () => void {
178
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => {}
179
+ listeners.add(onChange)
180
+ if (!observer) {
181
+ observer = new MutationObserver(() => {
182
+ for (const listener of [...listeners]) listener()
183
+ })
184
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] })
185
+ }
186
+ return () => {
187
+ listeners.delete(onChange)
188
+ if (listeners.size === 0 && observer) {
189
+ observer.disconnect()
190
+ observer = null
191
+ }
192
+ }
193
+ }
194
+
195
+ const serverLocale = () => FALLBACK_LOCALE
196
+
197
+ /**
198
+ * The page's locale (`<html lang>`), following changes made while the page runs. On the server and during
199
+ * hydration it is `FALLBACK_LOCALE` (`en-US`), so the markup matches; it switches right after.
200
+ */
201
+ function useDocumentLocale(): string {
202
+ return useSyncExternalStore(subscribe, documentLocale, serverLocale)
203
+ }
204
+
205
+ /** The locale a part renders in: its `locale` prop when given, else the page's (`useDocumentLocale`). */
206
+ function useLocale(locale?: string | null): string {
207
+ const page = useDocumentLocale()
208
+ return locale || page
209
+ }
210
+
211
+ /* ---------- end of inlined code ---------- */
78
212
 
79
213
  /* ------------------------------------------------------------------- types */
80
214
 
@@ -164,6 +298,47 @@ declare module "@tanstack/react-table" {
164
298
  }
165
299
  }
166
300
 
301
+ /* ------------------------------------------------------------ wording */
302
+
303
+ const TEXT = {
304
+ ja: {
305
+ tones: { positive: "良好", warning: "注意", negative: "要対応", neutral: "中立" } as Record<ChartTone, string>,
306
+ toneNote: (label: string) => `(${label})`,
307
+ selectAll: "全て選択",
308
+ selectRow: "行を選択",
309
+ trend: (points: number, first: string, last: string, min: string, max: string) =>
310
+ `${points}点の推移、${first}から${last}、最小${min}、最大${max}`,
311
+ noData: "データがありません",
312
+ pageSize: (size: number) => `${size}件`,
313
+ range: (total: number, start: number, end: number) => `${total}件中 ${start} - ${end} を表示`,
314
+ first: "最初",
315
+ previous: "前へ",
316
+ next: "次へ",
317
+ last: "最後",
318
+ columns: "表示する列",
319
+ exportCsv: "CSV を書き出す",
320
+ },
321
+ en: {
322
+ tones: { positive: "Good", warning: "Caution", negative: "Needs action", neutral: "Neutral" } as Record<ChartTone, string>,
323
+ toneNote: (label: string) => ` (${label})`,
324
+ selectAll: "Select all",
325
+ selectRow: "Select row",
326
+ trend: (points: number, first: string, last: string, min: string, max: string) =>
327
+ `Trend over ${points} points: ${first} to ${last}, min ${min}, max ${max}`,
328
+ noData: "No results.",
329
+ pageSize: (size: number) => `${size} / page`,
330
+ range: (total: number, start: number, end: number) => `Showing ${start}–${end} of ${total}`,
331
+ first: "First",
332
+ previous: "Previous",
333
+ next: "Next",
334
+ last: "Last",
335
+ columns: "Columns",
336
+ exportCsv: "Export CSV",
337
+ },
338
+ } satisfies Record<UiLanguage, unknown>
339
+
340
+ const useText = () => TEXT[uiLanguage(useLocale())]
341
+
167
342
  /* ------------------------------------------------------------ cell formatting */
168
343
 
169
344
  /** Resolve a column's `meta.format` for one row. */
@@ -187,29 +362,32 @@ function formatCellText<TData>(
187
362
  value: unknown,
188
363
  row: TData,
189
364
  meta: { format?: ValueFormat | ((row: TData) => ValueFormat); dateFormat?: DateStyle; timeZone?: string; nullLabel?: string },
365
+ locale: string,
190
366
  ): string {
191
367
  if (isMissingCell(value)) return meta.nullLabel ?? EMPTY_VALUE
192
368
  if (meta.dateFormat && (value instanceof Date || typeof value === "string" || typeof value === "number")) {
193
- const text = formatDate(value, meta.dateFormat, undefined, { timeZone: meta.timeZone })
369
+ const text = formatDate(value, meta.dateFormat, locale, { timeZone: meta.timeZone })
194
370
  return text === EMPTY_VALUE ? String(value) : text
195
371
  }
196
372
  if (typeof value !== "number") return String(value)
197
- return formatValue(value, resolveFormat(meta.format, row))
373
+ return formatValue(value, resolveFormat(meta.format, row), locale)
198
374
  }
199
375
 
200
376
  /** 意味色ごとの記号と色クラス。Tailwind が拾えるよう、クラスはリテラルで書く。 */
201
- const THRESHOLD_TONE_STYLES: Record<ChartTone, { Icon: LucideIcon; text: string; label: string }> = {
202
- positive: { Icon: CircleCheck, text: "text-positive", label: "良好" },
203
- warning: { Icon: TriangleAlert, text: "text-warning", label: "注意" },
204
- negative: { Icon: CircleX, text: "text-negative", label: "要対応" },
205
- neutral: { Icon: Minus, text: "text-neutral", label: "中立" },
377
+ const THRESHOLD_TONE_STYLES: Record<ChartTone, { Icon: LucideIcon; text: string }> = {
378
+ positive: { Icon: CircleCheck, text: "text-positive" },
379
+ warning: { Icon: TriangleAlert, text: "text-warning" },
380
+ negative: { Icon: CircleX, text: "text-negative" },
381
+ neutral: { Icon: Minus, text: "text-neutral" },
206
382
  }
207
383
 
208
384
  /** `meta.thresholds` 列のセル。当たった規則の意味色と記号を値に添える(当たらなければ値だけ)。 */
209
385
  function ThresholdCell({ value, text, thresholds }: { value: unknown; text: string; thresholds: ChartThreshold[] }) {
386
+ const t = useText()
210
387
  const tone = typeof value === "number" ? toneForValue(value, thresholds) : undefined
211
388
  if (!tone) return <div className="text-right tabular-nums">{text}</div>
212
- const { Icon, text: toneClass, label } = THRESHOLD_TONE_STYLES[tone]
389
+ const { Icon, text: toneClass } = THRESHOLD_TONE_STYLES[tone]
390
+ const label = t.tones[tone]
213
391
  return (
214
392
  <div
215
393
  data-slot="data-table-threshold-cell"
@@ -218,7 +396,7 @@ function ThresholdCell({ value, text, thresholds }: { value: unknown; text: stri
218
396
  >
219
397
  <Icon className="size-3.5 shrink-0" aria-hidden="true" />
220
398
  <span>{text}</span>
221
- <span className="sr-only">({label})</span>
399
+ <span className="sr-only">{t.toneNote(label)}</span>
222
400
  </div>
223
401
  )
224
402
  }
@@ -367,7 +545,7 @@ export interface DataTableExportProps {
367
545
  * <DataTableExport scope="page" />
368
546
  */
369
547
  scope?: CsvScope
370
- /** ボタンの文言。既定 `"CSV を書き出す"`。 */
548
+ /** ボタンの文言。既定はページの言語で `"CSV を書き出す"`(英語は `"Export CSV"`)。 */
371
549
  children?: ReactNode
372
550
  className?: string
373
551
  }
@@ -478,6 +656,8 @@ export function DataTable<TData, TValue>({
478
656
  className,
479
657
  children,
480
658
  }: DataTableProps<TData, TValue>) {
659
+ const locale = useLocale()
660
+ const t = TEXT[uiLanguage(locale)]
481
661
  const [sorting, setSorting] = useControlledState<SortingState>(sortingProp, [], onSortingChange)
482
662
 
483
663
  const [columnFilters, setColumnFilters] = useControlledState<ColumnFiltersState>(
@@ -526,7 +706,7 @@ export function DataTable<TData, TValue>({
526
706
  ...column,
527
707
  cell: ({ row, getValue }) => {
528
708
  const value = getValue()
529
- const text = formatCellText(value, row.original, meta ?? {})
709
+ const text = formatCellText(value, row.original, meta ?? {}, locale)
530
710
  if (thresholds) return <ThresholdCell value={value} text={text} thresholds={thresholds} />
531
711
  if (aligned) return <div className="text-right tabular-nums">{text}</div>
532
712
  if (meta?.dateFormat) return <span className="tabular-nums">{text}</span>
@@ -543,14 +723,14 @@ export function DataTable<TData, TValue>({
543
723
  checked={table.getIsAllPageRowsSelected()}
544
724
  indeterminate={table.getIsSomePageRowsSelected()}
545
725
  onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked)}
546
- aria-label="全て選択"
726
+ aria-label={t.selectAll}
547
727
  />
548
728
  ),
549
729
  cell: ({ row }) => (
550
730
  <Checkbox
551
731
  checked={row.getIsSelected()}
552
732
  onCheckedChange={(checked) => row.toggleSelected(checked)}
553
- aria-label="行を選択"
733
+ aria-label={t.selectRow}
554
734
  />
555
735
  ),
556
736
  enableSorting: false,
@@ -558,7 +738,7 @@ export function DataTable<TData, TValue>({
558
738
  }
559
739
 
560
740
  return [selectionColumn, ...formatted]
561
- }, [columns, enableRowSelection])
741
+ }, [columns, enableRowSelection, locale, t])
562
742
 
563
743
  // TanStack's own `autoResetPageIndex` queues `resetPageIndex()` from inside a render-time memo, which
564
744
  // can fire before the component has mounted ("Can't perform a React state update on a component that
@@ -707,23 +887,31 @@ export function sparklineColumn<TData>({
707
887
  const raw = getValue() as readonly (number | null | undefined)[] | null
708
888
  const series = (raw ?? []).filter((v): v is number => typeof v === "number" && Number.isFinite(v))
709
889
  if (series.length < SPARKLINE_MIN_POINTS) return <span className="text-muted-foreground">{EMPTY_VALUE}</span>
710
- const f = (v: number) => formatValue(v, format)
711
- const first = series[0] as number
712
- const last = series[series.length - 1] as number
713
890
  return (
714
891
  <div data-slot="data-table-sparkline-cell" className="flex h-5 items-center">
715
892
  <div aria-hidden="true" className="h-5 w-24 shrink-0">
716
893
  <Sparkline data={series.map((value) => ({ value }))} height={20} />
717
894
  </div>
718
- <span className="sr-only">
719
- {series.length}点の推移、{f(first)}から{f(last)}、最小{f(Math.min(...series))}、最大{f(Math.max(...series))}
720
- </span>
895
+ <SparklineSummary series={series} format={format} />
721
896
  </div>
722
897
  )
723
898
  },
724
899
  }
725
900
  }
726
901
 
902
+ /** The sparkline cell's screen-reader text, in the page's language. */
903
+ function SparklineSummary({ series, format }: { series: number[]; format: ValueFormat }) {
904
+ const locale = useLocale()
905
+ const f = (v: number) => formatValue(v, format, locale)
906
+ const first = series[0] as number
907
+ const last = series[series.length - 1] as number
908
+ return (
909
+ <span className="sr-only">
910
+ {TEXT[uiLanguage(locale)].trend(series.length, f(first), f(last), f(Math.min(...series)), f(Math.max(...series)))}
911
+ </span>
912
+ )
913
+ }
914
+
727
915
  /* ----------------------------------------------------------------- toolbar */
728
916
 
729
917
  export const DataTableToolbar = forwardRef<HTMLDivElement, DataTableToolbarProps>(
@@ -853,6 +1041,7 @@ function SortIcon({ sorted }: { sorted: false | "asc" | "desc" }) {
853
1041
  export const DataTableContent = forwardRef<HTMLDivElement, DataTableContentProps>(
854
1042
  ({ className, ...props }, ref) => {
855
1043
  const { table, visibleColumnCount, isLoading, enablePagination } = useDataTable()
1044
+ const t = useText()
856
1045
  const { comparisonOf, comparisonIds } = pairComparisons(table.getVisibleLeafColumns())
857
1046
 
858
1047
  return (
@@ -967,7 +1156,7 @@ export const DataTableContent = forwardRef<HTMLDivElement, DataTableContentProps
967
1156
  colSpan={visibleColumnCount}
968
1157
  className="h-24 text-center text-muted-foreground"
969
1158
  >
970
- データがありません
1159
+ {t.noData}
971
1160
  </TableCell>
972
1161
  </TableRow>
973
1162
  )}
@@ -984,6 +1173,7 @@ DataTableContent.displayName = "DataTableContent"
984
1173
  export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginationProps>(
985
1174
  ({ pageSizeOptions = [10, 20, 50], className, ...props }, ref) => {
986
1175
  const { table, isLoading } = useDataTable()
1176
+ const t = useText()
987
1177
 
988
1178
  const totalRows =
989
1179
  table.options.rowCount !== undefined
@@ -993,11 +1183,11 @@ export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginatio
993
1183
  const currentPageIndex = table.getState().pagination.pageIndex
994
1184
  const rangeStart = currentPageIndex * currentPageSize + 1
995
1185
  const rangeEnd = Math.min((currentPageIndex + 1) * currentPageSize, totalRows)
996
- // One list feeds both the menu and `items`, so the trigger shows "10件"
1186
+ // One list feeds both the menu and `items`, so the trigger shows "10 / page" (ja: "10件")
997
1187
  // rather than the raw "10" under Base UI's SelectValue.
998
1188
  const pageSizeItems = pageSizeOptions.map((size) => ({
999
1189
  value: String(size),
1000
- label: `${size}件`,
1190
+ label: t.pageSize(size),
1001
1191
  }))
1002
1192
 
1003
1193
  return (
@@ -1009,15 +1199,13 @@ export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginatio
1009
1199
  {...props}
1010
1200
  >
1011
1201
  <div className="flex items-center gap-2 text-sm text-muted-foreground">
1012
- <span>
1013
- {totalRows}件中 {rangeStart} - {rangeEnd} を表示
1014
- </span>
1202
+ <span>{t.range(totalRows, rangeStart, rangeEnd)}</span>
1015
1203
  <Select
1016
1204
  items={pageSizeItems}
1017
1205
  value={String(currentPageSize)}
1018
1206
  onValueChange={(value) => table.setPageSize(Number(value))}
1019
1207
  >
1020
- <SelectTrigger size="sm" className="w-[80px]">
1208
+ <SelectTrigger size="sm" className="min-w-[80px]">
1021
1209
  <SelectValue />
1022
1210
  </SelectTrigger>
1023
1211
  <SelectContent>
@@ -1037,7 +1225,7 @@ export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginatio
1037
1225
  onClick={() => table.firstPage()}
1038
1226
  disabled={isLoading || !table.getCanPreviousPage()}
1039
1227
  >
1040
- 最初
1228
+ {t.first}
1041
1229
  </Button>
1042
1230
  <Button
1043
1231
  variant="outline"
@@ -1045,7 +1233,7 @@ export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginatio
1045
1233
  onClick={() => table.previousPage()}
1046
1234
  disabled={isLoading || !table.getCanPreviousPage()}
1047
1235
  >
1048
- 前へ
1236
+ {t.previous}
1049
1237
  </Button>
1050
1238
  <Button
1051
1239
  variant="outline"
@@ -1053,7 +1241,7 @@ export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginatio
1053
1241
  onClick={() => table.nextPage()}
1054
1242
  disabled={isLoading || !table.getCanNextPage()}
1055
1243
  >
1056
- 次へ
1244
+ {t.next}
1057
1245
  </Button>
1058
1246
  <Button
1059
1247
  variant="outline"
@@ -1061,7 +1249,7 @@ export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginatio
1061
1249
  onClick={() => table.lastPage()}
1062
1250
  disabled={isLoading || !table.getCanNextPage()}
1063
1251
  >
1064
- 最後
1252
+ {t.last}
1065
1253
  </Button>
1066
1254
  </div>
1067
1255
  </div>
@@ -1077,6 +1265,7 @@ export const DataTableColumnVisibility = forwardRef<
1077
1265
  DataTableColumnVisibilityProps
1078
1266
  >(({ columnLabels, className }, ref) => {
1079
1267
  const { table } = useDataTable()
1268
+ const t = useText()
1080
1269
 
1081
1270
  const hideableColumns = table.getAllColumns().filter((column) => column.getCanHide())
1082
1271
 
@@ -1088,7 +1277,7 @@ export const DataTableColumnVisibility = forwardRef<
1088
1277
  ref={ref}
1089
1278
  render={<Button variant="outline" size="sm" className={cn(className)} />}
1090
1279
  data-slot="data-table-column-visibility"
1091
- aria-label="表示する列"
1280
+ aria-label={t.columns}
1092
1281
  >
1093
1282
  <Columns3 className="size-4" aria-hidden="true" />
1094
1283
  </DropdownMenuTrigger>
@@ -1153,14 +1342,16 @@ export function toCsv(
1153
1342
 
1154
1343
  /**
1155
1344
  * 表の現在の状態から CSV を作る。行は画面と同じ並べ替え・絞り込み後の順、列は表示中の列のみ。
1345
+ * `"display"` の書式のロケールは `locale`(既定はページのロケール、`<html lang>`)。
1156
1346
  *
1157
1347
  * @example
1158
1348
  * const csv = tableToCsv(table, { values: "raw", scope: "page" })
1159
1349
  */
1160
1350
  export function tableToCsv<TData>(
1161
1351
  table: TanstackTable<TData>,
1162
- { values = "display", scope = "filtered" }: { values?: CsvValues; scope?: CsvScope } = {},
1352
+ { values = "display", scope = "filtered", locale: localeArg }: { values?: CsvValues; scope?: CsvScope; locale?: string } = {},
1163
1353
  ): string {
1354
+ const locale = resolveLocale(localeArg)
1164
1355
  const columns = table.getVisibleLeafColumns().filter((column) => column.id !== "_select")
1165
1356
  const rows = scope === "page" ? table.getRowModel().rows : table.getSortedRowModel().rows
1166
1357
  const header = columns.map((column) =>
@@ -1170,7 +1361,7 @@ export function tableToCsv<TData>(
1170
1361
  columns.map((column): CsvCell => {
1171
1362
  const value = row.getValue(column.id)
1172
1363
  const meta = column.columnDef.meta
1173
- if (values === "display" && (meta?.format || meta?.dateFormat)) return formatCellText(value, row.original, meta)
1364
+ if (values === "display" && (meta?.format || meta?.dateFormat)) return formatCellText(value, row.original, meta, locale)
1174
1365
  if (value === null || value === undefined) return ""
1175
1366
  if (typeof value === "number" || typeof value === "string") return value
1176
1367
  if (value instanceof Date) return value.toISOString()
@@ -1185,13 +1376,14 @@ export function DataTableExport({
1185
1376
  fileName = "export.csv",
1186
1377
  values = "display",
1187
1378
  scope = "filtered",
1188
- children = "CSV を書き出す",
1379
+ children,
1189
1380
  className,
1190
1381
  }: DataTableExportProps) {
1191
1382
  const { table, isLoading } = useDataTable()
1383
+ const locale = useLocale()
1192
1384
 
1193
1385
  const onClick = () => {
1194
- const csv = tableToCsv(table, { values, scope })
1386
+ const csv = tableToCsv(table, { values, scope, locale })
1195
1387
  const url = URL.createObjectURL(new Blob([csv], { type: "text/csv;charset=utf-8" }))
1196
1388
  const a = document.createElement("a")
1197
1389
  a.href = url
@@ -1211,7 +1403,7 @@ export function DataTableExport({
1211
1403
  disabled={isLoading}
1212
1404
  >
1213
1405
  <Download className="size-4" aria-hidden="true" />
1214
- {children}
1406
+ {children ?? TEXT[uiLanguage(locale)].exportCsv}
1215
1407
  </Button>
1216
1408
  )
1217
1409
  }