@squadbase/vantage 0.6.1 → 0.6.2-dev1.31accb1
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-DY4AXANK.js +6 -0
- package/dist/add-DY4AXANK.js.map +1 -0
- package/dist/{build-O5TVNH4E.js → build-TVKFIVFQ.js} +8 -8
- package/dist/{build-O5TVNH4E.js.map → build-TVKFIVFQ.js.map} +1 -1
- package/dist/{check-WFBEDHDR.js → check-3KHZXZMQ.js} +6 -6
- package/dist/{check-WFBEDHDR.js.map → check-3KHZXZMQ.js.map} +1 -1
- package/dist/{chunk-PLST5Q7T.js → chunk-AG6RZ2RR.js} +1010 -23
- package/dist/chunk-AG6RZ2RR.js.map +1 -0
- package/dist/{chunk-KLEIVDUV.js → chunk-CEXAQQ5W.js} +150 -11
- package/dist/chunk-CEXAQQ5W.js.map +1 -0
- package/dist/{chunk-JGRMRXXZ.js → chunk-JKLLFVEU.js} +3 -3
- package/dist/{chunk-JGRMRXXZ.js.map → chunk-JKLLFVEU.js.map} +1 -1
- package/dist/{chunk-EP6JHXSR.js → chunk-LMUACBMA.js} +3 -3
- package/dist/{chunk-EP6JHXSR.js.map → chunk-LMUACBMA.js.map} +1 -1
- package/dist/{chunk-MXI3PZFT.js → chunk-OB6UBJXF.js} +156 -200
- package/dist/chunk-OB6UBJXF.js.map +1 -0
- package/dist/{chunk-BCE22JSM.js → chunk-P7W4LR3Z.js} +32 -13
- package/dist/chunk-P7W4LR3Z.js.map +1 -0
- package/dist/chunk-PXRX5PEC.js +54 -0
- package/dist/chunk-PXRX5PEC.js.map +1 -0
- package/dist/{chunk-3G43C6OL.js → chunk-QI3XQKHS.js} +39 -3
- package/dist/chunk-QI3XQKHS.js.map +1 -0
- package/dist/{chunk-QDZA5JSL.js → chunk-U2M6HGKQ.js} +3 -3
- package/dist/{chunk-QDZA5JSL.js.map → chunk-U2M6HGKQ.js.map} +1 -1
- package/dist/{chunk-T3FLAGI6.js → chunk-VA776YE5.js} +3 -3
- package/dist/{chunk-T3FLAGI6.js.map → chunk-VA776YE5.js.map} +1 -1
- package/dist/cli.js +14 -11
- package/dist/cli.js.map +1 -1
- package/dist/components/index.d.ts +223 -1
- package/dist/components/index.js +3 -2
- package/dist/{dev-NZRLOQCL.js → dev-GDKATCC7.js} +22 -11
- package/dist/dev-GDKATCC7.js.map +1 -0
- package/dist/doc/index.d.ts +4 -0
- package/dist/doc/index.js +107 -11
- package/dist/doc/index.js.map +1 -1
- package/dist/{docs-ZGDJUHJI.js → docs-EJJ3BECT.js} +3 -3
- package/dist/{docs-ZGDJUHJI.js.map → docs-EJJ3BECT.js.map} +1 -1
- package/dist/{doctor-VFKEMR54.js → doctor-WQXPXP6K.js} +7 -7
- package/dist/doctor-WQXPXP6K.js.map +1 -0
- package/dist/{preview-QDCIVVSR.js → preview-OGMCNQUO.js} +12 -4
- package/dist/preview-OGMCNQUO.js.map +1 -0
- package/dist/{routes-RHP5FVGD.js → routes-MWBWWOJ2.js} +5 -5
- package/dist/{routes-RHP5FVGD.js.map → routes-MWBWWOJ2.js.map} +1 -1
- package/dist/{search-RFBELGY7.js → search-6Y422SV6.js} +3 -3
- package/dist/{search-RFBELGY7.js.map → search-6Y422SV6.js.map} +1 -1
- package/dist/slide/index.js +61 -15
- package/dist/slide/index.js.map +1 -1
- package/dist/table/index.js +7 -5
- package/dist/table/index.js.map +1 -1
- package/dist/ui/index.d.ts +140 -10
- package/dist/ui/index.js +1406 -63
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-GF2P67M6.js → upgrade-HCANX4GQ.js} +6 -6
- package/dist/{upgrade-GF2P67M6.js.map → upgrade-HCANX4GQ.js.map} +1 -1
- package/dist/vite/index.js +3 -3
- package/docs/en/changelog.md +24 -0
- package/docs/en/cli-reference.md +30 -0
- package/docs/en/parts/page-shell.md +1 -1
- package/docs/en/routing.md +4 -2
- package/docs/index.json +291 -61
- package/docs/ja/changelog.md +50 -0
- package/docs/ja/cli-reference.md +29 -0
- package/docs/ja/components.md +33 -89
- 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/donut-chart.md +47 -0
- package/docs/ja/parts/echart.md +3 -0
- package/docs/ja/parts/line-chart.md +58 -0
- package/docs/ja/parts/page-shell.md +1 -1
- package/docs/ja/parts/progress-circle.md +34 -0
- package/docs/ja/parts/sparkline.md +1 -1
- package/docs/ja/parts/tracker.md +32 -0
- package/docs/ja/routing.md +8 -84
- 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 +18 -12
- package/registry/ui/accordion.tsx +74 -0
- package/registry/ui/alert-dialog.tsx +189 -0
- package/registry/ui/alert.tsx +78 -0
- package/registry/ui/app-shell.tsx +281 -0
- package/registry/ui/area-chart.tsx +1015 -0
- package/registry/ui/avatar.tsx +110 -0
- package/registry/ui/badge.tsx +54 -0
- package/registry/ui/bar-chart.tsx +1013 -0
- package/registry/ui/bar-list.tsx +302 -0
- package/registry/ui/breadcrumb.tsx +126 -0
- package/registry/ui/button.tsx +60 -0
- package/registry/ui/calendar.tsx +221 -0
- package/registry/ui/card.tsx +105 -0
- package/registry/ui/category-bar.tsx +281 -0
- package/registry/ui/checkbox.tsx +28 -0
- package/registry/ui/collapsible.tsx +21 -0
- package/registry/ui/combobox.tsx +310 -0
- package/registry/ui/command.tsx +196 -0
- package/registry/ui/dashboard-card.tsx +174 -0
- package/registry/ui/data-table.tsx +16 -91
- package/registry/ui/date-range-picker.tsx +209 -0
- package/registry/ui/dialog.tsx +159 -0
- package/registry/ui/donut-chart.tsx +1004 -0
- package/registry/ui/drawer.tsx +229 -0
- package/registry/ui/dropdown-menu.tsx +272 -0
- package/registry/ui/echart.tsx +486 -0
- package/registry/ui/field.tsx +240 -0
- package/registry/ui/filter-bar.tsx +365 -0
- package/registry/ui/funnel-steps.tsx +274 -0
- package/registry/ui/hover-card.tsx +52 -0
- package/registry/ui/input-group.tsx +158 -0
- package/registry/ui/input.tsx +22 -0
- package/registry/ui/kbd.tsx +28 -0
- package/registry/ui/label.tsx +20 -0
- package/registry/ui/line-chart.tsx +1007 -0
- package/registry/ui/metric-value.tsx +54 -0
- package/registry/ui/multi-select.tsx +226 -0
- package/registry/ui/page-shell.tsx +227 -0
- package/registry/ui/pagination.tsx +138 -0
- package/registry/ui/placeholder.tsx +30 -0
- package/registry/ui/popover.tsx +90 -0
- package/registry/ui/progress-circle.tsx +272 -0
- package/registry/ui/progress.tsx +83 -0
- package/registry/ui/radio-group.tsx +39 -0
- package/registry/ui/scroll-area.tsx +55 -0
- package/registry/ui/searchable-select.tsx +209 -0
- package/registry/ui/section-header.tsx +41 -0
- package/registry/ui/segmented-control.tsx +71 -0
- package/registry/ui/select.tsx +253 -0
- package/registry/ui/separator.tsx +25 -0
- package/registry/ui/sheet.tsx +137 -0
- package/registry/ui/sidebar.tsx +723 -0
- package/registry/ui/skeleton.tsx +15 -0
- package/registry/ui/slider.tsx +54 -0
- package/registry/ui/sparkline.tsx +155 -0
- package/registry/ui/spinner.tsx +12 -0
- package/registry/ui/states.tsx +61 -0
- package/registry/ui/status-badge.tsx +79 -0
- package/registry/ui/switch.tsx +32 -0
- package/registry/ui/table.tsx +116 -0
- package/registry/ui/tabs.tsx +82 -0
- package/registry/ui/textarea.tsx +20 -0
- package/registry/ui/toast.tsx +250 -0
- package/registry/ui/toggle-group.tsx +89 -0
- package/registry/ui/toggle.tsx +45 -0
- package/registry/ui/tooltip.tsx +66 -0
- package/registry/ui/tracker.tsx +236 -0
- package/registry/ui/trend-indicator.tsx +62 -0
- package/skills/vantage-add-feature/SKILL.md +9 -7
- package/skills/vantage-design/SKILL.md +3 -1
- package/skills/vantage-design/references/format.md +1 -1
- package/skills/vantage-pitfalls/SKILL.md +5 -1
- package/skills/vantage-slide/SKILL.md +17 -0
- package/templates/AGENTS.md +10 -1
- package/templates/agents/api.md +29 -0
- package/templates/agents/csv.md +2 -1
- package/templates/agents/doc.md +6 -3
- package/templates/agents/page.md +22 -8
- package/templates/agents/slide.md +13 -1
- package/templates/agents/xlsx.md +2 -1
- package/theme.css +51 -1
- package/dist/add-EISGSZAH.js +0 -6
- package/dist/add-EISGSZAH.js.map +0 -1
- package/dist/chunk-3G43C6OL.js.map +0 -1
- package/dist/chunk-BCE22JSM.js.map +0 -1
- package/dist/chunk-KLEIVDUV.js.map +0 -1
- package/dist/chunk-MXI3PZFT.js.map +0 -1
- package/dist/chunk-PLST5Q7T.js.map +0 -1
- package/dist/dev-NZRLOQCL.js.map +0 -1
- package/dist/doctor-VFKEMR54.js.map +0 -1
- package/dist/preview-QDCIVVSR.js.map +0 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Copied by `vantage add ui bar-list`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/vantage src/components/bar-list.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
|
|
3
|
+
/**
|
|
4
|
+
* BarList — a ranked list of horizontal bars with the value at the end of each
|
|
5
|
+
* row: top pages, sales by store, error counts by type. Plain HTML/CSS (no
|
|
6
|
+
* ECharts), so it stays cheap inside cards and follows the theme natively.
|
|
7
|
+
*/
|
|
8
|
+
import { forwardRef, useEffect, useState, type ComponentProps } from "react"
|
|
9
|
+
|
|
10
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
11
|
+
import type { ChartColor, ValueFormatter } from "@squadbase/vantage/components"
|
|
12
|
+
|
|
13
|
+
/* ---------- inlined from src/components/chart-utils.ts (internal to @squadbase/vantage) ---------- */
|
|
14
|
+
|
|
15
|
+
/** The series tokens, in palette order — the default `colors` of every typed chart. */
|
|
16
|
+
const DEFAULT_CHART_COLORS: readonly ChartColor[] = [
|
|
17
|
+
"chart-1",
|
|
18
|
+
"chart-2",
|
|
19
|
+
"chart-3",
|
|
20
|
+
"chart-4",
|
|
21
|
+
"chart-5",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const TOKEN_NAME_RE = /^chart-[1-5]$/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A chart colour as a CSS value: `"chart-2"` → `var(--chart-2)` (so it follows
|
|
28
|
+
* light/dark on its own), anything else passes through unchanged.
|
|
29
|
+
*/
|
|
30
|
+
function chartColorToCss(color: ChartColor): string {
|
|
31
|
+
const value = color.trim()
|
|
32
|
+
return TOKEN_NAME_RE.test(value) ? `var(--${value})` : value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The `i`-th colour, cycling through `colors` (the token palette when empty). */
|
|
36
|
+
function chartColorAt(colors: readonly ChartColor[] | undefined, i: number): ChartColor {
|
|
37
|
+
const palette = colors && colors.length > 0 ? colors : DEFAULT_CHART_COLORS
|
|
38
|
+
return palette[i % palette.length]!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `colors` extended (by cycling) to exactly `count` entries. */
|
|
42
|
+
function chartColorsFor(colors: readonly ChartColor[] | undefined, count: number): ChartColor[] {
|
|
43
|
+
return Array.from({ length: count }, (_, i) => chartColorAt(colors, i))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A translucent version of a CSS colour (works for `var(--…)` too). */
|
|
47
|
+
function tintCss(cssColor: string, percent: number): string {
|
|
48
|
+
return `color-mix(in oklab, ${cssColor} ${percent}%, transparent)`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const numberFormat = new Intl.NumberFormat("ja-JP", { maximumFractionDigits: 2 })
|
|
52
|
+
|
|
53
|
+
/** Default `valueFormatter`: grouped digits, up to two decimals (`1,234.5`). */
|
|
54
|
+
const defaultValueFormatter: ValueFormatter = (value) => numberFormat.format(value)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A data cell as a number, or `null` for a gap. Numeric strings count — SQL
|
|
58
|
+
* `DECIMAL` / `BIGINT` columns usually arrive as strings.
|
|
59
|
+
*/
|
|
60
|
+
function toChartNumber(value: unknown): number | null {
|
|
61
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null
|
|
62
|
+
if (typeof value === "bigint") return Number(value)
|
|
63
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
64
|
+
const n = Number(value)
|
|
65
|
+
return Number.isFinite(n) ? n : null
|
|
66
|
+
}
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Clamp `value` into `[min, max]`. */
|
|
71
|
+
function clamp(value: number, min: number, max: number): number {
|
|
72
|
+
return Math.min(max, Math.max(min, value))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ---------------------------------------------------------------- BarList */
|
|
76
|
+
|
|
77
|
+
type BarListSortOrder = "ascending" | "descending" | "none"
|
|
78
|
+
|
|
79
|
+
/** Sort a copy of the rows by value; `"none"` keeps the given order. */
|
|
80
|
+
function sortBarListData<T extends { value: number }>(data: readonly T[], sortOrder: BarListSortOrder): T[] {
|
|
81
|
+
const rows = [...data]
|
|
82
|
+
if (sortOrder === "ascending") rows.sort((a, b) => a.value - b.value)
|
|
83
|
+
else if (sortOrder === "descending") rows.sort((a, b) => b.value - a.value)
|
|
84
|
+
return rows
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Bar widths in percent of the largest value (at least a sliver for non-zero values). */
|
|
88
|
+
function barListWidths(values: readonly number[]): number[] {
|
|
89
|
+
const max = Math.max(0, ...values)
|
|
90
|
+
if (max === 0) return values.map(() => 0)
|
|
91
|
+
return values.map((v) => (v <= 0 ? 0 : Math.max(1, (v / max) * 100)))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ------------------------------------------------------------ CategoryBar */
|
|
95
|
+
|
|
96
|
+
interface CategoryBarSegment {
|
|
97
|
+
/** Width in percent of the whole bar. */
|
|
98
|
+
width: number
|
|
99
|
+
/** Where the segment starts, in percent. */
|
|
100
|
+
start: number
|
|
101
|
+
/** The running total at the segment's end — what its right-hand label shows. */
|
|
102
|
+
end: number
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Lay the values side by side; negative values count as zero. */
|
|
106
|
+
function categoryBarSegments(values: readonly number[]): CategoryBarSegment[] {
|
|
107
|
+
const clean = values.map((v) => (Number.isFinite(v) && v > 0 ? v : 0))
|
|
108
|
+
const total = clean.reduce((sum, v) => sum + v, 0)
|
|
109
|
+
let running = 0
|
|
110
|
+
return clean.map((v) => {
|
|
111
|
+
const start = total === 0 ? 0 : (running / total) * 100
|
|
112
|
+
running += v
|
|
113
|
+
return { width: total === 0 ? 0 : (v / total) * 100, start, end: running }
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface CategoryBarLabel {
|
|
118
|
+
/** Position in percent of the bar. */
|
|
119
|
+
position: number
|
|
120
|
+
/** The running total printed there. */
|
|
121
|
+
value: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Tick labels under the bar: `0` at the start, then the running total at each
|
|
126
|
+
* segment's end. A label closer than `minGap` percent to the one before is
|
|
127
|
+
* dropped so narrow segments don't print on top of each other — except the
|
|
128
|
+
* last one (the total), which replaces its neighbour instead.
|
|
129
|
+
*/
|
|
130
|
+
function categoryBarLabels(values: readonly number[], minGap = 8): CategoryBarLabel[] {
|
|
131
|
+
const segments = categoryBarSegments(values).filter((s) => s.width > 0)
|
|
132
|
+
if (segments.length === 0) return []
|
|
133
|
+
const labels: CategoryBarLabel[] = [{ position: 0, value: 0 }]
|
|
134
|
+
segments.forEach((s, i) => {
|
|
135
|
+
const label = { position: s.start + s.width, value: s.end }
|
|
136
|
+
const prev = labels[labels.length - 1]!
|
|
137
|
+
if (label.position - prev.position >= minGap) labels.push(label)
|
|
138
|
+
else if (i === segments.length - 1 && labels.length > 1) labels[labels.length - 1] = label
|
|
139
|
+
})
|
|
140
|
+
return labels
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Marker position in percent of the bar, clamped to its ends. */
|
|
144
|
+
function categoryBarMarkerPosition(value: number, values: readonly number[]): number {
|
|
145
|
+
const total = values.reduce((sum, v) => sum + (Number.isFinite(v) && v > 0 ? v : 0), 0)
|
|
146
|
+
if (total === 0) return 0
|
|
147
|
+
return clamp((value / total) * 100, 0, 100)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Index of the segment the marker sits on (the last one for a value past the end). */
|
|
151
|
+
function categoryBarMarkerSegment(value: number, values: readonly number[]): number {
|
|
152
|
+
const segments = categoryBarSegments(values)
|
|
153
|
+
const index = segments.findIndex((s) => s.width > 0 && value <= s.end)
|
|
154
|
+
if (index !== -1) return index
|
|
155
|
+
for (let i = segments.length - 1; i >= 0; i--) if (segments[i]!.width > 0) return i
|
|
156
|
+
return 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* --------------------------------------------------------- ProgressCircle */
|
|
160
|
+
|
|
161
|
+
interface ProgressCircleGeometry {
|
|
162
|
+
/** SVG width/height. */
|
|
163
|
+
size: number
|
|
164
|
+
/** Radius of the stroke's centre line. */
|
|
165
|
+
normalizedRadius: number
|
|
166
|
+
circumference: number
|
|
167
|
+
/** `stroke-dashoffset` for the value arc. */
|
|
168
|
+
offset: number
|
|
169
|
+
/** The value as a fraction of `max`, clamped to `[0, 1]`. */
|
|
170
|
+
ratio: number
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function progressCircleGeometry(
|
|
174
|
+
value: number,
|
|
175
|
+
max: number,
|
|
176
|
+
radius: number,
|
|
177
|
+
strokeWidth: number,
|
|
178
|
+
): ProgressCircleGeometry {
|
|
179
|
+
const ratio = max > 0 && Number.isFinite(value) ? clamp(value / max, 0, 1) : 0
|
|
180
|
+
const normalizedRadius = Math.max(0, radius - strokeWidth / 2)
|
|
181
|
+
const circumference = 2 * Math.PI * normalizedRadius
|
|
182
|
+
return { size: radius * 2, normalizedRadius, circumference, offset: circumference * (1 - ratio), ratio }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ---------- end of inlined code ---------- */
|
|
186
|
+
|
|
187
|
+
export interface BarListItem {
|
|
188
|
+
/** React の key。省略時は `name`。 */
|
|
189
|
+
key?: string
|
|
190
|
+
/** 行のラベル(バーの上に出る)。 */
|
|
191
|
+
name: string
|
|
192
|
+
value: number
|
|
193
|
+
/** 渡すとラベルがリンクになる。 */
|
|
194
|
+
href?: string
|
|
195
|
+
/** この行だけ色を変える。省略時は `color`。 */
|
|
196
|
+
color?: ChartColor
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface BarListProps extends Omit<ComponentProps<"div">, "children"> {
|
|
200
|
+
data: BarListItem[]
|
|
201
|
+
/** 右端の数値の書式。 */
|
|
202
|
+
valueFormatter?: ValueFormatter
|
|
203
|
+
/** 並べ順。既定 `"descending"`(大きい順)。`"none"` は渡した順。 */
|
|
204
|
+
sortOrder?: BarListSortOrder
|
|
205
|
+
/** バーの色。`"chart-1"`〜`"chart-5"` か CSS カラー。既定 `"chart-1"`(淡く塗る)。 */
|
|
206
|
+
color?: ChartColor
|
|
207
|
+
/** マウント時にバーを伸ばす。 */
|
|
208
|
+
showAnimation?: boolean
|
|
209
|
+
/** 渡すと行がボタンになり、クリックした行を受け取る。 */
|
|
210
|
+
onValueChange?: (item: BarListItem) => void
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export const BarList = forwardRef<HTMLDivElement, BarListProps>(function BarList(
|
|
214
|
+
{
|
|
215
|
+
data,
|
|
216
|
+
valueFormatter = defaultValueFormatter,
|
|
217
|
+
sortOrder = "descending",
|
|
218
|
+
color = "chart-1",
|
|
219
|
+
showAnimation = false,
|
|
220
|
+
onValueChange,
|
|
221
|
+
className,
|
|
222
|
+
...props
|
|
223
|
+
},
|
|
224
|
+
ref,
|
|
225
|
+
) {
|
|
226
|
+
const [mounted, setMounted] = useState(!showAnimation)
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (!showAnimation) return
|
|
229
|
+
const frame = requestAnimationFrame(() => setMounted(true))
|
|
230
|
+
return () => cancelAnimationFrame(frame)
|
|
231
|
+
}, [showAnimation])
|
|
232
|
+
|
|
233
|
+
const rows = sortBarListData(data, sortOrder)
|
|
234
|
+
const widths = barListWidths(rows.map((row) => row.value))
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div ref={ref} data-slot="bar-list" className={cn("flex justify-between gap-6 text-sm", className)} {...props}>
|
|
238
|
+
<div className="relative min-w-0 flex-1 space-y-1.5">
|
|
239
|
+
{rows.map((row, i) => {
|
|
240
|
+
const css = chartColorToCss(row.color ?? color)
|
|
241
|
+
const label = row.href ? (
|
|
242
|
+
<a
|
|
243
|
+
href={row.href}
|
|
244
|
+
target={/^https?:\/\//.test(row.href) ? "_blank" : undefined}
|
|
245
|
+
rel={/^https?:\/\//.test(row.href) ? "noreferrer" : undefined}
|
|
246
|
+
onClick={(event) => event.stopPropagation()}
|
|
247
|
+
className="truncate hover:underline"
|
|
248
|
+
>
|
|
249
|
+
{row.name}
|
|
250
|
+
</a>
|
|
251
|
+
) : (
|
|
252
|
+
<span className="truncate">{row.name}</span>
|
|
253
|
+
)
|
|
254
|
+
const content = (
|
|
255
|
+
<>
|
|
256
|
+
<span
|
|
257
|
+
aria-hidden="true"
|
|
258
|
+
data-slot="bar-list-bar"
|
|
259
|
+
className={cn(
|
|
260
|
+
"absolute inset-y-0 left-0 rounded-sm",
|
|
261
|
+
showAnimation && "transition-[width] duration-700 ease-out",
|
|
262
|
+
)}
|
|
263
|
+
style={{ width: `${mounted ? widths[i] : 0}%`, background: tintCss(css, 28) }}
|
|
264
|
+
/>
|
|
265
|
+
<span className="relative flex min-w-0 items-center px-2 text-foreground">{label}</span>
|
|
266
|
+
</>
|
|
267
|
+
)
|
|
268
|
+
const rowClass = "relative flex h-8 w-full items-center rounded-sm"
|
|
269
|
+
return onValueChange ? (
|
|
270
|
+
<button
|
|
271
|
+
key={row.key ?? row.name}
|
|
272
|
+
type="button"
|
|
273
|
+
data-slot="bar-list-row"
|
|
274
|
+
onClick={() => onValueChange(row)}
|
|
275
|
+
className={cn(
|
|
276
|
+
rowClass,
|
|
277
|
+
"text-left outline-none hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring",
|
|
278
|
+
)}
|
|
279
|
+
>
|
|
280
|
+
{content}
|
|
281
|
+
</button>
|
|
282
|
+
) : (
|
|
283
|
+
<div key={row.key ?? row.name} data-slot="bar-list-row" className={rowClass}>
|
|
284
|
+
{content}
|
|
285
|
+
</div>
|
|
286
|
+
)
|
|
287
|
+
})}
|
|
288
|
+
</div>
|
|
289
|
+
<div className="shrink-0 space-y-1.5 text-right">
|
|
290
|
+
{rows.map((row) => (
|
|
291
|
+
<div
|
|
292
|
+
key={row.key ?? row.name}
|
|
293
|
+
className="flex h-8 items-center justify-end font-medium text-foreground tabular-nums"
|
|
294
|
+
>
|
|
295
|
+
{valueFormatter(row.value)}
|
|
296
|
+
</div>
|
|
297
|
+
))}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
})
|
|
302
|
+
BarList.displayName = "BarList"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Copied by `vantage add ui breadcrumb`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/vantage src/ui/breadcrumb.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
5
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
8
|
+
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
|
|
9
|
+
|
|
10
|
+
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
|
|
11
|
+
return (
|
|
12
|
+
<nav
|
|
13
|
+
aria-label="breadcrumb"
|
|
14
|
+
data-slot="breadcrumb"
|
|
15
|
+
className={cn(className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
|
22
|
+
return (
|
|
23
|
+
<ol
|
|
24
|
+
data-slot="breadcrumb-list"
|
|
25
|
+
className={cn(
|
|
26
|
+
"flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground",
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
35
|
+
return (
|
|
36
|
+
<li
|
|
37
|
+
data-slot="breadcrumb-item"
|
|
38
|
+
className={cn("inline-flex items-center gap-1", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function BreadcrumbLink({
|
|
45
|
+
className,
|
|
46
|
+
render,
|
|
47
|
+
...props
|
|
48
|
+
}: useRender.ComponentProps<"a">) {
|
|
49
|
+
return useRender({
|
|
50
|
+
defaultTagName: "a",
|
|
51
|
+
props: mergeProps<"a">(
|
|
52
|
+
{
|
|
53
|
+
className: cn("transition-colors hover:text-foreground", className),
|
|
54
|
+
},
|
|
55
|
+
props
|
|
56
|
+
),
|
|
57
|
+
render,
|
|
58
|
+
state: {
|
|
59
|
+
slot: "breadcrumb-link",
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
|
65
|
+
return (
|
|
66
|
+
<span
|
|
67
|
+
data-slot="breadcrumb-page"
|
|
68
|
+
role="link"
|
|
69
|
+
aria-disabled="true"
|
|
70
|
+
aria-current="page"
|
|
71
|
+
className={cn("font-normal text-foreground", className)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function BreadcrumbSeparator({
|
|
78
|
+
children,
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}: React.ComponentProps<"li">) {
|
|
82
|
+
return (
|
|
83
|
+
<li
|
|
84
|
+
data-slot="breadcrumb-separator"
|
|
85
|
+
role="presentation"
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
className={cn("[&>svg]:size-3.5", className)}
|
|
88
|
+
{...props}
|
|
89
|
+
>
|
|
90
|
+
{children ?? (
|
|
91
|
+
<ChevronRightIcon className="cn-rtl-flip" />
|
|
92
|
+
)}
|
|
93
|
+
</li>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function BreadcrumbEllipsis({
|
|
98
|
+
className,
|
|
99
|
+
...props
|
|
100
|
+
}: React.ComponentProps<"span">) {
|
|
101
|
+
return (
|
|
102
|
+
<span
|
|
103
|
+
data-slot="breadcrumb-ellipsis"
|
|
104
|
+
role="presentation"
|
|
105
|
+
aria-hidden="true"
|
|
106
|
+
className={cn(
|
|
107
|
+
"flex size-5 items-center justify-center [&>svg]:size-4",
|
|
108
|
+
className
|
|
109
|
+
)}
|
|
110
|
+
{...props}
|
|
111
|
+
>
|
|
112
|
+
<MoreHorizontalIcon />
|
|
113
|
+
<span className="sr-only">More</span>
|
|
114
|
+
</span>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
Breadcrumb,
|
|
120
|
+
BreadcrumbList,
|
|
121
|
+
BreadcrumbItem,
|
|
122
|
+
BreadcrumbLink,
|
|
123
|
+
BreadcrumbPage,
|
|
124
|
+
BreadcrumbSeparator,
|
|
125
|
+
BreadcrumbEllipsis,
|
|
126
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copied by `vantage add ui button`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/vantage src/ui/button.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
|
|
3
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
7
|
+
|
|
8
|
+
const buttonVariants = cva(
|
|
9
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
14
|
+
outline:
|
|
15
|
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
18
|
+
ghost:
|
|
19
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
20
|
+
destructive:
|
|
21
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default:
|
|
26
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
27
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
28
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
29
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
30
|
+
icon: "size-8",
|
|
31
|
+
"icon-xs":
|
|
32
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
33
|
+
"icon-sm":
|
|
34
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
35
|
+
"icon-lg": "size-9",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "default",
|
|
40
|
+
size: "default",
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
function Button({
|
|
46
|
+
className,
|
|
47
|
+
variant = "default",
|
|
48
|
+
size = "default",
|
|
49
|
+
...props
|
|
50
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
51
|
+
return (
|
|
52
|
+
<ButtonPrimitive
|
|
53
|
+
data-slot="button"
|
|
54
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Button, buttonVariants }
|