@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,307 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: redirectTo
|
|
3
|
+
description: Guard function that redirects to another page
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/hooks/guards/redirect-to.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# redirectTo
|
|
9
|
+
|
|
10
|
+
Returns a guard result that redirects the user to a different URL. Used primarily for authentication flows and access control redirection.
|
|
11
|
+
|
|
12
|
+
## Signature
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
function redirectTo(to: string): GuardResult;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Parameters
|
|
19
|
+
|
|
20
|
+
### `to`
|
|
21
|
+
|
|
22
|
+
- **Type:** `string`
|
|
23
|
+
- **Required:** Yes
|
|
24
|
+
- **Description:** Target URL path to redirect to
|
|
25
|
+
|
|
26
|
+
## Return Type
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
{
|
|
30
|
+
type: "redirect";
|
|
31
|
+
to: string;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { redirectTo, type Guard } from "@tailor-platform/app-shell";
|
|
39
|
+
|
|
40
|
+
const redirectToLogin: Guard = () => {
|
|
41
|
+
return redirectTo("/login");
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## When to Use
|
|
46
|
+
|
|
47
|
+
Use `redirectTo()` when:
|
|
48
|
+
|
|
49
|
+
- ✅ User needs authentication (redirect to login)
|
|
50
|
+
- ✅ User needs to complete setup (redirect to onboarding)
|
|
51
|
+
- ✅ Access denied but you want to show why (redirect to error page)
|
|
52
|
+
- ✅ You want explicit navigation vs silent denial
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
55
|
+
|
|
56
|
+
### Require Authentication
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const requireAuth: Guard = ({ context }) => {
|
|
60
|
+
if (!context.currentUser) {
|
|
61
|
+
return redirectTo("/login");
|
|
62
|
+
}
|
|
63
|
+
return pass();
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Subscription Required
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const requirePremium: Guard = ({ context }) => {
|
|
71
|
+
if (context.currentUser?.plan !== "premium") {
|
|
72
|
+
return redirectTo("/upgrade");
|
|
73
|
+
}
|
|
74
|
+
return pass();
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Complete Profile
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const requireProfile: Guard = ({ context }) => {
|
|
82
|
+
if (!context.currentUser?.profileComplete) {
|
|
83
|
+
return redirectTo("/complete-profile");
|
|
84
|
+
}
|
|
85
|
+
return pass();
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Email Verification
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const requireVerifiedEmail: Guard = ({ context }) => {
|
|
93
|
+
if (!context.currentUser?.emailVerified) {
|
|
94
|
+
return redirectTo("/verify-email");
|
|
95
|
+
}
|
|
96
|
+
return pass();
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Terms Acceptance
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const requireTerms: Guard = ({ context }) => {
|
|
104
|
+
if (!context.currentUser?.termsAccepted) {
|
|
105
|
+
return redirectTo("/accept-terms");
|
|
106
|
+
}
|
|
107
|
+
return pass();
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Role-Based Redirect
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const redirectNonAdmins: Guard = ({ context }) => {
|
|
115
|
+
if (context.currentUser?.role !== "admin") {
|
|
116
|
+
return redirectTo("/access-denied");
|
|
117
|
+
}
|
|
118
|
+
return pass();
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Conditional Redirect
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const conditionalRedirect: Guard = ({ context }) => {
|
|
126
|
+
const user = context.currentUser;
|
|
127
|
+
|
|
128
|
+
if (!user) {
|
|
129
|
+
return redirectTo("/login");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (user.suspended) {
|
|
133
|
+
return redirectTo("/account-suspended");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!user.onboardingComplete) {
|
|
137
|
+
return redirectTo("/onboarding");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return pass();
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Async Redirect
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const asyncRedirect: Guard = async ({ context }) => {
|
|
148
|
+
const status = await fetch("/api/user-status");
|
|
149
|
+
const { active } = await status.json();
|
|
150
|
+
|
|
151
|
+
if (!active) {
|
|
152
|
+
return redirectTo("/reactivate");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return pass();
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## vs hidden()
|
|
160
|
+
|
|
161
|
+
| Aspect | redirectTo() | hidden() |
|
|
162
|
+
| --------------------- | ------------------------ | ----------------------- |
|
|
163
|
+
| **Result** | Redirect to URL | 404 Not Found |
|
|
164
|
+
| **User Experience** | Clear (shows why denied) | Silent (no explanation) |
|
|
165
|
+
| **URL Changes** | Yes | No |
|
|
166
|
+
| **Navigation Impact** | No effect | Hides from navigation |
|
|
167
|
+
| **Use Case** | Login flows, errors | Permission hiding |
|
|
168
|
+
|
|
169
|
+
### Example: When to Use Each
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// Use redirectTo() - User needs to take action
|
|
173
|
+
const requireAuth: Guard = ({ context }) => {
|
|
174
|
+
if (!context.currentUser) {
|
|
175
|
+
return redirectTo("/login"); // Clear: "You need to log in"
|
|
176
|
+
}
|
|
177
|
+
return pass();
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Use hidden() - Feature doesn't exist for user
|
|
181
|
+
const adminFeature: Guard = ({ context }) => {
|
|
182
|
+
if (!isAdmin(context)) {
|
|
183
|
+
return hidden(); // Silent: User doesn't even know this exists
|
|
184
|
+
}
|
|
185
|
+
return pass();
|
|
186
|
+
};
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Redirect Chains
|
|
190
|
+
|
|
191
|
+
Guards can create redirect chains:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const guards = [
|
|
195
|
+
// 1. Check authentication
|
|
196
|
+
({ context }) => {
|
|
197
|
+
if (!context.currentUser) {
|
|
198
|
+
return redirectTo("/login");
|
|
199
|
+
}
|
|
200
|
+
return pass();
|
|
201
|
+
},
|
|
202
|
+
// 2. Check onboarding (only runs if authenticated)
|
|
203
|
+
({ context }) => {
|
|
204
|
+
if (!context.currentUser?.onboardingComplete) {
|
|
205
|
+
return redirectTo("/onboarding");
|
|
206
|
+
}
|
|
207
|
+
return pass();
|
|
208
|
+
},
|
|
209
|
+
// 3. Check permissions (only runs if onboarded)
|
|
210
|
+
({ context }) => {
|
|
211
|
+
if (!context.currentUser?.hasAccess) {
|
|
212
|
+
return redirectTo("/upgrade");
|
|
213
|
+
}
|
|
214
|
+
return pass();
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Module vs Resource Redirects
|
|
220
|
+
|
|
221
|
+
### Module-Level
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const dashboardModule = defineModule({
|
|
225
|
+
path: "dashboard",
|
|
226
|
+
component: DashboardPage,
|
|
227
|
+
guards: [
|
|
228
|
+
({ context }) => {
|
|
229
|
+
if (!context.currentUser) {
|
|
230
|
+
return redirectTo("/login");
|
|
231
|
+
}
|
|
232
|
+
return pass();
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Accessing /dashboard without auth → Redirects to /login
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Resource-Level
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const settingsResource = defineResource({
|
|
244
|
+
path: "settings",
|
|
245
|
+
component: SettingsPage,
|
|
246
|
+
guards: [
|
|
247
|
+
({ context }) => {
|
|
248
|
+
if (!context.currentUser?.emailVerified) {
|
|
249
|
+
return redirectTo("/verify-email");
|
|
250
|
+
}
|
|
251
|
+
return pass();
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Accessing /settings without verified email → Redirects to /verify-email
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Limitations
|
|
260
|
+
|
|
261
|
+
### Not Supported in WithGuard
|
|
262
|
+
|
|
263
|
+
`redirectTo()` does **NOT** work with the `WithGuard` component:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// ❌ NOT SUPPORTED
|
|
267
|
+
<WithGuard
|
|
268
|
+
guards={[
|
|
269
|
+
() => redirectTo("/login") // This won't work!
|
|
270
|
+
]}
|
|
271
|
+
>
|
|
272
|
+
<Content />
|
|
273
|
+
</WithGuard>
|
|
274
|
+
|
|
275
|
+
// ✅ Use hidden() with fallback instead
|
|
276
|
+
<WithGuard
|
|
277
|
+
guards={[() => hidden()]}
|
|
278
|
+
fallback={<Navigate to="/login" />}
|
|
279
|
+
>
|
|
280
|
+
<Content />
|
|
281
|
+
</WithGuard>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Preserving Original URL
|
|
285
|
+
|
|
286
|
+
To preserve the original URL after redirect:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const requireAuth: Guard = ({ context }) => {
|
|
290
|
+
if (!context.currentUser) {
|
|
291
|
+
const currentPath = window.location.pathname;
|
|
292
|
+
return redirectTo(`/login?redirect=${encodeURIComponent(currentPath)}`);
|
|
293
|
+
}
|
|
294
|
+
return pass();
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// After login, redirect back:
|
|
298
|
+
// const redirect = searchParams.get('redirect');
|
|
299
|
+
// if (redirect) navigate(redirect);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Related
|
|
303
|
+
|
|
304
|
+
- [pass()](./pass.md) - Allow access and continue
|
|
305
|
+
- [hidden()](./hidden.md) - Deny access and return 404
|
|
306
|
+
- [Guards Overview](./overview.md) - Complete guard system guide
|
|
307
|
+
- [WithGuard Component](../../components/with-guard.md) - Component-level guards (doesn't support redirectTo)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: useLocation
|
|
3
|
+
description: Hook to access current location object with pathname, search, and state
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/hooks/router/use-location.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# useLocation
|
|
9
|
+
|
|
10
|
+
React Router hook to access the current location object. Re-exported from `react-router` for convenience.
|
|
11
|
+
|
|
12
|
+
## Signature
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const useLocation: () => Location;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Return Type
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
interface Location {
|
|
22
|
+
pathname: string; // Current path: "/products/123"
|
|
23
|
+
search: string; // Query string: "?sort=name&page=2"
|
|
24
|
+
hash: string; // Hash: "#details"
|
|
25
|
+
state: any; // State passed via navigate()
|
|
26
|
+
key: string; // Unique key for this location
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Access Current Path
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { useLocation } from "@tailor-platform/app-shell";
|
|
36
|
+
|
|
37
|
+
function Breadcrumb() {
|
|
38
|
+
const location = useLocation();
|
|
39
|
+
|
|
40
|
+
return <div>Current path: {location.pathname}</div>;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Check Active Route
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
function NavItem({ path, label }: { path: string; label: string }) {
|
|
48
|
+
const location = useLocation();
|
|
49
|
+
const isActive = location.pathname === path;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<a
|
|
53
|
+
href={path}
|
|
54
|
+
className={isActive ? "active" : ""}
|
|
55
|
+
>
|
|
56
|
+
{label}
|
|
57
|
+
</a>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Access Query Parameters
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function ProductsList() {
|
|
66
|
+
const location = useLocation();
|
|
67
|
+
const searchParams = new URLSearchParams(location.search);
|
|
68
|
+
|
|
69
|
+
const sort = searchParams.get("sort"); // "name"
|
|
70
|
+
const page = searchParams.get("page"); // "2"
|
|
71
|
+
|
|
72
|
+
return <div>Sort: {sort}, Page: {page}</div>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// URL: /products?sort=name&page=2
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Access Navigation State
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// Navigate with state
|
|
82
|
+
function ProductsList() {
|
|
83
|
+
const navigate = useNavigate();
|
|
84
|
+
|
|
85
|
+
const selectProduct = (id: string) => {
|
|
86
|
+
navigate(`/products/${id}`, {
|
|
87
|
+
state: { from: "list", scrollPosition: window.scrollY },
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Access state in target component
|
|
93
|
+
function ProductDetail() {
|
|
94
|
+
const location = useLocation();
|
|
95
|
+
const from = location.state?.from; // "list"
|
|
96
|
+
const scrollPos = location.state?.scrollPosition; // 450
|
|
97
|
+
|
|
98
|
+
return <div>Came from: {from}</div>;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Track Route Changes
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
function AnalyticsTracker() {
|
|
106
|
+
const location = useLocation();
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
// Track page view
|
|
110
|
+
analytics.pageView(location.pathname);
|
|
111
|
+
}, [location.pathname]);
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Preserve Query Params on Navigation
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
function FilterButton({ filter }: { filter: string }) {
|
|
121
|
+
const location = useLocation();
|
|
122
|
+
const navigate = useNavigate();
|
|
123
|
+
|
|
124
|
+
const applyFilter = () => {
|
|
125
|
+
const params = new URLSearchParams(location.search);
|
|
126
|
+
params.set("filter", filter);
|
|
127
|
+
navigate(`${location.pathname}?${params}`);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return <button onClick={applyFilter}>Apply Filter</button>;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Common Patterns
|
|
135
|
+
|
|
136
|
+
### Check Prefix Match
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
function isActiveRoute(pathname: string, prefix: string) {
|
|
140
|
+
return pathname.startsWith(prefix);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function NavItem({ path }: { path: string }) {
|
|
144
|
+
const location = useLocation();
|
|
145
|
+
const isActive = isActiveRoute(location.pathname, path);
|
|
146
|
+
|
|
147
|
+
return <a className={isActive ? "active" : ""}>{path}</a>;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Scroll to Hash
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
function ScrollToHash() {
|
|
155
|
+
const location = useLocation();
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (location.hash) {
|
|
159
|
+
const element = document.querySelector(location.hash);
|
|
160
|
+
element?.scrollIntoView({ behavior: "smooth" });
|
|
161
|
+
}
|
|
162
|
+
}, [location.hash]);
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Preserve Scroll Position
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
function Layout() {
|
|
172
|
+
const location = useLocation();
|
|
173
|
+
const scrollPositions = useRef<Record<string, number>>({});
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
// Save scroll position
|
|
177
|
+
scrollPositions.current[location.key] = window.scrollY;
|
|
178
|
+
|
|
179
|
+
// Restore scroll position
|
|
180
|
+
const savedPosition = scrollPositions.current[location.key];
|
|
181
|
+
if (savedPosition !== undefined) {
|
|
182
|
+
window.scrollTo(0, savedPosition);
|
|
183
|
+
}
|
|
184
|
+
}, [location]);
|
|
185
|
+
|
|
186
|
+
return <Outlet />;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## useSearchParams Alternative
|
|
191
|
+
|
|
192
|
+
For easier query parameter handling, use `useSearchParams`:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { useSearchParams } from "@tailor-platform/app-shell";
|
|
196
|
+
|
|
197
|
+
function ProductsList() {
|
|
198
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
199
|
+
|
|
200
|
+
const sort = searchParams.get("sort");
|
|
201
|
+
const page = searchParams.get("page");
|
|
202
|
+
|
|
203
|
+
const updateSort = (newSort: string) => {
|
|
204
|
+
setSearchParams({ sort: newSort, page });
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return <div>...</div>;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Important Notes
|
|
212
|
+
|
|
213
|
+
### Always Import from AppShell
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// ✅ Correct
|
|
217
|
+
import { useLocation } from "@tailor-platform/app-shell";
|
|
218
|
+
|
|
219
|
+
// ❌ Wrong - won't work with AppShell's router
|
|
220
|
+
import { useLocation } from "react-router";
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Location is Immutable
|
|
224
|
+
|
|
225
|
+
The location object is immutable. Don't try to modify it:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// ❌ Wrong
|
|
229
|
+
location.pathname = "/new-path";
|
|
230
|
+
|
|
231
|
+
// ✅ Correct - use navigate
|
|
232
|
+
navigate("/new-path");
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Related
|
|
236
|
+
|
|
237
|
+
- [useNavigate](./use-navigate.md) - Programmatic navigation
|
|
238
|
+
- [useParams](./use-params.md) - Access route parameters
|
|
239
|
+
- [useSearchParams](./use-navigate.md) - Query parameter management
|
|
240
|
+
- [Routing & Navigation](../../concepts/routing-navigation.md) - Navigation guide
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: useNavigate
|
|
3
|
+
description: Hook for programmatic navigation between routes
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/hooks/router/use-navigate.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# useNavigate
|
|
9
|
+
|
|
10
|
+
React Router hook for programmatic navigation. Re-exported from `react-router` for convenience.
|
|
11
|
+
|
|
12
|
+
## Signature
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const useNavigate: () => (to: string | number, options?: NavigateOptions) => void;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Navigate to Path
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { useNavigate } from "@tailor-platform/app-shell";
|
|
24
|
+
|
|
25
|
+
function MyComponent() {
|
|
26
|
+
const navigate = useNavigate();
|
|
27
|
+
|
|
28
|
+
const goToProducts = () => {
|
|
29
|
+
navigate("/products");
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return <button onClick={goToProducts}>View Products</button>;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Navigate with Dynamic Parameters
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
function ProductCard({ id }: { id: string }) {
|
|
40
|
+
const navigate = useNavigate();
|
|
41
|
+
|
|
42
|
+
const viewDetails = () => {
|
|
43
|
+
navigate(`/products/${id}`);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return <button onClick={viewDetails}>Details</button>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Navigate Back
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
function BackButton() {
|
|
54
|
+
const navigate = useNavigate();
|
|
55
|
+
|
|
56
|
+
return <button onClick={() => navigate(-1)}>Back</button>;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Navigate Forward
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
function ForwardButton() {
|
|
64
|
+
const navigate = useNavigate();
|
|
65
|
+
|
|
66
|
+
return <button onClick={() => navigate(1)}>Forward</button>;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Replace Current Entry
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
function LoginSuccess() {
|
|
74
|
+
const navigate = useNavigate();
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
// Replace current history entry (user can't go back to login)
|
|
78
|
+
navigate("/dashboard", { replace: true });
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
return <div>Redirecting...</div>;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Navigate with State
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
function ProductsList() {
|
|
89
|
+
const navigate = useNavigate();
|
|
90
|
+
|
|
91
|
+
const selectProduct = (product: Product) => {
|
|
92
|
+
navigate(`/products/${product.id}`, {
|
|
93
|
+
state: { from: "list" },
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return <ProductList onSelect={selectProduct} />;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Access state in target component
|
|
101
|
+
function ProductDetail() {
|
|
102
|
+
const location = useLocation();
|
|
103
|
+
const from = location.state?.from; // "list"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## NavigateOptions
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
interface NavigateOptions {
|
|
111
|
+
replace?: boolean; // Replace current entry instead of pushing
|
|
112
|
+
state?: any; // Pass state to target route
|
|
113
|
+
preventScrollReset?: boolean;
|
|
114
|
+
relative?: "route" | "path";
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Important Notes
|
|
119
|
+
|
|
120
|
+
### Import from AppShell
|
|
121
|
+
|
|
122
|
+
Always import from `@tailor-platform/app-shell`, not `react-router`:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// ✅ Correct
|
|
126
|
+
import { useNavigate } from "@tailor-platform/app-shell";
|
|
127
|
+
|
|
128
|
+
// ❌ Wrong - won't work with AppShell's router
|
|
129
|
+
import { useNavigate } from "react-router";
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This ensures you're using AppShell's router instance.
|
|
133
|
+
|
|
134
|
+
## Related
|
|
135
|
+
|
|
136
|
+
- [useParams](./use-params.md) - Access route parameters
|
|
137
|
+
- [useLocation](./use-location.md) - Access current location
|
|
138
|
+
- [Routing & Navigation](../../concepts/routing-navigation.md) - Navigation guide, including the `Navigate` component for declarative redirects
|