@squadbase/vantage 0.6.2-dev1.b332e5a → 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 (212) hide show
  1. package/dist/add-4GHF2TX2.js +6 -0
  2. package/dist/add-4GHF2TX2.js.map +1 -0
  3. package/dist/{build-TVKFIVFQ.js → build-X5NVCQKT.js} +7 -7
  4. package/dist/{build-TVKFIVFQ.js.map → build-X5NVCQKT.js.map} +1 -1
  5. package/dist/{check-3KHZXZMQ.js → check-ZBIH422T.js} +5 -5
  6. package/dist/{check-3KHZXZMQ.js.map → check-ZBIH422T.js.map} +1 -1
  7. package/dist/chunk-2ICNHUG6.js +1820 -0
  8. package/dist/chunk-2ICNHUG6.js.map +1 -0
  9. package/dist/{chunk-5ZOP4SQR.js → chunk-2N72FKLB.js} +12 -7
  10. package/dist/chunk-2N72FKLB.js.map +1 -0
  11. package/dist/{chunk-P7W4LR3Z.js → chunk-7DZGJQ3P.js} +3 -3
  12. package/dist/{chunk-P7W4LR3Z.js.map → chunk-7DZGJQ3P.js.map} +1 -1
  13. package/dist/{chunk-U2M6HGKQ.js → chunk-AABMKHEU.js} +4 -4
  14. package/dist/{chunk-U2M6HGKQ.js.map → chunk-AABMKHEU.js.map} +1 -1
  15. package/dist/{chunk-QI3XQKHS.js → chunk-GNXCX4WE.js} +10 -3
  16. package/dist/chunk-GNXCX4WE.js.map +1 -0
  17. package/dist/{chunk-PXRX5PEC.js → chunk-PDOBHMUQ.js} +3 -3
  18. package/dist/chunk-PDOBHMUQ.js.map +1 -0
  19. package/dist/{chunk-ZVANIULZ.js → chunk-RNTDBX6S.js} +4 -4
  20. package/dist/{chunk-ZVANIULZ.js.map → chunk-RNTDBX6S.js.map} +1 -1
  21. package/dist/{chunk-A2UUGASH.js → chunk-U736PVHY.js} +2 -2
  22. package/dist/chunk-U736PVHY.js.map +1 -0
  23. package/dist/{chunk-PVTHHV7X.js → chunk-V3LF7BE7.js} +27 -5
  24. package/dist/chunk-V3LF7BE7.js.map +1 -0
  25. package/dist/{chunk-LMUACBMA.js → chunk-WAMZWZJB.js} +53 -8
  26. package/dist/chunk-WAMZWZJB.js.map +1 -0
  27. package/dist/chunk-WRSX2MDK.js +7866 -0
  28. package/dist/chunk-WRSX2MDK.js.map +1 -0
  29. package/dist/{chunk-RPZAALCX.js → chunk-XPQSTZUU.js} +254 -164
  30. package/dist/chunk-XPQSTZUU.js.map +1 -0
  31. package/dist/{chunk-DTDVSFRY.js → chunk-XQUSGIDG.js} +17 -3
  32. package/dist/chunk-XQUSGIDG.js.map +1 -0
  33. package/dist/cli.js +12 -11
  34. package/dist/cli.js.map +1 -1
  35. package/dist/client/index.d.ts +32 -3
  36. package/dist/client/index.js +205 -12
  37. package/dist/client/index.js.map +1 -1
  38. package/dist/components/index.d.ts +1975 -28
  39. package/dist/components/index.js +4 -4
  40. package/dist/{dev-GDKATCC7.js → dev-4XEDTELV.js} +8 -8
  41. package/dist/dev-4XEDTELV.js.map +1 -0
  42. package/dist/doc/index.js +2 -2
  43. package/dist/doc/index.js.map +1 -1
  44. package/dist/{docs-EJJ3BECT.js → docs-JVP4RPKT.js} +3 -3
  45. package/dist/{docs-EJJ3BECT.js.map → docs-JVP4RPKT.js.map} +1 -1
  46. package/dist/{doctor-WQXPXP6K.js → doctor-5E2VEZPK.js} +5 -5
  47. package/dist/{doctor-WQXPXP6K.js.map → doctor-5E2VEZPK.js.map} +1 -1
  48. package/dist/markdown/index.js +1 -1
  49. package/dist/markdown/index.js.map +1 -1
  50. package/dist/{preview-OGMCNQUO.js → preview-T6CLSQVX.js} +3 -3
  51. package/dist/{preview-OGMCNQUO.js.map → preview-T6CLSQVX.js.map} +1 -1
  52. package/dist/query/index.d.ts +0 -11
  53. package/dist/query/index.js +2 -2
  54. package/dist/{routes-MWBWWOJ2.js → routes-3SGAFDMC.js} +4 -4
  55. package/dist/{routes-MWBWWOJ2.js.map → routes-3SGAFDMC.js.map} +1 -1
  56. package/dist/{search-6Y422SV6.js → search-5LC4XPB5.js} +3 -3
  57. package/dist/{search-6Y422SV6.js.map → search-5LC4XPB5.js.map} +1 -1
  58. package/dist/table/index.js +10 -9
  59. package/dist/table/index.js.map +1 -1
  60. package/dist/ui/index.d.ts +144 -11
  61. package/dist/ui/index.js +1339 -87
  62. package/dist/ui/index.js.map +1 -1
  63. package/dist/{upgrade-HCANX4GQ.js → upgrade-JHXZBGQS.js} +6 -6
  64. package/dist/{upgrade-HCANX4GQ.js.map → upgrade-JHXZBGQS.js.map} +1 -1
  65. package/dist/vite/index.d.ts +10 -0
  66. package/dist/vite/index.js +3 -3
  67. package/docs/en/components.md +29 -15
  68. package/docs/en/parts/data-table.md +22 -0
  69. package/docs/en/parts/delta.md +50 -0
  70. package/docs/en/parts/format.md +43 -0
  71. package/docs/en/parts/kpi-card.md +57 -0
  72. package/docs/en/parts/metric-value.md +3 -2
  73. package/docs/en/parts/sparkline.md +1 -1
  74. package/docs/en/parts/trend-indicator.md +17 -1
  75. package/docs/en/ui-and-theming.md +1 -1
  76. package/docs/index.json +366 -76
  77. package/docs/ja/changelog.md +31 -0
  78. package/docs/ja/components.md +57 -101
  79. package/docs/ja/page-types/csv.md +83 -0
  80. package/docs/ja/page-types/doc.md +83 -0
  81. package/docs/ja/page-types/excel.md +82 -0
  82. package/docs/ja/page-types/slide.md +132 -0
  83. package/docs/ja/page-types.md +47 -0
  84. package/docs/ja/parts/area-chart.md +61 -0
  85. package/docs/ja/parts/bar-chart.md +69 -0
  86. package/docs/ja/parts/bar-list.md +35 -0
  87. package/docs/ja/parts/category-bar.md +36 -0
  88. package/docs/ja/parts/data-table.md +22 -0
  89. package/docs/ja/parts/delta.md +50 -0
  90. package/docs/ja/parts/donut-chart.md +47 -0
  91. package/docs/ja/parts/echart.md +3 -0
  92. package/docs/ja/parts/format.md +44 -0
  93. package/docs/ja/parts/kpi-card.md +55 -0
  94. package/docs/ja/parts/line-chart.md +58 -0
  95. package/docs/ja/parts/metric-value.md +3 -2
  96. package/docs/ja/parts/progress-circle.md +34 -0
  97. package/docs/ja/parts/sparkline.md +2 -2
  98. package/docs/ja/parts/tracker.md +32 -0
  99. package/docs/ja/parts/trend-indicator.md +17 -1
  100. package/docs/ja/routing.md +8 -86
  101. package/docs/ja/ui/alert-dialog.md +41 -0
  102. package/docs/ja/ui/avatar.md +31 -0
  103. package/docs/ja/ui/combobox.md +74 -0
  104. package/docs/ja/ui/drawer.md +41 -0
  105. package/docs/ja/ui/field.md +42 -0
  106. package/docs/ja/ui/hover-card.md +21 -0
  107. package/docs/ja/ui/kbd.md +16 -0
  108. package/docs/ja/ui/pagination.md +36 -0
  109. package/docs/ja/ui/radio-group.md +21 -0
  110. package/docs/ja/ui/toast.md +46 -0
  111. package/docs/ja/ui-and-theming.md +14 -7
  112. package/package.json +13 -4
  113. package/registry/ui/accordion.tsx +76 -0
  114. package/registry/ui/alert-dialog.tsx +189 -0
  115. package/registry/ui/alert.tsx +80 -0
  116. package/registry/ui/app-shell.tsx +287 -0
  117. package/registry/ui/area-chart.tsx +3639 -0
  118. package/registry/ui/avatar.tsx +110 -0
  119. package/registry/ui/badge.tsx +56 -0
  120. package/registry/ui/bar-chart.tsx +3636 -0
  121. package/registry/ui/bar-list.tsx +3680 -0
  122. package/registry/ui/breadcrumb.tsx +128 -0
  123. package/registry/ui/button.tsx +62 -0
  124. package/registry/ui/calendar.tsx +223 -0
  125. package/registry/ui/card.tsx +107 -0
  126. package/registry/ui/category-bar.tsx +1255 -0
  127. package/registry/ui/chart-frame.tsx +1358 -0
  128. package/registry/ui/checkbox.tsx +30 -0
  129. package/registry/ui/collapsible.tsx +23 -0
  130. package/registry/ui/combobox.tsx +310 -0
  131. package/registry/ui/command.tsx +198 -0
  132. package/registry/ui/dashboard-card.tsx +217 -0
  133. package/registry/ui/data-table.tsx +693 -146
  134. package/registry/ui/date-range-picker.tsx +390 -0
  135. package/registry/ui/delta.tsx +188 -0
  136. package/registry/ui/dialog.tsx +161 -0
  137. package/registry/ui/donut-chart.tsx +3688 -0
  138. package/registry/ui/drawer.tsx +229 -0
  139. package/registry/ui/dropdown-menu.tsx +274 -0
  140. package/registry/ui/echart.tsx +890 -0
  141. package/registry/ui/field.tsx +240 -0
  142. package/registry/ui/filter-bar.tsx +392 -0
  143. package/registry/ui/funnel-steps.tsx +3541 -0
  144. package/registry/ui/heatmap.tsx +1752 -0
  145. package/registry/ui/hover-card.tsx +52 -0
  146. package/registry/ui/input-group.tsx +160 -0
  147. package/registry/ui/input.tsx +24 -0
  148. package/registry/ui/kbd.tsx +30 -0
  149. package/registry/ui/kpi-card.tsx +677 -0
  150. package/registry/ui/label.tsx +22 -0
  151. package/registry/ui/legend.tsx +3500 -0
  152. package/registry/ui/line-chart.tsx +3637 -0
  153. package/registry/ui/metric-value.tsx +62 -0
  154. package/registry/ui/multi-select.tsx +232 -0
  155. package/registry/ui/page-shell.tsx +282 -0
  156. package/registry/ui/pagination.tsx +140 -0
  157. package/registry/ui/placeholder.tsx +36 -0
  158. package/registry/ui/popover.tsx +92 -0
  159. package/registry/ui/progress-circle.tsx +867 -0
  160. package/registry/ui/progress.tsx +85 -0
  161. package/registry/ui/radio-group.tsx +39 -0
  162. package/registry/ui/sample-data.tsx +86 -0
  163. package/registry/ui/scatter-chart.tsx +4150 -0
  164. package/registry/ui/scroll-area.tsx +56 -0
  165. package/registry/ui/searchable-select.tsx +214 -0
  166. package/registry/ui/section-header.tsx +43 -0
  167. package/registry/ui/segmented-control.tsx +76 -0
  168. package/registry/ui/select.tsx +255 -0
  169. package/registry/ui/separator.tsx +27 -0
  170. package/registry/ui/sheet.tsx +139 -0
  171. package/registry/ui/sidebar.tsx +725 -0
  172. package/registry/ui/skeleton.tsx +17 -0
  173. package/registry/ui/slider.tsx +56 -0
  174. package/registry/ui/sparkline.tsx +230 -0
  175. package/registry/ui/spinner.tsx +14 -0
  176. package/registry/ui/states.tsx +90 -0
  177. package/registry/ui/status-badge.tsx +121 -0
  178. package/registry/ui/switch.tsx +34 -0
  179. package/registry/ui/table.tsx +118 -0
  180. package/registry/ui/tabs.tsx +84 -0
  181. package/registry/ui/textarea.tsx +22 -0
  182. package/registry/ui/toast.tsx +250 -0
  183. package/registry/ui/toggle-group.tsx +91 -0
  184. package/registry/ui/toggle.tsx +47 -0
  185. package/registry/ui/tooltip.tsx +68 -0
  186. package/registry/ui/tracker.tsx +843 -0
  187. package/registry/ui/trend-indicator.tsx +74 -0
  188. package/registry/ui/updated-at.tsx +122 -0
  189. package/registry/ui/waterfall-chart.tsx +4186 -0
  190. package/skills/vantage-add-feature/SKILL.md +9 -7
  191. package/skills/vantage-pitfalls/SKILL.md +5 -1
  192. package/templates/AGENTS.md +22 -1
  193. package/templates/agents/csv.md +2 -1
  194. package/templates/agents/doc.md +4 -3
  195. package/templates/agents/page.md +20 -6
  196. package/templates/agents/xlsx.md +2 -1
  197. package/theme.css +237 -136
  198. package/dist/add-DY4AXANK.js +0 -6
  199. package/dist/add-DY4AXANK.js.map +0 -1
  200. package/dist/chunk-5ZOP4SQR.js.map +0 -1
  201. package/dist/chunk-A2UUGASH.js.map +0 -1
  202. package/dist/chunk-DTDVSFRY.js.map +0 -1
  203. package/dist/chunk-LMUACBMA.js.map +0 -1
  204. package/dist/chunk-NKFBFDRZ.js +0 -2241
  205. package/dist/chunk-NKFBFDRZ.js.map +0 -1
  206. package/dist/chunk-PVTHHV7X.js.map +0 -1
  207. package/dist/chunk-PXRX5PEC.js.map +0 -1
  208. package/dist/chunk-QI3XQKHS.js.map +0 -1
  209. package/dist/chunk-RPZAALCX.js.map +0 -1
  210. package/dist/chunk-VA776YE5.js +0 -316
  211. package/dist/chunk-VA776YE5.js.map +0 -1
  212. package/dist/dev-GDKATCC7.js.map +0 -1
