@squadbase/vantage 0.6.2-dev1.54bde93 → 0.6.2-dev1.5c79ef5
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/dist/{build-A23M4GCH.js → build-U7GO26HR.js} +3 -3
- package/dist/{build-A23M4GCH.js.map → build-U7GO26HR.js.map} +1 -1
- package/dist/{check-FBHT6W6J.js → check-MRVAIHG5.js} +3 -3
- package/dist/{check-FBHT6W6J.js.map → check-MRVAIHG5.js.map} +1 -1
- package/dist/{chunk-57GHR5LN.js → chunk-3RJEOP3J.js} +29 -18
- package/dist/chunk-3RJEOP3J.js.map +1 -0
- package/dist/{chunk-QOXXOFGL.js → chunk-77CISBMG.js} +994 -464
- package/dist/chunk-77CISBMG.js.map +1 -0
- package/dist/{chunk-JXFCSF3K.js → chunk-EG4PX726.js} +12 -7
- package/dist/chunk-EG4PX726.js.map +1 -0
- package/dist/{chunk-2N72FKLB.js → chunk-NZ5QIIG4.js} +33 -11
- package/dist/chunk-NZ5QIIG4.js.map +1 -0
- package/dist/{chunk-5C2SEDPH.js → chunk-PJ4SPXHS.js} +4 -4
- package/dist/{chunk-5C2SEDPH.js.map → chunk-PJ4SPXHS.js.map} +1 -1
- package/dist/{chunk-7S52M2SL.js → chunk-QNOX7WLZ.js} +3 -3
- package/dist/{chunk-7S52M2SL.js.map → chunk-QNOX7WLZ.js.map} +1 -1
- package/dist/{chunk-PDOBHMUQ.js → chunk-ROHNXP7J.js} +72 -3
- package/dist/chunk-ROHNXP7J.js.map +1 -0
- package/dist/{chunk-6OTTGAPO.js → chunk-TRMHNMMZ.js} +24 -7
- package/dist/chunk-TRMHNMMZ.js.map +1 -0
- package/dist/cli.js +4 -4
- package/dist/components/index.d.ts +49 -29
- package/dist/components/index.js +5 -5
- package/dist/{delta-text-CvwBGJ95.d.ts → delta-text-CbrOaW83.d.ts} +4 -2
- package/dist/{dev-H4I2JL63.js → dev-O42JFQ64.js} +3 -3
- package/dist/{dev-H4I2JL63.js.map → dev-O42JFQ64.js.map} +1 -1
- package/dist/doc/index.d.ts +14 -14
- package/dist/doc/index.js +198 -84
- package/dist/doc/index.js.map +1 -1
- package/dist/{doctor-B2O3GJDU.js → doctor-YD3XAAOT.js} +3 -3
- package/dist/{doctor-B2O3GJDU.js.map → doctor-YD3XAAOT.js.map} +1 -1
- package/dist/{format-U0cQl069.d.ts → format-DihQkL1N.d.ts} +17 -11
- package/dist/slide/index.d.ts +2 -2
- package/dist/slide/index.js +95 -44
- package/dist/slide/index.js.map +1 -1
- package/dist/table/index.js +6 -6
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +5 -5
- package/docs/en/agent-skills.md +1 -1
- package/docs/ja/agent-skills.md +1 -1
- package/package.json +2 -2
- package/registry/ui/app-shell.tsx +138 -1
- package/registry/ui/area-chart.tsx +344 -85
- package/registry/ui/attention-list.tsx +177 -15
- package/registry/ui/bar-chart.tsx +341 -85
- package/registry/ui/bar-list.tsx +332 -77
- package/registry/ui/box-plot-chart.tsx +405 -97
- package/registry/ui/category-bar.tsx +248 -40
- package/registry/ui/chart-frame.tsx +257 -46
- package/registry/ui/data-table.tsx +229 -37
- package/registry/ui/date-range-picker.tsx +190 -36
- package/registry/ui/delta.tsx +171 -12
- package/registry/ui/donut-chart.tsx +339 -84
- package/registry/ui/echart.tsx +142 -4
- package/registry/ui/filter-bar.tsx +179 -20
- package/registry/ui/funnel-steps.tsx +354 -80
- package/registry/ui/heatmap.tsx +293 -52
- package/registry/ui/histogram-chart.tsx +395 -97
- package/registry/ui/kpi-card.tsx +245 -44
- package/registry/ui/legend.tsx +330 -76
- package/registry/ui/line-chart.tsx +344 -85
- package/registry/ui/multi-select.tsx +159 -9
- package/registry/ui/page-shell.tsx +141 -3
- package/registry/ui/paired-chart.tsx +349 -90
- package/registry/ui/progress-circle.tsx +205 -39
- package/registry/ui/sample-data.tsx +153 -9
- package/registry/ui/scatter-chart.tsx +391 -100
- package/registry/ui/searchable-select.tsx +145 -7
- package/registry/ui/states.tsx +170 -13
- package/registry/ui/status-badge.tsx +193 -28
- package/registry/ui/tracker.tsx +205 -39
- package/registry/ui/trend-indicator.tsx +149 -6
- package/registry/ui/updated-at.tsx +170 -16
- package/registry/ui/waterfall-chart.tsx +415 -102
- package/skills/vantage-design/SKILL.md +25 -27
- package/skills/vantage-design/references/extract.md +10 -13
- package/skills/vantage-design/references/format.md +12 -173
- package/skills/vantage-design/references/presets.md +0 -10
- package/templates/AGENTS.md +29 -15
- package/templates/agents/page.md +1 -1
- package/templates/agents/slide.md +7 -1
- package/dist/chunk-2N72FKLB.js.map +0 -1
- package/dist/chunk-57GHR5LN.js.map +0 -1
- package/dist/chunk-6OTTGAPO.js.map +0 -1
- package/dist/chunk-JXFCSF3K.js.map +0 -1
- package/dist/chunk-PDOBHMUQ.js.map +0 -1
- 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,
|
|
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
|
|
202
|
-
positive: { Icon: CircleCheck, text: "text-positive"
|
|
203
|
-
warning: { Icon: TriangleAlert, text: "text-warning"
|
|
204
|
-
negative: { Icon: CircleX, text: "text-negative"
|
|
205
|
-
neutral: { Icon: Minus, text: "text-neutral"
|
|
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
|
|
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"
|
|
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
|
-
/**
|
|
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
|
-
<
|
|
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:
|
|
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
|
|
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
|
}
|