@squadbase/vantage 0.6.2-dev1.ac99e02 → 0.6.2-dev1.d7c0911

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 (141) hide show
  1. package/dist/{build-MFWRPYKW.js → build-X5NVCQKT.js} +4 -4
  2. package/dist/{build-MFWRPYKW.js.map → build-X5NVCQKT.js.map} +1 -1
  3. package/dist/{chunk-IBGOCWJB.js → chunk-2ICNHUG6.js} +186 -82
  4. package/dist/chunk-2ICNHUG6.js.map +1 -0
  5. package/dist/{chunk-5ZOP4SQR.js → chunk-2N72FKLB.js} +12 -7
  6. package/dist/chunk-2N72FKLB.js.map +1 -0
  7. package/dist/{chunk-PXRX5PEC.js → chunk-PDOBHMUQ.js} +3 -3
  8. package/dist/chunk-PDOBHMUQ.js.map +1 -0
  9. package/dist/{chunk-A2UUGASH.js → chunk-U736PVHY.js} +2 -2
  10. package/dist/chunk-U736PVHY.js.map +1 -0
  11. package/dist/{chunk-QEWCYW3I.js → chunk-WAMZWZJB.js} +13 -3
  12. package/dist/chunk-WAMZWZJB.js.map +1 -0
  13. package/dist/chunk-WRSX2MDK.js +7866 -0
  14. package/dist/chunk-WRSX2MDK.js.map +1 -0
  15. package/dist/{chunk-OB6UBJXF.js → chunk-XPQSTZUU.js} +125 -35
  16. package/dist/chunk-XPQSTZUU.js.map +1 -0
  17. package/dist/cli.js +3 -3
  18. package/dist/components/index.d.ts +1831 -99
  19. package/dist/components/index.js +4 -4
  20. package/dist/{dev-DCTOHQ6W.js → dev-4XEDTELV.js} +4 -4
  21. package/dist/{dev-DCTOHQ6W.js.map → dev-4XEDTELV.js.map} +1 -1
  22. package/dist/doc/index.js +2 -2
  23. package/dist/doc/index.js.map +1 -1
  24. package/dist/markdown/index.js +1 -1
  25. package/dist/markdown/index.js.map +1 -1
  26. package/dist/table/index.js +5 -5
  27. package/dist/table/index.js.map +1 -1
  28. package/dist/ui/index.d.ts +4 -1
  29. package/dist/ui/index.js +21 -111
  30. package/dist/ui/index.js.map +1 -1
  31. package/dist/{upgrade-DBBE6GIR.js → upgrade-JHXZBGQS.js} +3 -3
  32. package/dist/{upgrade-DBBE6GIR.js.map → upgrade-JHXZBGQS.js.map} +1 -1
  33. package/dist/vite/index.js +2 -2
  34. package/docs/en/components.md +29 -15
  35. package/docs/en/parts/data-table.md +22 -0
  36. package/docs/en/parts/delta.md +50 -0
  37. package/docs/en/parts/format.md +43 -0
  38. package/docs/en/parts/kpi-card.md +57 -0
  39. package/docs/en/parts/metric-value.md +3 -2
  40. package/docs/en/parts/sparkline.md +1 -1
  41. package/docs/en/parts/trend-indicator.md +17 -1
  42. package/docs/en/ui-and-theming.md +1 -1
  43. package/docs/index.json +92 -32
  44. package/docs/ja/changelog.md +5 -0
  45. package/docs/ja/components.md +27 -15
  46. package/docs/ja/parts/data-table.md +22 -0
  47. package/docs/ja/parts/delta.md +50 -0
  48. package/docs/ja/parts/format.md +44 -0
  49. package/docs/ja/parts/kpi-card.md +55 -0
  50. package/docs/ja/parts/metric-value.md +3 -2
  51. package/docs/ja/parts/sparkline.md +1 -1
  52. package/docs/ja/parts/trend-indicator.md +17 -1
  53. package/docs/ja/ui-and-theming.md +2 -2
  54. package/package.json +2 -1
  55. package/registry/ui/accordion.tsx +4 -2
  56. package/registry/ui/alert-dialog.tsx +3 -3
  57. package/registry/ui/alert.tsx +3 -1
  58. package/registry/ui/app-shell.tsx +7 -1
  59. package/registry/ui/area-chart.tsx +2781 -157
  60. package/registry/ui/avatar.tsx +1 -1
  61. package/registry/ui/badge.tsx +3 -1
  62. package/registry/ui/bar-chart.tsx +2780 -157
  63. package/registry/ui/bar-list.tsx +3471 -93
  64. package/registry/ui/breadcrumb.tsx +3 -1
  65. package/registry/ui/button.tsx +3 -1
  66. package/registry/ui/calendar.tsx +3 -1
  67. package/registry/ui/card.tsx +4 -2
  68. package/registry/ui/category-bar.tsx +990 -16
  69. package/registry/ui/chart-frame.tsx +1358 -0
  70. package/registry/ui/checkbox.tsx +3 -1
  71. package/registry/ui/collapsible.tsx +3 -1
  72. package/registry/ui/combobox.tsx +2 -2
  73. package/registry/ui/command.tsx +3 -1
  74. package/registry/ui/dashboard-card.tsx +69 -26
  75. package/registry/ui/data-table.tsx +678 -56
  76. package/registry/ui/date-range-picker.tsx +245 -64
  77. package/registry/ui/delta.tsx +188 -0
  78. package/registry/ui/dialog.tsx +5 -3
  79. package/registry/ui/donut-chart.tsx +2857 -173
  80. package/registry/ui/drawer.tsx +1 -1
  81. package/registry/ui/dropdown-menu.tsx +5 -3
  82. package/registry/ui/echart.tsx +448 -44
  83. package/registry/ui/field.tsx +1 -1
  84. package/registry/ui/filter-bar.tsx +33 -6
  85. package/registry/ui/funnel-steps.tsx +3316 -49
  86. package/registry/ui/heatmap.tsx +1752 -0
  87. package/registry/ui/hover-card.tsx +2 -2
  88. package/registry/ui/input-group.tsx +3 -1
  89. package/registry/ui/input.tsx +3 -1
  90. package/registry/ui/kbd.tsx +3 -1
  91. package/registry/ui/kpi-card.tsx +677 -0
  92. package/registry/ui/label.tsx +3 -1
  93. package/registry/ui/legend.tsx +3500 -0
  94. package/registry/ui/line-chart.tsx +2781 -151
  95. package/registry/ui/metric-value.tsx +9 -1
  96. package/registry/ui/multi-select.tsx +11 -5
  97. package/registry/ui/page-shell.tsx +104 -49
  98. package/registry/ui/pagination.tsx +3 -1
  99. package/registry/ui/placeholder.tsx +8 -2
  100. package/registry/ui/popover.tsx +4 -2
  101. package/registry/ui/progress-circle.tsx +607 -12
  102. package/registry/ui/progress.tsx +3 -1
  103. package/registry/ui/radio-group.tsx +1 -1
  104. package/registry/ui/sample-data.tsx +86 -0
  105. package/registry/ui/scatter-chart.tsx +4150 -0
  106. package/registry/ui/scroll-area.tsx +3 -2
  107. package/registry/ui/searchable-select.tsx +8 -3
  108. package/registry/ui/section-header.tsx +4 -2
  109. package/registry/ui/segmented-control.tsx +7 -2
  110. package/registry/ui/select.tsx +4 -2
  111. package/registry/ui/separator.tsx +3 -1
  112. package/registry/ui/sheet.tsx +4 -2
  113. package/registry/ui/sidebar.tsx +4 -2
  114. package/registry/ui/skeleton.tsx +4 -2
  115. package/registry/ui/slider.tsx +3 -1
  116. package/registry/ui/sparkline.tsx +84 -9
  117. package/registry/ui/spinner.tsx +3 -1
  118. package/registry/ui/states.tsx +34 -5
  119. package/registry/ui/status-badge.tsx +81 -39
  120. package/registry/ui/switch.tsx +3 -1
  121. package/registry/ui/table.tsx +4 -2
  122. package/registry/ui/tabs.tsx +5 -3
  123. package/registry/ui/textarea.tsx +3 -1
  124. package/registry/ui/toast.tsx +2 -2
  125. package/registry/ui/toggle-group.tsx +3 -1
  126. package/registry/ui/toggle.tsx +3 -1
  127. package/registry/ui/tooltip.tsx +4 -2
  128. package/registry/ui/tracker.tsx +624 -17
  129. package/registry/ui/trend-indicator.tsx +13 -1
  130. package/registry/ui/updated-at.tsx +122 -0
  131. package/registry/ui/waterfall-chart.tsx +4186 -0
  132. package/templates/AGENTS.md +13 -1
  133. package/theme.css +237 -136
  134. package/dist/chunk-5ZOP4SQR.js.map +0 -1
  135. package/dist/chunk-A2UUGASH.js.map +0 -1
  136. package/dist/chunk-AG6RZ2RR.js +0 -3208
  137. package/dist/chunk-AG6RZ2RR.js.map +0 -1
  138. package/dist/chunk-IBGOCWJB.js.map +0 -1
  139. package/dist/chunk-OB6UBJXF.js.map +0 -1
  140. package/dist/chunk-PXRX5PEC.js.map +0 -1
  141. package/dist/chunk-QEWCYW3I.js.map +0 -1
