create-tsrouter-app 0.6.1 → 0.6.2
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/cli.js +4 -1
- package/dist/create-app.js +43 -72
- package/dist/environment.js +32 -0
- package/dist/mcp.js +3 -0
- package/package.json +2 -1
- package/src/cli.ts +5 -1
- package/src/create-app.ts +78 -79
- package/src/environment.ts +53 -0
- package/src/mcp.ts +3 -0
- package/templates/react/add-on/form/assets/src/components/demo.FormComponents.tsx +120 -0
- package/templates/react/add-on/form/assets/src/hooks/demo.form-context.ts +4 -0
- package/templates/react/add-on/form/assets/src/hooks/demo.form.ts +22 -0
- package/templates/react/add-on/form/assets/src/routes/demo.form.address.tsx.ejs +203 -0
- package/templates/react/add-on/form/assets/src/routes/demo.form.simple.tsx.ejs +79 -0
- package/templates/react/add-on/form/info.json +6 -2
- package/templates/react/add-on/form/package.json +2 -1
- package/templates/react/base/README.md.ejs +1 -1
- package/templates/react/example/tanchat/info.json +1 -1
- package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +310 -106
- package/templates/solid/add-on/form/package.json +1 -1
- package/tests/cra.test.ts +112 -0
- package/tests/snapshots/cra/cr-js-npm.json +34 -0
- package/tests/snapshots/cra/cr-ts-npm.json +35 -0
- package/tests/snapshots/cra/fr-ts-npm.json +35 -0
- package/tests/snapshots/cra/fr-ts-tw-npm.json +34 -0
- package/tests/test-utilities.ts +69 -0
- package/templates/react/add-on/form/assets/src/routes/demo.form.tsx.ejs +0 -62
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import { useAppForm } from '../hooks/demo.form'
|
|
4
|
+
|
|
5
|
+
<% if (codeRouter) { %>
|
|
6
|
+
import type { RootRoute } from '@tanstack/react-router'
|
|
7
|
+
<% } else { %>
|
|
8
|
+
export const Route = createFileRoute('/demo/form')({
|
|
9
|
+
component: AddressForm,
|
|
10
|
+
})
|
|
11
|
+
<% } %>
|
|
12
|
+
|
|
13
|
+
function AddressForm() {
|
|
14
|
+
const form = useAppForm({
|
|
15
|
+
defaultValues: {
|
|
16
|
+
fullName: '',
|
|
17
|
+
email: '',
|
|
18
|
+
address: {
|
|
19
|
+
street: '',
|
|
20
|
+
city: '',
|
|
21
|
+
state: '',
|
|
22
|
+
zipCode: '',
|
|
23
|
+
country: '',
|
|
24
|
+
},
|
|
25
|
+
phone: '',
|
|
26
|
+
},
|
|
27
|
+
validators: {
|
|
28
|
+
onBlur: ({ value }) => {
|
|
29
|
+
const errors = {
|
|
30
|
+
fields: {},
|
|
31
|
+
} as {
|
|
32
|
+
fields: Record<string, string>
|
|
33
|
+
}
|
|
34
|
+
if (value.fullName.trim().length === 0) {
|
|
35
|
+
errors.fields.fullName = 'Full name is required'
|
|
36
|
+
}
|
|
37
|
+
return errors
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
onSubmit: ({ value }) => {
|
|
41
|
+
console.log(value)
|
|
42
|
+
// Show success message
|
|
43
|
+
alert('Form submitted successfully!')
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
|
|
50
|
+
style={{
|
|
51
|
+
backgroundImage:
|
|
52
|
+
'radial-gradient(50% 50% at 5% 40%, #f4a460 0%, #8b4513 70%, #1a0f0a 100%)',
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
|
|
56
|
+
<form
|
|
57
|
+
onSubmit={(e) => {
|
|
58
|
+
e.preventDefault()
|
|
59
|
+
e.stopPropagation()
|
|
60
|
+
form.handleSubmit()
|
|
61
|
+
}}
|
|
62
|
+
className="space-y-6"
|
|
63
|
+
>
|
|
64
|
+
<form.AppField
|
|
65
|
+
name="fullName"
|
|
66
|
+
children={(field) => <field.TextField label="Full Name" />}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<form.AppField
|
|
70
|
+
name="email"
|
|
71
|
+
validators={{
|
|
72
|
+
onBlur: ({ value }) => {
|
|
73
|
+
if (!value || value.trim().length === 0) {
|
|
74
|
+
return 'Email is required'
|
|
75
|
+
}
|
|
76
|
+
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
|
|
77
|
+
return 'Invalid email address'
|
|
78
|
+
}
|
|
79
|
+
return undefined
|
|
80
|
+
},
|
|
81
|
+
}}
|
|
82
|
+
children={(field) => <field.TextField label="Email" />}
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
<form.AppField
|
|
86
|
+
name="address.street"
|
|
87
|
+
validators={{
|
|
88
|
+
onBlur: ({ value }) => {
|
|
89
|
+
if (!value || value.trim().length === 0) {
|
|
90
|
+
return 'Street address is required'
|
|
91
|
+
}
|
|
92
|
+
return undefined
|
|
93
|
+
},
|
|
94
|
+
}}
|
|
95
|
+
children={(field) => <field.TextField label="Street Address" />}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
99
|
+
<form.AppField
|
|
100
|
+
name="address.city"
|
|
101
|
+
validators={{
|
|
102
|
+
onBlur: ({ value }) => {
|
|
103
|
+
if (!value || value.trim().length === 0) {
|
|
104
|
+
return 'City is required'
|
|
105
|
+
}
|
|
106
|
+
return undefined
|
|
107
|
+
},
|
|
108
|
+
}}
|
|
109
|
+
children={(field) => <field.TextField label="City" />}
|
|
110
|
+
/>
|
|
111
|
+
<form.AppField
|
|
112
|
+
name="address.state"
|
|
113
|
+
validators={{
|
|
114
|
+
onBlur: ({ value }) => {
|
|
115
|
+
if (!value || value.trim().length === 0) {
|
|
116
|
+
return 'State is required'
|
|
117
|
+
}
|
|
118
|
+
return undefined
|
|
119
|
+
},
|
|
120
|
+
}}
|
|
121
|
+
children={(field) => <field.TextField label="State" />}
|
|
122
|
+
/>
|
|
123
|
+
<form.AppField
|
|
124
|
+
name="address.zipCode"
|
|
125
|
+
validators={{
|
|
126
|
+
onBlur: ({ value }) => {
|
|
127
|
+
if (!value || value.trim().length === 0) {
|
|
128
|
+
return 'Zip code is required'
|
|
129
|
+
}
|
|
130
|
+
if (!/^\d{5}(-\d{4})?$/.test(value)) {
|
|
131
|
+
return 'Invalid zip code format'
|
|
132
|
+
}
|
|
133
|
+
return undefined
|
|
134
|
+
},
|
|
135
|
+
}}
|
|
136
|
+
children={(field) => <field.TextField label="Zip Code" />}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<form.AppField
|
|
141
|
+
name="address.country"
|
|
142
|
+
validators={{
|
|
143
|
+
onBlur: ({ value }) => {
|
|
144
|
+
if (!value || value.trim().length === 0) {
|
|
145
|
+
return 'Country is required'
|
|
146
|
+
}
|
|
147
|
+
return undefined
|
|
148
|
+
},
|
|
149
|
+
}}
|
|
150
|
+
children={(field) => (
|
|
151
|
+
<field.Select label="Country">
|
|
152
|
+
<option value="">Select a country</option>
|
|
153
|
+
<option value="US">United States</option>
|
|
154
|
+
<option value="CA">Canada</option>
|
|
155
|
+
<option value="UK">United Kingdom</option>
|
|
156
|
+
<option value="AU">Australia</option>
|
|
157
|
+
<option value="DE">Germany</option>
|
|
158
|
+
<option value="FR">France</option>
|
|
159
|
+
<option value="JP">Japan</option>
|
|
160
|
+
</field.Select>
|
|
161
|
+
)}
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
<form.AppField
|
|
165
|
+
name="phone"
|
|
166
|
+
validators={{
|
|
167
|
+
onBlur: ({ value }) => {
|
|
168
|
+
if (!value || value.trim().length === 0) {
|
|
169
|
+
return 'Phone number is required'
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
!/^(\+\d{1,3})?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/.test(
|
|
173
|
+
value,
|
|
174
|
+
)
|
|
175
|
+
) {
|
|
176
|
+
return 'Invalid phone number format'
|
|
177
|
+
}
|
|
178
|
+
return undefined
|
|
179
|
+
},
|
|
180
|
+
}}
|
|
181
|
+
children={(field) => (
|
|
182
|
+
<field.TextField label="Phone" placeholder="123-456-7890" />
|
|
183
|
+
)}
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
<div className="flex justify-end">
|
|
187
|
+
<form.AppForm>
|
|
188
|
+
<form.SubscribeButton label="Submit" />
|
|
189
|
+
</form.AppForm>
|
|
190
|
+
</div>
|
|
191
|
+
</form>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
<% if (codeRouter) { %>
|
|
198
|
+
export default (parentRoute: RootRoute) => createRoute({
|
|
199
|
+
path: '/demo/form',
|
|
200
|
+
component: Addres,
|
|
201
|
+
getParentRoute: () => parentRoute,
|
|
202
|
+
})
|
|
203
|
+
<% } %>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/react-router'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { useAppForm } from '../hooks/demo.form'
|
|
5
|
+
|
|
6
|
+
<% if (codeRouter) { %>
|
|
7
|
+
import type { RootRoute } from '@tanstack/react-router'
|
|
8
|
+
<% } else { %>
|
|
9
|
+
export const Route = createFileRoute('/demo/form')({
|
|
10
|
+
component: SimpleForm,
|
|
11
|
+
})
|
|
12
|
+
<% } %>
|
|
13
|
+
|
|
14
|
+
const schema = z.object({
|
|
15
|
+
title: z.string().min(1, 'Title is required'),
|
|
16
|
+
description: z.string().min(1, 'Description is required'),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function SimpleForm() {
|
|
20
|
+
const form = useAppForm({
|
|
21
|
+
defaultValues: {
|
|
22
|
+
title: '',
|
|
23
|
+
description: '',
|
|
24
|
+
},
|
|
25
|
+
validators: {
|
|
26
|
+
onBlur: schema,
|
|
27
|
+
},
|
|
28
|
+
onSubmit: ({ value }) => {
|
|
29
|
+
console.log(value)
|
|
30
|
+
// Show success message
|
|
31
|
+
alert('Form submitted successfully!')
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
|
|
38
|
+
style={{
|
|
39
|
+
backgroundImage:
|
|
40
|
+
'radial-gradient(50% 50% at 5% 40%, #add8e6 0%, #0000ff 70%, #00008b 100%)',
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
|
|
44
|
+
<form
|
|
45
|
+
onSubmit={(e) => {
|
|
46
|
+
e.preventDefault()
|
|
47
|
+
e.stopPropagation()
|
|
48
|
+
form.handleSubmit()
|
|
49
|
+
}}
|
|
50
|
+
className="space-y-6"
|
|
51
|
+
>
|
|
52
|
+
<form.AppField
|
|
53
|
+
name="title"
|
|
54
|
+
children={(field) => <field.TextField label="Title" />}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<form.AppField
|
|
58
|
+
name="description"
|
|
59
|
+
children={(field) => <field.TextArea label="Description" />}
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<div className="flex justify-end">
|
|
63
|
+
<form.AppForm>
|
|
64
|
+
<form.SubscribeButton label="Submit" />
|
|
65
|
+
</form.AppForm>
|
|
66
|
+
</div>
|
|
67
|
+
</form>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
<% if (codeRouter) { %>
|
|
74
|
+
export default (parentRoute: RootRoute) => createRoute({
|
|
75
|
+
path: '/demo/form',
|
|
76
|
+
component: SimpleForm,
|
|
77
|
+
getParentRoute: () => parentRoute,
|
|
78
|
+
})
|
|
79
|
+
<% } %>
|
|
@@ -47,7 +47,7 @@ This project uses [Biome](https://biomejs.dev/) for linting and formatting. The
|
|
|
47
47
|
<% } %>
|
|
48
48
|
|
|
49
49
|
## Routing
|
|
50
|
-
<% if (fileRouter) { %>This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as
|
|
50
|
+
<% if (fileRouter) { %>This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.<% } else { %>This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a code based router. Which means that the routes are defined in code (in the `./src/main.<%= jsx %>` file). If you like you can also use a file based routing setup by following the [File Based Routing](https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing) guide.<% } %>
|
|
51
51
|
|
|
52
52
|
### Adding A Route
|
|
53
53
|
<% if (fileRouter) { %>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "TanStack Chat",
|
|
3
|
-
"description": "A chat example that uses TanStack Start and TanStack Store. Features chat with
|
|
3
|
+
"description": "A chat example that uses TanStack Start and TanStack Store. Features chat with Anthropic Sonnet, chat history and custom prompts.",
|
|
4
4
|
"phase": "example",
|
|
5
5
|
"templates": ["file-router"],
|
|
6
6
|
"link": "",
|