@squadbase/vantage 0.6.2-dev1.9365dcd → 0.6.2-dev1.9d7130b

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 (230) hide show
  1. package/dist/{build-X5NVCQKT.js → build-U7GO26HR.js} +7 -7
  2. package/dist/{build-X5NVCQKT.js.map → build-U7GO26HR.js.map} +1 -1
  3. package/dist/{check-ZBIH422T.js → check-MRVAIHG5.js} +5 -5
  4. package/dist/{check-ZBIH422T.js.map → check-MRVAIHG5.js.map} +1 -1
  5. package/dist/chunk-2IAPIOPS.js +152 -0
  6. package/dist/chunk-2IAPIOPS.js.map +1 -0
  7. package/dist/chunk-3RJEOP3J.js +174 -0
  8. package/dist/chunk-3RJEOP3J.js.map +1 -0
  9. package/dist/{chunk-HKSVYMYJ.js → chunk-77CISBMG.js} +1049 -1387
  10. package/dist/chunk-77CISBMG.js.map +1 -0
  11. package/dist/{chunk-V3LF7BE7.js → chunk-ANZLVSEP.js} +4 -5
  12. package/dist/chunk-ANZLVSEP.js.map +1 -0
  13. package/dist/{chunk-XQUSGIDG.js → chunk-CKWPK4RO.js} +10 -3
  14. package/dist/chunk-CKWPK4RO.js.map +1 -0
  15. package/dist/chunk-EG4PX726.js +678 -0
  16. package/dist/chunk-EG4PX726.js.map +1 -0
  17. package/dist/{chunk-2N72FKLB.js → chunk-NZ5QIIG4.js} +33 -11
  18. package/dist/chunk-NZ5QIIG4.js.map +1 -0
  19. package/dist/{chunk-7DZGJQ3P.js → chunk-PJ4SPXHS.js} +23 -6
  20. package/dist/chunk-PJ4SPXHS.js.map +1 -0
  21. package/dist/{chunk-XPQSTZUU.js → chunk-QNOX7WLZ.js} +5 -4
  22. package/dist/chunk-QNOX7WLZ.js.map +1 -0
  23. package/dist/{chunk-PDOBHMUQ.js → chunk-ROHNXP7J.js} +72 -3
  24. package/dist/chunk-ROHNXP7J.js.map +1 -0
  25. package/dist/{chunk-CEXAQQ5W.js → chunk-S5MDTHH7.js} +180 -18
  26. package/dist/chunk-S5MDTHH7.js.map +1 -0
  27. package/dist/{chunk-2ICNHUG6.js → chunk-SDJTTMLK.js} +64 -7
  28. package/dist/chunk-SDJTTMLK.js.map +1 -0
  29. package/dist/{chunk-WAMZWZJB.js → chunk-SHZM3EXB.js} +19 -8
  30. package/dist/chunk-SHZM3EXB.js.map +1 -0
  31. package/dist/chunk-TRMHNMMZ.js +61 -0
  32. package/dist/chunk-TRMHNMMZ.js.map +1 -0
  33. package/dist/{chunk-JKLLFVEU.js → chunk-UWSQUHX6.js} +4 -99
  34. package/dist/chunk-UWSQUHX6.js.map +1 -0
  35. package/dist/cli.js +6 -6
  36. package/dist/client/index.d.ts +2 -0
  37. package/dist/client/index.js +8 -2
  38. package/dist/client/index.js.map +1 -1
  39. package/dist/components/index.d.ts +57 -273
  40. package/dist/components/index.js +6 -3
  41. package/dist/delta-text-CbrOaW83.d.ts +17 -0
  42. package/dist/{dev-4XEDTELV.js → dev-O42JFQ64.js} +7 -7
  43. package/dist/{dev-4XEDTELV.js.map → dev-O42JFQ64.js.map} +1 -1
  44. package/dist/doc/index.d.ts +215 -2
  45. package/dist/doc/index.js +422 -4
  46. package/dist/doc/index.js.map +1 -1
  47. package/dist/{doctor-5E2VEZPK.js → doctor-YD3XAAOT.js} +5 -5
  48. package/dist/{doctor-5E2VEZPK.js.map → doctor-YD3XAAOT.js.map} +1 -1
  49. package/dist/format-DihQkL1N.d.ts +249 -0
  50. package/dist/query/index.js +2 -2
  51. package/dist/{routes-3SGAFDMC.js → routes-PVKNT777.js} +4 -4
  52. package/dist/{routes-3SGAFDMC.js.map → routes-PVKNT777.js.map} +1 -1
  53. package/dist/slide/index.d.ts +272 -1
  54. package/dist/slide/index.js +1023 -2
  55. package/dist/slide/index.js.map +1 -1
  56. package/dist/table/index.js +9 -6
  57. package/dist/table/index.js.map +1 -1
  58. package/dist/ui/index.d.ts +5 -2
  59. package/dist/ui/index.js +6 -6
  60. package/dist/ui/index.js.map +1 -1
  61. package/dist/{upgrade-JHXZBGQS.js → upgrade-6DT7ADRP.js} +4 -4
  62. package/dist/{upgrade-JHXZBGQS.js.map → upgrade-6DT7ADRP.js.map} +1 -1
  63. package/dist/vite/index.js +3 -3
  64. package/docs/en/agent-skills.md +1 -1
  65. package/docs/en/changelog.md +2 -0
  66. package/docs/en/components.md +13 -2
  67. package/docs/en/doc/delta-text.md +40 -0
  68. package/docs/en/doc/doc-action-items.md +49 -0
  69. package/docs/en/doc/doc-callout.md +38 -0
  70. package/docs/en/doc/doc-data-table.md +60 -0
  71. package/docs/en/doc/doc-executive-summary.md +64 -0
  72. package/docs/en/doc/doc-figure.md +56 -0
  73. package/docs/en/doc/doc-key-findings.md +61 -0
  74. package/docs/en/doc/doc-kpi-scorecard.md +80 -0
  75. package/docs/en/doc/doc-notes-and-sources.md +43 -0
  76. package/docs/en/doc/doc-report-header.md +46 -0
  77. package/docs/en/pages-and-metadata.md +15 -0
  78. package/docs/en/parts/area-chart.md +99 -0
  79. package/docs/en/parts/bar-chart.md +91 -0
  80. package/docs/en/parts/bar-list.md +50 -0
  81. package/docs/en/parts/category-bar.md +37 -0
  82. package/docs/en/parts/chart-frame.md +39 -0
  83. package/docs/en/parts/delta.md +4 -3
  84. package/docs/en/parts/donut-chart.md +72 -0
  85. package/docs/en/parts/funnel-steps.md +2 -1
  86. package/docs/en/parts/heatmap.md +53 -0
  87. package/docs/en/parts/kpi-card.md +2 -1
  88. package/docs/en/parts/legend.md +35 -0
  89. package/docs/en/parts/line-chart.md +94 -0
  90. package/docs/en/parts/progress-circle.md +33 -0
  91. package/docs/en/parts/sample-data.md +39 -0
  92. package/docs/en/parts/scatter-chart.md +56 -0
  93. package/docs/en/parts/tracker.md +36 -0
  94. package/docs/en/parts/updated-at.md +30 -0
  95. package/docs/en/parts/waterfall-chart.md +56 -0
  96. package/docs/en/slide/slide-chart-full.md +48 -0
  97. package/docs/en/slide/slide-chart-insight.md +52 -0
  98. package/docs/en/slide/slide-columns.md +46 -0
  99. package/docs/en/slide/slide-comparison.md +68 -0
  100. package/docs/en/slide/slide-cover.md +39 -0
  101. package/docs/en/slide/slide-exec-summary.md +47 -0
  102. package/docs/en/slide/slide-heading.md +38 -0
  103. package/docs/en/slide/slide-kpi-grid.md +55 -0
  104. package/docs/en/slide/slide-kpi-hero.md +58 -0
  105. package/docs/en/slide/slide-next-actions.md +49 -0
  106. package/docs/en/slide/slide-scorecard-table.md +54 -0
  107. package/docs/en/slide/slide-section.md +44 -0
  108. package/docs/en/slide/slide-source-line.md +34 -0
  109. package/docs/en/ui/alert-dialog.md +42 -0
  110. package/docs/en/ui/avatar.md +31 -0
  111. package/docs/en/ui/combobox.md +74 -0
  112. package/docs/en/ui/drawer.md +41 -0
  113. package/docs/en/ui/empty.md +1 -1
  114. package/docs/en/ui/error-state.md +4 -3
  115. package/docs/en/ui/field.md +42 -0
  116. package/docs/en/ui/hover-card.md +21 -0
  117. package/docs/en/ui/kbd.md +17 -0
  118. package/docs/en/ui/loading.md +1 -1
  119. package/docs/en/ui/pagination.md +36 -0
  120. package/docs/en/ui/radio-group.md +21 -0
  121. package/docs/en/ui/toast.md +44 -0
  122. package/docs/en/ui/toggle-group.md +4 -3
  123. package/docs/index.json +857 -77
  124. package/docs/ja/agent-skills.md +1 -1
  125. package/docs/ja/changelog.md +2 -0
  126. package/docs/ja/components.md +12 -1
  127. package/docs/ja/doc/delta-text.md +40 -0
  128. package/docs/ja/doc/doc-action-items.md +49 -0
  129. package/docs/ja/doc/doc-callout.md +38 -0
  130. package/docs/ja/doc/doc-data-table.md +60 -0
  131. package/docs/ja/doc/doc-executive-summary.md +64 -0
  132. package/docs/ja/doc/doc-figure.md +56 -0
  133. package/docs/ja/doc/doc-key-findings.md +61 -0
  134. package/docs/ja/doc/doc-kpi-scorecard.md +80 -0
  135. package/docs/ja/doc/doc-notes-and-sources.md +43 -0
  136. package/docs/ja/doc/doc-report-header.md +46 -0
  137. package/docs/ja/page-types/slide.md +1 -0
  138. package/docs/ja/pages-and-metadata.md +15 -0
  139. package/docs/ja/parts/area-chart.md +49 -25
  140. package/docs/ja/parts/bar-chart.md +50 -24
  141. package/docs/ja/parts/bar-list.md +31 -8
  142. package/docs/ja/parts/chart-frame.md +36 -0
  143. package/docs/ja/parts/delta.md +5 -4
  144. package/docs/ja/parts/donut-chart.md +45 -26
  145. package/docs/ja/parts/funnel-steps.md +27 -8
  146. package/docs/ja/parts/heatmap.md +45 -0
  147. package/docs/ja/parts/kpi-card.md +2 -1
  148. package/docs/ja/parts/legend.md +36 -0
  149. package/docs/ja/parts/line-chart.md +48 -22
  150. package/docs/ja/parts/sample-data.md +61 -0
  151. package/docs/ja/parts/scatter-chart.md +69 -0
  152. package/docs/ja/parts/updated-at.md +27 -0
  153. package/docs/ja/parts/waterfall-chart.md +61 -0
  154. package/docs/ja/slide/slide-chart-full.md +48 -0
  155. package/docs/ja/slide/slide-chart-insight.md +52 -0
  156. package/docs/ja/slide/slide-columns.md +46 -0
  157. package/docs/ja/slide/slide-comparison.md +68 -0
  158. package/docs/ja/slide/slide-cover.md +39 -0
  159. package/docs/ja/slide/slide-exec-summary.md +47 -0
  160. package/docs/ja/slide/slide-heading.md +38 -0
  161. package/docs/ja/slide/slide-kpi-grid.md +55 -0
  162. package/docs/ja/slide/slide-kpi-hero.md +58 -0
  163. package/docs/ja/slide/slide-next-actions.md +49 -0
  164. package/docs/ja/slide/slide-scorecard-table.md +54 -0
  165. package/docs/ja/slide/slide-section.md +44 -0
  166. package/docs/ja/slide/slide-source-line.md +34 -0
  167. package/docs/ja/ui/empty.md +1 -1
  168. package/docs/ja/ui/error-state.md +4 -3
  169. package/docs/ja/ui/loading.md +1 -1
  170. package/docs/ja/ui/toggle-group.md +4 -3
  171. package/package.json +2 -2
  172. package/registry/ui/app-shell.tsx +138 -1
  173. package/registry/ui/area-chart.tsx +389 -104
  174. package/registry/ui/attention-list.tsx +177 -15
  175. package/registry/ui/bar-chart.tsx +386 -104
  176. package/registry/ui/bar-list.tsx +376 -94
  177. package/registry/ui/box-plot-chart.tsx +450 -116
  178. package/registry/ui/category-bar.tsx +292 -57
  179. package/registry/ui/chart-frame.tsx +301 -63
  180. package/registry/ui/data-table.tsx +229 -37
  181. package/registry/ui/date-range-picker.tsx +190 -36
  182. package/registry/ui/delta.tsx +181 -19
  183. package/registry/ui/donut-chart.tsx +385 -103
  184. package/registry/ui/echart.tsx +313 -58
  185. package/registry/ui/filter-bar.tsx +179 -20
  186. package/registry/ui/funnel-steps.tsx +354 -80
  187. package/registry/ui/heatmap.tsx +338 -71
  188. package/registry/ui/histogram-chart.tsx +440 -116
  189. package/registry/ui/kpi-card.tsx +254 -48
  190. package/registry/ui/legend.tsx +374 -93
  191. package/registry/ui/line-chart.tsx +389 -104
  192. package/registry/ui/multi-select.tsx +159 -9
  193. package/registry/ui/page-shell.tsx +148 -10
  194. package/registry/ui/paired-chart.tsx +394 -109
  195. package/registry/ui/progress-circle.tsx +205 -39
  196. package/registry/ui/sample-data.tsx +154 -10
  197. package/registry/ui/scatter-chart.tsx +436 -119
  198. package/registry/ui/searchable-select.tsx +145 -7
  199. package/registry/ui/slider.tsx +1 -1
  200. package/registry/ui/states.tsx +170 -13
  201. package/registry/ui/status-badge.tsx +201 -36
  202. package/registry/ui/tooltip.tsx +6 -2
  203. package/registry/ui/tracker.tsx +214 -42
  204. package/registry/ui/trend-indicator.tsx +151 -8
  205. package/registry/ui/updated-at.tsx +170 -16
  206. package/registry/ui/waterfall-chart.tsx +460 -121
  207. package/skills/vantage-add-feature/SKILL.md +5 -0
  208. package/skills/vantage-design/SKILL.md +54 -54
  209. package/skills/vantage-design/references/extract.md +26 -27
  210. package/skills/vantage-design/references/format.md +12 -172
  211. package/skills/vantage-design/references/presets.md +50 -110
  212. package/skills/vantage-pitfalls/SKILL.md +5 -0
  213. package/skills/vantage-slide/SKILL.md +54 -36
  214. package/templates/AGENTS.md +35 -18
  215. package/templates/agents/api.md +7 -0
  216. package/templates/agents/doc.md +48 -0
  217. package/templates/agents/page.md +59 -2
  218. package/templates/agents/slide.md +77 -3
  219. package/theme.css +23 -19
  220. package/dist/chunk-2ICNHUG6.js.map +0 -1
  221. package/dist/chunk-2N72FKLB.js.map +0 -1
  222. package/dist/chunk-7DZGJQ3P.js.map +0 -1
  223. package/dist/chunk-CEXAQQ5W.js.map +0 -1
  224. package/dist/chunk-HKSVYMYJ.js.map +0 -1
  225. package/dist/chunk-JKLLFVEU.js.map +0 -1
  226. package/dist/chunk-PDOBHMUQ.js.map +0 -1
  227. package/dist/chunk-V3LF7BE7.js.map +0 -1
  228. package/dist/chunk-WAMZWZJB.js.map +0 -1
  229. package/dist/chunk-XPQSTZUU.js.map +0 -1
  230. package/dist/chunk-XQUSGIDG.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
