@tailor-platform/app-shell 1.15.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/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +208 -114
- package/dist/app-shell.js +3449 -3446
- package/dist/{appshell-DLVBFo5J.js → appshell-B0wh5Lq0.js} +625 -512
- package/dist/style-entry.d.ts +116 -96
- package/dist/testing.d.ts +116 -96
- package/dist/testing.js +1 -1
- package/dist/vite-plugin.d.ts +116 -96
- package/package.json +8 -8
- package/skills/app-shell-patterns/SKILL.md +101 -14
- 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} +258 -110
- package/skills/app-shell-patterns/references/migrations.md +34 -0
- package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +4 -27
- package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
- 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 +13 -27
- 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 -1009
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Routing and Navigation
|
|
3
|
+
description: Learn how to navigate between pages using React Router hooks and components re-exported from AppShell
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/concepts/routing-navigation.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# Routing and Navigation
|
|
9
|
+
|
|
10
|
+
AppShell owns the router. It builds the route tree from your modules and resources, constructs the router, and renders the `RouterProvider` itself. Your pages render _inside_ that router.
|
|
11
|
+
|
|
12
|
+
## Your app should not depend on `react-router`
|
|
13
|
+
|
|
14
|
+
**Do not add `react-router` to your application's dependencies.** Import everything you need for routing from `@tailor-platform/app-shell` instead.
|
|
15
|
+
|
|
16
|
+
This is not a style preference — it is what keeps your app working:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
// ✅ Do this
|
|
20
|
+
import { useNavigate, Link } from "@tailor-platform/app-shell";
|
|
21
|
+
|
|
22
|
+
// ❌ Not this
|
|
23
|
+
import { useNavigate, Link } from "react-router";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
React Router keeps its state in React context, and context identity belongs to a _module instance_. If your app resolves its own copy of `react-router` — a different version to the one AppShell resolved — the bundle ends up with two copies and therefore two unrelated routers. AppShell's own navigation keeps working, so the app looks fine at a glance, while every hook and component you imported yourself throws:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
useNavigate() may be used only in the context of a <Router> component.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
TypeScript cannot see this, so the build passes and the failure only appears at runtime. Under pnpm, an app that has not declared `react-router` cannot import it at all, which is the intended outcome: the rule enforces itself.
|
|
33
|
+
|
|
34
|
+
If you already have a direct dependency, the fix is to delete it and change the import paths. The APIs are identical — they are the same functions, re-exported.
|
|
35
|
+
|
|
36
|
+
## What AppShell gives you
|
|
37
|
+
|
|
38
|
+
Everything you need to _consume_ the router AppShell created:
|
|
39
|
+
|
|
40
|
+
**Location and route matching**
|
|
41
|
+
|
|
42
|
+
- `useLocation` — the current location object
|
|
43
|
+
- `useParams` — dynamic route parameters
|
|
44
|
+
- `useSearchParams` — read and update the query string
|
|
45
|
+
- `useMatch` — test whether a path matches the current location
|
|
46
|
+
- `useResolvedPath` — resolve a relative path against the current route
|
|
47
|
+
|
|
48
|
+
**Navigating**
|
|
49
|
+
|
|
50
|
+
- `useNavigate` — navigate programmatically
|
|
51
|
+
- `useNavigation` — the in-flight navigation, for pending UI
|
|
52
|
+
- `Link` — a client-side link
|
|
53
|
+
- `NavLink` — a link that knows when it is active, for nav menus
|
|
54
|
+
- `Navigate` — redirect declaratively during render
|
|
55
|
+
|
|
56
|
+
**Guarding unsaved work**
|
|
57
|
+
|
|
58
|
+
- `useBlocker` — intercept an in-app navigation away from unsaved changes
|
|
59
|
+
- `useBeforeUnload` — the same for closing or reloading the tab
|
|
60
|
+
|
|
61
|
+
**Errors**
|
|
62
|
+
|
|
63
|
+
- `useRouteError` — read the error inside an error boundary
|
|
64
|
+
|
|
65
|
+
Types come with them: `Location`, `NavigateFunction`, `NavigateOptions`, `To`, `Params`, `PathMatch`, `LinkProps`, `NavLinkProps`, `Navigation`, `Blocker`, and `BlockerFunction`.
|
|
66
|
+
|
|
67
|
+
### What it deliberately does not give you
|
|
68
|
+
|
|
69
|
+
**Router construction** — `createBrowserRouter`, `RouterProvider`, `BrowserRouter`, `MemoryRouter`, `Routes`, `Route`. AppShell builds the router from your modules and resources; a second one nested inside it is the bug this page is about. To control routing in tests, use the [`/testing` entry point](#testing) rather than building your own router.
|
|
70
|
+
|
|
71
|
+
**Data-router APIs** — `useLoaderData`, `Form`, `useSubmit`, `useFetcher`, `useActionData`, `useRevalidator`. AppShell does not wire up React Router's data layer, so these have nothing to talk to. Load data in the page component or your own data layer. (AppShell exports its own `Form`, which is part of the [form system](../components/form.md) and unrelated to React Router's.)
|
|
72
|
+
|
|
73
|
+
Missing something you need? Ask for it to be added here rather than adding a direct `react-router` dependency — that is the trade this rule makes, and requests are the mechanism that keeps it honest.
|
|
74
|
+
|
|
75
|
+
### One caveat
|
|
76
|
+
|
|
77
|
+
A third-party library that itself depends on `react-router` can still pull in a second copy. That is rare, and outside what AppShell can control. If routing starts throwing the error above, look for a duplicate — under pnpm, `pnpm why react-router`.
|
|
78
|
+
|
|
79
|
+
## Testing
|
|
80
|
+
|
|
81
|
+
Test helpers live in a separate entry point, `@tailor-platform/app-shell/testing`. Memory routing is kept out of the main entry because an app that shipped it would render correctly while the URL bar silently stopped tracking navigation — the `AppShell` you import from `@tailor-platform/app-shell` always uses browser routing, and pins it off regardless of what you pass.
|
|
82
|
+
|
|
83
|
+
### Pages and integration tests
|
|
84
|
+
|
|
85
|
+
Because AppShell owns the router, a test mounts AppShell rather than building a router. The `AppShell` exported from `/testing` is the same shell, and additionally accepts `memory` with `initialEntries` so it starts at a known URL without touching `window.location`:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { render, screen } from "@testing-library/react";
|
|
89
|
+
import { AppShell } from "@tailor-platform/app-shell/testing";
|
|
90
|
+
import { SidebarLayout } from "@tailor-platform/app-shell";
|
|
91
|
+
|
|
92
|
+
render(
|
|
93
|
+
<AppShell memory initialEntries={["/orders/A42"]} modules={modules}>
|
|
94
|
+
<SidebarLayout />
|
|
95
|
+
</AppShell>,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(await screen.findByText("Order A42")).toBeDefined();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`initialEntries` is the history stack to start with — the last entry is the current location. Each render owns its history, so tests do not leak navigation state into one another.
|
|
102
|
+
|
|
103
|
+
### Unit tests
|
|
104
|
+
|
|
105
|
+
A component that calls `useNavigate` or renders a `<Link>` needs a router above it, but mounting a whole AppShell for that is heavy. `TestRouter` provides just the router context:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { TestRouter } from "@tailor-platform/app-shell/testing";
|
|
109
|
+
|
|
110
|
+
render(
|
|
111
|
+
<TestRouter initialEntries={["/orders/A42"]}>
|
|
112
|
+
<SaveButton />
|
|
113
|
+
</TestRouter>,
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**If the component reads the route**, pass `path`. A router on its own matches nothing, so `useParams()` comes back empty even when `initialEntries` holds a URL that looks like it should match:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
render(
|
|
121
|
+
<TestRouter path="/orders/:id" initialEntries={["/orders/A42"]}>
|
|
122
|
+
<OrderBadge /> {/* useParams() -> { id: "A42" } */}
|
|
123
|
+
</TestRouter>,
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The same applies to `useMatch` and to relative `<Link>` targets. Compose your own providers — a GraphQL client, say — around `TestRouter` as needed.
|
|
128
|
+
|
|
129
|
+
Reach for `TestRouter` when the component under test is the subject; mount `AppShell memory` when the routing itself is.
|
|
130
|
+
|
|
131
|
+
## Example
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { useNavigate, useParams, useLocation, Link } from "@tailor-platform/app-shell";
|
|
135
|
+
|
|
136
|
+
const MyComponent = () => {
|
|
137
|
+
const navigate = useNavigate();
|
|
138
|
+
const { id } = useParams();
|
|
139
|
+
const location = useLocation();
|
|
140
|
+
|
|
141
|
+
const handleClick = () => {
|
|
142
|
+
// Navigate programmatically
|
|
143
|
+
navigate("/dashboard/overview");
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<div>
|
|
148
|
+
<p>Current path: {location.pathname}</p>
|
|
149
|
+
<p>Route param ID: {id}</p>
|
|
150
|
+
|
|
151
|
+
{/* Client-side link */}
|
|
152
|
+
<Link to="/products">View Products</Link>
|
|
153
|
+
|
|
154
|
+
<button onClick={handleClick}>Go to Dashboard</button>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Declarative Redirects
|
|
161
|
+
|
|
162
|
+
`Navigate` redirects as a render result, which avoids the `useEffect` + `navigate()` pattern (that pattern renders the old page for a frame before it redirects):
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { Navigate, useAppShellData } from "@tailor-platform/app-shell";
|
|
166
|
+
|
|
167
|
+
const AdminPage = () => {
|
|
168
|
+
const { currentUser } = useAppShellData();
|
|
169
|
+
|
|
170
|
+
if (currentUser?.role !== "admin") {
|
|
171
|
+
return <Navigate to="/dashboard" replace />;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return <AdminDashboard />;
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Pass `replace` when the redirect should not leave the abandoned route in history — otherwise the browser Back button lands the user right back on it, and bounces them forward again.
|
|
179
|
+
|
|
180
|
+
### Choosing between `Navigate` and `redirectTo()`
|
|
181
|
+
|
|
182
|
+
Both redirect, but they run at different points:
|
|
183
|
+
|
|
184
|
+
| | Runs | Use for |
|
|
185
|
+
| ---------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
186
|
+
| [`redirectTo()`](../api/guards/redirect-to.md) | Route guard, before the component mounts | Route-level access control — the preferred option when the decision can be made from guard context |
|
|
187
|
+
| `Navigate` | During render, from inside a component | Decisions that depend on component state, hooks, or fetched data |
|
|
188
|
+
|
|
189
|
+
Reach for `redirectTo()` first: it never mounts the component. `Navigate` is the fallback for cases a guard cannot express — notably [`WithGuard`](../components/with-guard.md), which does not support `redirectTo()`.
|
|
190
|
+
|
|
191
|
+
## Breadcrumbs
|
|
192
|
+
|
|
193
|
+
AppShell automatically generates breadcrumbs from your module and resource hierarchy. Each path segment corresponds to a breadcrumb item, using the `title` (or `meta.breadcrumbTitle`) defined in `defineModule` / `defineResource`.
|
|
194
|
+
|
|
195
|
+
### Static Breadcrumb Titles
|
|
196
|
+
|
|
197
|
+
Set a fixed breadcrumb title via `meta.breadcrumbTitle`:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
defineResource({
|
|
201
|
+
path: ":id",
|
|
202
|
+
meta: {
|
|
203
|
+
breadcrumbTitle: (segment) => `Order #${segment}`,
|
|
204
|
+
},
|
|
205
|
+
component: OrderDetailPage,
|
|
206
|
+
});
|
|
207
|
+
// Breadcrumb shows: "Orders > Order #12345"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Dynamic Breadcrumb Titles
|
|
211
|
+
|
|
212
|
+
Use the `useOverrideBreadcrumb` hook to replace a breadcrumb segment with a data-driven value from within the rendered page component:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { useOverrideBreadcrumb } from "@tailor-platform/app-shell";
|
|
216
|
+
|
|
217
|
+
defineResource({
|
|
218
|
+
path: ":id",
|
|
219
|
+
component: () => {
|
|
220
|
+
const { data } = useQuery(GET_ORDER, { variables: { id } });
|
|
221
|
+
|
|
222
|
+
// Breadcrumb updates reactively once data loads
|
|
223
|
+
useOverrideBreadcrumb(data?.order?.name);
|
|
224
|
+
|
|
225
|
+
return <OrderDetail />;
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
While `title` is `undefined` (e.g., loading), the override is cleared and the static title is shown. The override is automatically cleaned up on unmount.
|
|
231
|
+
|
|
232
|
+
See [useOverrideBreadcrumb](../api/use-override-breadcrumb.md) for the full API reference.
|
|
233
|
+
|
|
234
|
+
## Command Palette for Quick Navigation
|
|
235
|
+
|
|
236
|
+
AppShell includes a `CommandPalette` component that provides keyboard-driven quick navigation to any page in your application.
|
|
237
|
+
|
|
238
|
+
### Features
|
|
239
|
+
|
|
240
|
+
- **Keyboard Shortcut**: `Cmd+K` (Mac) or `Ctrl+K` (Windows/Linux)
|
|
241
|
+
- **Fuzzy Search**: Search by page title or path
|
|
242
|
+
- **Hierarchical Display**: Shows module > resource breadcrumbs
|
|
243
|
+
- **Keyboard Navigation**: Use arrow keys and Enter to navigate
|
|
244
|
+
- **Multilingual**: Supports English and Japanese locales
|
|
245
|
+
|
|
246
|
+
### Setup
|
|
247
|
+
|
|
248
|
+
The CommandPalette is built into `AppShell` and rendered automatically:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
import { AppShell, SidebarLayout } from "@tailor-platform/app-shell";
|
|
252
|
+
|
|
253
|
+
const App = () => (
|
|
254
|
+
<AppShell modules={modules} locale="en">
|
|
255
|
+
<SidebarLayout />
|
|
256
|
+
</AppShell>
|
|
257
|
+
);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The CommandPalette automatically:
|
|
261
|
+
|
|
262
|
+
- Collects all navigable routes from your module definitions
|
|
263
|
+
- Respects guards (modules/resources returning `hidden()` won't appear)
|
|
264
|
+
- Updates when navigation items change
|
|
265
|
+
- Adapts to the current locale
|
|
266
|
+
|
|
267
|
+
### User Experience
|
|
268
|
+
|
|
269
|
+
1. User presses `Cmd+K` / `Ctrl+K` anywhere in the app
|
|
270
|
+
2. Command palette dialog opens with fuzzy search
|
|
271
|
+
3. User types to filter pages (e.g., "order detail")
|
|
272
|
+
4. Navigate results with arrow keys
|
|
273
|
+
5. Press Enter to navigate to selected page
|
|
274
|
+
|
|
275
|
+
No configuration needed - it just works!
|
|
276
|
+
|
|
277
|
+
## Type-Safe Navigation with Generated Routes
|
|
278
|
+
|
|
279
|
+
When using file-based routing with the vite-plugin, you can enable automatic generation of type-safe route helpers. This provides compile-time checking for route paths and their parameters.
|
|
280
|
+
|
|
281
|
+
### Setup
|
|
282
|
+
|
|
283
|
+
Enable `generateTypedRoutes` in your vite config:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
// vite.config.ts
|
|
287
|
+
import { defineConfig } from "vite";
|
|
288
|
+
import react from "@vitejs/plugin-react";
|
|
289
|
+
import { appShellRoutes } from "@tailor-platform/app-shell/vite-plugin";
|
|
290
|
+
|
|
291
|
+
export default defineConfig({
|
|
292
|
+
plugins: [
|
|
293
|
+
react(),
|
|
294
|
+
appShellRoutes({
|
|
295
|
+
pagesDir: "src/pages",
|
|
296
|
+
// Enable with default output path ("src/routes.generated.ts")
|
|
297
|
+
generateTypedRoutes: true,
|
|
298
|
+
// Or customize output path:
|
|
299
|
+
// generateTypedRoutes: { output: "src/my-routes.ts" },
|
|
300
|
+
}),
|
|
301
|
+
],
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
This generates a `src/routes.generated.ts` file containing type definitions for all your routes.
|
|
306
|
+
|
|
307
|
+
### Generated File
|
|
308
|
+
|
|
309
|
+
The generated file exports a `paths` helper with a type-safe `for()` method:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
// src/routes.generated.ts (auto-generated)
|
|
313
|
+
import { createTypedPaths } from "@tailor-platform/app-shell";
|
|
314
|
+
|
|
315
|
+
type RouteParams = {
|
|
316
|
+
"/": {};
|
|
317
|
+
"/dashboard": {};
|
|
318
|
+
"/orders": {};
|
|
319
|
+
"/orders/:id": { id: string };
|
|
320
|
+
"/orders/:orderId/items/:itemId": { orderId: string; itemId: string };
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const paths = createTypedPaths<RouteParams>();
|
|
324
|
+
|
|
325
|
+
export type { RouteParams };
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Usage
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
import { useNavigate } from "@tailor-platform/app-shell";
|
|
332
|
+
import { paths } from "./routes.generated";
|
|
333
|
+
|
|
334
|
+
const MyComponent = () => {
|
|
335
|
+
const navigate = useNavigate();
|
|
336
|
+
|
|
337
|
+
// ✅ Static route - no params needed
|
|
338
|
+
const goToDashboard = () => {
|
|
339
|
+
navigate(paths.for("/dashboard"));
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// ✅ Dynamic route - params required and type-checked
|
|
343
|
+
const goToOrder = (orderId: string) => {
|
|
344
|
+
navigate(paths.for("/orders/:id", { id: orderId }));
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// ✅ Multiple params
|
|
348
|
+
const goToOrderItem = (orderId: string, itemId: string) => {
|
|
349
|
+
navigate(paths.for("/orders/:orderId/items/:itemId", { orderId, itemId }));
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// ✅ Query string passthrough
|
|
353
|
+
const goToOrderWithTab = (orderId: string) => {
|
|
354
|
+
navigate(paths.for("/orders/:id?tab=details", { id: orderId }));
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// ✅ Dynamic query values via template literal
|
|
358
|
+
const goToOrderWithDynamicQuery = (orderId: string, tab: string) => {
|
|
359
|
+
navigate(paths.for(`/orders/:id?tab=${tab}`, { id: orderId }));
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// ❌ TypeScript error: missing required params
|
|
363
|
+
// navigate(paths.for("/orders/:id"));
|
|
364
|
+
|
|
365
|
+
// ❌ TypeScript error: invalid path
|
|
366
|
+
// navigate(paths.for("/invalid/path"));
|
|
367
|
+
|
|
368
|
+
return <button onClick={goToDashboard}>Go to Dashboard</button>;
|
|
369
|
+
};
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Opt-In Design
|
|
373
|
+
|
|
374
|
+
This feature is opt-in. If you don't enable `generateTypedRoutes`, you can continue building paths dynamically:
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
// Still works without typed routes
|
|
378
|
+
navigate(`/orders/${orderId}`);
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### HMR Support
|
|
382
|
+
|
|
383
|
+
The generated file is automatically regenerated when:
|
|
384
|
+
|
|
385
|
+
- A new `page.tsx` is added
|
|
386
|
+
- A `page.tsx` is deleted
|
|
387
|
+
- The dev server starts
|