@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.
- package/dist/add-4GHF2TX2.js +6 -0
- package/dist/add-4GHF2TX2.js.map +1 -0
- package/dist/{build-TVKFIVFQ.js → build-X5NVCQKT.js} +7 -7
- package/dist/{build-TVKFIVFQ.js.map → build-X5NVCQKT.js.map} +1 -1
- package/dist/{check-3KHZXZMQ.js → check-ZBIH422T.js} +5 -5
- package/dist/{check-3KHZXZMQ.js.map → check-ZBIH422T.js.map} +1 -1
- package/dist/chunk-2ICNHUG6.js +1820 -0
- 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-P7W4LR3Z.js → chunk-7DZGJQ3P.js} +3 -3
- package/dist/{chunk-P7W4LR3Z.js.map → chunk-7DZGJQ3P.js.map} +1 -1
- package/dist/{chunk-U2M6HGKQ.js → chunk-AABMKHEU.js} +4 -4
- package/dist/{chunk-U2M6HGKQ.js.map → chunk-AABMKHEU.js.map} +1 -1
- package/dist/{chunk-QI3XQKHS.js → chunk-GNXCX4WE.js} +10 -3
- package/dist/chunk-GNXCX4WE.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-ZVANIULZ.js → chunk-RNTDBX6S.js} +4 -4
- package/dist/{chunk-ZVANIULZ.js.map → chunk-RNTDBX6S.js.map} +1 -1
- package/dist/{chunk-A2UUGASH.js → chunk-U736PVHY.js} +2 -2
- package/dist/chunk-U736PVHY.js.map +1 -0
- package/dist/{chunk-PVTHHV7X.js → chunk-V3LF7BE7.js} +27 -5
- package/dist/chunk-V3LF7BE7.js.map +1 -0
- package/dist/{chunk-LMUACBMA.js → chunk-WAMZWZJB.js} +53 -8
- 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-RPZAALCX.js → chunk-XPQSTZUU.js} +254 -164
- package/dist/chunk-XPQSTZUU.js.map +1 -0
- package/dist/{chunk-DTDVSFRY.js → chunk-XQUSGIDG.js} +17 -3
- package/dist/chunk-XQUSGIDG.js.map +1 -0
- package/dist/cli.js +12 -11
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +32 -3
- package/dist/client/index.js +205 -12
- package/dist/client/index.js.map +1 -1
- package/dist/components/index.d.ts +1975 -28
- package/dist/components/index.js +4 -4
- package/dist/{dev-GDKATCC7.js → dev-4XEDTELV.js} +8 -8
- package/dist/dev-4XEDTELV.js.map +1 -0
- package/dist/doc/index.js +2 -2
- package/dist/doc/index.js.map +1 -1
- package/dist/{docs-EJJ3BECT.js → docs-JVP4RPKT.js} +3 -3
- package/dist/{docs-EJJ3BECT.js.map → docs-JVP4RPKT.js.map} +1 -1
- package/dist/{doctor-WQXPXP6K.js → doctor-5E2VEZPK.js} +5 -5
- package/dist/{doctor-WQXPXP6K.js.map → doctor-5E2VEZPK.js.map} +1 -1
- package/dist/markdown/index.js +1 -1
- package/dist/markdown/index.js.map +1 -1
- package/dist/{preview-OGMCNQUO.js → preview-T6CLSQVX.js} +3 -3
- package/dist/{preview-OGMCNQUO.js.map → preview-T6CLSQVX.js.map} +1 -1
- package/dist/query/index.d.ts +0 -11
- package/dist/query/index.js +2 -2
- package/dist/{routes-MWBWWOJ2.js → routes-3SGAFDMC.js} +4 -4
- package/dist/{routes-MWBWWOJ2.js.map → routes-3SGAFDMC.js.map} +1 -1
- package/dist/{search-6Y422SV6.js → search-5LC4XPB5.js} +3 -3
- package/dist/{search-6Y422SV6.js.map → search-5LC4XPB5.js.map} +1 -1
- package/dist/table/index.js +10 -9
- package/dist/table/index.js.map +1 -1
- package/dist/ui/index.d.ts +144 -11
- package/dist/ui/index.js +1339 -87
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-HCANX4GQ.js → upgrade-JHXZBGQS.js} +6 -6
- package/dist/{upgrade-HCANX4GQ.js.map → upgrade-JHXZBGQS.js.map} +1 -1
- package/dist/vite/index.d.ts +10 -0
- package/dist/vite/index.js +3 -3
- 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 +366 -76
- package/docs/ja/changelog.md +31 -0
- package/docs/ja/components.md +57 -101
- package/docs/ja/page-types/csv.md +83 -0
- package/docs/ja/page-types/doc.md +83 -0
- package/docs/ja/page-types/excel.md +82 -0
- package/docs/ja/page-types/slide.md +132 -0
- package/docs/ja/page-types.md +47 -0
- package/docs/ja/parts/area-chart.md +61 -0
- package/docs/ja/parts/bar-chart.md +69 -0
- package/docs/ja/parts/bar-list.md +35 -0
- package/docs/ja/parts/category-bar.md +36 -0
- package/docs/ja/parts/data-table.md +22 -0
- package/docs/ja/parts/delta.md +50 -0
- package/docs/ja/parts/donut-chart.md +47 -0
- package/docs/ja/parts/echart.md +3 -0
- package/docs/ja/parts/format.md +44 -0
- package/docs/ja/parts/kpi-card.md +55 -0
- package/docs/ja/parts/line-chart.md +58 -0
- package/docs/ja/parts/metric-value.md +3 -2
- package/docs/ja/parts/progress-circle.md +34 -0
- package/docs/ja/parts/sparkline.md +2 -2
- package/docs/ja/parts/tracker.md +32 -0
- package/docs/ja/parts/trend-indicator.md +17 -1
- package/docs/ja/routing.md +8 -86
- package/docs/ja/ui/alert-dialog.md +41 -0
- package/docs/ja/ui/avatar.md +31 -0
- package/docs/ja/ui/combobox.md +74 -0
- package/docs/ja/ui/drawer.md +41 -0
- package/docs/ja/ui/field.md +42 -0
- package/docs/ja/ui/hover-card.md +21 -0
- package/docs/ja/ui/kbd.md +16 -0
- package/docs/ja/ui/pagination.md +36 -0
- package/docs/ja/ui/radio-group.md +21 -0
- package/docs/ja/ui/toast.md +46 -0
- package/docs/ja/ui-and-theming.md +14 -7
- package/package.json +13 -4
- package/registry/ui/accordion.tsx +76 -0
- package/registry/ui/alert-dialog.tsx +189 -0
- package/registry/ui/alert.tsx +80 -0
- package/registry/ui/app-shell.tsx +287 -0
- package/registry/ui/area-chart.tsx +3639 -0
- package/registry/ui/avatar.tsx +110 -0
- package/registry/ui/badge.tsx +56 -0
- package/registry/ui/bar-chart.tsx +3636 -0
- package/registry/ui/bar-list.tsx +3680 -0
- package/registry/ui/breadcrumb.tsx +128 -0
- package/registry/ui/button.tsx +62 -0
- package/registry/ui/calendar.tsx +223 -0
- package/registry/ui/card.tsx +107 -0
- package/registry/ui/category-bar.tsx +1255 -0
- package/registry/ui/chart-frame.tsx +1358 -0
- package/registry/ui/checkbox.tsx +30 -0
- package/registry/ui/collapsible.tsx +23 -0
- package/registry/ui/combobox.tsx +310 -0
- package/registry/ui/command.tsx +198 -0
- package/registry/ui/dashboard-card.tsx +217 -0
- package/registry/ui/data-table.tsx +693 -146
- package/registry/ui/date-range-picker.tsx +390 -0
- package/registry/ui/delta.tsx +188 -0
- package/registry/ui/dialog.tsx +161 -0
- package/registry/ui/donut-chart.tsx +3688 -0
- package/registry/ui/drawer.tsx +229 -0
- package/registry/ui/dropdown-menu.tsx +274 -0
- package/registry/ui/echart.tsx +890 -0
- package/registry/ui/field.tsx +240 -0
- package/registry/ui/filter-bar.tsx +392 -0
- package/registry/ui/funnel-steps.tsx +3541 -0
- package/registry/ui/heatmap.tsx +1752 -0
- package/registry/ui/hover-card.tsx +52 -0
- package/registry/ui/input-group.tsx +160 -0
- package/registry/ui/input.tsx +24 -0
- package/registry/ui/kbd.tsx +30 -0
- package/registry/ui/kpi-card.tsx +677 -0
- package/registry/ui/label.tsx +22 -0
- package/registry/ui/legend.tsx +3500 -0
- package/registry/ui/line-chart.tsx +3637 -0
- package/registry/ui/metric-value.tsx +62 -0
- package/registry/ui/multi-select.tsx +232 -0
- package/registry/ui/page-shell.tsx +282 -0
- package/registry/ui/pagination.tsx +140 -0
- package/registry/ui/placeholder.tsx +36 -0
- package/registry/ui/popover.tsx +92 -0
- package/registry/ui/progress-circle.tsx +867 -0
- package/registry/ui/progress.tsx +85 -0
- package/registry/ui/radio-group.tsx +39 -0
- package/registry/ui/sample-data.tsx +86 -0
- package/registry/ui/scatter-chart.tsx +4150 -0
- package/registry/ui/scroll-area.tsx +56 -0
- package/registry/ui/searchable-select.tsx +214 -0
- package/registry/ui/section-header.tsx +43 -0
- package/registry/ui/segmented-control.tsx +76 -0
- package/registry/ui/select.tsx +255 -0
- package/registry/ui/separator.tsx +27 -0
- package/registry/ui/sheet.tsx +139 -0
- package/registry/ui/sidebar.tsx +725 -0
- package/registry/ui/skeleton.tsx +17 -0
- package/registry/ui/slider.tsx +56 -0
- package/registry/ui/sparkline.tsx +230 -0
- package/registry/ui/spinner.tsx +14 -0
- package/registry/ui/states.tsx +90 -0
- package/registry/ui/status-badge.tsx +121 -0
- package/registry/ui/switch.tsx +34 -0
- package/registry/ui/table.tsx +118 -0
- package/registry/ui/tabs.tsx +84 -0
- package/registry/ui/textarea.tsx +22 -0
- package/registry/ui/toast.tsx +250 -0
- package/registry/ui/toggle-group.tsx +91 -0
- package/registry/ui/toggle.tsx +47 -0
- package/registry/ui/tooltip.tsx +68 -0
- package/registry/ui/tracker.tsx +843 -0
- package/registry/ui/trend-indicator.tsx +74 -0
- package/registry/ui/updated-at.tsx +122 -0
- package/registry/ui/waterfall-chart.tsx +4186 -0
- package/skills/vantage-add-feature/SKILL.md +9 -7
- package/skills/vantage-pitfalls/SKILL.md +5 -1
- package/templates/AGENTS.md +22 -1
- package/templates/agents/csv.md +2 -1
- package/templates/agents/doc.md +4 -3
- package/templates/agents/page.md +20 -6
- package/templates/agents/xlsx.md +2 -1
- package/theme.css +237 -136
- package/dist/add-DY4AXANK.js +0 -6
- package/dist/add-DY4AXANK.js.map +0 -1
- package/dist/chunk-5ZOP4SQR.js.map +0 -1
- package/dist/chunk-A2UUGASH.js.map +0 -1
- package/dist/chunk-DTDVSFRY.js.map +0 -1
- package/dist/chunk-LMUACBMA.js.map +0 -1
- package/dist/chunk-NKFBFDRZ.js +0 -2241
- package/dist/chunk-NKFBFDRZ.js.map +0 -1
- package/dist/chunk-PVTHHV7X.js.map +0 -1
- package/dist/chunk-PXRX5PEC.js.map +0 -1
- package/dist/chunk-QI3XQKHS.js.map +0 -1
- package/dist/chunk-RPZAALCX.js.map +0 -1
- package/dist/chunk-VA776YE5.js +0 -316
- package/dist/chunk-VA776YE5.js.map +0 -1
- 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 }
|