@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.
- package/dist/{build-MFWRPYKW.js → build-X5NVCQKT.js} +4 -4
- package/dist/{build-MFWRPYKW.js.map → build-X5NVCQKT.js.map} +1 -1
- package/dist/{chunk-IBGOCWJB.js → chunk-2ICNHUG6.js} +186 -82
- package/dist/chunk-2ICNHUG6.js.map +1 -0
- package/dist/{chunk-5ZOP4SQR.js → chunk-2N72FKLB.js} +12 -7
- package/dist/chunk-2N72FKLB.js.map +1 -0
- package/dist/{chunk-PXRX5PEC.js → chunk-PDOBHMUQ.js} +3 -3
- package/dist/chunk-PDOBHMUQ.js.map +1 -0
- package/dist/{chunk-A2UUGASH.js → chunk-U736PVHY.js} +2 -2
- package/dist/chunk-U736PVHY.js.map +1 -0
- package/dist/{chunk-QEWCYW3I.js → chunk-WAMZWZJB.js} +13 -3
- package/dist/chunk-WAMZWZJB.js.map +1 -0
- package/dist/chunk-WRSX2MDK.js +7866 -0
- package/dist/chunk-WRSX2MDK.js.map +1 -0
- package/dist/{chunk-OB6UBJXF.js → chunk-XPQSTZUU.js} +125 -35
- package/dist/chunk-XPQSTZUU.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/components/index.d.ts +1831 -99
- package/dist/components/index.js +4 -4
- package/dist/{dev-DCTOHQ6W.js → dev-4XEDTELV.js} +4 -4
- package/dist/{dev-DCTOHQ6W.js.map → dev-4XEDTELV.js.map} +1 -1
- package/dist/doc/index.js +2 -2
- package/dist/doc/index.js.map +1 -1
- package/dist/markdown/index.js +1 -1
- package/dist/markdown/index.js.map +1 -1
- package/dist/table/index.js +5 -5
- package/dist/table/index.js.map +1 -1
- package/dist/ui/index.d.ts +4 -1
- package/dist/ui/index.js +21 -111
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-DBBE6GIR.js → upgrade-JHXZBGQS.js} +3 -3
- package/dist/{upgrade-DBBE6GIR.js.map → upgrade-JHXZBGQS.js.map} +1 -1
- package/dist/vite/index.js +2 -2
- package/docs/en/components.md +29 -15
- package/docs/en/parts/data-table.md +22 -0
- package/docs/en/parts/delta.md +50 -0
- package/docs/en/parts/format.md +43 -0
- package/docs/en/parts/kpi-card.md +57 -0
- package/docs/en/parts/metric-value.md +3 -2
- package/docs/en/parts/sparkline.md +1 -1
- package/docs/en/parts/trend-indicator.md +17 -1
- package/docs/en/ui-and-theming.md +1 -1
- package/docs/index.json +92 -32
- package/docs/ja/changelog.md +5 -0
- package/docs/ja/components.md +27 -15
- package/docs/ja/parts/data-table.md +22 -0
- package/docs/ja/parts/delta.md +50 -0
- package/docs/ja/parts/format.md +44 -0
- package/docs/ja/parts/kpi-card.md +55 -0
- package/docs/ja/parts/metric-value.md +3 -2
- package/docs/ja/parts/sparkline.md +1 -1
- package/docs/ja/parts/trend-indicator.md +17 -1
- package/docs/ja/ui-and-theming.md +2 -2
- package/package.json +2 -1
- package/registry/ui/accordion.tsx +4 -2
- package/registry/ui/alert-dialog.tsx +3 -3
- package/registry/ui/alert.tsx +3 -1
- package/registry/ui/app-shell.tsx +7 -1
- package/registry/ui/area-chart.tsx +2781 -157
- package/registry/ui/avatar.tsx +1 -1
- package/registry/ui/badge.tsx +3 -1
- package/registry/ui/bar-chart.tsx +2780 -157
- package/registry/ui/bar-list.tsx +3471 -93
- package/registry/ui/breadcrumb.tsx +3 -1
- package/registry/ui/button.tsx +3 -1
- package/registry/ui/calendar.tsx +3 -1
- package/registry/ui/card.tsx +4 -2
- package/registry/ui/category-bar.tsx +990 -16
- package/registry/ui/chart-frame.tsx +1358 -0
- package/registry/ui/checkbox.tsx +3 -1
- package/registry/ui/collapsible.tsx +3 -1
- package/registry/ui/combobox.tsx +2 -2
- package/registry/ui/command.tsx +3 -1
- package/registry/ui/dashboard-card.tsx +69 -26
- package/registry/ui/data-table.tsx +678 -56
- package/registry/ui/date-range-picker.tsx +245 -64
- package/registry/ui/delta.tsx +188 -0
- package/registry/ui/dialog.tsx +5 -3
- package/registry/ui/donut-chart.tsx +2857 -173
- package/registry/ui/drawer.tsx +1 -1
- package/registry/ui/dropdown-menu.tsx +5 -3
- package/registry/ui/echart.tsx +448 -44
- package/registry/ui/field.tsx +1 -1
- package/registry/ui/filter-bar.tsx +33 -6
- package/registry/ui/funnel-steps.tsx +3316 -49
- package/registry/ui/heatmap.tsx +1752 -0
- package/registry/ui/hover-card.tsx +2 -2
- package/registry/ui/input-group.tsx +3 -1
- package/registry/ui/input.tsx +3 -1
- package/registry/ui/kbd.tsx +3 -1
- package/registry/ui/kpi-card.tsx +677 -0
- package/registry/ui/label.tsx +3 -1
- package/registry/ui/legend.tsx +3500 -0
- package/registry/ui/line-chart.tsx +2781 -151
- package/registry/ui/metric-value.tsx +9 -1
- package/registry/ui/multi-select.tsx +11 -5
- package/registry/ui/page-shell.tsx +104 -49
- package/registry/ui/pagination.tsx +3 -1
- package/registry/ui/placeholder.tsx +8 -2
- package/registry/ui/popover.tsx +4 -2
- package/registry/ui/progress-circle.tsx +607 -12
- package/registry/ui/progress.tsx +3 -1
- package/registry/ui/radio-group.tsx +1 -1
- package/registry/ui/sample-data.tsx +86 -0
- package/registry/ui/scatter-chart.tsx +4150 -0
- package/registry/ui/scroll-area.tsx +3 -2
- package/registry/ui/searchable-select.tsx +8 -3
- package/registry/ui/section-header.tsx +4 -2
- package/registry/ui/segmented-control.tsx +7 -2
- package/registry/ui/select.tsx +4 -2
- package/registry/ui/separator.tsx +3 -1
- package/registry/ui/sheet.tsx +4 -2
- package/registry/ui/sidebar.tsx +4 -2
- package/registry/ui/skeleton.tsx +4 -2
- package/registry/ui/slider.tsx +3 -1
- package/registry/ui/sparkline.tsx +84 -9
- package/registry/ui/spinner.tsx +3 -1
- package/registry/ui/states.tsx +34 -5
- package/registry/ui/status-badge.tsx +81 -39
- package/registry/ui/switch.tsx +3 -1
- package/registry/ui/table.tsx +4 -2
- package/registry/ui/tabs.tsx +5 -3
- package/registry/ui/textarea.tsx +3 -1
- package/registry/ui/toast.tsx +2 -2
- package/registry/ui/toggle-group.tsx +3 -1
- package/registry/ui/toggle.tsx +3 -1
- package/registry/ui/tooltip.tsx +4 -2
- package/registry/ui/tracker.tsx +624 -17
- package/registry/ui/trend-indicator.tsx +13 -1
- package/registry/ui/updated-at.tsx +122 -0
- package/registry/ui/waterfall-chart.tsx +4186 -0
- package/templates/AGENTS.md +13 -1
- package/theme.css +237 -136
- package/dist/chunk-5ZOP4SQR.js.map +0 -1
- package/dist/chunk-A2UUGASH.js.map +0 -1
- package/dist/chunk-AG6RZ2RR.js +0 -3208
- package/dist/chunk-AG6RZ2RR.js.map +0 -1
- package/dist/chunk-IBGOCWJB.js.map +0 -1
- package/dist/chunk-OB6UBJXF.js.map +0 -1
- package/dist/chunk-PXRX5PEC.js.map +0 -1
- 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/
|
|
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 {
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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:
|
|
52
|
-
getValue: ()
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
*
|
|
212
|
+
* 選択中の日付範囲。型はこの部品独自の `DateRange`。
|
|
83
213
|
* react-day-picker の `DateRange` とは別物なので注意 — そちらが必要なら
|
|
84
214
|
* このファイルの末尾で re-export している `RdpDateRange` を使う。
|
|
85
215
|
*
|
|
86
216
|
* @example
|
|
87
|
-
* import type
|
|
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
|
|
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 = "
|
|
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"
|
|
141
|
-
return `${format(value.from, "yyyy/MM/dd"
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 }
|
package/registry/ui/dialog.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Copied by `vantage add ui dialog`. This file is yours to edit.
|
|
2
|
-
// Generated from @squadbase/
|
|
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}
|