- /** 会計年度の開始月の既定(4 月始まり)。 */
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
- * ラベルの言語の既定(英語)。他部品の `DEFAULT_LOCALE`(ja-JP)とは違い、この部品だけ英語を既定にしている。
41
- * 従来のプリセットが英語で、既存の利用者の見た目を変えないため。日本語にするなら `locale="ja-JP"` を渡す。
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
- export const DEFAULT_DATE_PICKER_LOCALE = "en-US"
159
+ function useDocumentLocale(): string {
160
+ return useSyncExternalStore(subscribe, documentLocale, serverLocale)
161
+ }
44
162
 
45
- /** 1〜12 の整数だけを開始月として受け、それ以外は既定の 4 月に戻す。 */
46
- export function normalizeFiscalYearStartMonth(month: number | undefined): number {
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
- : DEFAULT_FISCAL_YEAR_START_MONTH
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
- { unit, fiscalYearStartMonth, offset = 0 }: { unit: "year" | "quarter"; fiscalYearStartMonth?: number; offset?: number },
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 isJapanese = (locale: string) => locale.toLowerCase().startsWith("ja")
110
-
111
- /** 週の始まり(月曜)。日本のビジネス慣習に合わせ、「今週」「先週」はどの locale でも月曜始まり。 */
112
- const WEEK_OPTIONS = { weekStartsOn: 1 } as const
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) => (isJapanese(locale) ? ja : undefined)
259
+ const calendarLocale = (locale: string) => (uiLanguage(locale) === "ja" ? ja : undefined)
116
260
 
