@tailor-platform/app-shell 1.14.0 → 1.16.0
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/LICENSE.md +21 -0
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +1203 -316
- package/dist/app-shell.js +4562 -3307
- package/dist/{appshell-CtS8Y3LW.js → appshell-B0wh5Lq0.js} +850 -549
- package/dist/style-entry.d.ts +118 -98
- package/dist/testing.d.ts +118 -98
- package/dist/testing.js +1 -1
- package/dist/themes/_template.css +1 -1
- package/dist/vite-plugin.d.ts +118 -98
- package/package.json +13 -13
- package/skills/app-shell-patterns/SKILL.md +108 -24
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
- package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
- package/skills/app-shell-patterns/references/api/define-module.md +247 -0
- package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
- package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
- package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
- package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
- package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
- package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
- package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
- package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +126 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +146 -0
- package/skills/app-shell-patterns/references/api/use-app-shell.md +143 -0
- package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
- package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
- package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
- package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
- package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
- package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
- package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
- package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
- package/skills/app-shell-patterns/references/api/use-toast.md +107 -0
- package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
- package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
- package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
- package/skills/app-shell-patterns/references/components/alert.md +202 -0
- package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
- package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
- package/skills/app-shell-patterns/references/components/attachment.md +138 -0
- package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
- package/skills/app-shell-patterns/references/components/avatar.md +80 -0
- package/skills/app-shell-patterns/references/components/badge.md +309 -0
- package/skills/app-shell-patterns/references/components/button.md +164 -0
- package/skills/app-shell-patterns/references/components/card.md +109 -0
- package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
- package/skills/app-shell-patterns/references/components/combobox.md +318 -0
- package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
- package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
- package/skills/app-shell-patterns/references/components/data-table.md +953 -0
- package/skills/app-shell-patterns/references/components/date-picker.md +248 -0
- package/skills/app-shell-patterns/references/components/default-header.md +75 -0
- package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
- package/skills/app-shell-patterns/references/components/description-card.md +569 -0
- package/skills/app-shell-patterns/references/components/dialog.md +164 -0
- package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
- package/skills/app-shell-patterns/references/components/form.md +378 -0
- package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
- package/skills/app-shell-patterns/references/components/grid.md +184 -0
- package/skills/app-shell-patterns/references/components/input.md +83 -0
- package/skills/app-shell-patterns/references/components/layout.md +539 -0
- package/skills/app-shell-patterns/references/components/menu.md +218 -0
- package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
- package/skills/app-shell-patterns/references/components/select.md +283 -0
- package/skills/app-shell-patterns/references/components/sheet.md +237 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +466 -0
- package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
- package/skills/app-shell-patterns/references/components/spinner.md +81 -0
- package/skills/app-shell-patterns/references/components/table.md +188 -0
- package/skills/app-shell-patterns/references/components/tabs.md +158 -0
- package/skills/app-shell-patterns/references/components/textarea.md +122 -0
- package/skills/app-shell-patterns/references/components/timeline.md +317 -0
- package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
- package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
- package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +350 -0
- package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +11 -4
- package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +387 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +388 -0
- package/skills/app-shell-patterns/references/{fundamental/design-system.md → concepts/styling-theming.md} +260 -105
- package/skills/app-shell-patterns/references/migrations.md +34 -0
- package/skills/app-shell-patterns/references/pages/document-detail.md +1125 -0
- package/skills/app-shell-patterns/references/patterns/form-composer.md +44 -46
- package/skills/app-shell-patterns/references/patterns/form-modal.md +39 -100
- package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
- package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
- package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
- package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +14 -28
- package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +11 -86
- package/skills/app-shell-patterns/references/fundamental/components.md +0 -981
- package/skills/app-shell-patterns/references/patterns/detail-hero-with-actions.md +0 -201
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CommandPalette
|
|
3
|
+
description: Keyboard-driven quick navigation with fuzzy search for all routes in your application
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/command-palette.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# CommandPalette
|
|
9
|
+
|
|
10
|
+
`CommandPalette` provides a keyboard-driven quick navigation interface that allows users to search and navigate to any page in your application. Activated with `Cmd+K` (Mac) or `Ctrl+K` (Windows), it offers fuzzy search across all accessible routes.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
The CommandPalette is built into `AppShell` and rendered automatically. No explicit import or placement is needed:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { AppShell, SidebarLayout } from "@tailor-platform/app-shell";
|
|
18
|
+
|
|
19
|
+
function App() {
|
|
20
|
+
return (
|
|
21
|
+
<AppShell modules={modules}>
|
|
22
|
+
<SidebarLayout />
|
|
23
|
+
</AppShell>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The CommandPalette will automatically:
|
|
29
|
+
|
|
30
|
+
- ✅ Index all navigatable routes from your modules
|
|
31
|
+
- ✅ Provide fuzzy search functionality
|
|
32
|
+
- ✅ Show breadcrumb paths for context
|
|
33
|
+
- ✅ Resolve dynamic params on the current route branch into real paths
|
|
34
|
+
- ✅ Display module icons
|
|
35
|
+
- ✅ Respect access control (hidden routes won't appear)
|
|
36
|
+
|
|
37
|
+
## Keyboard Shortcuts
|
|
38
|
+
|
|
39
|
+
### Opening/Closing
|
|
40
|
+
|
|
41
|
+
| Shortcut | Action |
|
|
42
|
+
| -------------------------- | --------------------- |
|
|
43
|
+
| `Cmd + K` (Mac) | Toggle CommandPalette |
|
|
44
|
+
| `Ctrl + K` (Windows/Linux) | Toggle CommandPalette |
|
|
45
|
+
| `Esc` | Close CommandPalette |
|
|
46
|
+
|
|
47
|
+
### Navigation
|
|
48
|
+
|
|
49
|
+
| Shortcut | Action |
|
|
50
|
+
| -------------- | -------------------------------- |
|
|
51
|
+
| `↑` / `↓` | Navigate up/down through results |
|
|
52
|
+
| `Enter` | Navigate to selected route |
|
|
53
|
+
| Type to search | Filter routes by fuzzy search |
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
### Fuzzy Search
|
|
58
|
+
|
|
59
|
+
The CommandPalette uses intelligent fuzzy search that matches:
|
|
60
|
+
|
|
61
|
+
- **Route titles** - Page names from your resource meta
|
|
62
|
+
- **Paths** - URL segments
|
|
63
|
+
- **Breadcrumbs** - Full navigation path
|
|
64
|
+
|
|
65
|
+
Example searches:
|
|
66
|
+
|
|
67
|
+
- `dash` → Matches "Dashboard"
|
|
68
|
+
- `prod det` → Matches "Products > Product Details"
|
|
69
|
+
- `ord` → Matches "Orders", "Order Details", etc.
|
|
70
|
+
|
|
71
|
+
### Breadcrumb Display
|
|
72
|
+
|
|
73
|
+
Each result shows its full navigation path for context:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
🏠 Dashboard > Analytics > Revenue
|
|
77
|
+
📦 Products > Categories > Electronics
|
|
78
|
+
📋 Orders > History > 2024
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
When the current URL already fixes a dynamic segment, the palette keeps routes on that branch navigable and displays those resolved values in the path. It only substitutes params on the current route branch, not into sibling routes.
|
|
82
|
+
|
|
83
|
+
For example, on `/users/42` the palette can include `/users/42` and `/users/42/profile`, and does **not** incorrectly rewrite sibling routes like `/users/new`.
|
|
84
|
+
|
|
85
|
+
### Access Control Integration
|
|
86
|
+
|
|
87
|
+
The CommandPalette automatically respects your route guards:
|
|
88
|
+
|
|
89
|
+
- Hidden routes (via `hidden()` guard) don't appear in search
|
|
90
|
+
- Protected routes are filtered based on current user permissions
|
|
91
|
+
- Dynamic updates when context changes
|
|
92
|
+
|
|
93
|
+
### Icon Display
|
|
94
|
+
|
|
95
|
+
Module icons are displayed next to search results for visual recognition:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
const productsModule = defineModule({
|
|
99
|
+
path: "products",
|
|
100
|
+
component: ProductsPage,
|
|
101
|
+
meta: {
|
|
102
|
+
title: "Products",
|
|
103
|
+
icon: <Package />, // Shown in CommandPalette
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Contextual Actions
|
|
109
|
+
|
|
110
|
+
In addition to navigation results, the CommandPalette can surface page-level actions. Use the `useRegisterCommandPaletteActions` hook to register actions that appear in the **Actions** section of the palette. Actions are unregistered automatically when the component unmounts.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { useRegisterCommandPaletteActions } from "@tailor-platform/app-shell";
|
|
114
|
+
|
|
115
|
+
function OrderDetailPage() {
|
|
116
|
+
useRegisterCommandPaletteActions("Order Actions", [
|
|
117
|
+
{ key: "confirm", label: "Confirm order", onSelect: handleConfirm },
|
|
118
|
+
{ key: "cancel", label: "Cancel order", icon: <XIcon />, onSelect: handleCancel },
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Parameters
|
|
124
|
+
|
|
125
|
+
| Parameter | Type | Description |
|
|
126
|
+
| --------- | ------------------------ | -------------------------------------------------------- |
|
|
127
|
+
| `group` | `string` | Display group name for the actions shown in the palette |
|
|
128
|
+
| `actions` | `CommandPaletteAction[]` | List of actions to register (see `CommandPaletteAction`) |
|
|
129
|
+
|
|
130
|
+
### `CommandPaletteAction`
|
|
131
|
+
|
|
132
|
+
| Property | Type | Description |
|
|
133
|
+
| ---------- | ----------------------------- | ----------------------------------------------------------------------------- |
|
|
134
|
+
| `key` | `string` | Unique key for React reconciliation |
|
|
135
|
+
| `label` | `string` | Visible label shown in the palette; also used for search matching |
|
|
136
|
+
| `icon` | `ReactNode` | Optional icon rendered next to the label |
|
|
137
|
+
| `group` | `string` | Optional group override; defaults to the `group` parameter passed to the hook |
|
|
138
|
+
| `onSelect` | `() => void \| Promise<void>` | Callback invoked when the user selects the action |
|
|
139
|
+
|
|
140
|
+
> **Note:** `icon` changes alone do not trigger re-registration. To reflect a dynamic icon update, also change the action's `key` or `label`.
|
|
141
|
+
|
|
142
|
+
### ActionPanel Integration
|
|
143
|
+
|
|
144
|
+
When you use the [`ActionPanel`](./action-panel.md) component, its enabled actions (not `disabled`, not `loading`, and with an `onClick` handler) are automatically registered to the CommandPalette under the panel's `title`. No additional setup is required.
|
|
145
|
+
|
|
146
|
+
For more details see [`useRegisterCommandPaletteActions`](../api/use-register-command-palette-actions.md).
|
|
147
|
+
|
|
148
|
+
## Async Search Sources
|
|
149
|
+
|
|
150
|
+
In addition to built-in page search and contextual actions, you can wire async, prefix-activated search sources into the CommandPalette via the `searchSources` prop on `AppShell`.
|
|
151
|
+
|
|
152
|
+
### How It Works
|
|
153
|
+
|
|
154
|
+
1. Pass an array of `SearchSource` objects to `AppShell`'s `searchSources` prop.
|
|
155
|
+
2. When the user types a source's `prefix` followed by `:` (e.g. `ORD:`), the palette switches into **search mode**: the Actions and Pages sections are hidden and only results from that source are shown.
|
|
156
|
+
3. On the **empty-input** state, the palette renders a **Search Modes** section listing every registered source so users can activate a mode with a single click.
|
|
157
|
+
|
|
158
|
+
### Basic Example
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import {
|
|
162
|
+
AppShell,
|
|
163
|
+
SidebarLayout,
|
|
164
|
+
DefaultSidebar,
|
|
165
|
+
type SearchSource,
|
|
166
|
+
} from "@tailor-platform/app-shell";
|
|
167
|
+
|
|
168
|
+
const searchSources: readonly SearchSource[] = [
|
|
169
|
+
{
|
|
170
|
+
prefix: "ORD",
|
|
171
|
+
title: "Orders",
|
|
172
|
+
search: async (query, { signal }) => {
|
|
173
|
+
const results = await api.searchOrders(query, { signal });
|
|
174
|
+
return results.map((o) => ({
|
|
175
|
+
key: o.id,
|
|
176
|
+
label: o.number,
|
|
177
|
+
description: `${o.customerName} — ${o.date}`,
|
|
178
|
+
path: `/orders/${o.id}`,
|
|
179
|
+
}));
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
prefix: "CUST",
|
|
184
|
+
title: "Customers",
|
|
185
|
+
search: async (query, { signal }) => {
|
|
186
|
+
const results = await api.searchCustomers(query, { signal });
|
|
187
|
+
return results.map((c) => ({
|
|
188
|
+
key: c.id,
|
|
189
|
+
label: c.name,
|
|
190
|
+
path: `/customers/${c.id}`,
|
|
191
|
+
}));
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
function App() {
|
|
197
|
+
return (
|
|
198
|
+
<AppShell modules={modules} searchSources={searchSources}>
|
|
199
|
+
<SidebarLayout sidebar={<DefaultSidebar />} />
|
|
200
|
+
</AppShell>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### `SearchSource`
|
|
206
|
+
|
|
207
|
+
| Property | Type | Required | Description |
|
|
208
|
+
| -------- | -------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
|
|
209
|
+
| `prefix` | `string` | ✅ | Mode-activating prefix (e.g. `"ORD"`). Case-sensitive, alphanumeric only. |
|
|
210
|
+
| `title` | `string` | ✅ | Heading shown above results and in the Search Modes list (e.g. `"Orders"`). |
|
|
211
|
+
| `icon` | `ReactNode` | No | Optional icon shown next to each result that lacks its own icon. |
|
|
212
|
+
| `search` | `(query: string, options: { signal: AbortSignal }) => Promise<CommandPaletteSearchResult[]>` | ✅ | Async function invoked with the query text after the prefix and an `AbortSignal` for cancellation. |
|
|
213
|
+
|
|
214
|
+
### `CommandPaletteSearchResult`
|
|
215
|
+
|
|
216
|
+
| Property | Type | Required | Description |
|
|
217
|
+
| ------------- | ----------- | -------- | -------------------------------------------------------------- |
|
|
218
|
+
| `key` | `string` | ✅ | Unique key for React reconciliation. |
|
|
219
|
+
| `label` | `string` | ✅ | Visible label shown in the palette (e.g. `"Order #ORD-1234"`). |
|
|
220
|
+
| `description` | `string` | No | Optional secondary text (e.g. `"山田太郎 - 2024/01/15"`). |
|
|
221
|
+
| `icon` | `ReactNode` | No | Optional icon for this specific result. |
|
|
222
|
+
| `path` | `string` | ✅ | Resolved navigation path (e.g. `"/orders/abc-123"`). |
|
|
223
|
+
|
|
224
|
+
### Prefix Syntax
|
|
225
|
+
|
|
226
|
+
The prefix match is **case-sensitive**. When a user types `ORD:`, the string after the colon (including any space) is forwarded to `search` as the query:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
User types: ORD:
|
|
230
|
+
→ search("", { signal }) — empty query, show all or hint
|
|
231
|
+
|
|
232
|
+
User types: ORD:12345
|
|
233
|
+
→ search("12345", { signal })
|
|
234
|
+
|
|
235
|
+
User types: ORD: alice
|
|
236
|
+
→ search(" alice", { signal }) — leading space preserved
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Extending the built-in palette
|
|
240
|
+
|
|
241
|
+
The supported extension points are:
|
|
242
|
+
|
|
243
|
+
- `searchSources` on `AppShell` for async, prefix-activated search modes
|
|
244
|
+
- [`useRegisterCommandPaletteActions`](../api/use-register-command-palette-actions.md) for page-level actions
|
|
245
|
+
- [`ActionPanel`](./action-panel.md), whose enabled rows are registered automatically
|
|
246
|
+
|
|
247
|
+
`CommandPalette` itself is built into `AppShell`; there is no separate public hook for rebuilding the palette UI from scratch.
|
|
248
|
+
|
|
249
|
+
## Examples
|
|
250
|
+
|
|
251
|
+
### Basic Usage
|
|
252
|
+
|
|
253
|
+
The CommandPalette works automatically with your module definitions:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
const modules = [
|
|
257
|
+
defineModule({
|
|
258
|
+
path: "dashboard",
|
|
259
|
+
component: DashboardPage,
|
|
260
|
+
meta: { title: "Dashboard", icon: <Home /> },
|
|
261
|
+
resources: [
|
|
262
|
+
defineResource({
|
|
263
|
+
path: "analytics",
|
|
264
|
+
component: AnalyticsPage,
|
|
265
|
+
meta: { title: "Analytics" },
|
|
266
|
+
}),
|
|
267
|
+
],
|
|
268
|
+
}),
|
|
269
|
+
defineModule({
|
|
270
|
+
path: "products",
|
|
271
|
+
component: ProductsPage,
|
|
272
|
+
meta: { title: "Products", icon: <Package /> },
|
|
273
|
+
resources: [
|
|
274
|
+
defineResource({
|
|
275
|
+
path: ":id",
|
|
276
|
+
component: ProductDetailPage,
|
|
277
|
+
meta: { title: "Product Details" },
|
|
278
|
+
}),
|
|
279
|
+
],
|
|
280
|
+
}),
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
// CommandPalette will show:
|
|
284
|
+
// - Dashboard
|
|
285
|
+
// - Dashboard > Analytics
|
|
286
|
+
// - Products
|
|
287
|
+
// - Products > Product Details (when applicable)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### With Access Control
|
|
291
|
+
|
|
292
|
+
Routes hidden by guards won't appear in search:
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
const adminModule = defineModule({
|
|
296
|
+
path: "admin",
|
|
297
|
+
component: AdminPage,
|
|
298
|
+
meta: { title: "Admin Panel" },
|
|
299
|
+
guards: [
|
|
300
|
+
({ context }) => {
|
|
301
|
+
return context.currentUser?.role === "admin" ? pass() : hidden(); // Won't show in CommandPalette for non-admins
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Search Behavior
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
User types: "prod"
|
|
311
|
+
Results:
|
|
312
|
+
📦 Products
|
|
313
|
+
📦 Products > Product Details
|
|
314
|
+
📦 Products > Categories
|
|
315
|
+
|
|
316
|
+
User types: "dash ana"
|
|
317
|
+
Results:
|
|
318
|
+
🏠 Dashboard > Analytics
|
|
319
|
+
|
|
320
|
+
User types: "ord 123"
|
|
321
|
+
Results:
|
|
322
|
+
📋 Orders > Order #123 (if dynamic routes are indexed)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Design
|
|
326
|
+
|
|
327
|
+
The CommandPalette follows these design principles:
|
|
328
|
+
|
|
329
|
+
### Visual Design
|
|
330
|
+
|
|
331
|
+
- **Clean, focused UI** - Minimal distractions
|
|
332
|
+
- **Clear hierarchy** - Module icons, titles, and breadcrumbs
|
|
333
|
+
- **Active state** - Highlight selected result
|
|
334
|
+
- **Empty state** - "No results found" message
|
|
335
|
+
|
|
336
|
+
### UX Patterns
|
|
337
|
+
|
|
338
|
+
- **Immediate feedback** - Results update as you type
|
|
339
|
+
- **Keyboard-first** - All actions accessible via keyboard
|
|
340
|
+
- **Contextual information** - Breadcrumbs show location
|
|
341
|
+
- **Quick access** - Single shortcut to open
|
|
342
|
+
|
|
343
|
+
### Performance
|
|
344
|
+
|
|
345
|
+
- **Instant search** - Client-side fuzzy matching
|
|
346
|
+
- **Debounced input** - Smooth typing experience
|
|
347
|
+
- **Lazy loading** - Routes loaded only when needed
|
|
348
|
+
|
|
349
|
+
## Best Practices
|
|
350
|
+
|
|
351
|
+
### Do:
|
|
352
|
+
|
|
353
|
+
- ✅ Provide clear, descriptive titles in resource meta
|
|
354
|
+
- ✅ Use meaningful module icons for visual recognition
|
|
355
|
+
- ✅ Structure modules logically for better discoverability
|
|
356
|
+
- ✅ Keep route hierarchies shallow when possible
|
|
357
|
+
|
|
358
|
+
### Don't:
|
|
359
|
+
|
|
360
|
+
- ❌ Use generic titles like "Page 1", "Section A"
|
|
361
|
+
- ❌ Create deeply nested resources (hard to navigate)
|
|
362
|
+
- ❌ Forget to set meta.title on resources
|
|
363
|
+
- ❌ Use similar titles for different routes
|
|
364
|
+
|
|
365
|
+
## Accessibility
|
|
366
|
+
|
|
367
|
+
- **Keyboard navigation** - Full keyboard support
|
|
368
|
+
- **ARIA labels** - Proper labels for screen readers
|
|
369
|
+
- **Focus management** - Focus trapped in dialog when open
|
|
370
|
+
- **ESC to close** - Standard dialog behavior
|
|
371
|
+
|
|
372
|
+
## Related Components
|
|
373
|
+
|
|
374
|
+
- [AppShell](./app-shell.md) - Root component that provides CommandPalette
|
|
375
|
+
- [SidebarLayout](./sidebar-layout.md) - Layout that includes CommandPalette
|
|
376
|
+
|
|
377
|
+
## Related Concepts
|
|
378
|
+
|
|
379
|
+
- [Modules and Resources](../concepts/modules-and-resources.md) - Define routes that appear in CommandPalette
|
|
380
|
+
- [Routing and Navigation](../concepts/routing-navigation.md) - Navigation system
|
|
381
|
+
- [Guards and Permissions](../api/guards/overview.md) - Control route visibility
|
|
382
|
+
|
|
383
|
+
## Troubleshooting
|
|
384
|
+
|
|
385
|
+
### Routes not appearing in search
|
|
386
|
+
|
|
387
|
+
**Problem:** Some routes don't show up in CommandPalette
|
|
388
|
+
|
|
389
|
+
**Solutions:**
|
|
390
|
+
|
|
391
|
+
1. Check that the resource has a `meta.title` defined
|
|
392
|
+
2. Verify the route isn't hidden by a guard
|
|
393
|
+
3. Ensure the route is navigatable (not a redirect-only route)
|
|
394
|
+
|
|
395
|
+
### Search not matching expected routes
|
|
396
|
+
|
|
397
|
+
**Problem:** Typing a route name doesn't find it
|
|
398
|
+
|
|
399
|
+
**Solutions:**
|
|
400
|
+
|
|
401
|
+
1. Try typing the breadcrumb path: "Products Categories"
|
|
402
|
+
2. Use abbreviations: "prod cat"
|
|
403
|
+
3. Check spelling of the title in resource meta
|
|
404
|
+
|
|
405
|
+
### CommandPalette not opening
|
|
406
|
+
|
|
407
|
+
**Problem:** `Cmd+K` / `Ctrl+K` doesn't work
|
|
408
|
+
|
|
409
|
+
**Solutions:**
|
|
410
|
+
|
|
411
|
+
1. Check if another app is capturing the shortcut
|
|
412
|
+
2. Verify you're using `SidebarLayout` (CommandPalette is included)
|
|
413
|
+
3. Check browser console for JavaScript errors
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CsvImporter
|
|
3
|
+
description: Guided multi-step CSV import flow with drag-and-drop upload, column mapping, validation, and inline error correction
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/csv-importer.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# CsvImporter
|
|
9
|
+
|
|
10
|
+
The `CsvImporter` component provides a guided, multi-step CSV import flow rendered inside a drawer. It handles drag-and-drop file upload, interactive column mapping, Standard Schema validation, inline cell editing, and async server-side validation.
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import {
|
|
16
|
+
CsvImporter,
|
|
17
|
+
useCsvImporter,
|
|
18
|
+
csv,
|
|
19
|
+
type CsvSchema,
|
|
20
|
+
type CsvColumn,
|
|
21
|
+
type CsvImportEvent,
|
|
22
|
+
type CsvCellIssue,
|
|
23
|
+
type CsvCorrection,
|
|
24
|
+
type CsvColumnMapping,
|
|
25
|
+
type ParsedRow,
|
|
26
|
+
type InferCsvRow,
|
|
27
|
+
} from "@tailor-platform/app-shell";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Basic Usage
|
|
31
|
+
|
|
32
|
+
Use the `useCsvImporter` hook to manage state, then render `<CsvImporter>` with the returned props.
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { CsvImporter, useCsvImporter, csv } from "@tailor-platform/app-shell";
|
|
36
|
+
import { Button } from "@tailor-platform/app-shell";
|
|
37
|
+
|
|
38
|
+
function ProductImport() {
|
|
39
|
+
const { open, props } = useCsvImporter({
|
|
40
|
+
schema: {
|
|
41
|
+
columns: [
|
|
42
|
+
{
|
|
43
|
+
key: "name",
|
|
44
|
+
label: "Name",
|
|
45
|
+
required: true,
|
|
46
|
+
aliases: ["product_name"],
|
|
47
|
+
schema: csv.string({ min: 1 }),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: "price",
|
|
51
|
+
label: "Price",
|
|
52
|
+
schema: csv.number({ min: 0 }),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: "active",
|
|
56
|
+
label: "Active",
|
|
57
|
+
schema: csv.boolean(),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
onImport: (event) => {
|
|
62
|
+
console.log(event.summary);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<Button onClick={open}>Import CSV</Button>
|
|
69
|
+
<CsvImporter {...props} />
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## `useCsvImporter`
|
|
76
|
+
|
|
77
|
+
The `useCsvImporter` hook manages the open/close state and returns both an `open` function and `props` to spread onto `<CsvImporter>`.
|
|
78
|
+
|
|
79
|
+
### Options
|
|
80
|
+
|
|
81
|
+
| Option | Type | Default | Description |
|
|
82
|
+
| ------------- | -------------------------------------------------- | ------------------ | --------------------------------------------------------------------------- |
|
|
83
|
+
| `schema` | `CsvSchema` | — | Column definitions for the import (see [CsvSchema](#csvschema)) |
|
|
84
|
+
| `onImport` | `(event: CsvImportEvent) => void \| Promise<void>` | — | Called when the user confirms the import after resolving all errors |
|
|
85
|
+
| `onValidate` | `(rows: ParsedRow[]) => Promise<CsvCellIssue[]>` | — | Optional async callback for server-side validation after schema checks pass |
|
|
86
|
+
| `maxFileSize` | `number` | `10485760` (10 MB) | Maximum allowed file size in bytes |
|
|
87
|
+
|
|
88
|
+
### Return Value
|
|
89
|
+
|
|
90
|
+
| Property | Type | Description |
|
|
91
|
+
| -------- | ------------------ | ----------------------------------------------------- |
|
|
92
|
+
| `open` | `() => void` | Function to programmatically open the importer drawer |
|
|
93
|
+
| `props` | `CsvImporterProps` | Props object to spread directly onto `<CsvImporter>` |
|
|
94
|
+
|
|
95
|
+
## `CsvImporter` Props
|
|
96
|
+
|
|
97
|
+
`CsvImporter` is intended to be used with props returned from `useCsvImporter`. Spread the `props` object directly onto the component.
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<CsvImporter {...props} />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Schemas
|
|
104
|
+
|
|
105
|
+
### `CsvSchema`
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
type CsvSchema = {
|
|
109
|
+
columns: CsvColumn[];
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `CsvColumn`
|
|
114
|
+
|
|
115
|
+
| Property | Type | Default | Description |
|
|
116
|
+
| ------------- | ------------------ | ------- | --------------------------------------------------------------------------------------- |
|
|
117
|
+
| `key` | `string` | — | Internal key; becomes the object key in parsed row data |
|
|
118
|
+
| `label` | `string` | — | Display label shown in the mapping UI |
|
|
119
|
+
| `description` | `string` | — | Optional hint shown in the mapping UI |
|
|
120
|
+
| `required` | `boolean` | `false` | Whether this column must be mapped before proceeding |
|
|
121
|
+
| `aliases` | `string[]` | — | Alternative CSV header names for automatic matching |
|
|
122
|
+
| `schema` | `StandardSchemaV1` | — | Standard Schema validator for coercion and validation (see [csv helpers](#csv-helpers)) |
|
|
123
|
+
|
|
124
|
+
## `csv` Helpers
|
|
125
|
+
|
|
126
|
+
Built-in Standard Schema validators for common CSV column types. Each helper handles both coercion and validation in a single declaration.
|
|
127
|
+
|
|
128
|
+
| Helper | Output type | Description |
|
|
129
|
+
| ------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------- |
|
|
130
|
+
| `csv.string(options?: { min?: number; max?: number })` | `string` | Pass-through string with optional length constraints |
|
|
131
|
+
| `csv.number(options?: { min?: number; max?: number; integer?: boolean })` | `number` | Coerces raw CSV string to a number; rejects `NaN` |
|
|
132
|
+
| `csv.boolean(options?: { truthy?: string[]; falsy?: string[] })` | `boolean` | Recognises `"true"/"1"/"yes"` and `"false"/"0"/"no"` (case-insensitive) |
|
|
133
|
+
| `csv.date()` | `Date` | Coerces raw CSV string to a `Date`; rejects unparseable values |
|
|
134
|
+
| `csv.enum(values: string[])` | `T extends string` | Validates the value is one of the allowed strings (case-sensitive) |
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// String with required check (min: 1)
|
|
138
|
+
csv.string({ min: 1 });
|
|
139
|
+
|
|
140
|
+
// Number with lower bound
|
|
141
|
+
csv.number({ min: 0 });
|
|
142
|
+
|
|
143
|
+
// Integer only
|
|
144
|
+
csv.number({ integer: true });
|
|
145
|
+
|
|
146
|
+
// Boolean with defaults: truthy = ["true","1","yes"], falsy = ["false","0","no"]
|
|
147
|
+
csv.boolean();
|
|
148
|
+
|
|
149
|
+
// Date
|
|
150
|
+
csv.date();
|
|
151
|
+
|
|
152
|
+
// Enum
|
|
153
|
+
csv.enum(["active", "inactive", "pending"]);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Server-side Validation
|
|
157
|
+
|
|
158
|
+
Use `onValidate` to perform async checks (e.g. uniqueness constraints) after schema validation passes. Return an array of `CsvCellIssue` objects to mark cells with errors or warnings.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
const { open, props } = useCsvImporter({
|
|
162
|
+
schema: { columns: [...] },
|
|
163
|
+
onValidate: async (rows) => {
|
|
164
|
+
const issues = await checkUniqueness(rows);
|
|
165
|
+
return issues; // CsvCellIssue[]
|
|
166
|
+
},
|
|
167
|
+
onImport: (event) => { /* ... */ },
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `CsvCellIssue`
|
|
172
|
+
|
|
173
|
+
| Property | Type | Description |
|
|
174
|
+
| ----------- | ---------------------- | ---------------------------------------------------- |
|
|
175
|
+
| `rowIndex` | `number` | 0-based row index |
|
|
176
|
+
| `columnKey` | `string` | The schema column key |
|
|
177
|
+
| `level` | `"error" \| "warning"` | `"error"` blocks import; `"warning"` allows import |
|
|
178
|
+
| `message` | `string` | Human-readable message displayed inline in the table |
|
|
179
|
+
|
|
180
|
+
## Import Event
|
|
181
|
+
|
|
182
|
+
The `onImport` callback receives a `CsvImportEvent` with the following shape:
|
|
183
|
+
|
|
184
|
+
| Property | Type | Description |
|
|
185
|
+
| ------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
186
|
+
| `file` | `File` | The original file selected by the user |
|
|
187
|
+
| `mappings` | `CsvColumnMapping[]` | The confirmed column mappings |
|
|
188
|
+
| `corrections` | `CsvCorrection[]` | Corrections made by the user in the review step |
|
|
189
|
+
| `issues` | `CsvCellIssue[]` | Any remaining issues (warnings only — errors are resolved before import) |
|
|
190
|
+
| `summary` | `{ totalRows, validRows, correctedRows, skippedRows, warningRows }` | Summary statistics |
|
|
191
|
+
| `buildRows()` | `() => Promise<InferCsvRow<T>[]>` | Reconstruct fully-processed rows with schema coercion and corrections |
|
|
192
|
+
|
|
193
|
+
## `event.buildRows()`
|
|
194
|
+
|
|
195
|
+
A method on the import event that reconstructs the fully processed row data on the client side. The return type is inferred from the schema definition — column keys become object keys, and `StandardSchemaV1` output types are preserved.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
onImport: async (event) => {
|
|
199
|
+
const rows = await event.buildRows();
|
|
200
|
+
// rows is typed based on schema — e.g. { name: string; price: number; ... }[]
|
|
201
|
+
await saveToBackend(rows);
|
|
202
|
+
},
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
> If you are sending the file, mappings, and corrections to a backend for processing, you do not need `buildRows()`.
|
|
206
|
+
|
|
207
|
+
## i18n
|
|
208
|
+
|
|
209
|
+
`CsvImporter` includes built-in English and Japanese labels. No additional setup is required.
|
|
210
|
+
|
|
211
|
+
## Related Components
|
|
212
|
+
|
|
213
|
+
- [Sheet](./sheet.md) — Slide-in panel (the drawer used internally by CsvImporter)
|
|
214
|
+
- [Button](./button.md) — Use as the trigger to open the importer
|