@@ -1,13 +1,28 @@
1
1
  // Copied by `vantage add ui date-range-picker`. This file is yours to edit.
2
- // Generated from @squadbase/vantage src/components/date-range-picker.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
2
+ // Generated from @squadbase/squadkit registry/components/date-range-picker.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
3
+ "use client"
4
+
3
5
  /**
4
6
  * DateRangePicker — a range calendar in a popover with quick presets down the
5
7
  * side. On mobile the presets stack above a single month; on desktop they sit
6
8
  * beside a two-month view.
7
9
  */
8
- import { forwardRef, useCallback, useState } from "react"
10
+ import { forwardRef, useCallback, useMemo, useState } from "react"
9
11
  import { CalendarIcon } from "lucide-react"
10
- import { endOfDay, endOfMonth, format, startOfDay, startOfMonth, subDays, subMonths } from "date-fns"
12
+ import {
13
+ addMonths,
14
+ endOfDay,
15
+ endOfMonth,
16
+ endOfWeek,
17
+ format,
18
+ isSameDay,
19
+ startOfDay,
20
+ startOfMonth,
21
+ startOfWeek,
22
+ subDays,
23
+ subMonths,
24
+ subWeeks,
25
+ } from "date-fns"
11
26
  import { ja } from "date-fns/locale"