117
261
  /**
118
262
  * 既定のプリセット一覧を作る。ラベルは `locale`(`ja*` なら日本語、それ以外は英語)、
119
- * 「今四半期」「前四半期」「今年度」「前年度」は `fiscalYearStartMonth` を基準にする。
120
- * 「今〜」は期の初日から今日まで、「前〜」「先月」「先週」は前の期の全期間。週は月曜始まり。`getValue` は押した時点の日付で計算する。
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 = DEFAULT_DATE_PICKER_LOCALE,
127
- fiscalYearStartMonth = DEFAULT_FISCAL_YEAR_START_MONTH,
272
+ locale: localeArg,
273
+ fiscalYearStartMonth,
128
274
  now = () => new Date(),
129
275
  }: { locale?: string; fiscalYearStartMonth?: number; now?: () => Date } = {}): PresetItem[] {
130
- const t = PRESET_LABELS[isJapanese(locale) ? "ja" : "en"]
131
- const startMonth = normalizeFiscalYearStartMonth(fiscalYearStartMonth)
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
- /** 既定のプリセット(英語・4 月始まり)。`locale` / `fiscalYearStartMonth` を変えるなら `createDatePresets` か DateRangePicker の同名 props。 */
208
- export const defaultPresets: PresetItem[] = createDatePresets()
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
- * プリセットのラベルとカレンダーの月名・曜日の言語。`ja*` なら日本語、それ以外は英語。既定 `"en-US"`(英語)。
235
- * 他部品の既定は ja-JP だが、この部品は従来のプリセットが英語だったので既存の見た目を保つため英語を既定にしている。
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)。「今年度」「前年度」と、年度基準の「今四半期」「前四半期」の区切りになる。既定 `4`(4 月始まり)。
243
- * 範囲外・整数でない値は 4 として扱う。
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 = DEFAULT_DATE_PICKER_LOCALE,
265
- fiscalYearStartMonth = DEFAULT_FISCAL_YEAR_START_MONTH,
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 format(value.from, "yyyy/MM/dd")
300
- return `${format(value.from, "yyyy/MM/dd")} - ${format(value.to, "yyyy/MM/dd")}`
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={isJapanese(locale) ? 1 : undefined}
525
+ weekStartsOn={weekStartsOn(locale)}
372
526
  numberOfMonths={isMobile ? 1 : 2}
