@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.
- package/README.md +24 -4
- package/dist/{chunk-ZGDU5YLH.js → chunk-5L2MH2NG.js} +16 -7
- package/dist/chunk-5L2MH2NG.js.map +1 -0
- package/dist/chunk-7IPAXPPY.js +51 -0
- package/dist/chunk-7IPAXPPY.js.map +1 -0
- package/dist/chunk-A2UUGASH.js +12 -0
- package/dist/chunk-A2UUGASH.js.map +1 -0
- package/dist/chunk-DTDVSFRY.js +29 -0
- package/dist/chunk-DTDVSFRY.js.map +1 -0
- package/dist/chunk-WQZYXXQW.js +2249 -0
- package/dist/chunk-WQZYXXQW.js.map +1 -0
- package/dist/{chunk-73J5ZD4C.js → chunk-YLAB6UQS.js} +17 -3
- package/dist/chunk-YLAB6UQS.js.map +1 -0
- package/dist/cli.js +607 -36
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/components/index.d.ts +575 -0
- package/dist/components/index.js +2097 -0
- package/dist/components/index.js.map +1 -0
- package/dist/{define-page-B6y9TOfZ.d.ts → define-page-BfhrK99G.d.ts} +5 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/markdown/index.d.ts +9 -0
- package/dist/markdown/index.js +26 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/query/index.d.ts +95 -2
- package/dist/query/index.js +102 -1
- package/dist/query/index.js.map +1 -1
- package/dist/router/index.d.ts +86 -1
- package/dist/router/index.js +71 -7
- package/dist/router/index.js.map +1 -1
- package/dist/server/node.js +6 -1
- package/dist/server/node.js.map +1 -1
- package/dist/ui/index.d.ts +316 -200
- package/dist/ui/index.js +566 -511
- package/dist/ui/index.js.map +1 -1
- package/dist/vite/index.js +2 -2
- package/docs/en/agent-skills.md +89 -0
- package/docs/en/api-and-server.md +84 -0
- package/docs/en/build-and-deploy.md +280 -0
- package/docs/en/cli-reference.md +311 -0
- package/docs/en/components.md +235 -0
- package/docs/en/data-fetching.md +142 -0
- package/docs/en/environment-variables.md +69 -0
- package/docs/en/getting-started.md +95 -0
- package/docs/en/index.md +65 -0
- package/docs/en/markdown/markdown-renderer.md +44 -0
- package/docs/en/pages-and-metadata.md +59 -0
- package/docs/en/parts/app-shell.md +54 -0
- package/docs/en/parts/dashboard-card.md +64 -0
- package/docs/en/parts/data-table.md +86 -0
- package/docs/en/parts/date-range-picker.md +41 -0
- package/docs/en/parts/echart.md +53 -0
- package/docs/en/parts/filter-bar.md +66 -0
- package/docs/en/parts/funnel-steps.md +33 -0
- package/docs/en/parts/metric-value.md +23 -0
- package/docs/en/parts/multi-select.md +27 -0
- package/docs/en/parts/page-shell.md +45 -0
- package/docs/en/parts/placeholder.md +38 -0
- package/docs/en/parts/searchable-select.md +47 -0
- package/docs/en/parts/section-header.md +20 -0
- package/docs/en/parts/segmented-control.md +25 -0
- package/docs/en/parts/sparkline.md +47 -0
- package/docs/en/parts/status-badge.md +44 -0
- package/docs/en/parts/trend-indicator.md +26 -0
- package/docs/en/routing.md +177 -0
- package/docs/en/ui/accordion.md +36 -0
- package/docs/en/ui/alert.md +31 -0
- package/docs/en/ui/badge.md +23 -0
- package/docs/en/ui/breadcrumb.md +38 -0
- package/docs/en/ui/button.md +39 -0
- package/docs/en/ui/calendar.md +28 -0
- package/docs/en/ui/card.md +35 -0
- package/docs/en/ui/checkbox.md +34 -0
- package/docs/en/ui/cn.md +34 -0
- package/docs/en/ui/collapsible.md +21 -0
- package/docs/en/ui/command.md +37 -0
- package/docs/en/ui/dialog.md +52 -0
- package/docs/en/ui/dropdown-menu.md +50 -0
- package/docs/en/ui/empty.md +22 -0
- package/docs/en/ui/error-state.md +50 -0
- package/docs/en/ui/input-group.md +30 -0
- package/docs/en/ui/input.md +28 -0
- package/docs/en/ui/label.md +19 -0
- package/docs/en/ui/loading.md +19 -0
- package/docs/en/ui/popover.md +34 -0
- package/docs/en/ui/progress.md +28 -0
- package/docs/en/ui/scroll-area.md +23 -0
- package/docs/en/ui/select.md +63 -0
- package/docs/en/ui/separator.md +15 -0
- package/docs/en/ui/sheet.md +28 -0
- package/docs/en/ui/sidebar.md +92 -0
- package/docs/en/ui/skeleton.md +16 -0
- package/docs/en/ui/slider.md +21 -0
- package/docs/en/ui/spinner.md +14 -0
- package/docs/en/ui/switch.md +19 -0
- package/docs/en/ui/table.md +24 -0
- package/docs/en/ui/tabs.md +23 -0
- package/docs/en/ui/textarea.md +15 -0
- package/docs/en/ui/toggle-group.md +26 -0
- package/docs/en/ui/toggle.md +19 -0
- package/docs/en/ui/tooltip.md +22 -0
- package/docs/en/ui/use-is-mobile.md +18 -0
- package/docs/en/ui-and-theming.md +95 -0
- package/docs/index.json +1346 -0
- package/docs/ja/agent-skills.md +87 -0
- package/docs/ja/api-and-server.md +84 -0
- package/docs/ja/build-and-deploy.md +279 -0
- package/docs/ja/cli-reference.md +305 -0
- package/docs/ja/components.md +231 -0
- package/docs/ja/data-fetching.md +142 -0
- package/docs/ja/environment-variables.md +68 -0
- package/docs/ja/getting-started.md +95 -0
- package/docs/ja/index.md +65 -0
- package/docs/ja/markdown/markdown-renderer.md +43 -0
- package/docs/ja/pages-and-metadata.md +59 -0
- package/docs/ja/parts/app-shell.md +54 -0
- package/docs/ja/parts/dashboard-card.md +64 -0
- package/docs/ja/parts/data-table.md +85 -0
- package/docs/ja/parts/date-range-picker.md +41 -0
- package/docs/ja/parts/echart.md +52 -0
- package/docs/ja/parts/filter-bar.md +64 -0
- package/docs/ja/parts/funnel-steps.md +33 -0
- package/docs/ja/parts/metric-value.md +25 -0
- package/docs/ja/parts/multi-select.md +27 -0
- package/docs/ja/parts/page-shell.md +45 -0
- package/docs/ja/parts/placeholder.md +37 -0
- package/docs/ja/parts/searchable-select.md +47 -0
- package/docs/ja/parts/section-header.md +20 -0
- package/docs/ja/parts/segmented-control.md +26 -0
- package/docs/ja/parts/sparkline.md +47 -0
- package/docs/ja/parts/status-badge.md +44 -0
- package/docs/ja/parts/trend-indicator.md +26 -0
- package/docs/ja/routing.md +175 -0
- package/docs/ja/ui/accordion.md +37 -0
- package/docs/ja/ui/alert.md +31 -0
- package/docs/ja/ui/badge.md +23 -0
- package/docs/ja/ui/breadcrumb.md +38 -0
- package/docs/ja/ui/button.md +39 -0
- package/docs/ja/ui/calendar.md +28 -0
- package/docs/ja/ui/card.md +35 -0
- package/docs/ja/ui/checkbox.md +34 -0
- package/docs/ja/ui/cn.md +34 -0
- package/docs/ja/ui/collapsible.md +21 -0
- package/docs/ja/ui/command.md +36 -0
- package/docs/ja/ui/dialog.md +51 -0
- package/docs/ja/ui/dropdown-menu.md +50 -0
- package/docs/ja/ui/empty.md +22 -0
- package/docs/ja/ui/error-state.md +50 -0
- package/docs/ja/ui/input-group.md +30 -0
- package/docs/ja/ui/input.md +28 -0
- package/docs/ja/ui/label.md +19 -0
- package/docs/ja/ui/loading.md +19 -0
- package/docs/ja/ui/popover.md +34 -0
- package/docs/ja/ui/progress.md +28 -0
- package/docs/ja/ui/scroll-area.md +24 -0
- package/docs/ja/ui/select.md +64 -0
- package/docs/ja/ui/separator.md +15 -0
- package/docs/ja/ui/sheet.md +28 -0
- package/docs/ja/ui/sidebar.md +92 -0
- package/docs/ja/ui/skeleton.md +16 -0
- package/docs/ja/ui/slider.md +21 -0
- package/docs/ja/ui/spinner.md +14 -0
- package/docs/ja/ui/switch.md +19 -0
- package/docs/ja/ui/table.md +24 -0
- package/docs/ja/ui/tabs.md +24 -0
- package/docs/ja/ui/textarea.md +15 -0
- package/docs/ja/ui/toggle-group.md +27 -0
- package/docs/ja/ui/toggle.md +19 -0
- package/docs/ja/ui/tooltip.md +22 -0
- package/docs/ja/ui/use-is-mobile.md +18 -0
- package/docs/ja/ui-and-theming.md +95 -0
- package/package.json +29 -4
- package/registry/blocks/sales-overview.tsx +43 -19
- package/registry/ui/data-table.tsx +702 -102
- package/skills/vantage-add-feature/SKILL.md +153 -0
- package/skills/vantage-app/SKILL.md +291 -0
- package/skills/vantage-pitfalls/SKILL.md +139 -0
- package/templates/AGENTS.md +313 -0
- package/theme.css +178 -40
- package/dist/chunk-73J5ZD4C.js.map +0 -1
- package/dist/chunk-ATYZ45XL.js +0 -19
- package/dist/chunk-ATYZ45XL.js.map +0 -1
- 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
|
+
```
|
package/docs/en/ui/cn.md
ADDED
|
@@ -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.
|