12
27
  import type { DateRange as RdpDateRange } from "react-day-picker"
13
28
 
@@ -18,81 +33,219 @@ import { Calendar } from "@squadbase/vantage/ui"
18
33
  import { Popover, PopoverContent, PopoverTrigger } from "@squadbase/vantage/ui"
19
34
  import type { DateRange, PresetItem } from "@squadbase/vantage/components"
20
35
 
21
- export const defaultPresets: PresetItem[] = [
22
- {
23
- label: "Today",
24
- getValue: () => {
25
- const today = new Date()
26
- return { from: startOfDay(today), to: endOfDay(today) }
27
- },
28
- },
29
- {
30
- label: "Yesterday",
31
- getValue: () => {
32
- const yesterday = subDays(new Date(), 1)
33
- return { from: startOfDay(yesterday), to: endOfDay(yesterday) }
34
- },
36
+ /** 会計年度の開始月の既定(4 月始まり)。 */
37
+ export const DEFAULT_FISCAL_YEAR_START_MONTH = 4
38
+
39
+ /**
40
+ * ラベルの言語の既定(英語)。他部品の `DEFAULT_LOCALE`(ja-JP)とは違い、この部品だけ英語を既定にしている。
41
+ * 従来のプリセットが英語で、既存の利用者の見た目を変えないため。日本語にするなら `locale="ja-JP"` を渡す。
42
+ */
43
+ export const DEFAULT_DATE_PICKER_LOCALE = "en-US"
44
+
45
+ /** 1〜12 の整数だけを開始月として受け、それ以外は既定の 4 月に戻す。 */
46
+ export function normalizeFiscalYearStartMonth(month: number | undefined): number {
47
+ return typeof month === "number" && Number.isInteger(month) && month >= 1 && month <= 12
48
+ ? month
49
+ : DEFAULT_FISCAL_YEAR_START_MONTH
50
+ }
51
+
52
+ /**
53
+ * `date` を含む年度(または年度基準の四半期)の全期間。`offset` で前後にずらす(`-1` が前の年度/四半期)。
54
+ * 年度は `fiscalYearStartMonth`(1-12)の 1 日に始まり 12 か月続く。四半期は年度の初日から 3 か月ごと。
55
+ *
56
+ * @example
57
+ * // 4 月始まりで 2026-02-10 は 2025 年度(2025-04-01〜2026-03-31)
58
+ * fiscalPeriodRange(new Date(2026, 1, 10), { unit: "year", fiscalYearStartMonth: 4 })
59
+ */
60
+ export function fiscalPeriodRange(
61
+ date: Date,
62
+ { unit, fiscalYearStartMonth, offset = 0 }: { unit: "year" | "quarter"; fiscalYearStartMonth?: number; offset?: number },
63
+ ): { from: Date; to: Date } {
64
+ const startMonth = normalizeFiscalYearStartMonth(fiscalYearStartMonth)
65
+ const length = unit === "year" ? 12 : 3
66
+ const sinceYearStart = (date.getMonth() - (startMonth - 1) + 12) % 12
67
+ const into = unit === "year" ? sinceYearStart : sinceYearStart % 3
68
+ const periodStart = addMonths(startOfMonth(subMonths(date, into)), offset * length)
69
+ return { from: periodStart, to: endOfMonth(addMonths(periodStart, length - 1)) }
70
+ }
71
+
72
+ const PRESET_LABELS = {
73
+ ja: {
74
+ today: "今日",
75
+ yesterday: "昨日",
76
+ last7: "過去 7 日間",
77
+ last14: "過去 14 日間",
78
+ last28: "過去 28 日間",
79
+ last30: "過去 30 日間",
80
+ last90: "過去 90 日間",
81
+ thisWeek: "今週",
82
+ lastWeek: "先週",
83
+ thisMonth: "今月",
84
+ lastMonth: "先月",
85
+ thisQuarter: "今四半期",
86
+ lastQuarter: "前四半期",
87
+ thisFiscalYear: "今年度",
88
+ lastFiscalYear: "前年度",
35
89
  },
36
- {
37
- label: "Last 7 days",
38
- getValue: () => {
39
- const today = new Date()
40
- return { from: startOfDay(subDays(today, 6)), to: endOfDay(today) }
41
- },
90
+ en: {
91
+ today: "Today",
92
+ yesterday: "Yesterday",
93
+ last7: "Last 7 days",
94
+ last14: "Last 14 days",
95
+ last28: "Last 28 days",
96
+ last30: "Last 30 days",
97
+ last90: "Last 90 days",
98
+ thisWeek: "This week",
99
+ lastWeek: "Last week",
100
+ thisMonth: "This month",
101
+ lastMonth: "Last month",
102
+ thisQuarter: "This quarter",
103
+ lastQuarter: "Last quarter",
104
+ thisFiscalYear: "This fiscal year",
105
+ lastFiscalYear: "Last fiscal year",
42
106
  },
43
- {
44
- label: "Last 14 days",
45
- getValue: () => {
46
- const today = new Date()
47
- return { from: startOfDay(subDays(today, 13)), to: endOfDay(today) }
107
+ } as const
108
+
109
+ const isJapanese = (locale: string) => locale.toLowerCase().startsWith("ja")
110
+
111
+ /** 週の始まり(月曜)。日本のビジネス慣習に合わせ、「今週」「先週」はどの locale でも月曜始まり。 */
112
+ const WEEK_OPTIONS = { weekStartsOn: 1 } as const
113
+
114
+ /** カレンダーに渡す date-fns の locale。`ja*` なら日本語、それ以外は react-day-picker の既定(英語)。 */
115
+ const calendarLocale = (locale: string) => (isJapanese(locale) ? ja : undefined)
116
+
117
+ /**
118
+ * 既定のプリセット一覧を作る。ラベルは `locale`(`ja*` なら日本語、それ以外は英語)、
119
+ * 「今四半期」「前四半期」「今年度」「前年度」は `fiscalYearStartMonth` を基準にする。
120
+ * 「今〜」は期の初日から今日まで、「前〜」「先月」「先週」は前の期の全期間。週は月曜始まり。`getValue` は押した時点の日付で計算する。
121
+ *
122
+ * @example
123
+ * createDatePresets({ locale: "ja-JP", fiscalYearStartMonth: 4 })
124
+ */
125
+ export function createDatePresets({
126
+ locale = DEFAULT_DATE_PICKER_LOCALE,
127
+ fiscalYearStartMonth = DEFAULT_FISCAL_YEAR_START_MONTH,
128
+ now = () => new Date(),
129
+ }: { locale?: string; fiscalYearStartMonth?: number; now?: () => Date } = {}): PresetItem[] {
130
+ const t = PRESET_LABELS[isJapanese(locale) ? "ja" : "en"]
131
+ const startMonth = normalizeFiscalYearStartMonth(fiscalYearStartMonth)
132
+ const lastDays = (days: number) => () => {
133
+ const today = now()
134
+ return { from: startOfDay(subDays(today, days - 1)), to: endOfDay(today) }
135
+ }
136
+ const toDate = (unit: "year" | "quarter") => () => {
137
+ const today = now()
138
+ return {
139
+ from: fiscalPeriodRange(today, { unit, fiscalYearStartMonth: startMonth }).from,
140
+ to: endOfDay(today),
141
+ }
142
+ }
143
+ const previous = (unit: "year" | "quarter") => () =>
144
+ fiscalPeriodRange(now(), { unit, fiscalYearStartMonth: startMonth, offset: -1 })
145
+ return [
146
+ { label: t.today, getValue: lastDays(1) },
147
+ {
148
+ label: t.yesterday,
149
+ getValue: () => {
150
+ const yesterday = subDays(now(), 1)
151
+ return { from: startOfDay(yesterday), to: endOfDay(yesterday) }
152
+ },
48
153
  },
49
- },
50
- {
51
- label: "Last 30 days",
52
- getValue: () => {
53
- const today = new Date()
54
- return { from: startOfDay(subDays(today, 29)), to: endOfDay(today) }
154
+ { label: t.last7, getValue: lastDays(7) },
155
+ { label: t.last14, getValue: lastDays(14) },
156
+ { label: t.last28, getValue: lastDays(28) },
157
+ { label: t.last30, getValue: lastDays(30) },
158
+ { label: t.last90, getValue: lastDays(90) },
159
+ {
160
+ label: t.thisWeek,
161
+ getValue: () => {
162
+ const today = now()
163
+ return { from: startOfWeek(today, WEEK_OPTIONS), to: endOfDay(today) }
164
+ },
55
165
  },
56
- },
57
- {
58
- label: "Last 90 days",
59
- getValue: () => {
60
- const today = new Date()
61
- return { from: startOfDay(subDays(today, 89)), to: endOfDay(today) }
166
+ {
167
+ label: t.lastWeek,
168
+ getValue: () => {
169
+ const lastWeek = subWeeks(now(), 1)
170
+ return { from: startOfWeek(lastWeek, WEEK_OPTIONS), to: endOfWeek(lastWeek, WEEK_OPTIONS) }
171
+ },
62
172
  },
63
- },
64
- {
65
- label: "This month",
66
- getValue: () => {
67
- const today = new Date()
68
- return { from: startOfMonth(today), to: endOfDay(today) }
173
+ {
174
+ label: t.thisMonth,
175
+ getValue: () => {
176
+ const today = now()
177
+ return { from: startOfMonth(today), to: endOfDay(today) }
178
+ },
69
179
  },
70
- },
71
- {
72
- label: "Last month",
73
- getValue: () => {
74
- const lastMonth = subMonths(new Date(), 1)
75
- return { from: startOfMonth(lastMonth), to: endOfMonth(lastMonth) }
180
+ {
181
+ label: t.lastMonth,
182
+ getValue: () => {
183
+ const lastMonth = subMonths(now(), 1)
184
+ return { from: startOfMonth(lastMonth), to: endOfMonth(lastMonth) }
185
+ },
76
186
  },
77
- },
78
- ]
187
+ { label: t.thisQuarter, getValue: toDate("quarter") },
188
+ { label: t.lastQuarter, getValue: previous("quarter") },
189
+ { label: t.thisFiscalYear, getValue: toDate("year") },
190
+ { label: t.lastFiscalYear, getValue: previous("year") },
191
+ ]
192
+ }
193
+
194
+ /**
195
+ * `value` と日単位で同じ範囲を返す最初のプリセットを探す(無ければ `undefined`)。`getValue` は呼んだ時点の日付で計算される。
196
+ * 「今日」と「今週」が月曜に重なるときなど、複数が一致したら並びの早いほうを返す。
197
+ */
198
+ export function findMatchingPreset(presets: PresetItem[], value: DateRange | undefined): PresetItem | undefined {
199
+ const { from, to } = value ?? {}
200
+ if (!from || !to) return undefined
201
+ return presets.find((preset) => {
202
+ const range = preset.getValue()
203
+ return range.from != null && range.to != null && isSameDay(range.from, from) && isSameDay(range.to, to)
204
+ })
205
+ }
206
+
207
+ /** 既定のプリセット(英語・4 月始まり)。`locale` / `fiscalYearStartMonth` を変えるなら `createDatePresets` か DateRangePicker の同名 props。 */
208
+ export const defaultPresets: PresetItem[] = createDatePresets()
79
209
 
80
210
  export interface DateRangePickerProps {
81
211
  /**
82
- * 選択中の日付範囲。型は Vantage 独自の `DateRange`。
212
+ * 選択中の日付範囲。型はこの部品独自の `DateRange`。
83
213
  * react-day-picker の `DateRange` とは別物なので注意 — そちらが必要なら
84
214
  * このファイルの末尾で re-export している `RdpDateRange` を使う。
85
215
  *
86
216
  * @example
87
- * import type { DateRange } from "@squadbase/vantage/components"
217
+ * import { DateRangePicker, type DateRange } from "@/components/date-range-picker"
218
+ * // Vantage: import { DateRangePicker, type DateRange } from "@squadbase/vantage/components"
88
219
  *
89
220
  * const [range, setRange] = useState<DateRange | undefined>()
90
221
  * <DateRangePicker value={range} onChange={setRange} />
91
222
  */
92
223
  value?: DateRange
93
224
  onChange?: (range: DateRange) => void
225
+ /**
226
+ * ショートカットの一覧。指定すると `locale` / `fiscalYearStartMonth` による既定の一覧の代わりに使う。
227
+ *
228
+ * @example
229
+ * presets={[{ label: "直近 10 日", getValue: () => ({ from: subDays(new Date(), 9), to: new Date() }) }]}
230
+ */
94
231
  presets?: PresetItem[]
95
232
  showPresets?: boolean
233
+ /**
234
+ * プリセットのラベルとカレンダーの月名・曜日の言語。`ja*` なら日本語、それ以外は英語。既定 `"en-US"`(英語)。
235
+ * 他部品の既定は ja-JP だが、この部品は従来のプリセットが英語だったので既存の見た目を保つため英語を既定にしている。
236
+ *
237
+ * @example
238
+ * <DateRangePicker locale="ja-JP" />
239
+ */
240
+ locale?: string
241
+ /**
242
+ * 年度の開始月(1-12)。「今年度」「前年度」と、年度基準の「今四半期」「前四半期」の区切りになる。既定 `4`(4 月始まり)。
243
+ * 範囲外・整数でない値は 4 として扱う。
244
+ *
245
+ * @example
246
+ * <DateRangePicker fiscalYearStartMonth={1} />
247
+ */
248
+ fiscalYearStartMonth?: number
96
249
  maxDate?: Date
97
250
  minDate?: Date
98
251
  placeholder?: string
@@ -106,11 +259,13 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
106
259
  {
107
260
  value,
108
261
  onChange,
109
- presets = defaultPresets,
262
+ presets: presetsProp,
110
263
  showPresets = true,
264
+ locale = DEFAULT_DATE_PICKER_LOCALE,
265
+ fiscalYearStartMonth = DEFAULT_FISCAL_YEAR_START_MONTH,
111
266
  maxDate,
112
267
  minDate,
113
- placeholder = "Select date range",
268
+ placeholder = "期間を選択",
114
269
  disabled = false,
115
270
  className,
116
271
  align = "start",
@@ -119,6 +274,10 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
119
274
  ) {
120
275
  const [open, setOpen] = useState(false)
121
276
  const isMobile = useIsMobile()
277
+ const presets = useMemo(
278
+ () => presetsProp ?? createDatePresets({ locale, fiscalYearStartMonth }),
279
+ [presetsProp, locale, fiscalYearStartMonth],
280
+ )
122
281
 
123
282
  const handleCalendarSelect = useCallback(
124
283
  (range: RdpDateRange | undefined) => {
@@ -137,9 +296,16 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
137
296
 
138
297
  const formatDateRange = () => {
139
298
  if (!value?.from) return placeholder
140
- if (!value.to) return format(value.from, "yyyy/MM/dd", { locale: ja })
141
- return `${format(value.from, "yyyy/MM/dd", { locale: ja })} - ${format(value.to, "yyyy/MM/dd", { locale: ja })}`
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")}`
142
301
  }
302
+ // 選んだ期間がプリセットの結果と一致するときは、日付の前にそのプリセット名を出す。
303
+ const selectedPreset = findMatchingPreset(presets, value)
304
+ const presetLabel = selectedPreset?.label
305
+
306
+ // 開いたとき、右の月が終了月・左がその前月になるよう、表示月(左の月)を決める。1 か月表示(モバイル)は終了月。
307
+ const endMonth = value?.to ?? value?.from
308
+ const defaultMonth = endMonth ? (isMobile ? endMonth : subMonths(endMonth, 1)) : undefined
143
309
 
144
310
  const calendarSelected: RdpDateRange | undefined = value?.from
145
311
  ? { from: value.from, to: value.to ?? undefined }
@@ -160,15 +326,22 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
160
326
  )}
161
327
  />
162
328
  }
163
- aria-label={formatDateRange()}
329
+ aria-label={presetLabel ? `${presetLabel} ${formatDateRange()}` : formatDateRange()}
164
330
  aria-expanded={open}
165
331
  >
166
332
  <CalendarIcon className="size-4" aria-hidden="true" />
167
- <span>{formatDateRange()}</span>
333
+ {presetLabel ? (
334
+ <>
335
+ <span>{presetLabel}</span>
336
+ <span className="text-muted-foreground">{formatDateRange()}</span>
337
+ </>
338
+ ) : (
339
+ <span>{formatDateRange()}</span>
340
+ )}
168
341
  </PopoverTrigger>
169
342
  <PopoverContent
170
343
  align={align}
171
- className={cn("w-auto p-0", isMobile ? "flex flex-col" : "flex")}
344
+ className={cn("w-auto gap-0 p-0", isMobile ? "flex-col" : "flex-row")}
172
345
  >
173
346
  {showPresets && (
174
347
  <div className={cn("flex flex-col gap-1 p-3", isMobile ? "border-b" : "border-r")}>
@@ -177,7 +350,11 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
177
350
  key={preset.label}
178
351
  variant="ghost"
179
352
  size="sm"
180
- className="justify-start text-xs"
353
+ aria-pressed={selectedPreset === preset}
354
+ className={cn(
355
+ "justify-start text-xs",
356
+ selectedPreset === preset && "bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary",
357
+ )}
181
358
  onClick={() => handlePresetClick(preset)}
182
359
  >
183
360
  {preset.label}
@@ -187,7 +364,11 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
187
364
  )}
188
365
  <div className="p-3">
189
366
  <Calendar
367
+ key={`${open}-${isMobile}`}
190
368
  mode="range"
369
+ defaultMonth={defaultMonth}
370
+ locale={calendarLocale(locale)}
371
+ weekStartsOn={isJapanese(locale) ? 1 : undefined}
191
372
  numberOfMonths={isMobile ? 1 : 2}
192
373
  selected={calendarSelected}
193
374
  onSelect={handleCalendarSelect}
@@ -0,0 +1,188 @@
1
+ // Copied by `vantage add ui delta`. This file is yours to edit.
2
+ // Generated from @squadbase/squadkit registry/components/delta.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
3
+ "use client"
4
+
5
+ /**
6
+ * Delta — the change of a value against a comparison, rendered as arrow + signed
7
+ * change + comparison label ("↑ +12.4% 前月比") on one line.
8
+ *
9
+ * Direction, rate, colour and wording are all derived here from `value` and
10
+ * `comparison` (via `computeComparison`); none of them can be passed in, so they
11
+ * 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
+ * where a rise is bad (churn, error rate).
14
+ *
15
+ * - A flat change (inside `neutralBand`) uses a right arrow (→), never a minus-like dash, so it cannot
16
+ * read as "− +0.19pt".
17
+ * - The label comes from `comparison.vs` and `comparison.grain`: a monthly
18
+ * `prior-period` reads "前月比", `prior-year` "前年同月比".
19
+ * - Pass the metric's `format`: a `percent` metric (a rate) shows the difference
20
+ * in percentage points ("−0.5pt") instead of a relative change ("−19.2%"). A minus is
21
+ * always U+2212, and the digits of a relative change / points are fixed (default 1:
22
+ * "+14.0%" beside "+11.9%"); `digits` sets the same count for every card in a group.
23
+ * - A running total (YTD / MTD) sets `comparison.toDate`: `prior-year` then reads
24
+ * "前年同期比" instead of "前年比" whatever the grain.
25
+ * - When the values cannot be compared (a null value, no previous one), it shows a single muted
26
+ * "—" with no arrow (and the label, so "— 前年同月比" says which comparison is missing). The
27
+ * screen-reader text says "比較できません".
28
+ *
29
+ * @example
30
+ * import { Delta } from "@/components/delta"
31
+ * // Vantage: import { Delta } from "@squadbase/vantage/components"
32
+ *
33
+ * <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
+ * comparison={{ previous: 0.026, vs: "prior-year", grain: "month" }} />
36
+ */
37
+ import { forwardRef, type HTMLAttributes } from "react"
38
+ import { ArrowDown, ArrowRight, ArrowUp } from "lucide-react"
39
+ import { cva, type VariantProps } from "class-variance-authority"
40
+
41
+ import { cn } from "@squadbase/vantage/ui"
42
+ import {
43
+ computeComparison,
44
+ defaultChangeDisplay,
45
+ formatChange,
46
+ formatChangeAmount,
47
+ type ChangeDisplay,
48
+ type Comparison,
49
+ type ComparisonResult,
50
+ type Polarity,
51
+ } from "@squadbase/vantage/components"
52
+ import { DEFAULT_LOCALE, EMPTY_VALUE, type ValueFormat } from "@squadbase/vantage/components"
53
+
54
+ const deltaVariants = cva(
55
+ "inline-flex w-fit shrink-0 items-center gap-1 font-medium whitespace-nowrap tabular-nums",
56
+ {
57
+ variants: {
58
+ variant: {
59
+ badge: "h-5 rounded-4xl px-2 py-0.5 text-xs font-semibold [&>svg]:size-3",
60
+ inline: "text-sm [&>svg]:size-3.5",
61
+ },
62
+ sentiment: {
63
+ positive: "text-positive",
64
+ negative: "text-negative",
65
+ neutral: "text-neutral",
66
+ },
67
+ },
68
+ 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" },
72
+ ],
73
+ defaultVariants: {
74
+ variant: "badge",
75
+ sentiment: "neutral",
76
+ },
77
+ },
78
+ )
79
+
80
+ export interface DeltaProps
81
+ extends
82
+ Omit<HTMLAttributes<HTMLSpanElement>, "children">,
83
+ Pick<VariantProps<typeof deltaVariants>, "variant"> {
84
+ /** The current value. The previous one comes from `comparison`. */
85
+ value: number | null | undefined
86
+ /** What the value is compared against. Required so the delta always carries its label. */
87
+ comparison: Comparison
88
+ /** Whether a rise is good. Default `"higher-is-better"`. */
89
+ polarity?: Polarity
90
+ /** Absolute ratio below which the change reads as flat (0.01 = 1%). Default 0.01. */
91
+ neutralBand?: number
92
+ /** Format of the metric itself; a `percent` metric shows its change in points. */
93
+ format?: ValueFormat
94
+ /**
95
+ * `"relative"` ("+12.4%"), `"points"` ("-0.5pt") or `"difference"` (the absolute difference in the
96
+ * metric's own `format`, "+¥530万"). Default: points for a `percent` format, else relative.
97
+ */
98
+ change?: ChangeDisplay
99
+ /**
100
+ * 変化(`"relative"` の率・`"points"` の pt)の小数桁数。固定で、末尾の 0 も出す。例: `digits={2}` → "+0.14pt"。
101
+ * 同じカード群で桁がばらつかないよう、全部に同じ値を渡す。未指定は 1 桁("+14.0%")。`"points"` は未指定のとき `format.digits` に従う。
102
+ */
103
+ digits?: number
104
+ /** Locale for the change. Default `"ja-JP"`. */
105
+ locale?: string
106
+ /** Show the comparison label ("前月比") after the change. Default true. */
107
+ showLabel?: boolean
108
+ }
109
+
110
+ /** Text shown when the values cannot be compared (no previous value, or a zero baseline): the same "—" as every missing value. */
111
+ export const DELTA_NOT_COMPARABLE = EMPTY_VALUE
112
+
113
+ /**
114
+ * Screen-reader text, e.g. "前月比 12.4% 増加" / "前月比 0.5 ポイント 減少" / "前月比 横ばい" / "前月比 比較できません".
115
+ * The amount carries no sign, since the verb already says the direction ("−0.5 ポイント 減少" would negate twice).
116
+ */
117
+ export function deltaAriaLabel(
118
+ result: ComparisonResult,
119
+ locale: string = DEFAULT_LOCALE,
120
+ change: ChangeDisplay = "relative",
121
+ format?: ValueFormat,
122
+ digits?: number,
123
+ ): string {
124
+ 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" ? "増加" : "減少"}`
130
+ }
131
+
132
+ export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
133
+ (
134
+ {
135
+ value,
136
+ comparison,
137
+ polarity,
138
+ neutralBand,
139
+ format,
140
+ change,
141
+ digits,
142
+ locale = DEFAULT_LOCALE,
143
+ showLabel = true,
144
+ variant = "badge",
145
+ className,
146
+ ...props
147
+ },
148
+ ref,
149
+ ) => {
150
+ // Nothing to compare against yet (a draft fills props in steps).
151
+ if (!comparison) return null
152
+ const result = computeComparison(value, comparison, { polarity, neutralBand })
153
+ const display = change ?? defaultChangeDisplay(format)
154
+ const formatDigits = typeof format === "object" && format !== null && "digits" in format ? format.digits : undefined
155
+ const changeDigits = display === "points" ? (digits ?? formatDigits) : digits
156
+ const comparable = result.direction !== "none"
157
+ const Icon = result.direction === "up" ? ArrowUp : result.direction === "down" ? ArrowDown : ArrowRight
158
+ const text = formatChange(result, display, locale, format, changeDigits)
159
+
160
+ return (
161
+ <span
162
+ ref={ref}
163
+ role="img"
164
+ data-slot="delta"
165
+ data-direction={result.direction}
166
+ data-sentiment={result.sentiment}
167
+ aria-label={deltaAriaLabel(result, locale, display, format, changeDigits)}
168
+ className={cn(
169
+ deltaVariants({ variant, sentiment: result.sentiment }),
170
+ !comparable && "text-muted-foreground",
171
+ className,
172
+ )}
173
+ {...props}
174
+ >
175
+ {comparable && <Icon aria-hidden="true" />}
176
+ <span aria-hidden="true">{text ?? DELTA_NOT_COMPARABLE}</span>
177
+ {showLabel && (
178
+ <span aria-hidden="true" className="font-normal text-muted-foreground">
179
+ {result.label}
180
+ </span>
181
+ )}
182
+ </span>
183
+ )
184
+ },
185
+ )
186
+ Delta.displayName = "Delta"
187
+
188
+ export { deltaVariants }
@@ -1,5 +1,7 @@
1
1
  // Copied by `vantage add ui dialog`. This file is yours to edit.
2
- // Generated from @squadbase/vantage src/ui/dialog.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
2
+ // Generated from @squadbase/squadkit registry/ui/dialog.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
3
+ "use client"
4
+
3
5
  import * as React from "react"
4
6
  import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
5
7
 
@@ -31,7 +33,7 @@ function DialogOverlay({
31
33
  <DialogPrimitive.Backdrop
32
34
  data-slot="dialog-overlay"
33
35
  className={cn(
34
- "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
36
+ "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in motion-reduce:animate-none! data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
35
37
  className
36
38
  )}
37
39
  {...props}
@@ -53,7 +55,7 @@ function DialogContent({
53
55
  <DialogPrimitive.Popup
54
56
  data-slot="dialog-content"
55
57
  className={cn(
56
- "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
58
+ "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in motion-reduce:animate-none! data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
57
59
  className
58
60
  )}
59
61
  {...props}