@caddis/cli 0.0.0 → 0.1.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 +21 -0
- package/README.md +150 -1
- package/bundles/antigravity-plugin/agents/code-reviewer.md +47 -0
- package/bundles/antigravity-plugin/agents/preflight.md +53 -0
- package/bundles/antigravity-plugin/guard_agy.py +338 -0
- package/bundles/antigravity-plugin/hooks.json +36 -0
- package/bundles/antigravity-plugin/mcp_config.json +8 -0
- package/bundles/antigravity-plugin/mcp_ping_server.py +55 -0
- package/bundles/antigravity-plugin/plugin.json +5 -0
- package/bundles/antigravity-plugin/session_end_agy.py +57 -0
- package/bundles/antigravity-plugin/skills/_registry.md +115 -0
- package/bundles/antigravity-plugin/skills/add-rules/SKILL.md +45 -0
- package/bundles/antigravity-plugin/skills/api-design/SKILL.md +249 -0
- package/bundles/antigravity-plugin/skills/backend-development/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/best-practices/SKILL.md +500 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +263 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +326 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +245 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/anti-patterns.md +571 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/before-after-examples.md +1114 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/best-practices-guide.md +513 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/common-workflows.md +692 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/prompt-patterns.md +547 -0
- package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +57 -0
- package/bundles/antigravity-plugin/skills/ci-cd-pipeline/SKILL.md +315 -0
- package/bundles/antigravity-plugin/skills/code-documentation/SKILL.md +271 -0
- package/bundles/antigravity-plugin/skills/code-review/SKILL.md +122 -0
- package/bundles/antigravity-plugin/skills/codebase-audit/SKILL.md +204 -0
- package/bundles/antigravity-plugin/skills/context-curator/SKILL.md +157 -0
- package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +40 -0
- package/bundles/antigravity-plugin/skills/css-architecture/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
- package/bundles/antigravity-plugin/skills/database-design/SKILL.md +177 -0
- package/bundles/antigravity-plugin/skills/db-diagram/SKILL.md +148 -0
- package/bundles/antigravity-plugin/skills/db-diagram/scripts/sql_to_graph.py +1212 -0
- package/bundles/antigravity-plugin/skills/digress/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/draw-io/SKILL.md +162 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/aws-icons.md +677 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/layout-guidelines.md +142 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/troubleshooting.md +118 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/workflows.md +103 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/convert-drawio-to-png.sh +25 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/find_aws_icon.py +79 -0
- package/bundles/antigravity-plugin/skills/error-handling/SKILL.md +260 -0
- package/bundles/antigravity-plugin/skills/excalidraw-db/SKILL.md +38 -0
- package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +300 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +198 -0
- package/bundles/antigravity-plugin/skills/frontend-design/SKILL.md +193 -0
- package/bundles/antigravity-plugin/skills/frontend-design/references/my-tech-stack.md +127 -0
- package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +195 -0
- package/bundles/antigravity-plugin/skills/git-commit/SKILL.md +285 -0
- package/bundles/antigravity-plugin/skills/golden-plan/SKILL.md +577 -0
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +100 -0
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +114 -0
- package/bundles/antigravity-plugin/skills/javascript-typescript/SKILL.md +142 -0
- package/bundles/antigravity-plugin/skills/kb/SKILL.md +60 -0
- package/bundles/antigravity-plugin/skills/mermaid-db/SKILL.md +33 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/SKILL.md +236 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/advanced-features.md +556 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/architecture-diagrams.md +192 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/c4-diagrams.md +410 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/class-diagrams.md +361 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/erd-diagrams.md +510 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/flowcharts.md +450 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/sequence-diagrams.md +394 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/troubleshooting.md +335 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/workflows.md +418 -0
- package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +68 -0
- package/bundles/antigravity-plugin/skills/mockup/SKILL.md +242 -0
- package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +243 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/canvas-utils.ts +171 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/dot-field.template.tsx +203 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/flow-field.template.tsx +263 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/node-shape.template.tsx +261 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/shape-sampler.ts +281 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple-morph.template.tsx +167 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple.template.tsx +175 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/trail-ghost.template.tsx +266 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/usage-examples.md +320 -0
- package/bundles/antigravity-plugin/skills/playwright/API_REFERENCE.md +653 -0
- package/bundles/antigravity-plugin/skills/playwright/SKILL.md +454 -0
- package/bundles/antigravity-plugin/skills/playwright/lib/helpers.js +441 -0
- package/bundles/antigravity-plugin/skills/playwright/package.json +26 -0
- package/bundles/antigravity-plugin/skills/playwright/run.js +228 -0
- package/bundles/antigravity-plugin/skills/prd/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/preflight/SKILL.md +435 -0
- package/bundles/antigravity-plugin/skills/python/SKILL.md +388 -0
- package/bundles/antigravity-plugin/skills/react-best-practices/SKILL.md +269 -0
- package/bundles/antigravity-plugin/skills/react-dev/README.md +404 -0
- package/bundles/antigravity-plugin/skills/react-dev/SKILL.md +459 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/generic-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/server-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/event-handlers.md +574 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/hooks.md +456 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-19-patterns.md +638 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-router.md +1002 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/tanstack-router.md +587 -0
- package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +486 -0
- package/bundles/antigravity-plugin/skills/resume/SKILL.md +36 -0
- package/bundles/antigravity-plugin/skills/security-review/SKILL.md +196 -0
- package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +103 -0
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +102 -0
- package/bundles/antigravity-plugin/skills/skill-creator/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/SKILL.md +491 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/analyzer.md +274 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/comparator.md +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/grader.md +223 -0
- package/bundles/antigravity-plugin/skills/skill-creator/assets/eval_review.html +146 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/bundles/antigravity-plugin/skills/skill-creator/references/schemas.md +430 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/__init__.py +0 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/generate_report.py +326 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/improve_description.py +247 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/package_skill.py +136 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_eval.py +310 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_loop.py +328 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/utils.py +47 -0
- package/bundles/antigravity-plugin/skills/sql/SKILL.md +321 -0
- package/bundles/antigravity-plugin/skills/tdd/SKILL.md +37 -0
- package/bundles/antigravity-plugin/skills/tdd-workflow/SKILL.md +188 -0
- package/bundles/antigravity-plugin/skills/technical-writing/SKILL.md +286 -0
- package/bundles/antigravity-plugin/skills/test-strategy/SKILL.md +155 -0
- package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +84 -0
- package/bundles/antigravity-plugin/skills/ui-review/SKILL.md +176 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/framework-fixes.md +471 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/visual-checklist.md +236 -0
- package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +77 -0
- package/bundles/antigravity-plugin/skills/use-model/SKILL.md +64 -0
- package/bundles/antigravity-plugin/skills/using-git-worktrees/SKILL.md +217 -0
- package/bundles/antigravity-plugin/skills/version/SKILL.md +18 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/SKILL.md +732 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/SKILL.md +97 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/console_logging.py +35 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/scripts/with_server.py +106 -0
- package/bundles/antigravity-plugin/skills/windows-deployment/SKILL.md +880 -0
- package/bundles/antigravity-plugin/skills/writing-plans/SKILL.md +384 -0
- package/bundles/antigravity-plugin/statusline-command-agy.sh +91 -0
- package/bundles/antigravity-plugin/warm_start_agy.py +149 -0
- package/bundles/manifest.json +6 -0
- package/dist/cli.js +5363 -0
- package/package.json +61 -4
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
# TanStack Router TypeScript Patterns
|
|
2
|
+
|
|
3
|
+
TanStack Router provides full type safety for routes, params, search params, and loader data.
|
|
4
|
+
|
|
5
|
+
## Basic Route Definition
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { createRoute, createRootRoute } from '@tanstack/react-router';
|
|
9
|
+
|
|
10
|
+
// Root route
|
|
11
|
+
const rootRoute = createRootRoute({
|
|
12
|
+
component: RootLayout,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Basic route
|
|
16
|
+
const indexRoute = createRoute({
|
|
17
|
+
getParentRoute: () => rootRoute,
|
|
18
|
+
path: '/',
|
|
19
|
+
component: HomePage,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Route tree
|
|
23
|
+
const routeTree = rootRoute.addChildren([indexRoute]);
|
|
24
|
+
|
|
25
|
+
// Router
|
|
26
|
+
const router = createRouter({ routeTree });
|
|
27
|
+
|
|
28
|
+
// Type for router - use in app
|
|
29
|
+
type Router = typeof router;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Route with Params
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createRoute } from '@tanstack/react-router';
|
|
36
|
+
import { useParams } from '@tanstack/react-router';
|
|
37
|
+
|
|
38
|
+
const userRoute = createRoute({
|
|
39
|
+
getParentRoute: () => rootRoute,
|
|
40
|
+
path: '/users/$userId',
|
|
41
|
+
component: UserPage,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function UserPage() {
|
|
45
|
+
const { userId } = useParams({ from: userRoute.id });
|
|
46
|
+
// userId is typed as string
|
|
47
|
+
|
|
48
|
+
return <div>User ID: {userId}</div>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Multiple params
|
|
52
|
+
const postRoute = createRoute({
|
|
53
|
+
getParentRoute: () => rootRoute,
|
|
54
|
+
path: '/users/$userId/posts/$postId',
|
|
55
|
+
component: PostPage,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function PostPage() {
|
|
59
|
+
const { userId, postId } = useParams({ from: postRoute.id });
|
|
60
|
+
// Both typed as string
|
|
61
|
+
|
|
62
|
+
return <div>Post {postId} by user {userId}</div>;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Search Params with Validation
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { z } from 'zod';
|
|
70
|
+
|
|
71
|
+
const productsRoute = createRoute({
|
|
72
|
+
getParentRoute: () => rootRoute,
|
|
73
|
+
path: '/products',
|
|
74
|
+
component: ProductsPage,
|
|
75
|
+
validateSearch: z.object({
|
|
76
|
+
category: z.string().optional(),
|
|
77
|
+
sortBy: z.enum(['price', 'name', 'rating']).default('name'),
|
|
78
|
+
page: z.number().int().positive().default(1),
|
|
79
|
+
perPage: z.number().int().positive().default(20),
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function ProductsPage() {
|
|
84
|
+
const search = useSearch({ from: productsRoute.id });
|
|
85
|
+
// search is typed from Zod schema:
|
|
86
|
+
// {
|
|
87
|
+
// category?: string;
|
|
88
|
+
// sortBy: 'price' | 'name' | 'rating';
|
|
89
|
+
// page: number;
|
|
90
|
+
// perPage: number;
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div>
|
|
95
|
+
Category: {search.category || 'All'}
|
|
96
|
+
Sort by: {search.sortBy}
|
|
97
|
+
Page: {search.page}
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Loader Data
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
type User = {
|
|
107
|
+
id: string;
|
|
108
|
+
name: string;
|
|
109
|
+
email: string;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const userRoute = createRoute({
|
|
113
|
+
getParentRoute: () => rootRoute,
|
|
114
|
+
path: '/users/$userId',
|
|
115
|
+
component: UserPage,
|
|
116
|
+
loader: async ({ params }) => {
|
|
117
|
+
const user = await fetchUser(params.userId);
|
|
118
|
+
return { user };
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
function UserPage() {
|
|
123
|
+
const { user } = useLoaderData({ from: userRoute.id });
|
|
124
|
+
// user typed as User
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div>
|
|
128
|
+
<h1>{user.name}</h1>
|
|
129
|
+
<p>{user.email}</p>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function fetchUser(id: string): Promise<User> {
|
|
135
|
+
const res = await fetch(`/api/users/${id}`);
|
|
136
|
+
return res.json();
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Loader with Search Params
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const productsRoute = createRoute({
|
|
144
|
+
getParentRoute: () => rootRoute,
|
|
145
|
+
path: '/products',
|
|
146
|
+
component: ProductsPage,
|
|
147
|
+
validateSearch: z.object({
|
|
148
|
+
category: z.string().optional(),
|
|
149
|
+
page: z.number().default(1),
|
|
150
|
+
}),
|
|
151
|
+
loader: async ({ search }) => {
|
|
152
|
+
// search is typed from validateSearch
|
|
153
|
+
const products = await fetchProducts({
|
|
154
|
+
category: search.category,
|
|
155
|
+
page: search.page,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return { products };
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
function ProductsPage() {
|
|
163
|
+
const { products } = useLoaderData({ from: productsRoute.id });
|
|
164
|
+
const search = useSearch({ from: productsRoute.id });
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div>
|
|
168
|
+
<h1>Products - {search.category || 'All'}</h1>
|
|
169
|
+
<ul>
|
|
170
|
+
{products.map((p) => (
|
|
171
|
+
<li key={p.id}>{p.name}</li>
|
|
172
|
+
))}
|
|
173
|
+
</ul>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Type-Safe Navigation
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { useNavigate, Link } from '@tanstack/react-router';
|
|
183
|
+
|
|
184
|
+
function ProductList() {
|
|
185
|
+
const navigate = useNavigate();
|
|
186
|
+
|
|
187
|
+
const goToProduct = (productId: string) => {
|
|
188
|
+
navigate({
|
|
189
|
+
to: '/products/$productId',
|
|
190
|
+
params: { productId }, // Type-checked
|
|
191
|
+
search: { tab: 'reviews' }, // Type-checked against validateSearch
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div>
|
|
197
|
+
{products.map((product) => (
|
|
198
|
+
<Link
|
|
199
|
+
key={product.id}
|
|
200
|
+
to="/products/$productId"
|
|
201
|
+
params={{ productId: product.id }}
|
|
202
|
+
search={{ tab: 'details' }}
|
|
203
|
+
>
|
|
204
|
+
{product.name}
|
|
205
|
+
</Link>
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Search Params Manipulation
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { useNavigate, useSearch } from '@tanstack/react-router';
|
|
216
|
+
|
|
217
|
+
const productsRoute = createRoute({
|
|
218
|
+
getParentRoute: () => rootRoute,
|
|
219
|
+
path: '/products',
|
|
220
|
+
validateSearch: z.object({
|
|
221
|
+
category: z.string().optional(),
|
|
222
|
+
sortBy: z.enum(['price', 'name']).default('name'),
|
|
223
|
+
page: z.number().default(1),
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
function ProductFilters() {
|
|
228
|
+
const navigate = useNavigate({ from: productsRoute.id });
|
|
229
|
+
const search = useSearch({ from: productsRoute.id });
|
|
230
|
+
|
|
231
|
+
const updateCategory = (category: string) => {
|
|
232
|
+
navigate({
|
|
233
|
+
search: (prev) => ({
|
|
234
|
+
...prev,
|
|
235
|
+
category, // Type-checked
|
|
236
|
+
page: 1, // Reset page
|
|
237
|
+
}),
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const updateSort = (sortBy: 'price' | 'name') => {
|
|
242
|
+
navigate({
|
|
243
|
+
search: (prev) => ({ ...prev, sortBy }),
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<div>
|
|
249
|
+
<select value={search.category || ''} onChange={(e) => updateCategory(e.target.value)}>
|
|
250
|
+
<option value="">All Categories</option>
|
|
251
|
+
<option value="electronics">Electronics</option>
|
|
252
|
+
<option value="books">Books</option>
|
|
253
|
+
</select>
|
|
254
|
+
|
|
255
|
+
<select value={search.sortBy} onChange={(e) => updateSort(e.target.value as 'price' | 'name')}>
|
|
256
|
+
<option value="name">Name</option>
|
|
257
|
+
<option value="price">Price</option>
|
|
258
|
+
</select>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Nested Routes
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const usersRoute = createRoute({
|
|
268
|
+
getParentRoute: () => rootRoute,
|
|
269
|
+
path: '/users',
|
|
270
|
+
component: UsersLayout,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const usersIndexRoute = createRoute({
|
|
274
|
+
getParentRoute: () => usersRoute,
|
|
275
|
+
path: '/',
|
|
276
|
+
component: UsersList,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const userDetailRoute = createRoute({
|
|
280
|
+
getParentRoute: () => usersRoute,
|
|
281
|
+
path: '$userId',
|
|
282
|
+
component: UserDetail,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const routeTree = rootRoute.addChildren([
|
|
286
|
+
usersRoute.addChildren([
|
|
287
|
+
usersIndexRoute,
|
|
288
|
+
userDetailRoute,
|
|
289
|
+
]),
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
// Layout component with outlet
|
|
293
|
+
function UsersLayout() {
|
|
294
|
+
return (
|
|
295
|
+
<div>
|
|
296
|
+
<h1>Users</h1>
|
|
297
|
+
<Outlet /> {/* Renders child route */}
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Before Load Hook
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
const protectedRoute = createRoute({
|
|
307
|
+
getParentRoute: () => rootRoute,
|
|
308
|
+
path: '/dashboard',
|
|
309
|
+
beforeLoad: async ({ location }) => {
|
|
310
|
+
const isAuthenticated = await checkAuth();
|
|
311
|
+
|
|
312
|
+
if (!isAuthenticated) {
|
|
313
|
+
throw redirect({
|
|
314
|
+
to: '/login',
|
|
315
|
+
search: { redirect: location.href },
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { user: await getCurrentUser() };
|
|
320
|
+
},
|
|
321
|
+
component: Dashboard,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
function Dashboard() {
|
|
325
|
+
const { user } = useLoaderData({ from: protectedRoute.id });
|
|
326
|
+
// user available from beforeLoad
|
|
327
|
+
|
|
328
|
+
return <div>Welcome, {user.name}</div>;
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Error Handling
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const userRoute = createRoute({
|
|
336
|
+
getParentRoute: () => rootRoute,
|
|
337
|
+
path: '/users/$userId',
|
|
338
|
+
component: UserPage,
|
|
339
|
+
errorComponent: UserErrorPage,
|
|
340
|
+
loader: async ({ params }) => {
|
|
341
|
+
const user = await fetchUser(params.userId);
|
|
342
|
+
if (!user) {
|
|
343
|
+
throw new Error('User not found');
|
|
344
|
+
}
|
|
345
|
+
return { user };
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
function UserErrorPage({ error }: { error: Error }) {
|
|
350
|
+
return (
|
|
351
|
+
<div>
|
|
352
|
+
<h1>Error</h1>
|
|
353
|
+
<p>{error.message}</p>
|
|
354
|
+
<Link to="/users">Back to users</Link>
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Pending Component
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
const userRoute = createRoute({
|
|
364
|
+
getParentRoute: () => rootRoute,
|
|
365
|
+
path: '/users/$userId',
|
|
366
|
+
component: UserPage,
|
|
367
|
+
pendingComponent: UserPageSkeleton,
|
|
368
|
+
loader: async ({ params }) => {
|
|
369
|
+
const user = await fetchUser(params.userId);
|
|
370
|
+
return { user };
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
function UserPageSkeleton() {
|
|
375
|
+
return (
|
|
376
|
+
<div>
|
|
377
|
+
<div className="skeleton-header" />
|
|
378
|
+
<div className="skeleton-body" />
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Router Context
|
|
385
|
+
|
|
386
|
+
Share data across all routes.
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
type RouterContext = {
|
|
390
|
+
auth: { user: User | null };
|
|
391
|
+
queryClient: QueryClient;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const rootRoute = createRootRoute<RouterContext>({
|
|
395
|
+
component: RootLayout,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const router = createRouter({
|
|
399
|
+
routeTree,
|
|
400
|
+
context: {
|
|
401
|
+
auth: { user: null },
|
|
402
|
+
queryClient: new QueryClient(),
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Access in route
|
|
407
|
+
const userRoute = createRoute({
|
|
408
|
+
getParentRoute: () => rootRoute,
|
|
409
|
+
path: '/users/$userId',
|
|
410
|
+
beforeLoad: ({ context }) => {
|
|
411
|
+
// context typed as RouterContext
|
|
412
|
+
console.log(context.auth.user);
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Route Masks
|
|
418
|
+
|
|
419
|
+
Hide actual URL structure.
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const userRoute = createRoute({
|
|
423
|
+
getParentRoute: () => rootRoute,
|
|
424
|
+
path: '/users/$userId',
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Navigate with mask
|
|
428
|
+
navigate({
|
|
429
|
+
to: '/users/$userId',
|
|
430
|
+
params: { userId: '123' },
|
|
431
|
+
mask: {
|
|
432
|
+
to: '/profile',
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// URL shows /profile but renders /users/123
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Search Param Middleware
|
|
440
|
+
|
|
441
|
+
Transform search params before validation.
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const productsRoute = createRoute({
|
|
445
|
+
getParentRoute: () => rootRoute,
|
|
446
|
+
path: '/products',
|
|
447
|
+
validateSearch: z.object({
|
|
448
|
+
tags: z.array(z.string()).default([]),
|
|
449
|
+
}),
|
|
450
|
+
loaderDeps: ({ search }) => ({ tags: search.tags }),
|
|
451
|
+
loader: async ({ deps }) => {
|
|
452
|
+
const products = await fetchProducts({ tags: deps.tags });
|
|
453
|
+
return { products };
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// URL: /products?tags=electronics&tags=sale
|
|
458
|
+
// search.tags = ['electronics', 'sale']
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Type Helpers
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import type { RouteIds, RouteById } from '@tanstack/react-router';
|
|
465
|
+
|
|
466
|
+
// Get all route IDs
|
|
467
|
+
type AllRouteIds = RouteIds<typeof router>;
|
|
468
|
+
|
|
469
|
+
// Get specific route type
|
|
470
|
+
type UserRoute = RouteById<typeof router, '/users/$userId'>;
|
|
471
|
+
|
|
472
|
+
// Extract params type
|
|
473
|
+
type UserParams = UserRoute['types']['allParams'];
|
|
474
|
+
|
|
475
|
+
// Extract search type
|
|
476
|
+
type UserSearch = UserRoute['types']['fullSearchSchema'];
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Preloading Routes
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import { useRouter } from '@tanstack/react-router';
|
|
483
|
+
|
|
484
|
+
function ProductCard({ productId }: { productId: string }) {
|
|
485
|
+
const router = useRouter();
|
|
486
|
+
|
|
487
|
+
const preloadProduct = () => {
|
|
488
|
+
router.preloadRoute({
|
|
489
|
+
to: '/products/$productId',
|
|
490
|
+
params: { productId },
|
|
491
|
+
});
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<Link
|
|
496
|
+
to="/products/$productId"
|
|
497
|
+
params={{ productId }}
|
|
498
|
+
onMouseEnter={preloadProduct} // Preload on hover
|
|
499
|
+
>
|
|
500
|
+
View Product
|
|
501
|
+
</Link>
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Route Matching
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
import { useMatches, useMatch } from '@tanstack/react-router';
|
|
510
|
+
|
|
511
|
+
function Navigation() {
|
|
512
|
+
const matches = useMatches();
|
|
513
|
+
// Array of all matched routes
|
|
514
|
+
|
|
515
|
+
const userMatch = useMatch({ from: userRoute.id, shouldThrow: false });
|
|
516
|
+
// userMatch is typed, null if not matched
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
<nav>
|
|
520
|
+
{matches.map((match) => (
|
|
521
|
+
<span key={match.id}>{match.pathname}</span>
|
|
522
|
+
))}
|
|
523
|
+
</nav>
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## File-Based Routing (Code Generation)
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// routes/__root.tsx
|
|
532
|
+
export const Route = createRootRoute({
|
|
533
|
+
component: RootLayout,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// routes/index.tsx
|
|
537
|
+
export const Route = createFileRoute('/')({
|
|
538
|
+
component: HomePage,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// routes/users/$userId.tsx
|
|
542
|
+
export const Route = createFileRoute('/users/$userId')({
|
|
543
|
+
component: UserPage,
|
|
544
|
+
loader: async ({ params }) => {
|
|
545
|
+
const user = await fetchUser(params.userId);
|
|
546
|
+
return { user };
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Generate route tree with CLI
|
|
551
|
+
// npm run generate-routes
|
|
552
|
+
|
|
553
|
+
// Import generated routes
|
|
554
|
+
import { routeTree } from './routeTree.gen';
|
|
555
|
+
const router = createRouter({ routeTree });
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Integration with React Query
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
import { queryOptions, useQuery } from '@tanstack/react-query';
|
|
562
|
+
|
|
563
|
+
const userQueryOptions = (userId: string) =>
|
|
564
|
+
queryOptions({
|
|
565
|
+
queryKey: ['user', userId],
|
|
566
|
+
queryFn: () => fetchUser(userId),
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const userRoute = createRoute({
|
|
570
|
+
getParentRoute: () => rootRoute,
|
|
571
|
+
path: '/users/$userId',
|
|
572
|
+
loader: ({ context, params }) => {
|
|
573
|
+
// Prefetch with React Query
|
|
574
|
+
return context.queryClient.ensureQueryData(userQueryOptions(params.userId));
|
|
575
|
+
},
|
|
576
|
+
component: UserPage,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
function UserPage() {
|
|
580
|
+
const { userId } = useParams({ from: userRoute.id });
|
|
581
|
+
|
|
582
|
+
// Use same query options in component
|
|
583
|
+
const { data: user } = useQuery(userQueryOptions(userId));
|
|
584
|
+
|
|
585
|
+
return <div>{user?.name}</div>;
|
|
586
|
+
}
|
|
587
|
+
```
|