@@ -0,0 +1,390 @@
1
+ // Copied by `vantage add ui date-range-picker`. This file is yours to edit.
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
+
5
+ /**
6
+ * DateRangePicker — a range calendar in a popover with quick presets down the
7
+ * side. On mobile the presets stack above a single month; on desktop they sit
8
+ * beside a two-month view.
9
+ */
10
+ import { forwardRef, useCallback, useMemo, useState } from "react"
11
+ import { CalendarIcon } from "lucide-react"
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"
26
+ import { ja } from "date-fns/locale"
27
+ import type { DateRange as RdpDateRange } from "react-day-picker"
28
+
29
+ import { cn } from "@squadbase/vantage/ui"
30
+ import { useIsMobile } from "@squadbase/vantage/ui"
31
+ import { Button } from "@squadbase/vantage/ui"
32
+ import { Calendar } from "@squadbase/vantage/ui"
33
+ import { Popover, PopoverContent, PopoverTrigger } from "@squadbase/vantage/ui"
34
+ import type { DateRange, PresetItem } from "@squadbase/vantage/components"
35
+
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: "前年度",
89
+ },
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",
106
+ },
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
+ },
153
+ },
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
+ },
165
+ },
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
+ },
172
+ },
173
+ {
174
+ label: t.thisMonth,
175
+ getValue: () => {
176
+ const today = now()
177
+ return { from: startOfMonth(today), to: endOfDay(today) }
178
+ },
179
+ },
180
+ {
181
+ label: t.lastMonth,
182
+ getValue: () => {
183
+ const lastMonth = subMonths(now(), 1)
184
+ return { from: startOfMonth(lastMonth), to: endOfMonth(lastMonth) }
185
+ },
186
+ },
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()
209
+
210
+ export interface DateRangePickerProps {
211
+ /**
212
+ * 選択中の日付範囲。型はこの部品独自の `DateRange`。
213
+ * react-day-picker の `DateRange` とは別物なので注意 — そちらが必要なら
214
+ * このファイルの末尾で re-export している `RdpDateRange` を使う。
215
+ *
216
+ * @example
217
+ * import { DateRangePicker, type DateRange } from "@/components/date-range-picker"
218
+ * // Vantage: import { DateRangePicker, type DateRange } from "@squadbase/vantage/components"
219
+ *
220
+ * const [range, setRange] = useState<DateRange | undefined>()
221
+ * <DateRangePicker value={range} onChange={setRange} />
222
+ */
223
+ value?: DateRange
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
+ */
231
+ presets?: PresetItem[]
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
249
+ maxDate?: Date
250
+ minDate?: Date
251
+ placeholder?: string
252
+ disabled?: boolean
253
+ className?: string
254
+ align?: "start" | "center" | "end"
255
+ }
256
+
257
+ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
258
+ function DateRangePicker(
259
+ {
260
+ value,
261
+ onChange,
262
+ presets: presetsProp,
263
+ showPresets = true,
264
+ locale = DEFAULT_DATE_PICKER_LOCALE,
265
+ fiscalYearStartMonth = DEFAULT_FISCAL_YEAR_START_MONTH,
266
+ maxDate,
267
+ minDate,
268
+ placeholder = "期間を選択",
269
+ disabled = false,
270
+ className,
271
+ align = "start",
272
+ },
273
+ ref,
274
+ ) {
275
+ const [open, setOpen] = useState(false)
276
+ const isMobile = useIsMobile()
277
+ const presets = useMemo(
278
+ () => presetsProp ?? createDatePresets({ locale, fiscalYearStartMonth }),
279
+ [presetsProp, locale, fiscalYearStartMonth],
280
+ )
281
+
282
+ const handleCalendarSelect = useCallback(
283
+ (range: RdpDateRange | undefined) => {
284
+ onChange?.({ from: range?.from, to: range?.to })
285
+ },
286
+ [onChange],
287
+ )
288
+
289
+ const handlePresetClick = useCallback(
290
+ (preset: PresetItem) => {
291
+ onChange?.(preset.getValue())
292
+ setOpen(false)
293
+ },
294
+ [onChange],
295
+ )
296
+
297
+ 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")}`
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
309
+
310
+ const calendarSelected: RdpDateRange | undefined = value?.from
311
+ ? { from: value.from, to: value.to ?? undefined }
312
+ : undefined
313
+
314
+ return (
315
+ <div ref={ref} data-slot="date-range-picker">
316
+ <Popover open={open} onOpenChange={setOpen}>
317
+ <PopoverTrigger
318
+ render={
319
+ <Button
320
+ variant="outline"
321
+ disabled={disabled}
322
+ className={cn(
323
+ "w-auto justify-start text-left font-normal",
324
+ !value?.from && "text-muted-foreground",
325
+ className,
326
+ )}
327
+ />
328
+ }
329
+ aria-label={presetLabel ? `${presetLabel} ${formatDateRange()}` : formatDateRange()}
330
+ aria-expanded={open}
331
+ >
332
+ <CalendarIcon className="size-4" aria-hidden="true" />
333
+ {presetLabel ? (
334
+ <>
335
+ <span>{presetLabel}</span>
336
+ <span className="text-muted-foreground">{formatDateRange()}</span>
337
+ </>
338
+ ) : (
339
+ <span>{formatDateRange()}</span>
340
+ )}
341
+ </PopoverTrigger>
342
+ <PopoverContent
343
+ align={align}
344
+ className={cn("w-auto gap-0 p-0", isMobile ? "flex-col" : "flex-row")}
345
+ >
346
+ {showPresets && (
347
+ <div className={cn("flex flex-col gap-1 p-3", isMobile ? "border-b" : "border-r")}>
348
+ {presets.map((preset) => (
349
+ <Button
350
+ key={preset.label}
351
+ variant="ghost"
352
+ size="sm"
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
+ )}
358
+ onClick={() => handlePresetClick(preset)}
359
+ >
360
+ {preset.label}
361
+ </Button>
362
+ ))}
363
+ </div>
364
+ )}
365
+ <div className="p-3">
366
+ <Calendar
367
+ key={`${open}-${isMobile}`}
368
+ mode="range"
369
+ defaultMonth={defaultMonth}
370
+ locale={calendarLocale(locale)}
371
+ weekStartsOn={isJapanese(locale) ? 1 : undefined}
372
+ numberOfMonths={isMobile ? 1 : 2}
373
+ selected={calendarSelected}
374
+ onSelect={handleCalendarSelect}
375
+ disabled={(date) => {
376
+ if (maxDate && date > maxDate) return true
377
+ if (minDate && date < minDate) return true
378
+ return false
379
+ }}
380
+ />
381
+ </div>
382
+ </PopoverContent>
383
+ </Popover>
384
+ </div>
385
+ )
386
+ },
387
+ )
388
+ DateRangePicker.displayName = "DateRangePicker"
389
+
390
+ export type { DateRange as RdpDateRange } from "react-day-picker"
@@ -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 }