@squadbase/vantage 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +24 -4
  2. package/dist/{chunk-ZGDU5YLH.js → chunk-5L2MH2NG.js} +16 -7
  3. package/dist/chunk-5L2MH2NG.js.map +1 -0
  4. package/dist/chunk-7IPAXPPY.js +51 -0
  5. package/dist/chunk-7IPAXPPY.js.map +1 -0
  6. package/dist/chunk-A2UUGASH.js +12 -0
  7. package/dist/chunk-A2UUGASH.js.map +1 -0
  8. package/dist/chunk-DTDVSFRY.js +29 -0
  9. package/dist/chunk-DTDVSFRY.js.map +1 -0
  10. package/dist/chunk-WQZYXXQW.js +2249 -0
  11. package/dist/chunk-WQZYXXQW.js.map +1 -0
  12. package/dist/{chunk-73J5ZD4C.js → chunk-YLAB6UQS.js} +17 -3
  13. package/dist/chunk-YLAB6UQS.js.map +1 -0
  14. package/dist/cli.js +607 -36
  15. package/dist/cli.js.map +1 -1
  16. package/dist/client/index.d.ts +2 -1
  17. package/dist/client/index.js +3 -1
  18. package/dist/client/index.js.map +1 -1
  19. package/dist/components/index.d.ts +575 -0
  20. package/dist/components/index.js +2097 -0
  21. package/dist/components/index.js.map +1 -0
  22. package/dist/{define-page-B6y9TOfZ.d.ts → define-page-BfhrK99G.d.ts} +5 -0
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/markdown/index.d.ts +9 -0
  27. package/dist/markdown/index.js +26 -0
  28. package/dist/markdown/index.js.map +1 -0
  29. package/dist/query/index.d.ts +95 -2
  30. package/dist/query/index.js +102 -1
  31. package/dist/query/index.js.map +1 -1
  32. package/dist/router/index.d.ts +86 -1
  33. package/dist/router/index.js +71 -7
  34. package/dist/router/index.js.map +1 -1
  35. package/dist/server/node.js +6 -1
  36. package/dist/server/node.js.map +1 -1
  37. package/dist/ui/index.d.ts +316 -200
  38. package/dist/ui/index.js +566 -511
  39. package/dist/ui/index.js.map +1 -1
  40. package/dist/vite/index.js +2 -2
  41. package/docs/en/agent-skills.md +89 -0
  42. package/docs/en/api-and-server.md +84 -0
  43. package/docs/en/build-and-deploy.md +280 -0
  44. package/docs/en/cli-reference.md +311 -0
  45. package/docs/en/components.md +235 -0
  46. package/docs/en/data-fetching.md +142 -0
  47. package/docs/en/environment-variables.md +69 -0
  48. package/docs/en/getting-started.md +95 -0
  49. package/docs/en/index.md +65 -0
  50. package/docs/en/markdown/markdown-renderer.md +44 -0
  51. package/docs/en/pages-and-metadata.md +59 -0
  52. package/docs/en/parts/app-shell.md +54 -0
  53. package/docs/en/parts/dashboard-card.md +64 -0
  54. package/docs/en/parts/data-table.md +86 -0
  55. package/docs/en/parts/date-range-picker.md +41 -0
  56. package/docs/en/parts/echart.md +53 -0
  57. package/docs/en/parts/filter-bar.md +66 -0
  58. package/docs/en/parts/funnel-steps.md +33 -0
  59. package/docs/en/parts/metric-value.md +23 -0
  60. package/docs/en/parts/multi-select.md +27 -0
  61. package/docs/en/parts/page-shell.md +45 -0
  62. package/docs/en/parts/placeholder.md +38 -0
  63. package/docs/en/parts/searchable-select.md +47 -0
  64. package/docs/en/parts/section-header.md +20 -0
  65. package/docs/en/parts/segmented-control.md +25 -0
  66. package/docs/en/parts/sparkline.md +47 -0
  67. package/docs/en/parts/status-badge.md +44 -0
  68. package/docs/en/parts/trend-indicator.md +26 -0
  69. package/docs/en/routing.md +177 -0
  70. package/docs/en/ui/accordion.md +36 -0
  71. package/docs/en/ui/alert.md +31 -0
  72. package/docs/en/ui/badge.md +23 -0
  73. package/docs/en/ui/breadcrumb.md +38 -0
  74. package/docs/en/ui/button.md +39 -0
  75. package/docs/en/ui/calendar.md +28 -0
  76. package/docs/en/ui/card.md +35 -0
  77. package/docs/en/ui/checkbox.md +34 -0
  78. package/docs/en/ui/cn.md +34 -0
  79. package/docs/en/ui/collapsible.md +21 -0
  80. package/docs/en/ui/command.md +37 -0
  81. package/docs/en/ui/dialog.md +52 -0
  82. package/docs/en/ui/dropdown-menu.md +50 -0
  83. package/docs/en/ui/empty.md +22 -0
  84. package/docs/en/ui/error-state.md +50 -0
  85. package/docs/en/ui/input-group.md +30 -0
  86. package/docs/en/ui/input.md +28 -0
  87. package/docs/en/ui/label.md +19 -0
  88. package/docs/en/ui/loading.md +19 -0
  89. package/docs/en/ui/popover.md +34 -0
  90. package/docs/en/ui/progress.md +28 -0
  91. package/docs/en/ui/scroll-area.md +23 -0
  92. package/docs/en/ui/select.md +63 -0
  93. package/docs/en/ui/separator.md +15 -0
  94. package/docs/en/ui/sheet.md +28 -0
  95. package/docs/en/ui/sidebar.md +92 -0
  96. package/docs/en/ui/skeleton.md +16 -0
  97. package/docs/en/ui/slider.md +21 -0
  98. package/docs/en/ui/spinner.md +14 -0
  99. package/docs/en/ui/switch.md +19 -0
  100. package/docs/en/ui/table.md +24 -0
  101. package/docs/en/ui/tabs.md +23 -0
  102. package/docs/en/ui/textarea.md +15 -0
  103. package/docs/en/ui/toggle-group.md +26 -0
  104. package/docs/en/ui/toggle.md +19 -0
  105. package/docs/en/ui/tooltip.md +22 -0
  106. package/docs/en/ui/use-is-mobile.md +18 -0
  107. package/docs/en/ui-and-theming.md +95 -0
  108. package/docs/index.json +1346 -0
  109. package/docs/ja/agent-skills.md +87 -0
  110. package/docs/ja/api-and-server.md +84 -0
  111. package/docs/ja/build-and-deploy.md +279 -0
  112. package/docs/ja/cli-reference.md +305 -0
  113. package/docs/ja/components.md +231 -0
  114. package/docs/ja/data-fetching.md +142 -0
  115. package/docs/ja/environment-variables.md +68 -0
  116. package/docs/ja/getting-started.md +95 -0
  117. package/docs/ja/index.md +65 -0
  118. package/docs/ja/markdown/markdown-renderer.md +43 -0
  119. package/docs/ja/pages-and-metadata.md +59 -0
  120. package/docs/ja/parts/app-shell.md +54 -0
  121. package/docs/ja/parts/dashboard-card.md +64 -0
  122. package/docs/ja/parts/data-table.md +85 -0
  123. package/docs/ja/parts/date-range-picker.md +41 -0
  124. package/docs/ja/parts/echart.md +52 -0
  125. package/docs/ja/parts/filter-bar.md +64 -0
  126. package/docs/ja/parts/funnel-steps.md +33 -0
  127. package/docs/ja/parts/metric-value.md +25 -0
  128. package/docs/ja/parts/multi-select.md +27 -0
  129. package/docs/ja/parts/page-shell.md +45 -0
  130. package/docs/ja/parts/placeholder.md +37 -0
  131. package/docs/ja/parts/searchable-select.md +47 -0
  132. package/docs/ja/parts/section-header.md +20 -0
  133. package/docs/ja/parts/segmented-control.md +26 -0
  134. package/docs/ja/parts/sparkline.md +47 -0
  135. package/docs/ja/parts/status-badge.md +44 -0
  136. package/docs/ja/parts/trend-indicator.md +26 -0
  137. package/docs/ja/routing.md +175 -0
  138. package/docs/ja/ui/accordion.md +37 -0
  139. package/docs/ja/ui/alert.md +31 -0
  140. package/docs/ja/ui/badge.md +23 -0
  141. package/docs/ja/ui/breadcrumb.md +38 -0
  142. package/docs/ja/ui/button.md +39 -0
  143. package/docs/ja/ui/calendar.md +28 -0
  144. package/docs/ja/ui/card.md +35 -0
  145. package/docs/ja/ui/checkbox.md +34 -0
  146. package/docs/ja/ui/cn.md +34 -0
  147. package/docs/ja/ui/collapsible.md +21 -0
  148. package/docs/ja/ui/command.md +36 -0
  149. package/docs/ja/ui/dialog.md +51 -0
  150. package/docs/ja/ui/dropdown-menu.md +50 -0
  151. package/docs/ja/ui/empty.md +22 -0
  152. package/docs/ja/ui/error-state.md +50 -0
  153. package/docs/ja/ui/input-group.md +30 -0
  154. package/docs/ja/ui/input.md +28 -0
  155. package/docs/ja/ui/label.md +19 -0
  156. package/docs/ja/ui/loading.md +19 -0
  157. package/docs/ja/ui/popover.md +34 -0
  158. package/docs/ja/ui/progress.md +28 -0
  159. package/docs/ja/ui/scroll-area.md +24 -0
  160. package/docs/ja/ui/select.md +64 -0
  161. package/docs/ja/ui/separator.md +15 -0
  162. package/docs/ja/ui/sheet.md +28 -0
  163. package/docs/ja/ui/sidebar.md +92 -0
  164. package/docs/ja/ui/skeleton.md +16 -0
  165. package/docs/ja/ui/slider.md +21 -0
  166. package/docs/ja/ui/spinner.md +14 -0
  167. package/docs/ja/ui/switch.md +19 -0
  168. package/docs/ja/ui/table.md +24 -0
  169. package/docs/ja/ui/tabs.md +24 -0
  170. package/docs/ja/ui/textarea.md +15 -0
  171. package/docs/ja/ui/toggle-group.md +27 -0
  172. package/docs/ja/ui/toggle.md +19 -0
  173. package/docs/ja/ui/tooltip.md +22 -0
  174. package/docs/ja/ui/use-is-mobile.md +18 -0
  175. package/docs/ja/ui-and-theming.md +95 -0
  176. package/package.json +29 -4
  177. package/registry/blocks/sales-overview.tsx +43 -19
  178. package/registry/ui/data-table.tsx +702 -102
  179. package/skills/vantage-add-feature/SKILL.md +153 -0
  180. package/skills/vantage-app/SKILL.md +291 -0
  181. package/skills/vantage-pitfalls/SKILL.md +139 -0
  182. package/templates/AGENTS.md +313 -0
  183. package/theme.css +178 -40
  184. package/dist/chunk-73J5ZD4C.js.map +0 -1
  185. package/dist/chunk-ATYZ45XL.js +0 -19
  186. package/dist/chunk-ATYZ45XL.js.map +0 -1
  187. package/dist/chunk-ZGDU5YLH.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ # SegmentedControl
