@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
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* DateRangePicker — a range calendar in a popover with quick presets down the
|
|
7
7
|
* side. On mobile the presets stack above a single month; on desktop they sit
|
|
8
|
-
* beside a two-month view.
|
|
8
|
+
* beside a two-month view. The labels, the date format, the fiscal year start and the
|
|
9
|
+
* first day of the week follow the page (`<html lang>`) unless `locale` is given.
|
|
9
10
|
*/
|
|
10
11
|
import { forwardRef, useCallback, useMemo, useState } from "react"
|
|
11
12
|
import { CalendarIcon } from "lucide-react"
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
endOfDay,
|
|
15
16
|
endOfMonth,
|
|
16
17
|
endOfWeek,
|
|
17
|
-
format,
|
|
18
18
|
isSameDay,
|
|
19
19
|
startOfDay,
|
|
20
20
|
startOfMonth,
|
|
@@ -28,30 +28,169 @@ import type { DateRange as RdpDateRange } from "react-day-picker"
|
|
|
28
28
|
|
|
29
29
|
import { cn } from "@squadbase/vantage/ui"
|
|
30
30
|
import { useIsMobile } from "@squadbase/vantage/ui"
|
|
31
|
+
import { formatDate } from "@squadbase/vantage/components"
|
|
31
32
|
import { Button } from "@squadbase/vantage/ui"
|
|
32
33
|
import { Calendar } from "@squadbase/vantage/ui"
|
|
33
34
|
import { Popover, PopoverContent, PopoverTrigger } from "@squadbase/vantage/ui"
|
|
34
35
|
import type { DateRange, PresetItem } from "@squadbase/vantage/components"
|
|
36
|
+
import { useSyncExternalStore } from "react"
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
export const DEFAULT_FISCAL_YEAR_START_MONTH = 4
|
|
38
|
+
/* ---------- inlined from @squadbase/squadkit registry/lib/locale.ts (internal to @squadbase/vantage) ---------- */
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
41
|
+
* The display language of the parts and the defaults that follow it.
|
|
42
|
+
*
|
|
43
|
+
* The language comes from `<html lang>`: a tag starting with `ja` reads Japanese, anything else English.
|
|
44
|
+
* Without a usable `lang` (missing, invalid, or no `document` — SSR, Node) the locale is `FALLBACK_LOCALE`
|
|
45
|
+
* (`en-US`). A `locale` passed explicitly (a prop, an argument) always wins.
|
|
46
|
+
*
|
|
47
|
+
* Parts read the locale through `useLocale` (`hooks/use-locale`) and pass it down; the pure helpers here
|
|
48
|
+
* and in `format` / `comparison` / `chart-*` take it as an argument and fall back to `documentLocale()`.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/** The language of the built-in wording: Japanese or English. */
|
|
52
|
+
type UiLanguage = "ja" | "en"
|
|
53
|
+
|
|
54
|
+
/** The locale used when `<html lang>` gives none (and on the server). */
|
|
55
|
+
const FALLBACK_LOCALE = "en-US"
|
|
56
|
+
|
|
57
|
+
/** `"ja"` when `locale` starts with `ja` (`ja`, `ja-JP`), else `"en"`. A missing locale is `"en"`. */
|
|
58
|
+
function uiLanguage(locale: string | null | undefined): UiLanguage {
|
|
59
|
+
return typeof locale === "string" && locale.trim().toLowerCase().startsWith("ja") ? "ja" : "en"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A BCP 47 tag in its canonical form (`en-gb` → `en-GB`), or `FALLBACK_LOCALE` when it is empty or invalid. */
|
|
63
|
+
function normalizeLocale(tag: string | null | undefined): string {
|
|
64
|
+
const value = tag?.trim()
|
|
65
|
+
if (!value) return FALLBACK_LOCALE
|
|
66
|
+
try {
|
|
67
|
+
return Intl.getCanonicalLocales(value)[0] ?? FALLBACK_LOCALE
|
|
68
|
+
} catch {
|
|
69
|
+
return FALLBACK_LOCALE
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The locale of the page: `document.documentElement.lang`, canonicalized. `FALLBACK_LOCALE` when it is empty or
|
|
75
|
+
* invalid, or there is no `document`. Parts read it through `useLocale`, never while rendering.
|
|
76
|
+
*/
|
|
77
|
+
function documentLocale(): string {
|
|
78
|
+
if (typeof document === "undefined") return FALLBACK_LOCALE
|
|
79
|
+
return normalizeLocale(document.documentElement?.getAttribute("lang"))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `locale` when given, else the page's locale (`documentLocale()`). */
|
|
83
|
+
function resolveLocale(locale?: string | null): string {
|
|
84
|
+
return locale || documentLocale()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Picks the wording table of the locale's language. */
|
|
88
|
+
function pickLocale<T>(table: { ja: T; en: T }, locale?: string | null): T {
|
|
89
|
+
return table[uiLanguage(resolveLocale(locale))]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The currency `format="currency"` uses without a `currency`: JPY for Japanese, USD otherwise. */
|
|
93
|
+
function defaultCurrency(locale?: string | null): string {
|
|
94
|
+
return uiLanguage(resolveLocale(locale)) === "ja" ? "JPY" : "USD"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The month a fiscal year starts in (1–12) when none is given: April for Japanese, January otherwise. */
|
|
98
|
+
function defaultFiscalYearStartMonth(locale?: string | null): number {
|
|
99
|
+
return uiLanguage(resolveLocale(locale)) === "ja" ? 4 : 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A day of the week, 0 = Sunday … 6 = Saturday (the `Date#getDay` / date-fns numbering). */
|
|
103
|
+
type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
104
|
+
|
|
105
|
+
/** Locales whose week starts on Sunday when the runtime has no week data. */
|
|
106
|
+
const SUNDAY_FIRST = new Set(["en", "en-US", "en-CA"])
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The first day of the week: Monday for Japanese; otherwise the locale's convention from `Intl.Locale`
|
|
110
|
+
* (`en-US` Sunday, `en-GB` Monday). Without week data in the runtime: Sunday for `en` / `en-US` / `en-CA`,
|
|
111
|
+
* Monday for the rest.
|
|
112
|
+
*/
|
|
113
|
+
function weekStartsOn(locale?: string | null): WeekDay {
|
|
114
|
+
const tag = normalizeLocale(resolveLocale(locale))
|
|
115
|
+
if (uiLanguage(tag) === "ja") return 1
|
|
116
|
+
try {
|
|
117
|
+
const info = new Intl.Locale(tag) as Intl.Locale & {
|
|
118
|
+
getWeekInfo?: () => { firstDay: number }
|
|
119
|
+
weekInfo?: { firstDay: number }
|
|
120
|
+
}
|
|
121
|
+
const firstDay = info.getWeekInfo?.().firstDay ?? info.weekInfo?.firstDay
|
|
122
|
+
if (typeof firstDay === "number" && firstDay >= 1 && firstDay <= 7) return (firstDay % 7) as WeekDay
|
|
123
|
+
} catch {
|
|
124
|
+
// Fall through to the table below.
|
|
125
|
+
}
|
|
126
|
+
return SUNDAY_FIRST.has(tag) ? 0 : 1
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* ---------- inlined from @squadbase/squadkit registry/hooks/use-locale.ts (internal to @squadbase/vantage) ---------- */
|
|
130
|
+
|
|
131
|
+
const listeners = new Set<() => void>()
|
|
132
|
+
let observer: MutationObserver | null = null
|
|
133
|
+
|
|
134
|
+
/** One observer on `<html lang>` for every subscribed part. */
|
|
135
|
+
function subscribe(onChange: () => void): () => void {
|
|
136
|
+
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => {}
|
|
137
|
+
listeners.add(onChange)
|
|
138
|
+
if (!observer) {
|
|
139
|
+
observer = new MutationObserver(() => {
|
|
140
|
+
for (const listener of [...listeners]) listener()
|
|
141
|
+
})
|
|
142
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] })
|
|
143
|
+
}
|
|
144
|
+
return () => {
|
|
145
|
+
listeners.delete(onChange)
|
|
146
|
+
if (listeners.size === 0 && observer) {
|
|
147
|
+
observer.disconnect()
|
|
148
|
+
observer = null
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const serverLocale = () => FALLBACK_LOCALE
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The page's locale (`<html lang>`), following changes made while the page runs. On the server and during
|
|
157
|
+
* hydration it is `FALLBACK_LOCALE` (`en-US`), so the markup matches; it switches right after.
|
|
42
158
|
*/
|
|
43
|
-
|
|
159
|
+
function useDocumentLocale(): string {
|
|
160
|
+
return useSyncExternalStore(subscribe, documentLocale, serverLocale)
|
|
161
|
+
}
|
|
44
162
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
163
|
+
/** The locale a part renders in: its `locale` prop when given, else the page's (`useDocumentLocale`). */
|
|
164
|
+
function useLocale(locale?: string | null): string {
|
|
165
|
+
const page = useDocumentLocale()
|
|
166
|
+
return locale || page
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ---------- end of inlined code ---------- */
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 会計年度の開始月のフォールバック(1 月始まり)。ロケールから決まる既定は `defaultFiscalYearStartMonth`
|
|
173
|
+
* (`<html lang>` が ja なら 4 月、それ以外は 1 月)。
|
|
174
|
+
*/
|
|
175
|
+
export const DEFAULT_FISCAL_YEAR_START_MONTH = 1
|
|
176
|
+
|
|
177
|
+
/** ラベルの言語のフォールバック(英語)。`locale` を渡さなければ、ほかの部品と同じくページの `<html lang>` に従う。 */
|
|
178
|
+
export const DEFAULT_DATE_PICKER_LOCALE = FALLBACK_LOCALE
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 1〜12 の整数だけを開始月として受け、それ以外は `locale` の既定(ja は 4 月、それ以外は 1 月)に戻す。
|
|
182
|
+
* `locale` の既定はページのロケール(`<html lang>`)。
|
|
183
|
+
*/
|
|
184
|
+
export function normalizeFiscalYearStartMonth(month: number | undefined, locale?: string): number {
|
|
47
185
|
return typeof month === "number" && Number.isInteger(month) && month >= 1 && month <= 12
|
|
48
186
|
? month
|
|
49
|
-
:
|
|
187
|
+
: defaultFiscalYearStartMonth(locale)
|
|
50
188
|
}
|
|
51
189
|
|
|
52
190
|
/**
|
|
53
191
|
* `date` を含む年度(または年度基準の四半期)の全期間。`offset` で前後にずらす(`-1` が前の年度/四半期)。
|
|
54
192
|
* 年度は `fiscalYearStartMonth`(1-12)の 1 日に始まり 12 か月続く。四半期は年度の初日から 3 か月ごと。
|
|
193
|
+
* `fiscalYearStartMonth` を省くと `locale`(既定はページのロケール)の既定(ja は 4 月、それ以外は 1 月)。
|
|
55
194
|
*
|
|
56
195
|
* @example
|
|
57
196
|
* // 4 月始まりで 2026-02-10 は 2025 年度(2025-04-01〜2026-03-31)
|
|
@@ -59,9 +198,14 @@ export function normalizeFiscalYearStartMonth(month: number | undefined): number
|
|
|
59
198
|
*/
|
|
60
199
|
export function fiscalPeriodRange(
|
|
61
200
|
date: Date,
|
|
62
|
-
{
|
|
201
|
+
{
|
|
202
|
+
unit,
|
|
203
|
+
fiscalYearStartMonth,
|
|
204
|
+
offset = 0,
|
|
205
|
+
locale,
|
|
206
|
+
}: { unit: "year" | "quarter"; fiscalYearStartMonth?: number; offset?: number; locale?: string },
|
|
63
207
|
): { from: Date; to: Date } {
|
|
64
|
-
const startMonth = normalizeFiscalYearStartMonth(fiscalYearStartMonth)
|
|
208
|
+
const startMonth = normalizeFiscalYearStartMonth(fiscalYearStartMonth, locale)
|
|
65
209
|
const length = unit === "year" ? 12 : 3
|
|
66
210
|
const sinceYearStart = (date.getMonth() - (startMonth - 1) + 12) % 12
|
|
67
211
|
const into = unit === "year" ? sinceYearStart : sinceYearStart % 3
|
|
@@ -106,29 +250,33 @@ const PRESET_LABELS = {
|
|
|
106
250
|
},
|
|
107
251
|
} as const
|
|
108
252
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
253
|
+
const TEXT = {
|
|
254
|
+
ja: { placeholder: "期間を選択", rangeSeparator: " - " },
|
|
255
|
+
en: { placeholder: "Pick a date range", rangeSeparator: " – " },
|
|
256
|
+
}
|
|
113
257
|
|
|
114
258
|
/** カレンダーに渡す date-fns の locale。`ja*` なら日本語、それ以外は react-day-picker の既定(英語)。 */
|
|
115
|
-
const calendarLocale = (locale: string) => (
|
|
259
|
+
const calendarLocale = (locale: string) => (uiLanguage(locale) === "ja" ? ja : undefined)
|
|
116
260
|
|
|
117
261
|
/**
|
|
118
262
|
* 既定のプリセット一覧を作る。ラベルは `locale`(`ja*` なら日本語、それ以外は英語)、
|
|
119
|
-
* 「今四半期」「前四半期」「今年度」「前年度」は `fiscalYearStartMonth
|
|
120
|
-
*
|
|
263
|
+
* 「今四半期」「前四半期」「今年度」「前年度」は `fiscalYearStartMonth`(省くと ja は 4 月、それ以外は 1 月)を基準にする。
|
|
264
|
+
* 「今〜」は期の初日から今日まで、「前〜」「先月」「先週」は前の期の全期間。週の始まりは `locale` の慣習
|
|
265
|
+
* (ja は月曜、`en-US` は日曜、`en-GB` は月曜。`weekStartsOn`)。`getValue` は押した時点の日付で計算する。
|
|
266
|
+
* `locale` の既定はページのロケール(`<html lang>`、無ければ `"en-US"`)。
|
|
121
267
|
*
|
|
122
268
|
* @example
|
|
123
269
|
* createDatePresets({ locale: "ja-JP", fiscalYearStartMonth: 4 })
|
|
124
270
|
*/
|
|
125
271
|
export function createDatePresets({
|
|
126
|
-
locale
|
|
127
|
-
fiscalYearStartMonth
|
|
272
|
+
locale: localeArg,
|
|
273
|
+
fiscalYearStartMonth,
|
|
128
274
|
now = () => new Date(),
|
|
129
275
|
}: { locale?: string; fiscalYearStartMonth?: number; now?: () => Date } = {}): PresetItem[] {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
276
|
+
const locale = resolveLocale(localeArg)
|
|
277
|
+
const t = PRESET_LABELS[uiLanguage(locale)]
|
|
278
|
+
const startMonth = normalizeFiscalYearStartMonth(fiscalYearStartMonth, locale)
|
|
279
|
+
const WEEK_OPTIONS = { weekStartsOn: weekStartsOn(locale) }
|
|
132
280
|
const lastDays = (days: number) => () => {
|
|
133
281
|
const today = now()
|
|
134
282
|
return { from: startOfDay(subDays(today, days - 1)), to: endOfDay(today) }
|
|
@@ -204,8 +352,12 @@ export function findMatchingPreset(presets: PresetItem[], value: DateRange | und
|
|
|
204
352
|
})
|
|
205
353
|
}
|
|
206
354
|
|
|
207
|
-
/**
|
|
208
|
-
|
|
355
|
+
/**
|
|
356
|
+
* フォールバック(`en-US`:英語・1 月始まり・日曜始まり)のプリセット。DateRangePicker は `presets` を渡さなければ
|
|
357
|
+
* ページのロケールからプリセットを作るので、これを渡す必要はない。`locale` / `fiscalYearStartMonth` を変えるなら
|
|
358
|
+
* `createDatePresets` か DateRangePicker の同名 props。
|
|
359
|
+
*/
|
|
360
|
+
export const defaultPresets: PresetItem[] = createDatePresets({ locale: FALLBACK_LOCALE })
|
|
209
361
|
|
|
210
362
|
export interface DateRangePickerProps {
|
|
211
363
|
/**
|
|
@@ -231,16 +383,16 @@ export interface DateRangePickerProps {
|
|
|
231
383
|
presets?: PresetItem[]
|
|
232
384
|
showPresets?: boolean
|
|
233
385
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
386
|
+
* プリセットのラベル・日付の書式・カレンダーの月名と曜日・週の始まりのロケール。`ja*` なら日本語、それ以外は英語。
|
|
387
|
+
* 既定はページのロケール(`<html lang>`、無ければ `"en-US"`)。
|
|
236
388
|
*
|
|
237
389
|
* @example
|
|
238
390
|
* <DateRangePicker locale="ja-JP" />
|
|
239
391
|
*/
|
|
240
392
|
locale?: string
|
|
241
393
|
/**
|
|
242
|
-
* 年度の開始月(1-12
|
|
243
|
-
*
|
|
394
|
+
* 年度の開始月(1-12)。「今年度」「前年度」と、年度基準の「今四半期」「前四半期」の区切りになる。
|
|
395
|
+
* 既定はロケールで決まる(ja は `4`、それ以外は `1`)。範囲外・整数でない値はその既定として扱う。
|
|
244
396
|
*
|
|
245
397
|
* @example
|
|
246
398
|
* <DateRangePicker fiscalYearStartMonth={1} />
|
|
@@ -261,11 +413,11 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
261
413
|
onChange,
|
|
262
414
|
presets: presetsProp,
|
|
263
415
|
showPresets = true,
|
|
264
|
-
locale
|
|
265
|
-
fiscalYearStartMonth
|
|
416
|
+
locale: localeProp,
|
|
417
|
+
fiscalYearStartMonth,
|
|
266
418
|
maxDate,
|
|
267
419
|
minDate,
|
|
268
|
-
placeholder
|
|
420
|
+
placeholder,
|
|
269
421
|
disabled = false,
|
|
270
422
|
className,
|
|
271
423
|
align = "start",
|
|
@@ -274,6 +426,8 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
274
426
|
) {
|
|
275
427
|
const [open, setOpen] = useState(false)
|
|
276
428
|
const isMobile = useIsMobile()
|
|
429
|
+
const locale = useLocale(localeProp)
|
|
430
|
+
const t = TEXT[uiLanguage(locale)]
|
|
277
431
|
const presets = useMemo(
|
|
278
432
|
() => presetsProp ?? createDatePresets({ locale, fiscalYearStartMonth }),
|
|
279
433
|
[presetsProp, locale, fiscalYearStartMonth],
|
|
@@ -295,9 +449,9 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
295
449
|
)
|
|
296
450
|
|
|
297
451
|
const formatDateRange = () => {
|
|
298
|
-
if (!value?.from) return placeholder
|
|
299
|
-
if (!value.to) return
|
|
300
|
-
return `${
|
|
452
|
+
if (!value?.from) return placeholder ?? t.placeholder
|
|
453
|
+
if (!value.to) return formatDate(value.from, "date", locale)
|
|
454
|
+
return `${formatDate(value.from, "date", locale)}${t.rangeSeparator}${formatDate(value.to, "date", locale)}`
|
|
301
455
|
}
|
|
302
456
|
// 選んだ期間がプリセットの結果と一致するときは、日付の前にそのプリセット名を出す。
|
|
303
457
|
const selectedPreset = findMatchingPreset(presets, value)
|
|
@@ -368,7 +522,7 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
368
522
|
mode="range"
|
|
369
523
|
defaultMonth={defaultMonth}
|
|
370
524
|
locale={calendarLocale(locale)}
|
|
371
|
-
weekStartsOn={
|
|
525
|
+
weekStartsOn={weekStartsOn(locale)}
|
|
372
526
|
numberOfMonths={isMobile ? 1 : 2}
|
|
373
527
|
selected={calendarSelected}
|
|
374
528
|
onSelect={handleCalendarSelect}
|
package/registry/ui/delta.tsx
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Delta — the change of a value against a comparison, rendered as arrow + signed
|
|
7
|
-
* change + comparison label ("↑ +12.4% 前月比") on one line
|
|
7
|
+
* change + comparison label ("↑ +12.4% vs. prior month"; ja: "↑ +12.4% 前月比") on one line, in the page's
|
|
8
|
+
* language (`<html lang>`) unless `locale` is given.
|
|
8
9
|
*
|
|
9
10
|
* Direction, rate, colour and wording are all derived here from `value` and
|
|
10
11
|
* `comparison` (via `computeComparison`); none of them can be passed in, so they
|
|
@@ -49,7 +50,141 @@ import {
|
|
|
49
50
|
type ComparisonResult,
|
|
50
51
|
type Polarity,
|
|
51
52
|
} from "@squadbase/vantage/components"
|
|
52
|
-
import {
|
|
53
|
+
import { EMPTY_VALUE, type ValueFormat } from "@squadbase/vantage/components"
|
|
54
|
+
import { useSyncExternalStore } from "react"
|
|
55
|
+
|
|
56
|
+
/* ---------- inlined from @squadbase/squadkit registry/lib/locale.ts (internal to @squadbase/vantage) ---------- */
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The display language of the parts and the defaults that follow it.
|
|
60
|
+
*
|
|
61
|
+
* The language comes from `<html lang>`: a tag starting with `ja` reads Japanese, anything else English.
|
|
62
|
+
* Without a usable `lang` (missing, invalid, or no `document` — SSR, Node) the locale is `FALLBACK_LOCALE`
|
|
63
|
+
* (`en-US`). A `locale` passed explicitly (a prop, an argument) always wins.
|
|
64
|
+
*
|
|
65
|
+
* Parts read the locale through `useLocale` (`hooks/use-locale`) and pass it down; the pure helpers here
|
|
66
|
+
* and in `format` / `comparison` / `chart-*` take it as an argument and fall back to `documentLocale()`.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/** The language of the built-in wording: Japanese or English. */
|
|
70
|
+
type UiLanguage = "ja" | "en"
|
|
71
|
+
|
|
72
|
+
/** The locale used when `<html lang>` gives none (and on the server). */
|
|
73
|
+
const FALLBACK_LOCALE = "en-US"
|
|
74
|
+
|
|
75
|
+
/** `"ja"` when `locale` starts with `ja` (`ja`, `ja-JP`), else `"en"`. A missing locale is `"en"`. */
|
|
76
|
+
function uiLanguage(locale: string | null | undefined): UiLanguage {
|
|
77
|
+
return typeof locale === "string" && locale.trim().toLowerCase().startsWith("ja") ? "ja" : "en"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A BCP 47 tag in its canonical form (`en-gb` → `en-GB`), or `FALLBACK_LOCALE` when it is empty or invalid. */
|
|
81
|
+
function normalizeLocale(tag: string | null | undefined): string {
|
|
82
|
+
const value = tag?.trim()
|
|
83
|
+
if (!value) return FALLBACK_LOCALE
|
|
84
|
+
try {
|
|
85
|
+
return Intl.getCanonicalLocales(value)[0] ?? FALLBACK_LOCALE
|
|
86
|
+
} catch {
|
|
87
|
+
return FALLBACK_LOCALE
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The locale of the page: `document.documentElement.lang`, canonicalized. `FALLBACK_LOCALE` when it is empty or
|
|
93
|
+
* invalid, or there is no `document`. Parts read it through `useLocale`, never while rendering.
|
|
94
|
+
*/
|
|
95
|
+
function documentLocale(): string {
|
|
96
|
+
if (typeof document === "undefined") return FALLBACK_LOCALE
|
|
97
|
+
return normalizeLocale(document.documentElement?.getAttribute("lang"))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** `locale` when given, else the page's locale (`documentLocale()`). */
|
|
101
|
+
function resolveLocale(locale?: string | null): string {
|
|
102
|
+
return locale || documentLocale()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Picks the wording table of the locale's language. */
|
|
106
|
+
function pickLocale<T>(table: { ja: T; en: T }, locale?: string | null): T {
|
|
107
|
+
return table[uiLanguage(resolveLocale(locale))]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The currency `format="currency"` uses without a `currency`: JPY for Japanese, USD otherwise. */
|
|
111
|
+
function defaultCurrency(locale?: string | null): string {
|
|
112
|
+
return uiLanguage(resolveLocale(locale)) === "ja" ? "JPY" : "USD"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The month a fiscal year starts in (1–12) when none is given: April for Japanese, January otherwise. */
|
|
116
|
+
function defaultFiscalYearStartMonth(locale?: string | null): number {
|
|
117
|
+
return uiLanguage(resolveLocale(locale)) === "ja" ? 4 : 1
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** A day of the week, 0 = Sunday … 6 = Saturday (the `Date#getDay` / date-fns numbering). */
|
|
121
|
+
type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
122
|
+
|
|
123
|
+
/** Locales whose week starts on Sunday when the runtime has no week data. */
|
|
124
|
+
const SUNDAY_FIRST = new Set(["en", "en-US", "en-CA"])
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The first day of the week: Monday for Japanese; otherwise the locale's convention from `Intl.Locale`
|
|
128
|
+
* (`en-US` Sunday, `en-GB` Monday). Without week data in the runtime: Sunday for `en` / `en-US` / `en-CA`,
|
|
129
|
+
* Monday for the rest.
|
|
130
|
+
*/
|
|
131
|
+
function weekStartsOn(locale?: string | null): WeekDay {
|
|
132
|
+
const tag = normalizeLocale(resolveLocale(locale))
|
|
133
|
+
if (uiLanguage(tag) === "ja") return 1
|
|
134
|
+
try {
|
|
135
|
+
const info = new Intl.Locale(tag) as Intl.Locale & {
|
|
136
|
+
getWeekInfo?: () => { firstDay: number }
|
|
137
|
+
weekInfo?: { firstDay: number }
|
|
138
|
+
}
|
|
139
|
+
const firstDay = info.getWeekInfo?.().firstDay ?? info.weekInfo?.firstDay
|
|
140
|
+
if (typeof firstDay === "number" && firstDay >= 1 && firstDay <= 7) return (firstDay % 7) as WeekDay
|
|
141
|
+
} catch {
|
|
142
|
+
// Fall through to the table below.
|
|
143
|
+
}
|
|
144
|
+
return SUNDAY_FIRST.has(tag) ? 0 : 1
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ---------- inlined from @squadbase/squadkit registry/hooks/use-locale.ts (internal to @squadbase/vantage) ---------- */
|
|
148
|
+
|
|
149
|
+
const listeners = new Set<() => void>()
|
|
150
|
+
let observer: MutationObserver | null = null
|
|
151
|
+
|
|
152
|
+
/** One observer on `<html lang>` for every subscribed part. */
|
|
153
|
+
function subscribe(onChange: () => void): () => void {
|
|
154
|
+
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => {}
|
|
155
|
+
listeners.add(onChange)
|
|
156
|
+
if (!observer) {
|
|
157
|
+
observer = new MutationObserver(() => {
|
|
158
|
+
for (const listener of [...listeners]) listener()
|
|
159
|
+
})
|
|
160
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] })
|
|
161
|
+
}
|
|
162
|
+
return () => {
|
|
163
|
+
listeners.delete(onChange)
|
|
164
|
+
if (listeners.size === 0 && observer) {
|
|
165
|
+
observer.disconnect()
|
|
166
|
+
observer = null
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const serverLocale = () => FALLBACK_LOCALE
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The page's locale (`<html lang>`), following changes made while the page runs. On the server and during
|
|
175
|
+
* hydration it is `FALLBACK_LOCALE` (`en-US`), so the markup matches; it switches right after.
|
|
176
|
+
*/
|
|
177
|
+
function useDocumentLocale(): string {
|
|
178
|
+
return useSyncExternalStore(subscribe, documentLocale, serverLocale)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The locale a part renders in: its `locale` prop when given, else the page's (`useDocumentLocale`). */
|
|
182
|
+
function useLocale(locale?: string | null): string {
|
|
183
|
+
const page = useDocumentLocale()
|
|
184
|
+
return locale || page
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ---------- end of inlined code ---------- */
|
|
53
188
|
|
|
54
189
|
const deltaVariants = cva(
|
|
55
190
|
"inline-flex w-fit shrink-0 items-center gap-1 font-medium whitespace-nowrap tabular-nums",
|
|
@@ -103,7 +238,7 @@ export interface DeltaProps
|
|
|
103
238
|
* 同じカード群で桁がばらつかないよう、全部に同じ値を渡す。未指定は 1 桁("+14.0%")。`"points"` は未指定のとき `format.digits` に従う。
|
|
104
239
|
*/
|
|
105
240
|
digits?: number
|
|
106
|
-
/** Locale for the change. Default `"
|
|
241
|
+
/** Locale for the change and its label. Default: the page's (`<html lang>`, else `"en-US"`). */
|
|
107
242
|
locale?: string
|
|
108
243
|
/** Show the comparison label ("前月比") after the change. Default true. */
|
|
109
244
|
showLabel?: boolean
|
|
@@ -112,23 +247,42 @@ export interface DeltaProps
|
|
|
112
247
|
/** Text shown when the values cannot be compared (no previous value, or a zero baseline): the same "—" as every missing value. */
|
|
113
248
|
export const DELTA_NOT_COMPARABLE = EMPTY_VALUE
|
|
114
249
|
|
|
250
|
+
const ARIA_TEXT = {
|
|
251
|
+
ja: {
|
|
252
|
+
none: (label: string) => `${label} 比較できません`,
|
|
253
|
+
flat: (label: string) => `${label} 横ばい`,
|
|
254
|
+
points: " ポイント",
|
|
255
|
+
change: (label: string, amount: string, up: boolean) => `${label} ${amount} ${up ? "増加" : "減少"}`,
|
|
256
|
+
},
|
|
257
|
+
en: {
|
|
258
|
+
none: (label: string) => `${label}: not comparable`,
|
|
259
|
+
flat: (label: string) => `Flat ${label}`,
|
|
260
|
+
points: " points",
|
|
261
|
+
change: (label: string, amount: string, up: boolean) => `${up ? "Up" : "Down"} ${amount} ${label}`,
|
|
262
|
+
},
|
|
263
|
+
}
|
|
264
|
+
|
|
115
265
|
/**
|
|
116
|
-
* Screen-reader text, e.g. "
|
|
266
|
+
* Screen-reader text, e.g. "Up 12.4% vs. prior month" / "Down 0.5 points vs. prior month" / "Flat vs. prior month" /
|
|
267
|
+
* "vs. prior month: not comparable" (ja: "前月比 12.4% 増加" / "前月比 0.5 ポイント 減少" / "前月比 横ばい" / "前月比 比較できません").
|
|
117
268
|
* The amount carries no sign, since the verb already says the direction ("−0.5 ポイント 減少" would negate twice).
|
|
269
|
+
* `locale` defaults to the page's (`<html lang>`).
|
|
118
270
|
*/
|
|
119
271
|
export function deltaAriaLabel(
|
|
120
272
|
result: ComparisonResult,
|
|
121
|
-
locale
|
|
273
|
+
locale?: string,
|
|
122
274
|
change: ChangeDisplay = "relative",
|
|
123
275
|
format?: ValueFormat,
|
|
124
276
|
digits?: number,
|
|
125
277
|
): string {
|
|
278
|
+
const resolved = resolveLocale(locale)
|
|
279
|
+
const t = ARIA_TEXT[uiLanguage(resolved)]
|
|
126
280
|
const { label, direction, ratio } = result
|
|
127
|
-
if (direction === "none" || ratio === null) return
|
|
128
|
-
if (direction === "flat") return
|
|
129
|
-
const amount = formatChangeAmount(result, change,
|
|
130
|
-
if (amount === null) return
|
|
131
|
-
return
|
|
281
|
+
if (direction === "none" || ratio === null) return t.none(label)
|
|
282
|
+
if (direction === "flat") return t.flat(label)
|
|
283
|
+
const amount = formatChangeAmount(result, change, resolved, format, digits)
|
|
284
|
+
if (amount === null) return t.none(label)
|
|
285
|
+
return t.change(label, change === "points" ? amount.replace(/pt$/, t.points) : amount, direction === "up")
|
|
132
286
|
}
|
|
133
287
|
|
|
134
288
|
export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
|
|
@@ -142,7 +296,7 @@ export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
|
|
|
142
296
|
format,
|
|
143
297
|
change,
|
|
144
298
|
digits,
|
|
145
|
-
locale
|
|
299
|
+
locale: localeProp,
|
|
146
300
|
showLabel = true,
|
|
147
301
|
variant = "badge",
|
|
148
302
|
className,
|
|
@@ -150,9 +304,14 @@ export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
|
|
|
150
304
|
},
|
|
151
305
|
ref,
|
|
152
306
|
) => {
|
|
307
|
+
const locale = useLocale(localeProp)
|
|
153
308
|
// Nothing to compare against yet (a draft fills props in steps).
|
|
154
309
|
if (!comparison) return null
|
|
155
|
-
const result = computeComparison(value, comparison, {
|
|
310
|
+
const result = computeComparison(value, comparison, {
|
|
311
|
+
polarity: betterWhen ? (betterWhen === "down" ? "lower-is-better" : "higher-is-better") : polarity,
|
|
312
|
+
neutralBand,
|
|
313
|
+
locale,
|
|
314
|
+
})
|
|
156
315
|
const display = change ?? defaultChangeDisplay(format)
|
|
157
316
|
const formatDigits = typeof format === "object" && format !== null && "digits" in format ? format.digits : undefined
|
|
158
317
|
const changeDigits = display === "points" ? (digits ?? formatDigits) : digits
|