373
527
  selected={calendarSelected}
374
528
  onSelect={handleCalendarSelect}
@@ -4,12 +4,13 @@
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
11
12
  * cannot contradict each other. Colour is never the only signal: the arrow and
12
- * the sign always accompany it. Set `polarity="lower-is-better"` for metrics
13
+ * the sign always accompany it. Set `betterWhen="down"` for metrics
13
14
  * where a rise is bad (churn, error rate).
14
15
  *
15
16
  * - A flat change (inside `neutralBand`) uses a right arrow (→), never a minus-like dash, so it cannot
@@ -31,7 +32,7 @@
31
32
  * // Vantage: import { Delta } from "@squadbase/vantage/components"
32
33
  *
33
34
  * <Delta value={48_200_000} comparison={{ previous: 42_900_000, vs: "prior-period", grain: "month" }} />
34
- * <Delta value={0.021} format="percent" polarity="lower-is-better"
35
+ * <Delta value={0.021} format="percent" betterWhen="down"
35
36
  * comparison={{ previous: 0.026, vs: "prior-year", grain: "month" }} />
36
37
  */
37
38
  import { forwardRef, type HTMLAttributes } from "react"
@@ -49,14 +50,148 @@ import {
49
50
  type ComparisonResult,
50
51
  type Polarity,
51
52
  } from "@squadbase/vantage/components"