2
+
3
+ > Single-choice segments — it hides ToggleGroup's array.
4
+
5
+ A row of two to four choices. It is built on [`ToggleGroup`](ui/toggle-group), but
6
+ **because it's single choice `value` is one string** — the array API is absorbed here.
7
+
8
+ ```tsx
9
+ import { SegmentedControl } from "@squadbase/vantage/components";
10
+ import type { SegmentOption } from "@squadbase/vantage/components";
11
+ ```
12
+
13
+ | Prop | Type | Default | Description |
14
+ | --- | --- | --- | --- |
15
+ | `options` | `SegmentOption[]` | | An array of `{ label, value, icon?, disabled? }`. |
16
+ | `value` | `string` | | The selected value — **a single string**. |
17
+ | `onChange` | `(value: string) => void` | | Fired on change. |
18
+ | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Icon scale. |
19
+ | `fullWidth` | `boolean` | | Stretch to the parent's width. |
20
+ | `ariaLabel` | `string` | | Describes the group. |
21
+
22
+ Pass a `lucide-react` component straight to `icon`.
23
+
24
+ Beyond five options, [`Select`](ui/select) reads better; when the choices are whole
25
+ views, [`Tabs`](ui/tabs) fits.
@@ -0,0 +1,47 @@
1
+ # Sparkline
2
+
3
+ > An inline trend chart for table cells and KPI tiles.
4
+
5
+ Draws a sequence of numbers as a small line or bar chart — no axes, no legend. It is **a single
6
+ `<svg>`, not an `EChart`**, so dozens of them inside table cells or tiles stay cheap.
7
+
8
+ ```tsx
9
+ import { Sparkline } from "@squadbase/vantage/components";
10
+ import type { SparklineDataPoint, SparklineVariant } from "@squadbase/vantage/components";
11
+ ```
12
+
13
+ | Prop | Type | Default | Description |
14
+ | --- | --- | --- | --- |
15
+ | `data` | `SparklineDataPoint[]` | | An array of `{ value: number; label?: string }`. **Required.** An empty array draws nothing. |
16
+ | `variant` | `"line" \| "bar"` | `"line"` | Line or bars. |
17
+ | `height` | `number` | `40` | Height in px; the width fills the parent. |
18
+ | `color` | `string` | `"text-chart-1"` | A Tailwind **text-* class**; it drives stroke and fill through `currentColor`. |
19
+ | `area` | `boolean` | `false` | Fill under the line (`line` only). |
20
+ | `animate` | `boolean` | `false` | Draw in on mount. |
21
+ | `className` | `string` | | Classes to add. |
22
+
23
+ Any remaining props go straight to the `<svg>`.
24
+
25
+ > [!WARNING]
26
+ > `color` is a **class name**. Something like `color="#3b82f6"` does nothing — both the stroke and the
27
+ > fill read `currentColor`, so pass a utility such as `text-chart-2`. Change the theme tokens
28
+ > (`--chart-1` … `--chart-5`) and the colours follow.
29
+
30
+ ## Sizing
31
+
32
+ `preserveAspectRatio="none"` stretches the chart horizontally, so **the parent decides the width**.
33
+ Inside a table cell, constrain both.
34
+
35
+ ```tsx
36
+ <Sparkline data={points} height={16} className="w-20 shrink-0" />
37
+ ```
38
+
39
+ ## Sparkline or EChart
40
+
41
+ Reach for [`EChart`](parts/echart) when you need axes, a legend, tooltips or multiple
42
+ series. `Sparkline` conveys the *shape* of a series and nothing else — **no ticks, no numbers**.
43
+
44
+ > [!NOTE]
45
+ > It is `aria-hidden="true"`, so screen readers skip it. Put the actual number next to it as text —
46
+ > [`MetricValue`](parts/metric-value) or
47
+ > [`TrendIndicator`](parts/trend-indicator).
@@ -0,0 +1,44 @@
1
+ # StatusBadge
2
+
3
+ > A dot-and-label status badge, coloured by name.
4
+
5
+ Shows a row's or record's state as a coloured dot with a label. A built-in map turns the status
6
+ string into a colour, and `colorMap` extends or overrides it.
7
+
8
+ ```tsx
9
+ import { StatusBadge } from "@squadbase/vantage/components";
10
+ ```
11
+
12
+ | Prop | Type | Description |
13
+ | --- | --- | --- |
14
+ | `status` | `string` | The status string; it picks the colour. |
15
+ | `label` | `string` | The text to show; defaults to `status`. |
16
+ | `colorMap` | `Record<string, string>` | Status → colour name, merged over the built-in map. |
17
+ | `className` | `string` | Classes to add. |
18
+
19
+ ## The built-in map
20
+
21
+ | `status` | Colour |
22
+ | --- | --- |
23
+ | `active` | green |
24
+ | `inactive` | gray |
25
+ | `warning` | yellow |
26
+ | `error` | red |
27
+ | `pending` | blue |
28
+
29
+ Anything unknown falls back to gray. To add your own states, map them to one of
30
+ `green` / `gray` / `yellow` / `red` / `blue`.
31
+
32
+ ```tsx
33
+ <StatusBadge status="reviewing" label="Reviewing" colorMap={{ reviewing: "blue" }} />
34
+ ```
35
+
36
+ It drops straight into a table cell.
37
+
38
+ ```tsx
39
+ {
40
+ accessorKey: "status",
41
+ header: "Status",
42
+ cell: ({ row }) => <StatusBadge status={row.original.status} />,
43
+ }
44
+ ```
@@ -0,0 +1,26 @@
1
+ # TrendIndicator
2
+
3
+ > An arrow and a percentage; positiveIsGood decides the colour.
4
+
5
+ Shows a change as an arrow plus a percentage. **Up doesn't always mean good**, so the colour comes
6
+ from `direction` combined with `positiveIsGood` — pass `positiveIsGood={false}` for metrics like
7
+ churn, where down is the good direction.
8
+
9
+ ```tsx
10
+ import { TrendIndicator } from "@squadbase/vantage/components";
11
+ import type { TrendDirection } from "@squadbase/vantage/components";
12
+ ```
13
+
14
+ | Prop | Type | Default | Description |
15
+ | --- | --- | --- | --- |
16
+ | `value` | `number` | | The change (`12.4` means 12.4%). |
17
+ | `direction` | `"up" \| "down" \| "neutral"` | | Which way the arrow points. **Required.** |
18
+ | `positiveIsGood` | `boolean` | `true` | Whether up is good; `false` flips the colours. |
19
+ | `className` | `string` | | Classes to add. |
20
+
21
+ > [!WARNING]
22
+ > `direction` is required — it is never inferred from the sign of `value`, so that "it went down, and
23
+ > that's good" stays expressible.
24
+
25
+ There are three colours: `--chart-1` for the good direction, `--primary` for the bad one, and
26
+ `--muted-foreground` for `neutral`. Change the theme tokens and these follow.
@@ -0,0 +1,177 @@
1
+ # File-based routing
2
+
3
+ > Dropping a file creates a route. Conventions for pages, layouts, dynamic params, and 404/error.
4
+
5
+ In Vantage, **where you place a file is its URL**. You never configure a router.
6
+
7
+ ## Page conventions
8
+
9
+ | File | Route |
10
+ |---|---|
11
+ | `index.tsx` | `/` |
12
+ | `monthly-analysis.tsx` | `/monthly-analysis` |
13
+ | `sales/index.tsx` | `/sales` |
14
+ | `sales/[customerId].tsx` | `/sales/:customerId` |
15
+ | `sales/[...path].tsx` | `/sales/*` (catchall) |
16
+ | `_layout.tsx` | Layout for that directory (nestable) |
17
+ | `_404.tsx` | Not-found UI (**root only**) |
18
+ | `_error.tsx` | Error UI (**root only**) |
19
+
20
+ - `[name]` → dynamic param, `[...name]` → catchall.
21
+ - `index` maps to its own directory (the name drops from the URL).
22
+ - Files starting with `_` are reserved (`_layout`/`_404`/`_error`) or private (not routed).
23
+
24
+ ## Directories that are never routed
25
+
26
+ These directories are never scanned. Put whatever code you like here.
27
+
28
+ ```text
29
+ components/ Reusable React components
30
+ hooks/ Custom hooks
31
+ lib/ Utilities
32
+ server/ API (its presence enables the server)
33
+ public/ Static assets
34
+ ```
35
+
36
+ > [!NOTE]
37
+ > Imports between files use **runtime `.js` specifiers** (e.g. `./components/revenue-chart.js`) —
38
+ > required for ESM Node resolution.
39
+
40
+ ## The three spellings of a dynamic param
41
+
42
+ The same param appears in three spellings depending on where it is. **They must match.**
43
+
44
+ | Where | Spelling | Example |
45
+ |---|---|---|
46
+ | Filename | `[id]` | `sales/[customerId].tsx` |
47
+ | Display route | `:id` | `/sales/:customerId` |
48
+ | Link | `$id` + `params` | `to="/sales/$customerId"` `params={{ customerId }}` |
49
+
50
+ ```tsx
51
+ import { Link, useParams } from "@squadbase/vantage/router";
52
+
53
+ function Row({ customerId }: { customerId: string }) {
54
+ return (
55
+ <Link to="/sales/$customerId" params={{ customerId }}>
56
+ Open customer
57
+ </Link>
58
+ );
59
+ }
60
+
61
+ // sales/[customerId].tsx
62
+ export default function Customer() {
63
+ const { customerId } = useParams({ strict: false });
64
+ return <div>Customer {customerId}</div>;
65
+ }
66
+ ```
67
+
68
+ ## Layouts and nesting
69
+
70
+ `_layout.tsx` renders an `<Outlet />` that wraps child routes. Place one per directory to nest them.
71
+
72
+ ```tsx
73
+ // _layout.tsx
74
+ import { Outlet } from "@squadbase/vantage/router";
75
+
76
+ export default function RootLayout() {
77
+ return (
78
+ <div className="min-h-screen">
79
+ <nav className="border-b p-4">My Dashboard</nav>
80
+ <Outlet />
81
+ </div>
82
+ );
83
+ }
84
+ ```
85
+
86
+ `_404.tsx` and `_error.tsx` are recognized at the **root only**.
87
+
88
+ ## Build a nav from the route list
89
+
90
+ `useRoutes()` returns every page route in the app. The file system is the source of truth, so adding
91
+ one page file adds one nav entry — there is no link array to maintain.
92
+
93
+ ```tsx
94
+ // _layout.tsx
95
+ import { Link, Outlet, useCurrentRoute, useRoutes } from "@squadbase/vantage/router";
96
+
97
+ export default function RootLayout() {
98
+ // Dynamic routes (/sales/:customerId) have no single URL, so leave them out.
99
+ const routes = useRoutes().filter((route) => !route.dynamic);
100
+ const current = useCurrentRoute();
101
+
102
+ return (
103
+ <div>
104
+ <nav>
105
+ {routes.map((route) => (
106
+ <Link key={route.path} to={route.to} aria-current={route.path === current?.path}>
107
+ {route.label}
108
+ </Link>
109
+ ))}
110
+ </nav>
111
+ <Outlet />
112
+ </div>
113
+ );
114
+ }
115
+ ```
116
+
117
+ The order is Vantage's scan order — shallow before deep, static before dynamic, alphabetical — which
118
+ is the order a nav wants.
119
+
120
+ Each entry is a `RouteInfo`:
121
+
122
+ | Field | Type | Contents |
123
+ |---|---|---|
124
+ | `path` | `string` | Display path, `/sales/:customerId` |
125
+ | `to` | `string` | The form `Link`'s `to` expects, `/sales/$customerId` |
126
+ | `params` | `string[]` | Dynamic parameter names; a catch-all is `_splat` |
127
+ | `dynamic` | `boolean` | Whether the route has dynamic parameters |
128
+ | `index` | `boolean` | Whether it is a directory's index route |
129
+ | `label` | `string` | Display name: `navLabel`, else `title`, else `path` |
130
+ | `title` / `description` / `navLabel` | `string \| undefined` | Values from [`definePage`](pages-and-metadata) |
131
+
132
+ `useCurrentRoute()` returns the route being rendered, or `undefined` on the 404 route. Beyond the
133
+ "you are here" check above, it fits breadcrumbs and page headings.
134
+
135
+ > [!NOTE]
136
+ > `label` prefers `navLabel` because `title` drives `document.title` and therefore tends to carry the
137
+ > site name, which is usually too long for a nav.
138
+
139
+ ## Keep filter state in the URL
140
+
141
+ A dashboard's filters belong in the URL: the view survives a reload and can be pasted to a colleague.
142
+ `useSearchParam` reads and writes one search-param key with a `useState` shape.
143
+
144
+ ```tsx
145
+ import { useSearchParam } from "@squadbase/vantage/router";
146
+ import { SegmentedControl } from "@squadbase/vantage/components";
147
+
148
+ export default function Sales() {
149
+ const [region, setRegion] = useSearchParam("region", "all");
150
+
151
+ return <SegmentedControl options={REGIONS} value={region} onChange={setRegion} />;
152
+ }
153
+ ```
154
+
155
+ - The value is **always a string** (`?year=2024` parses as a number; this hook normalizes it back).
156
+ - Writing the **default value** (`"all"` above) or `null` removes the key from the URL.
157
+ - History defaults to `replace`, so filtering doesn't fill the back button. Pass `{ replace: false }`
158
+ to push instead.
159
+
160
+ For anything JSON-serializable — an array, an object — use `useSearchState`, which also accepts a
161
+ functional update.
162
+
163
+ ```tsx
164
+ import { useSearchState } from "@squadbase/vantage/router";
165
+
166
+ const [segments, setSegments] = useSearchState<string[]>("segments", []);
167
+ setSegments((prev) => [...prev, "enterprise"]);
168
+ ```
169
+
170
+ ## HMR and route regeneration
171
+
172
+ Adding or removing a `.tsx` page or a `server/api` file regenerates routes and triggers a full
173
+ reload. Editing a page body applies in place via React Fast Refresh.
174
+
175
+ ## What to read next
176
+
177
+ - [Pages & definePage](pages-and-metadata) — the page contract and metadata
@@ -0,0 +1,36 @@
1
+ # Accordion
2
+
3
+ > A stack of headings that open and close. One at a time by default.
4
+
5
+ A list whose headings toggle their content open and closed. One panel at a time by default; add
6
+ `openMultiple` to let several stay open at once.
7
+
8
+ ```tsx
9
+ import {
10
+ Accordion,
11
+ AccordionItem,
12
+ AccordionTrigger,
13
+ AccordionContent,
14
+ } from "@squadbase/vantage/ui";
15
+ ```
16
+
17
+ ## Parts
18
+
19
+ | Part | Role |
20
+ | --- | --- |
21
+ | `Accordion` | The root. `value` / `defaultValue` is an **array of open items** |
22
+ | `AccordionItem` | One item, identified by `value` |
23
+ | `AccordionTrigger` | The heading button; the chevron comes for free |
24
+ | `AccordionContent` | The panel that opens |
25
+
26
+ ## Several open at once
27
+
28
+ ```tsx
29
+ <Accordion openMultiple defaultValue={["a", "b"]}>
30
+ ```
31
+
32
+ > [!NOTE]
33
+ > `value` is an **array**, not the string Radix used — that's Base UI's API. Even for a single open
34
+ > panel you pass `defaultValue={["freshness"]}`.
35
+
36
+ For one standalone disclosure, [`Collapsible`](ui/collapsible) is lighter.
@@ -0,0 +1,31 @@
1
+ # Alert
2
+
3
+ > An in-page notice that flags something while the content is still shown.
4
+
5
+ An in-page notice. Where [`ErrorState`](ui/error-state) says "this view failed",
6
+ `Alert` flags something while the content is still shown.
7
+
8
+ ```tsx
9
+ import { Alert, AlertTitle, AlertDescription, AlertAction } from "@squadbase/vantage/ui";
10
+ ```
11
+
12
+ ## Parts and variants
13
+
14
+ | Part | Role |
15
+ | --- | --- |
16
+ | `Alert` | The frame. `variant`: `default` \| `destructive` |
17
+ | `AlertTitle` | The heading |
18
+ | `AlertDescription` | The body |
19
+ | `AlertAction` | An action pinned to the right |
20
+
21
+ Drop an icon as a direct child of `Alert` and the grid becomes two columns on its own. Importing
22
+ straight from `lucide-react` is fine.
23
+
24
+ ```tsx
25
+ import { Info } from "lucide-react";
26
+
27
+ <Alert>
28
+ <Info />
29
+ <AlertTitle>This view is read-only</AlertTitle>
30
+ </Alert>;
31
+ ```
@@ -0,0 +1,23 @@
1
+ # Badge
2
+
3
+ > A small label for counts, states and tags.
4
+
5
+ A small label to sit beside text. For a row's status with a coloured dot, reach for
6
+ [`StatusBadge`](parts/status-badge) instead.
7
+
8
+ ```tsx
9
+ import { Badge, badgeVariants } from "@squadbase/vantage/ui";
10
+ ```
11
+
12
+ ## Variants
13
+
14
+ `default` / `secondary` / `destructive` / `outline` / `ghost` / `link`.
15
+
16
+ `badgeVariants` returns the class string, for when you want the badge look on a different element
17
+ such as an `<a>`.
18
+
19
+ ```tsx
20
+ <a href="/docs" className={badgeVariants({ variant: "outline" })}>
21
+ Documentation
22
+ </a>
23
+ ```
@@ -0,0 +1,38 @@
1
+ # Breadcrumb
2
+
3
+ > A breadcrumb trail showing where the page sits.
4
+
5
+ Shows where the current page sits in the hierarchy. Most useful on drill-down detail pages such as
6
+ `/sales/$customerId`.
7
+
8
+ ```tsx
9
+ import {
10
+ Breadcrumb,
11
+ BreadcrumbList,
12
+ BreadcrumbItem,
13
+ BreadcrumbLink,
14
+ BreadcrumbPage,
15
+ BreadcrumbSeparator,
16
+ BreadcrumbEllipsis,
17
+ } from "@squadbase/vantage/ui";
18
+ ```
19
+
20
+ ## Parts
21
+
22
+ | Part | Role |
23
+ | --- | --- |
24
+ | `BreadcrumbLink` | An ancestor (a link) |
25
+ | `BreadcrumbPage` | Where you are now — not a link |
26
+ | `BreadcrumbSeparator` | The divider; pass children to swap the glyph |
27
+ | `BreadcrumbEllipsis` | Collapsed middle levels |
28
+
29
+ ## With the router's link
30
+
31
+ `BreadcrumbLink` takes a `render` prop to become another component. Pass Vantage's
32
+ [`Link`](routing) for client-side navigation.
33
+
34
+ ```tsx
35
+ import { Link } from "@squadbase/vantage/router";
36
+
37
+ <BreadcrumbLink render={<Link to="/sales" />}>Sales</BreadcrumbLink>;
38
+ ```
@@ -0,0 +1,39 @@
1
+ # Button
2
+
3
+ > The button, with six variants and eight sizes.
4
+
5
+ The component you'll reach for most. `variant` carries the meaning, `size` the scale.
6
+
7
+ ```tsx
8
+ import { Button, buttonVariants } from "@squadbase/vantage/ui";
9
+ ```
10
+
11
+ | Prop | Type | Default | Description |
12
+ | --- | --- | --- | --- |
13
+ | `variant` | `"default" \| "secondary" \| "outline" \| "ghost" \| "destructive" \| "link"` | `"default"` | Look and meaning. |
14
+ | `size` | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg"` | `"default"` | Height and padding. The `icon` sizes are square. |
15
+ | `render` | `ReactElement` | | Render as a different element (Radix's `asChild`). |
16
+
17
+ Drop an icon in as a child and its size and spacing are handled. For icon-only buttons always pass
18
+ `size="icon"` and an `aria-label`.
19
+
20
+ ## A link that looks like a button
21
+
22
+ Pass an element to `render` — Base UI has no `asChild`.
23
+
24
+ ```tsx
25
+ import { Link } from "@squadbase/vantage/router";
26
+
27
+ <Button render={<Link to="/sales" />}>View sales</Button>;
28
+ ```
29
+
30
+ When you only want the class string, call `buttonVariants({ variant, size })`.
31
+
32
+ ## Pending state
33
+
34
+ ```tsx
35
+ <Button disabled={isPending}>
36
+ {isPending && <Spinner />}
37
+ Save
38
+ </Button>
39
+ ```
@@ -0,0 +1,28 @@
1
+ # Calendar
2
+
3
+ > The date calendar, built on react-day-picker.
4
+
5
+ A calendar for single dates, multiple dates or a range. For picking a period on a dashboard,
6
+ [`DateRangePicker`](parts/date-range-picker) gets you there faster than using this
7
+ directly.
8
+
9
+ ```tsx
10
+ import { Calendar, CalendarDayButton } from "@squadbase/vantage/ui";
11
+ ```
12
+
13
+ The props are [react-day-picker](https://daypicker.dev)'s `DayPicker` props. Vantage adds only
14
+ three of its own:
15
+
16
+ | Prop | Type | Default | Description |
17
+ | --- | --- | --- | --- |
18
+ | `captionLayout` | `"label" \| "dropdown" \| "dropdown-months" \| "dropdown-years"` | `"label"` | Whether the month/year caption is text or a select. |
19
+ | `buttonVariant` | `Button variant` | `"ghost"` | Look of the previous/next month buttons. |
20
+ | `showOutsideDays` | `boolean` | `true` | Dim and show the neighbouring months' days. |
21
+
22
+ `mode` is `"single"` / `"multiple"` / `"range"`, and the types of `selected` and `onSelect` follow
23
+ it. `CalendarDayButton` is one day cell — swap it when you want a dot or label under the date.
24
+
25
+ > [!NOTE]
26
+ > Change the locale by passing a `date-fns` locale to the `locale` prop. `date-fns` is one of
27
+ > Vantage's dependencies, but avoid importing it directly from app code — keeping locale handling
28
+ > inside [`DateRangePicker`](parts/date-range-picker) is the safer path.
@@ -0,0 +1,35 @@
1
+ # Card
2
+
3
+ > The generic card. For dashboard tiles use DashboardCard.
4
+
5
+ The generic card. For dashboard tiles, [`DashboardCard`](parts/dashboard-card) —
6
+ which already has the spacing and a skeleton — fits better.
7
+
8
+ ```tsx
9
+ import {
10
+ Card,
11
+ CardHeader,
12
+ CardTitle,
13
+ CardDescription,
14
+ CardAction,
15
+ CardContent,
16
+ CardFooter,
17
+ } from "@squadbase/vantage/ui";
18
+ ```
19
+
20
+ ## Parts
21
+
22
+ | Part | Role |
23
+ | --- | --- |
24
+ | `Card` | The frame. `size`: `default` \| `sm` (changes inner spacing) |
25
+ | `CardHeader` | The heading row |
26
+ | `CardTitle` / `CardDescription` | Heading and supporting text |
27
+ | `CardAction` | The right end of the heading row; adding it makes the header two columns |
28
+ | `CardContent` | The body |
29
+ | `CardFooter` | The bottom; adding it tightens the card's bottom padding |
30
+
31
+ Spacing comes from a single `--card-spacing` CSS variable, so you can override it per card.
32
+
33
+ ```tsx
34
+ <Card className="[--card-spacing:--spacing(6)]">…</Card>
35
+ ```
@@ -0,0 +1,34 @@
1
+ # Checkbox
2
+
3
+ > The checkbox. The mixed state is a separate indeterminate prop.
4
+
5
+ An on/off input. In the Base UI build `checked` is strictly a boolean and the mixed state lives on
6
+ its own prop — the biggest difference if you're coming from Radix.
7
+
8
+ ```tsx
9
+ import { Checkbox } from "@squadbase/vantage/ui";
10
+ ```
11
+
12
+ | Prop | Type | Description |
13
+ | --- | --- | --- |
14
+ | `checked` | `boolean` | The checked state. `"indeterminate"` is not accepted. |
15
+ | `indeterminate` | `boolean` | The mixed state, independent of `checked`. |
16
+ | `onCheckedChange` | `(checked: boolean) => void` | Fired on change; the value is always a boolean. |
17
+ | `disabled` | `boolean` | Disable interaction. |
18
+
19
+ > [!WARNING]
20
+ > Radix's `checked={"indeterminate"}` is a type error here. Under Base UI, pass `indeterminate` as its
21
+ > own prop.
22
+
23
+ ## Select-all in a table
24
+
25
+ This is exactly what [`DataTable`](parts/data-table)'s selection column does.
26
+
27
+ ```tsx
28
+ <Checkbox
29
+ checked={table.getIsAllPageRowsSelected()}
30
+ indeterminate={table.getIsSomePageRowsSelected()}
31
+ onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked)}
32
+ aria-label="Select all"
33
+ />
34
+ ```
@@ -0,0 +1,34 @@
1
+ # cn()
2
+
3
+ > The helper that builds class names and resolves conflicts.
4
+
5
+ Builds a `className` conditionally: `clsx` resolves the conditions, `tailwind-merge` resolves
6
+ conflicting utilities.
7
+
8
+ ```tsx
9
+ import { cn } from "@squadbase/vantage/ui";
10
+ import type { ClassValue } from "@squadbase/vantage/ui";
11
+
12
+ <div className={cn("p-4", isActive && "bg-accent", className)} />;
13
+ ```
14
+
15
+ ## Last one wins
16
+
17
+ Because it runs through `tailwind-merge`, conflicting utilities resolve **in favour of the later
18
+ one**.
19
+
20
+ ```tsx
21
+ cn("p-2", "p-4"); // → "p-4"
22
+ ```
23
+
24
+ That is what lets a `className` you pass to a component override its defaults. Every built-in
25
+ component runs its classes through `cn()`, so `className` always behaves as an override.
26
+
27
+ ```tsx
28
+ // Reliably beats the default bg-primary
29
+ <Button className="bg-emerald-600 hover:bg-emerald-700">Run</Button>
30
+ ```
31
+
32
+ > [!NOTE]
33
+ > You never need to import `clsx` or `tailwind-merge` yourself — and shouldn't from app code. `cn()`
34
+ > is the public seam for both.
@@ -0,0 +1,21 @@
1
+ # Collapsible
2
+
3
+ > A single unstyled disclosure.
4
+
5
+ An unstyled primitive that opens and closes one region. For a stack of headings, use
6
+ [`Accordion`](ui/accordion).
7
+
8
+ ```tsx
9
+ import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@squadbase/vantage/ui";
10
+ ```
11
+
12
+ Control it with `open` / `defaultOpen` / `onOpenChange`. Both the trigger and the panel get
13
+ `data-open` while open, so the chevron can rotate in CSS alone.
14
+
15
+ ```tsx
16
+ <ChevronRight className="transition-transform data-open:rotate-90" />
17
+ ```
18
+
19
+ > [!NOTE]
20
+ > `data-open` is a custom variant declared in Vantage's `theme.css`. The Radix spelling
21
+ > `data-[state=open]:` is wired to the same variant, so either works.