@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,17 @@
|
|
|
1
|
+
// Copied by `vantage add ui skeleton`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/skeleton.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
6
|
+
|
|
7
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="skeleton"
|
|
11
|
+
className={cn("animate-pulse motion-reduce:animate-none rounded-md bg-muted", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { Skeleton }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copied by `vantage add ui slider`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/slider.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
import { Slider as SliderPrimitive } from "@base-ui/react/slider"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
8
|
+
|
|
9
|
+
function Slider({
|
|
10
|
+
className,
|
|
11
|
+
defaultValue,
|
|
12
|
+
value,
|
|
13
|
+
min = 0,
|
|
14
|
+
max = 100,
|
|
15
|
+
...props
|
|
16
|
+
}: SliderPrimitive.Root.Props) {
|
|
17
|
+
const _values = Array.isArray(value)
|
|
18
|
+
? value
|
|
19
|
+
: Array.isArray(defaultValue)
|
|
20
|
+
? defaultValue
|
|
21
|
+
: [min, max]
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<SliderPrimitive.Root
|
|
25
|
+
className={cn("data-horizontal:w-full data-vertical:h-full", className)}
|
|
26
|
+
data-slot="slider"
|
|
27
|
+
defaultValue={defaultValue}
|
|
28
|
+
value={value}
|
|
29
|
+
min={min}
|
|
30
|
+
max={max}
|
|
31
|
+
thumbAlignment="edge"
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
<SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">
|
|
35
|
+
<SliderPrimitive.Track
|
|
36
|
+
data-slot="slider-track"
|
|
37
|
+
className="relative grow overflow-hidden rounded-full bg-muted select-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1"
|
|
38
|
+
>
|
|
39
|
+
<SliderPrimitive.Indicator
|
|
40
|
+
data-slot="slider-range"
|
|
41
|
+
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
|
|
42
|
+
/>
|
|
43
|
+
</SliderPrimitive.Track>
|
|
44
|
+
{Array.from({ length: _values.length }, (_, index) => (
|
|
45
|
+
<SliderPrimitive.Thumb
|
|
46
|
+
data-slot="slider-thumb"
|
|
47
|
+
key={index}
|
|
48
|
+
className="relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50"
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
</SliderPrimitive.Control>
|
|
52
|
+
</SliderPrimitive.Root>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Slider }
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Copied by `vantage add ui sparkline`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/charts/sparkline.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sparkline — an inline trend micro-chart. Deliberately *not* an `EChart`: a
|
|
7
|
+
* sparkline sits inside table cells and KPI tiles by the dozen, where one
|
|
8
|
+
* ECharts instance per cell would be wasteful. This renders a single stateless
|
|
9
|
+
* `<svg>` instead, so hundreds of them stay cheap.
|
|
10
|
+
*/
|
|
11
|
+
import { forwardRef, useEffect, useState, type SVGAttributes } from "react"
|
|
12
|
+
|
|
13
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
14
|
+
import type { ChartHighlight } from "@squadbase/vantage/components"
|
|
15
|
+
|
|
16
|
+
export type SparklineVariant = "line" | "bar"
|
|
17
|
+
|
|
18
|
+
export interface SparklineDataPoint {
|
|
19
|
+
value: number
|
|
20
|
+
label?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SparklineProps extends SVGAttributes<SVGSVGElement> {
|
|
24
|
+
data: SparklineDataPoint[]
|
|
25
|
+
variant?: SparklineVariant
|
|
26
|
+
height?: number
|
|
27
|
+
/** Tailwind text-colour class; drives both stroke and fill via `currentColor`. */
|
|
28
|
+
color?: string
|
|
29
|
+
area?: boolean
|
|
30
|
+
/** マウント時に線を引く / 棒を伸ばす。既定 `false`(`prefers-reduced-motion` では常に無効)。 */
|
|
31
|
+
animate?: boolean
|
|
32
|
+
/**
|
|
33
|
+
* 点の強調(Tufte 流)。`"auto"` は最新・最大・最小の 3 点だけに小さな点を打つ
|
|
34
|
+
* (最新は系列の色、最大・最小は muted)。`"latest"` は最新の 1 点だけ、`"none"` は線だけ。`variant="line"` のときだけ効く。既定 `"auto"`。
|
|
35
|
+
*/
|
|
36
|
+
highlight?: ChartHighlight | "latest"
|
|
37
|
+
/** 指定すると `role="img"` で読み上げ対象にする。省略時は装飾として `aria-hidden`。 */
|
|
38
|
+
"aria-label"?: string
|
|
39
|
+
className?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const VB_WIDTH = 100
|
|
43
|
+
|
|
44
|
+
/** Map raw values onto SVG y-coordinates (inverted, padded). */
|
|
45
|
+
function normalizeData(values: number[], height: number, vPad = 2): number[] {
|
|
46
|
+
const min = Math.min(...values)
|
|
47
|
+
const max = Math.max(...values)
|
|
48
|
+
const range = max - min
|
|
49
|
+
const drawHeight = height - vPad * 2
|
|
50
|
+
if (range === 0) return values.map(() => vPad + drawHeight / 2)
|
|
51
|
+
return values.map((v) => height - vPad - ((v - min) / range) * drawHeight)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildLinePath(ys: number[]): string {
|
|
55
|
+
if (ys.length === 0) return ""
|
|
56
|
+
const step = ys.length > 1 ? VB_WIDTH / (ys.length - 1) : 0
|
|
57
|
+
return ys
|
|
58
|
+
.map((y, i) => `${i === 0 ? "M" : "L"}${(i * step).toFixed(2)},${y.toFixed(2)}`)
|
|
59
|
+
.join(" ")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildAreaPath(ys: number[], height: number): string {
|
|
63
|
+
if (ys.length === 0) return ""
|
|
64
|
+
const step = ys.length > 1 ? VB_WIDTH / (ys.length - 1) : 0
|
|
65
|
+
const lastX = ((ys.length - 1) * step).toFixed(2)
|
|
66
|
+
return `${buildLinePath(ys)} L${lastX},${height} L0,${height} Z`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type SparklinePointKind = "latest" | "max" | "min"
|
|
70
|
+
|
|
71
|
+
export interface SparklinePoint {
|
|
72
|
+
kind: SparklinePointKind
|
|
73
|
+
index: number
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The points a Tufte-style sparkline marks: the latest, the max and the min.
|
|
78
|
+
* A point that is both latest and max/min is drawn once, as `latest`; a flat
|
|
79
|
+
* series marks only the latest. Ties pick the most recent occurrence.
|
|
80
|
+
*/
|
|
81
|
+
export function sparklineHighlightPoints(values: number[]): SparklinePoint[] {
|
|
82
|
+
if (values.length === 0) return []
|
|
83
|
+
const last = values.length - 1
|
|
84
|
+
const points: SparklinePoint[] = [{ kind: "latest", index: last }]
|
|
85
|
+
let maxI = 0
|
|
86
|
+
let minI = 0
|
|
87
|
+
let max = values[0] as number
|
|
88
|
+
let min = max
|
|
89
|
+
values.forEach((v, i) => {
|
|
90
|
+
if (v >= max) [max, maxI] = [v, i]
|
|
91
|
+
if (v <= min) [min, minI] = [v, i]
|
|
92
|
+
})
|
|
93
|
+
const latest = values[last] as number
|
|
94
|
+
if (max === min) return points
|
|
95
|
+
if (max !== latest) points.push({ kind: "max", index: maxI })
|
|
96
|
+
if (min !== latest) points.push({ kind: "min", index: minI })
|
|
97
|
+
return points
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface BarRect {
|
|
101
|
+
x: number
|
|
102
|
+
y: number
|
|
103
|
+
w: number
|
|
104
|
+
h: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildBarRects(ys: number[], height: number, gap = 1): BarRect[] {
|
|
108
|
+
const barW = Math.max(1, VB_WIDTH / ys.length - gap)
|
|
109
|
+
return ys.map((y, i) => ({ x: i * (barW + gap), y, w: barW, h: height - y }))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Rows not given yet (a draft fills props in steps): draw the empty state, keep the same array between renders. */
|
|
113
|
+
const NO_ROWS: never[] = []
|
|
114
|
+
|
|
115
|
+
export const Sparkline = forwardRef<SVGSVGElement, SparklineProps>(function Sparkline(
|
|
116
|
+
{
|
|
117
|
+
data = NO_ROWS,
|
|
118
|
+
variant = "line",
|
|
119
|
+
height = 40,
|
|
120
|
+
color = "text-chart-1",
|
|
121
|
+
area = false,
|
|
122
|
+
animate = false,
|
|
123
|
+
highlight = "auto",
|
|
124
|
+
"aria-label": ariaLabel,
|
|
125
|
+
className,
|
|
126
|
+
...props
|
|
127
|
+
},
|
|
128
|
+
ref,
|
|
129
|
+
) {
|
|
130
|
+
const [mounted, setMounted] = useState(false)
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (animate) setMounted(true)
|
|
134
|
+
}, [animate])
|
|
135
|
+
|
|
136
|
+
if (data.length === 0) return null
|
|
137
|
+
|
|
138
|
+
const values = data.map((d) => d.value)
|
|
139
|
+
const ys = normalizeData(values, height)
|
|
140
|
+
const step = ys.length > 1 ? VB_WIDTH / (ys.length - 1) : 0
|
|
141
|
+
const points =
|
|
142
|
+
variant !== "line"
|
|
143
|
+
? []
|
|
144
|
+
: highlight === "auto"
|
|
145
|
+
? sparklineHighlightPoints(values)
|
|
146
|
+
: highlight === "latest"
|
|
147
|
+
? [{ kind: "latest" as const, index: values.length - 1 }]
|
|
148
|
+
: []
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<svg
|
|
152
|
+
ref={ref}
|
|
153
|
+
data-slot="sparkline"
|
|
154
|
+
viewBox={`0 0 ${VB_WIDTH} ${height}`}
|
|
155
|
+
preserveAspectRatio="none"
|
|
156
|
+
width="100%"
|
|
157
|
+
height={height}
|
|
158
|
+
role={ariaLabel ? "img" : undefined}
|
|
159
|
+
aria-label={ariaLabel}
|
|
160
|
+
aria-hidden={ariaLabel ? undefined : true}
|
|
161
|
+
className={cn("overflow-visible", className)}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
{variant === "line" && (
|
|
165
|
+
<>
|
|
166
|
+
{area && (
|
|
167
|
+
<path
|
|
168
|
+
d={buildAreaPath(ys, height)}
|
|
169
|
+
strokeWidth={0}
|
|
170
|
+
className={cn("fill-current opacity-15", color)}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
173
|
+
<path
|
|
174
|
+
d={buildLinePath(ys)}
|
|
175
|
+
fill="none"
|
|
176
|
+
strokeWidth={1.5}
|
|
177
|
+
strokeLinecap="round"
|
|
178
|
+
strokeLinejoin="round"
|
|
179
|
+
vectorEffect="non-scaling-stroke"
|
|
180
|
+
pathLength={animate ? 1 : undefined}
|
|
181
|
+
strokeDasharray={animate ? 1 : undefined}
|
|
182
|
+
strokeDashoffset={animate ? (mounted ? 0 : 1) : undefined}
|
|
183
|
+
className={cn("stroke-current", animate && "motion-reduce:transition-none!", color)}
|
|
184
|
+
style={animate ? { transition: "stroke-dashoffset 0.6s ease-in-out" } : undefined}
|
|
185
|
+
/>
|
|
186
|
+
{/* A zero-length round-capped stroke with a non-scaling width stays a
|
|
187
|
+
circle even though the viewBox stretches (`preserveAspectRatio="none"`). */}
|
|
188
|
+
{points.map((p) => {
|
|
189
|
+
const x = (p.index * step).toFixed(2)
|
|
190
|
+
const y = (ys[p.index] ?? 0).toFixed(2)
|
|
191
|
+
return (
|
|
192
|
+
<path
|
|
193
|
+
key={p.kind}
|
|
194
|
+
data-slot="sparkline-point"
|
|
195
|
+
data-point={p.kind}
|
|
196
|
+
d={`M${x},${y} L${x},${y}`}
|
|
197
|
+
fill="none"
|
|
198
|
+
strokeWidth={p.kind === "latest" ? 5 : 4}
|
|
199
|
+
strokeLinecap="round"
|
|
200
|
+
vectorEffect="non-scaling-stroke"
|
|
201
|
+
className={cn("stroke-current", p.kind === "latest" ? color : "text-muted-foreground")}
|
|
202
|
+
/>
|
|
203
|
+
)
|
|
204
|
+
})}
|
|
205
|
+
</>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{variant === "bar" &&
|
|
209
|
+
buildBarRects(ys, height).map((rect, i) => (
|
|
210
|
+
<rect
|
|
211
|
+
key={i}
|
|
212
|
+
x={rect.x}
|
|
213
|
+
y={animate ? (mounted ? rect.y : height) : rect.y}
|
|
214
|
+
width={rect.w}
|
|
215
|
+
height={animate ? (mounted ? rect.h : 0) : rect.h}
|
|
216
|
+
rx={0.5}
|
|
217
|
+
className={cn("fill-current", animate && "motion-reduce:transition-none!", color)}
|
|
218
|
+
style={
|
|
219
|
+
animate
|
|
220
|
+
? {
|
|
221
|
+
transition: `height 0.4s ease-out ${i * 0.02}s, y 0.4s ease-out ${i * 0.02}s`,
|
|
222
|
+
}
|
|
223
|
+
: undefined
|
|
224
|
+
}
|
|
225
|
+
/>
|
|
226
|
+
))}
|
|
227
|
+
</svg>
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
Sparkline.displayName = "Sparkline"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copied by `vantage add ui spinner`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/spinner.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
6
|
+
import { Loader2Icon } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
9
|
+
return (
|
|
10
|
+
<Loader2Icon data-slot="spinner" role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { Spinner }
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copied by `vantage add ui states`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/states.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Loading / Empty / ErrorState — the three non-data states of a panel, worded for
|
|
7
|
+
* business users. Defaults are Japanese and say what is happening, not why the
|
|
8
|
+
* system failed: keep raw errors ("upstream timeout after 30s") in `details`,
|
|
9
|
+
* which stays folded behind "詳細を表示".
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { ErrorState } from "@/components/ui/states"
|
|
13
|
+
* // Vantage: import { ErrorState } from "@squadbase/vantage/ui"
|
|
14
|
+
*
|
|
15
|
+
* <ErrorState
|
|
16
|
+
* title="売上を読み込めませんでした"
|
|
17
|
+
* message="データソースから時間内に応答がありませんでした。しばらくしてから再試行してください。"
|
|
18
|
+
* details={error.message}
|
|
19
|
+
* action={<Button variant="outline" onClick={retry}>再試行する</Button>}
|
|
20
|
+
* />
|
|
21
|
+
*/
|
|
22
|
+
import type { ReactNode } from "react"
|
|
23
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
24
|
+
|
|
25
|
+
export function Loading({ label = "読み込んでいます…", className }: { label?: string; className?: string }) {
|
|
26
|
+
return (
|
|
27
|
+
<div className={cn("flex min-h-[30vh] items-center justify-center", className)} role="status" aria-live="polite">
|
|
28
|
+
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
|
29
|
+
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
30
|
+
{label}
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Empty({
|
|
37
|
+
title = "表示できるデータがありません",
|
|
38
|
+
description,
|
|
39
|
+
icon,
|
|
40
|
+
action,
|
|
41
|
+
className,
|
|
42
|
+
}: {
|
|
43
|
+
title?: string
|
|
44
|
+
description?: string
|
|
45
|
+
icon?: ReactNode
|
|
46
|
+
action?: ReactNode
|
|
47
|
+
className?: string
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<div className={cn("flex flex-col items-center justify-center gap-2 p-10 text-center", className)}>
|
|
51
|
+
{icon ? <div className="text-3xl text-muted-foreground">{icon}</div> : null}
|
|
52
|
+
<p className="font-medium">{title}</p>
|
|
53
|
+
{description ? <p className="max-w-sm text-sm text-muted-foreground">{description}</p> : null}
|
|
54
|
+
{action ? <div className="mt-2">{action}</div> : null}
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ErrorState({
|
|
60
|
+
title = "データを読み込めませんでした",
|
|
61
|
+
message = "時間をおいて再試行してください。解決しない場合は管理者にお問い合わせください。",
|
|
62
|
+
details,
|
|
63
|
+
action,
|
|
64
|
+
className,
|
|
65
|
+
}: {
|
|
66
|
+
title?: string
|
|
67
|
+
/** What happened and what to do next, in plain words. */
|
|
68
|
+
message?: ReactNode
|
|
69
|
+
/** Technical detail (error message, status code), folded behind "詳細を表示". */
|
|
70
|
+
details?: ReactNode
|
|
71
|
+
action?: ReactNode
|
|
72
|
+
className?: string
|
|
73
|
+
}) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
className={cn("rounded-xl border border-destructive/30 bg-destructive/5 p-6", className)}
|
|
77
|
+
role="alert"
|
|
78
|
+
>
|
|
79
|
+
<p className="font-semibold text-destructive">{title}</p>
|
|
80
|
+
{message ? <p className="mt-1 text-sm text-muted-foreground">{message}</p> : null}
|
|
81
|
+
{details ? (
|
|
82
|
+
<details className="mt-2 text-xs text-muted-foreground">
|
|
83
|
+
<summary className="cursor-pointer select-none">詳細を表示</summary>
|
|
84
|
+
<div className="mt-1 font-mono break-all">{details}</div>
|
|
85
|
+
</details>
|
|
86
|
+
) : null}
|
|
87
|
+
{action ? <div className="mt-3">{action}</div> : null}
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Copied by `vantage add ui status-badge`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/components/status-badge.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* StatusBadge — a dot-plus-label badge for row/record status. Statuses map to a
|
|
7
|
+
* tone through `defaultColorMap`; pass `colorMap` to extend or override it.
|
|
8
|
+
*
|
|
9
|
+
* Tones are the theme's semantic tokens — `positive` | `negative` | `warning` |
|
|
10
|
+
* `info` | `neutral` — drawn as a `--<tone>` dot and text on a `--<tone>-subtle`
|
|
11
|
+
* background. The legacy colour names `green` / `red` / `yellow` / `blue` /
|
|
12
|
+
* `gray` are still accepted and map to positive / negative / warning / info /
|
|
13
|
+
* neutral. Unknown statuses and tones fall back to `neutral`.
|
|
14
|
+
*
|
|
15
|
+
* The default label comes from `STATUS_VOCABULARY` (one table of status, 日本語ラベル and tone), so the same
|
|
16
|
+
* state is worded the same everywhere (Tracker legends import it too):
|
|
17
|
+
*
|
|
18
|
+
* | status | ラベル | 色調 |
|
|
19
|
+
* |---|---|---|
|
|
20
|
+
* | `success` | 正常 | positive |
|
|
21
|
+
* | `partial` | 一部失敗 | warning |
|
|
22
|
+
* | `failed` | 失敗 | negative |
|
|
23
|
+
* | `delayed` | 遅延 | warning |
|
|
24
|
+
* | `running` | 実行中 | info |
|
|
25
|
+
* | `stopped` | 停止中 | neutral |
|
|
26
|
+
* | `active` / `inactive` | 有効 / 無効 | positive / neutral |
|
|
27
|
+
* | `warning` / `error` / `pending` | 警告 / エラー / 保留中 | warning / negative / info |
|
|
28
|
+
*/
|
|
29
|
+
import { forwardRef } from "react"
|
|
30
|
+
|
|
31
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
32
|
+
import { Badge } from "@squadbase/vantage/ui"
|
|
33
|
+
|
|
34
|
+
type StatusTone = "positive" | "negative" | "warning" | "info" | "neutral"
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 状態の語彙の正本: status → 日本語ラベルと色調。StatusBadge の既定の表示と、Tracker の凡例など
|
|
38
|
+
* 同じ状態を語る他の場所が、同じ語を使うためにここから参照する。
|
|
39
|
+
*/
|
|
40
|
+
export const STATUS_VOCABULARY = {
|
|
41
|
+
success: { label: "正常", tone: "positive" },
|
|
42
|
+
partial: { label: "一部失敗", tone: "warning" },
|
|
43
|
+
failed: { label: "失敗", tone: "negative" },
|
|
44
|
+
delayed: { label: "遅延", tone: "warning" },
|
|
45
|
+
running: { label: "実行中", tone: "info" },
|
|
46
|
+
stopped: { label: "停止中", tone: "neutral" },
|
|
47
|
+
active: { label: "有効", tone: "positive" },
|
|
48
|
+
inactive: { label: "無効", tone: "neutral" },
|
|
49
|
+
warning: { label: "警告", tone: "warning" },
|
|
50
|
+
error: { label: "エラー", tone: "negative" },
|
|
51
|
+
pending: { label: "保留中", tone: "info" },
|
|
52
|
+
} as const satisfies Record<string, { label: string; tone: StatusTone }>
|
|
53
|
+
|
|
54
|
+
export type StatusKey = keyof typeof STATUS_VOCABULARY
|
|
55
|
+
|
|
56
|
+
/** 状態の日本語ラベル。語彙に無い status は渡された文字列のまま返す。 */
|
|
57
|
+
export function statusLabel(status: string): string {
|
|
58
|
+
return Object.hasOwn(STATUS_VOCABULARY, status) ? STATUS_VOCABULARY[status as StatusKey].label : status
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const defaultColorMap: Record<string, string> = Object.fromEntries(
|
|
62
|
+
Object.entries(STATUS_VOCABULARY).map(([key, { tone }]) => [key, tone]),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
/** Legacy colour names → tones. */
|
|
66
|
+
const legacyColorTones: Record<string, StatusTone> = {
|
|
67
|
+
green: "positive",
|
|
68
|
+
gray: "neutral",
|
|
69
|
+
yellow: "warning",
|
|
70
|
+
red: "negative",
|
|
71
|
+
blue: "info",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* One literal class set per tone — Tailwind only emits classes it can see
|
|
76
|
+
* literally in the source.
|
|
77
|
+
*/
|
|
78
|
+
const toneStyles: Record<StatusTone, { dot: string; bg: string; text: string }> = {
|
|
79
|
+
positive: { dot: "bg-positive", bg: "bg-positive-subtle", text: "text-positive" },
|
|
80
|
+
negative: { dot: "bg-negative", bg: "bg-negative-subtle", text: "text-negative" },
|
|
81
|
+
warning: { dot: "bg-warning", bg: "bg-warning-subtle", text: "text-warning" },
|
|
82
|
+
info: { dot: "bg-info", bg: "bg-info-subtle", text: "text-info" },
|
|
83
|
+
neutral: { dot: "bg-neutral", bg: "bg-neutral-subtle", text: "text-neutral" },
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const resolveTone = (color: string | undefined): StatusTone => {
|
|
87
|
+
if (color === undefined) return "neutral"
|
|
88
|
+
if (Object.hasOwn(toneStyles, color)) return color as StatusTone
|
|
89
|
+
return legacyColorTones[color] ?? "neutral"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface StatusBadgeProps {
|
|
93
|
+
status: string
|
|
94
|
+
label?: string
|
|
95
|
+
/** status → tone (`positive` | `negative` | `warning` | `info` | `neutral`) or legacy colour name. */
|
|
96
|
+
colorMap?: Partial<Record<string, string>>
|
|
97
|
+
className?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const StatusBadge = forwardRef<HTMLSpanElement, StatusBadgeProps>(function StatusBadge(
|
|
101
|
+
{ status, label, colorMap, className },
|
|
102
|
+
ref,
|
|
103
|
+
) {
|
|
104
|
+
const mergedColorMap = { ...defaultColorMap, ...colorMap }
|
|
105
|
+
const style = toneStyles[resolveTone(mergedColorMap[status])]
|
|
106
|
+
const displayLabel = label ?? statusLabel(status)
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Badge
|
|
110
|
+
ref={ref}
|
|
111
|
+
data-slot="status-badge"
|
|
112
|
+
variant="outline"
|
|
113
|
+
className={cn("gap-1.5 border-transparent", style.bg, style.text, className)}
|
|
114
|
+
aria-label={displayLabel}
|
|
115
|
+
>
|
|
116
|
+
<span className={cn("inline-block size-2 rounded-full", style.dot)} aria-hidden="true" />
|
|
117
|
+
{displayLabel}
|
|
118
|
+
</Badge>
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
StatusBadge.displayName = "StatusBadge"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copied by `vantage add ui switch`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/switch.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
8
|
+
|
|
9
|
+
function Switch({
|
|
10
|
+
className,
|
|
11
|
+
size = "default",
|
|
12
|
+
...props
|
|
13
|
+
}: SwitchPrimitive.Root.Props & {
|
|
14
|
+
size?: "sm" | "default"
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<SwitchPrimitive.Root
|
|
18
|
+
data-slot="switch"
|
|
19
|
+
data-size={size}
|
|
20
|
+
className={cn(
|
|
21
|
+
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
<SwitchPrimitive.Thumb
|
|
27
|
+
data-slot="switch-thumb"
|
|
28
|
+
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
|
29
|
+
/>
|
|
30
|
+
</SwitchPrimitive.Root>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { Switch }
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Copied by `vantage add ui table`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/table.tsx by `npm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
import * as React from "react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
8
|
+
|
|
9
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="table-container"
|
|
13
|
+
className="relative w-full overflow-x-auto"
|
|
14
|
+
>
|
|
15
|
+
<table
|
|
16
|
+
data-slot="table"
|
|
17
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
25
|
+
return (
|
|
26
|
+
<thead
|
|
27
|
+
data-slot="table-header"
|
|
28
|
+
className={cn("[&_tr]:border-b", className)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
35
|
+
return (
|
|
36
|
+
<tbody
|
|
37
|
+
data-slot="table-body"
|
|
38
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
45
|
+
return (
|
|
46
|
+
<tfoot
|
|
47
|
+
data-slot="table-footer"
|
|
48
|
+
className={cn(
|
|
49
|
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
58
|
+
return (
|
|
59
|
+
<tr
|
|
60
|
+
data-slot="table-row"
|
|
61
|
+
className={cn(
|
|
62
|
+
"border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
71
|
+
return (
|
|
72
|
+
<th
|
|
73
|
+
data-slot="table-head"
|
|
74
|
+
className={cn(
|
|
75
|
+
"h-10 px-2 text-left align-middle text-xs font-medium whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
84
|
+
return (
|
|
85
|
+
<td
|
|
86
|
+
data-slot="table-cell"
|
|
87
|
+
className={cn(
|
|
88
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
89
|
+
className
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function TableCaption({
|
|
97
|
+
className,
|
|
98
|
+
...props
|
|
99
|
+
}: React.ComponentProps<"caption">) {
|
|
100
|
+
return (
|
|
101
|
+
<caption
|
|
102
|
+
data-slot="table-caption"
|
|
103
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
Table,
|
|
111
|
+
TableHeader,
|
|
112
|
+
TableBody,
|
|
113
|
+
TableFooter,
|
|
114
|
+
TableHead,
|
|
115
|
+
TableRow,
|
|
116
|
+
TableCell,
|
|
117
|
+
TableCaption,
|
|
118
|
+
}
|