52
- import { DEFAULT_LOCALE, EMPTY_VALUE, type ValueFormat } from "@squadbase/vantage/components"
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",
56
191
  {
57
192
  variants: {
58
193
  variant: {
59
- badge: "h-5 rounded-4xl px-2 py-0.5 text-xs font-semibold [&>svg]:size-3",
194
+ badge: "h-5 rounded-md border px-2 py-0.5 text-xs font-medium [&>svg]:size-3",
60
195
  inline: "text-sm [&>svg]:size-3.5",
61
196
  },
62
197
  sentiment: {
@@ -66,9 +201,9 @@ const deltaVariants = cva(
66
201
  },
67
202
  },
68
203
  compoundVariants: [
69
- { variant: "badge", sentiment: "positive", className: "bg-positive-subtle" },
70
- { variant: "badge", sentiment: "negative", className: "bg-negative-subtle" },
71
- { variant: "badge", sentiment: "neutral", className: "bg-neutral-subtle" },
204
+ { variant: "badge", sentiment: "positive", className: "border-positive/20 bg-positive-subtle dark:border-positive/25" },
205
+ { variant: "badge", sentiment: "negative", className: "border-negative/20 bg-negative-subtle dark:border-negative/25" },
206
+ { variant: "badge", sentiment: "neutral", className: "border-neutral/20 bg-neutral-subtle dark:border-neutral/25" },
72
207
  ],
73
208
  defaultVariants: {
74
209
  variant: "badge",
@@ -85,7 +220,9 @@ export interface DeltaProps
85
220
  value: number | null | undefined
86
221
  /** What the value is compared against. Required so the delta always carries its label. */
87
222
  comparison: Comparison
88
- /** Whether a rise is good. Default `"higher-is-better"`. */
223
+ /** Which direction is good: `"up"` (default) or `"down"` (churn, error rate). Wins over `polarity`. */
224
+ betterWhen?: "up" | "down"
225
+ /** @deprecated Older name of `betterWhen` (`"higher-is-better"` / `"lower-is-better"`); `"neutral"` is only reachable here. `betterWhen` wins if both are given. */
89
226
  polarity?: Polarity
90
227
  /** Absolute ratio below which the change reads as flat (0.01 = 1%). Default 0.01. */
91
228
  neutralBand?: number
@@ -101,7 +238,7 @@ export interface DeltaProps
101
238
  * 同じカード群で桁がばらつかないよう、全部に同じ値を渡す。未指定は 1 桁("+14.0%")。`"points"` は未指定のとき `format.digits` に従う。
102
239
  */
103
240
  digits?: number
104
- /** Locale for the change. Default `"ja-JP"`. */
241
+ /** Locale for the change and its label. Default: the page's (`<html lang>`, else `"en-US"`). */
105
242
  locale?: string
106
243
  /** Show the comparison label ("前月比") after the change. Default true. */
107
244
  showLabel?: boolean
@@ -110,23 +247,42 @@ export interface DeltaProps
110
247
  /** Text shown when the values cannot be compared (no previous value, or a zero baseline): the same "—" as every missing value. */
111
248
  export const DELTA_NOT_COMPARABLE = EMPTY_VALUE
112
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
+
113
265
  /**
114
- * Screen-reader text, e.g. "前月比 12.4% 増加" / "前月比 0.5 ポイント 減少" / "前月比 横ばい" / "前月比 比較できません".
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 ポイント 減少" / "前月比 横ばい" / "前月比 比較できません").
115
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>`).
116
270
  */
117
271
  export function deltaAriaLabel(
118
272
  result: ComparisonResult,
119
- locale: string = DEFAULT_LOCALE,
273
+ locale?: string,
120
274
  change: ChangeDisplay = "relative",
121
275
  format?: ValueFormat,
122
276
  digits?: number,
123
277
  ): string {
278
+ const resolved = resolveLocale(locale)
279
+ const t = ARIA_TEXT[uiLanguage(resolved)]
124
280
  const { label, direction, ratio } = result
125
- if (direction === "none" || ratio === null) return `${label} 比較できません`
126
- if (direction === "flat") return `${label} 横ばい`
127
- const amount = formatChangeAmount(result, change, locale, format, digits)
128
- if (amount === null) return `${label} 比較できません`
129
- return `${label} ${change === "points" ? amount.replace(/pt$/, " ポイント") : amount} ${direction === "up" ? "増加" : "減少"}`
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")
130
286
  }
131
287
 
132
288
  export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
@@ -134,12 +290,13 @@ export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
134
290
  {
135
291
  value,
136
292
  comparison,
293
+ betterWhen,
137
294
  polarity,
138
295
  neutralBand,
139
296
  format,
140
297
  change,
141
298
  digits,
142
- locale = DEFAULT_LOCALE,
299
+ locale: localeProp,
143
300
  showLabel = true,
144
301
  variant = "badge",
145
302
  className,
@@ -147,9 +304,14 @@ export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
147
304
  },
148
305
  ref,
149
306
  ) => {
307
+ const locale = useLocale(localeProp)
150
308
  // Nothing to compare against yet (a draft fills props in steps).
151
309
  if (!comparison) return null
152
- const result = computeComparison(value, comparison, { polarity, neutralBand })
310
+ const result = computeComparison(value, comparison, {
311
+ polarity: betterWhen ? (betterWhen === "down" ? "lower-is-better" : "higher-is-better") : polarity,
312
+ neutralBand,
313
+ locale,
314
+ })
153
315
  const display = change ?? defaultChangeDisplay(format)
154
316
  const formatDigits = typeof format === "object" && format !== null && "digits" in format ? format.digits : undefined
155
317
  const changeDigits = display === "points" ? (digits ?? formatDigits) : digits