@tanstack/cta-framework-react-cra 0.10.0-alpha.20

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.
Files changed (166) hide show
  1. package/ADD-ON-AUTHORING.md +169 -0
  2. package/LICENSE +21 -0
  3. package/add-ons/clerk/README.md +3 -0
  4. package/add-ons/clerk/assets/_dot_env.local.append +2 -0
  5. package/add-ons/clerk/assets/src/integrations/clerk/header-user.tsx +19 -0
  6. package/add-ons/clerk/assets/src/integrations/clerk/provider.tsx +18 -0
  7. package/add-ons/clerk/assets/src/routes/demo.clerk.tsx +20 -0
  8. package/add-ons/clerk/info.json +28 -0
  9. package/add-ons/clerk/logo.svg +6 -0
  10. package/add-ons/clerk/package.json +5 -0
  11. package/add-ons/clerk/small-logo.svg +5 -0
  12. package/add-ons/convex/README.md +4 -0
  13. package/add-ons/convex/assets/_dot_cursorrules.append +93 -0
  14. package/add-ons/convex/assets/_dot_env.local.append +3 -0
  15. package/add-ons/convex/assets/convex/products.ts +8 -0
  16. package/add-ons/convex/assets/convex/schema.ts +10 -0
  17. package/add-ons/convex/assets/src/integrations/convex/provider.tsx +20 -0
  18. package/add-ons/convex/assets/src/routes/demo.convex.tsx +33 -0
  19. package/add-ons/convex/info.json +23 -0
  20. package/add-ons/convex/package.json +6 -0
  21. package/add-ons/convex/small-logo.svg +5 -0
  22. package/add-ons/form/assets/src/components/demo.FormComponents.tsx.ejs +300 -0
  23. package/add-ons/form/assets/src/hooks/demo.form-context.ts +4 -0
  24. package/add-ons/form/assets/src/hooks/demo.form.ts +22 -0
  25. package/add-ons/form/assets/src/routes/demo.form.address.tsx.ejs +213 -0
  26. package/add-ons/form/assets/src/routes/demo.form.simple.tsx.ejs +77 -0
  27. package/add-ons/form/info.json +31 -0
  28. package/add-ons/form/package.json +6 -0
  29. package/add-ons/form/small-logo.svg +1 -0
  30. package/add-ons/module-federation/assets/module-federation.config.js.ejs +31 -0
  31. package/add-ons/module-federation/assets/src/demo-mf-component.tsx +3 -0
  32. package/add-ons/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
  33. package/add-ons/module-federation/info.json +8 -0
  34. package/add-ons/module-federation/package.json +5 -0
  35. package/add-ons/netlify/README.md +11 -0
  36. package/add-ons/netlify/info.json +8 -0
  37. package/add-ons/netlify/small-logo.svg +1 -0
  38. package/add-ons/sentry/assets/_dot_cursorrules.append +22 -0
  39. package/add-ons/sentry/assets/_dot_env.local.append +11 -0
  40. package/add-ons/sentry/assets/src/app/global-middleware.ts +11 -0
  41. package/add-ons/sentry/assets/src/routes/demo.sentry.testing.tsx +489 -0
  42. package/add-ons/sentry/info.json +17 -0
  43. package/add-ons/sentry/package.json +5 -0
  44. package/add-ons/sentry/small-logo.svg +1 -0
  45. package/add-ons/shadcn/README.md +7 -0
  46. package/add-ons/shadcn/assets/_dot_cursorrules.append +7 -0
  47. package/add-ons/shadcn/assets/components.json +21 -0
  48. package/add-ons/shadcn/assets/src/lib/utils.ts +6 -0
  49. package/add-ons/shadcn/assets/src/styles.css +138 -0
  50. package/add-ons/shadcn/info.json +8 -0
  51. package/add-ons/shadcn/package.json +9 -0
  52. package/add-ons/shadcn/small-logo.svg +1 -0
  53. package/add-ons/start/assets/_dot_gitignore.append +2 -0
  54. package/add-ons/start/assets/app.config.ts.ejs +32 -0
  55. package/add-ons/start/assets/src/api.ts +6 -0
  56. package/add-ons/start/assets/src/client.tsx.ejs +33 -0
  57. package/add-ons/start/assets/src/router.tsx.ejs +48 -0
  58. package/add-ons/start/assets/src/routes/api.demo-names.ts +11 -0
  59. package/add-ons/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
  60. package/add-ons/start/assets/src/routes/demo.start.server-funcs.tsx +50 -0
  61. package/add-ons/start/assets/src/ssr.tsx.ejs +30 -0
  62. package/add-ons/start/info.json +31 -0
  63. package/add-ons/start/package.json +13 -0
  64. package/add-ons/start/small-logo.svg +1 -0
  65. package/add-ons/store/assets/src/lib/demo-store.ts +13 -0
  66. package/add-ons/store/assets/src/routes/demo.store.tsx.ejs +75 -0
  67. package/add-ons/store/info.json +16 -0
  68. package/add-ons/store/package.json +6 -0
  69. package/add-ons/store/small-logo.svg +1 -0
  70. package/add-ons/t3env/README.md +16 -0
  71. package/add-ons/t3env/assets/src/env.ts +39 -0
  72. package/add-ons/t3env/info.json +8 -0
  73. package/add-ons/t3env/package.json +6 -0
  74. package/add-ons/t3env/small-logo.svg +6 -0
  75. package/add-ons/tRPC/assets/src/integrations/trpc/init.ts +9 -0
  76. package/add-ons/tRPC/assets/src/integrations/trpc/react.ts +4 -0
  77. package/add-ons/tRPC/assets/src/integrations/trpc/router.ts +21 -0
  78. package/add-ons/tRPC/assets/src/routes/api.trpc.$.tsx +16 -0
  79. package/add-ons/tRPC/info.json +9 -0
  80. package/add-ons/tRPC/package.json +9 -0
  81. package/add-ons/tRPC/small-logo.svg +1 -0
  82. package/add-ons/table/assets/src/data/demo-table-data.ts +50 -0
  83. package/add-ons/table/assets/src/routes/demo.table.tsx.ejs +373 -0
  84. package/add-ons/table/info.json +16 -0
  85. package/add-ons/table/package.json +7 -0
  86. package/add-ons/table/small-logo.svg +1 -0
  87. package/add-ons/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +5 -0
  88. package/add-ons/tanstack-query/assets/src/integrations/tanstack-query/root-provider.tsx.ejs +70 -0
  89. package/add-ons/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +53 -0
  90. package/add-ons/tanstack-query/info.json +28 -0
  91. package/add-ons/tanstack-query/package.json +6 -0
  92. package/add-ons/tanstack-query/small-logo.svg +1 -0
  93. package/dist/index.js +18 -0
  94. package/dist/types/index.d.ts +1 -0
  95. package/examples/tanchat/README.md +37 -0
  96. package/examples/tanchat/assets/_dot_env.local.append +2 -0
  97. package/examples/tanchat/assets/public/example-guitar-flowers.jpg +0 -0
  98. package/examples/tanchat/assets/public/example-guitar-motherboard.jpg +0 -0
  99. package/examples/tanchat/assets/public/example-guitar-racing.jpg +0 -0
  100. package/examples/tanchat/assets/public/example-guitar-steamer-trunk.jpg +0 -0
  101. package/examples/tanchat/assets/public/example-guitar-superhero.jpg +0 -0
  102. package/examples/tanchat/assets/public/example-guitar-traveling.jpg +0 -0
  103. package/examples/tanchat/assets/public/example-guitar-video-games.jpg +0 -0
  104. package/examples/tanchat/assets/src/components/example-AIAssistant.tsx +173 -0
  105. package/examples/tanchat/assets/src/components/example-GuitarRecommendation.tsx +47 -0
  106. package/examples/tanchat/assets/src/data/example-guitars.ts +83 -0
  107. package/examples/tanchat/assets/src/demo.index.css +220 -0
  108. package/examples/tanchat/assets/src/integrations/tanchat/header-user.tsx +5 -0
  109. package/examples/tanchat/assets/src/routes/api.messages.ts +24 -0
  110. package/examples/tanchat/assets/src/routes/api.sse.ts +23 -0
  111. package/examples/tanchat/assets/src/routes/example.chat.tsx +159 -0
  112. package/examples/tanchat/assets/src/routes/example.guitars/$guitarId.tsx +50 -0
  113. package/examples/tanchat/assets/src/routes/example.guitars/index.tsx +54 -0
  114. package/examples/tanchat/assets/src/store/example-assistant.ts +3 -0
  115. package/examples/tanchat/assets/src/utils/demo.ai.ts +62 -0
  116. package/examples/tanchat/assets/src/utils/demo.sse.ts +31 -0
  117. package/examples/tanchat/assets/src/utils/demo.tools.ts +47 -0
  118. package/examples/tanchat/info.json +35 -0
  119. package/examples/tanchat/package.json +16 -0
  120. package/examples/tanchat/small-logo.svg +1 -0
  121. package/package.json +34 -0
  122. package/project/base/README.md.ejs +558 -0
  123. package/project/base/_dot_gitignore +5 -0
  124. package/project/base/_dot_vscode/settings.json.ejs +35 -0
  125. package/project/base/index.html.ejs +20 -0
  126. package/project/base/package.json +30 -0
  127. package/project/base/public/favicon.ico +0 -0
  128. package/project/base/public/logo192.png +0 -0
  129. package/project/base/public/logo512.png +0 -0
  130. package/project/base/public/manifest.json +25 -0
  131. package/project/base/public/robots.txt +3 -0
  132. package/project/base/src/App.css.ejs +38 -0
  133. package/project/base/src/App.test.tsx.ejs +10 -0
  134. package/project/base/src/App.tsx.ejs +63 -0
  135. package/project/base/src/components/Header.tsx.ejs +27 -0
  136. package/project/base/src/logo.svg +44 -0
  137. package/project/base/src/main.tsx.ejs +155 -0
  138. package/project/base/src/reportWebVitals.ts.ejs +28 -0
  139. package/project/base/src/routes/__root.tsx.ejs +82 -0
  140. package/project/base/src/routes/index.tsx.ejs +67 -0
  141. package/project/base/src/styles.css.ejs +15 -0
  142. package/project/base/tsconfig.json.ejs +29 -0
  143. package/project/base/vite.config.js.ejs +23 -0
  144. package/project/packages.json +20 -0
  145. package/src/index.ts +26 -0
  146. package/tests/react-cra.test.ts +150 -0
  147. package/tests/snapshots/react-cra/cr-js-form-npm.json +29 -0
  148. package/tests/snapshots/react-cra/cr-js-npm.json +23 -0
  149. package/tests/snapshots/react-cra/cr-ts-npm.json +24 -0
  150. package/tests/snapshots/react-cra/cr-ts-start-npm.json +28 -0
  151. package/tests/snapshots/react-cra/cr-ts-start-tanstack-query-npm.json +31 -0
  152. package/tests/snapshots/react-cra/fr-ts-biome-npm.json +26 -0
  153. package/tests/snapshots/react-cra/fr-ts-npm.json +24 -0
  154. package/tests/snapshots/react-cra/fr-ts-tw-npm.json +23 -0
  155. package/tests/test-utilities.ts +44 -0
  156. package/toolchains/biome/assets/biome.json +31 -0
  157. package/toolchains/biome/info.json +8 -0
  158. package/toolchains/biome/package.json +10 -0
  159. package/toolchains/biome/small-logo.svg +5 -0
  160. package/toolchains/eslint/assets/_dot_prettierignore +3 -0
  161. package/toolchains/eslint/assets/eslint.config.js +5 -0
  162. package/toolchains/eslint/assets/prettier.config.js +10 -0
  163. package/toolchains/eslint/info.json +8 -0
  164. package/toolchains/eslint/package.json +11 -0
  165. package/toolchains/eslint/small-logo.svg +7 -0
  166. package/tsconfig.json +17 -0
@@ -0,0 +1,300 @@
1
+ import { useStore } from '@tanstack/react-form'
2
+
3
+ import { useFieldContext, useFormContext } from '../hooks/demo.form-context'
4
+ <% if (addOnEnabled.shadcn) { %>
5
+ import { Button } from '@/components/ui/button'
6
+ import { Input } from '@/components/ui/input'
7
+ import { Textarea as ShadcnTextarea } from '@/components/ui/textarea'
8
+ import * as ShadcnSelect from '@/components/ui/select'
9
+ import { Slider as ShadcnSlider } from '@/components/ui/slider'
10
+ import { Switch as ShadcnSwitch } from '@/components/ui/switch'
11
+ import { Label } from '@/components/ui/label'
12
+
13
+ export function SubscribeButton({ label }: { label: string }) {
14
+ const form = useFormContext()
15
+ return (
16
+ <form.Subscribe selector={(state) => state.isSubmitting}>
17
+ {(isSubmitting) => (
18
+ <Button type="submit" disabled={isSubmitting}>
19
+ {label}
20
+ </Button>
21
+ )}
22
+ </form.Subscribe>
23
+ )
24
+ }
25
+
26
+ function ErrorMessages({
27
+ errors,
28
+ }: {
29
+ errors: Array<string | { message: string }>
30
+ }) {
31
+ return (
32
+ <>
33
+ {errors.map((error) => (
34
+ <div
35
+ key={typeof error === 'string' ? error : error.message}
36
+ className="text-red-500 mt-1 font-bold"
37
+ >
38
+ {typeof error === 'string' ? error : error.message}
39
+ </div>
40
+ ))}
41
+ </>
42
+ )
43
+ }
44
+
45
+ export function TextField({
46
+ label,
47
+ placeholder,
48
+ }: {
49
+ label: string
50
+ placeholder?: string
51
+ }) {
52
+ const field = useFieldContext<string>()
53
+ const errors = useStore(field.store, (state) => state.meta.errors)
54
+
55
+ return (
56
+ <div>
57
+ <Label htmlFor={label} className="mb-2 text-xl font-bold">
58
+ {label}
59
+ </Label>
60
+ <Input
61
+ value={field.state.value}
62
+ placeholder={placeholder}
63
+ onBlur={field.handleBlur}
64
+ onChange={(e) => field.handleChange(e.target.value)}
65
+ />
66
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
67
+ </div>
68
+ )
69
+ }
70
+
71
+ export function TextArea({
72
+ label,
73
+ rows = 3,
74
+ }: {
75
+ label: string
76
+ rows?: number
77
+ }) {
78
+ const field = useFieldContext<string>()
79
+ const errors = useStore(field.store, (state) => state.meta.errors)
80
+
81
+ return (
82
+ <div>
83
+ <Label htmlFor={label} className="mb-2 text-xl font-bold">
84
+ {label}
85
+ </Label>
86
+ <ShadcnTextarea
87
+ id={label}
88
+ value={field.state.value}
89
+ onBlur={field.handleBlur}
90
+ rows={rows}
91
+ onChange={(e) => field.handleChange(e.target.value)}
92
+ />
93
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
94
+ </div>
95
+ )
96
+ }
97
+
98
+ export function Select({
99
+ label,
100
+ values,
101
+ placeholder,
102
+ }: {
103
+ label: string
104
+ values: Array<{ label: string; value: string }>
105
+ placeholder?: string
106
+ }) {
107
+ const field = useFieldContext<string>()
108
+ const errors = useStore(field.store, (state) => state.meta.errors)
109
+
110
+ return (
111
+ <div>
112
+ <ShadcnSelect.Select
113
+ name={field.name}
114
+ value={field.state.value}
115
+ onValueChange={(value) => field.handleChange(value)}
116
+ >
117
+ <ShadcnSelect.SelectTrigger className="w-full">
118
+ <ShadcnSelect.SelectValue placeholder={placeholder} />
119
+ </ShadcnSelect.SelectTrigger>
120
+ <ShadcnSelect.SelectContent>
121
+ <ShadcnSelect.SelectGroup>
122
+ <ShadcnSelect.SelectLabel>{label}</ShadcnSelect.SelectLabel>
123
+ {values.map((value) => (
124
+ <ShadcnSelect.SelectItem key={value.value} value={value.value}>
125
+ {value.label}
126
+ </ShadcnSelect.SelectItem>
127
+ ))}
128
+ </ShadcnSelect.SelectGroup>
129
+ </ShadcnSelect.SelectContent>
130
+ </ShadcnSelect.Select>
131
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
132
+ </div>
133
+ )
134
+ }
135
+
136
+ export function Slider({ label }: { label: string }) {
137
+ const field = useFieldContext<number>()
138
+ const errors = useStore(field.store, (state) => state.meta.errors)
139
+
140
+ return (
141
+ <div>
142
+ <Label htmlFor={label} className="mb-2 text-xl font-bold">
143
+ {label}
144
+ </Label>
145
+ <ShadcnSlider
146
+ id={label}
147
+ onBlur={field.handleBlur}
148
+ value={[field.state.value]}
149
+ onValueChange={(value) => field.handleChange(value[0])}
150
+ />
151
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
152
+ </div>
153
+ )
154
+ }
155
+
156
+ export function Switch({ label }: { label: string }) {
157
+ const field = useFieldContext<boolean>()
158
+ const errors = useStore(field.store, (state) => state.meta.errors)
159
+
160
+ return (
161
+ <div>
162
+ <div className="flex items-center gap-2">
163
+ <ShadcnSwitch
164
+ id={label}
165
+ onBlur={field.handleBlur}
166
+ checked={field.state.value}
167
+ onCheckedChange={(checked) => field.handleChange(checked)}
168
+ />
169
+ <Label htmlFor={label}>{label}</Label>
170
+ </div>
171
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
172
+ </div>
173
+ )
174
+ }
175
+
176
+ <% } else { %>
177
+ export function SubscribeButton({ label }: { label: string }) {
178
+ const form = useFormContext()
179
+ return (
180
+ <form.Subscribe selector={(state) => state.isSubmitting}>
181
+ {(isSubmitting) => (
182
+ <button
183
+ type="submit"
184
+ disabled={isSubmitting}
185
+ className="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors disabled:opacity-50"
186
+ >
187
+ {label}
188
+ </button>
189
+ )}
190
+ </form.Subscribe>
191
+ )
192
+ }
193
+
194
+ function ErrorMessages({
195
+ errors,
196
+ }: {
197
+ errors: Array<string | { message: string }>
198
+ }) {
199
+ return (
200
+ <>
201
+ {errors.map((error) => (
202
+ <div
203
+ key={typeof error === 'string' ? error : error.message}
204
+ className="text-red-500 mt-1 font-bold"
205
+ >
206
+ {typeof error === 'string' ? error : error.message}
207
+ </div>
208
+ ))}
209
+ </>
210
+ )
211
+ }
212
+
213
+ export function TextField({
214
+ label,
215
+ placeholder,
216
+ }: {
217
+ label: string
218
+ placeholder?: string
219
+ }) {
220
+ const field = useFieldContext<string>()
221
+ const errors = useStore(field.store, (state) => state.meta.errors)
222
+
223
+ return (
224
+ <div>
225
+ <label htmlFor={label} className="block font-bold mb-1 text-xl">
226
+ {label}
227
+ <input
228
+ value={field.state.value}
229
+ placeholder={placeholder}
230
+ onBlur={field.handleBlur}
231
+ onChange={(e) => field.handleChange(e.target.value)}
232
+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
233
+ />
234
+ </label>
235
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
236
+ </div>
237
+ )
238
+ }
239
+
240
+ export function TextArea({
241
+ label,
242
+ rows = 3,
243
+ }: {
244
+ label: string
245
+ rows?: number
246
+ }) {
247
+ const field = useFieldContext<string>()
248
+ const errors = useStore(field.store, (state) => state.meta.errors)
249
+
250
+ return (
251
+ <div>
252
+ <label htmlFor={label} className="block font-bold mb-1 text-xl">
253
+ {label}
254
+ <textarea
255
+ value={field.state.value}
256
+ onBlur={field.handleBlur}
257
+ rows={rows}
258
+ onChange={(e) => field.handleChange(e.target.value)}
259
+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
260
+ />
261
+ </label>
262
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
263
+ </div>
264
+ )
265
+ }
266
+
267
+ export function Select({
268
+ label,
269
+ values,
270
+ }: {
271
+ label: string
272
+ values: Array<{ label: string; value: string }>
273
+ placeholder?: string
274
+ }) {
275
+ const field = useFieldContext<string>()
276
+ const errors = useStore(field.store, (state) => state.meta.errors)
277
+
278
+ return (
279
+ <div>
280
+ <label htmlFor={label} className="block font-bold mb-1 text-xl">
281
+ {label}
282
+ </label>
283
+ <select
284
+ name={field.name}
285
+ value={field.state.value}
286
+ onBlur={field.handleBlur}
287
+ onChange={(e) => field.handleChange(e.target.value)}
288
+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
289
+ >
290
+ {values.map((value) => (
291
+ <option key={value.value} value={value.value}>
292
+ {value.label}
293
+ </option>
294
+ ))}
295
+ </select>
296
+ {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
297
+ </div>
298
+ )
299
+ }
300
+ <% } %>
@@ -0,0 +1,4 @@
1
+ import { createFormHookContexts } from '@tanstack/react-form'
2
+
3
+ export const { fieldContext, useFieldContext, formContext, useFormContext } =
4
+ createFormHookContexts()
@@ -0,0 +1,22 @@
1
+ import { createFormHook } from '@tanstack/react-form'
2
+
3
+ import {
4
+ Select,
5
+ SubscribeButton,
6
+ TextArea,
7
+ TextField,
8
+ } from '../components/demo.FormComponents'
9
+ import { fieldContext, formContext } from './demo.form-context'
10
+
11
+ export const { useAppForm } = createFormHook({
12
+ fieldComponents: {
13
+ TextField,
14
+ Select,
15
+ TextArea,
16
+ },
17
+ formComponents: {
18
+ SubscribeButton,
19
+ },
20
+ fieldContext,
21
+ formContext,
22
+ })
@@ -0,0 +1,213 @@
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 name="fullName">
65
+ {(field) => <field.TextField label="Full Name" />}
66
+ </form.AppField>
67
+
68
+ <form.AppField
69
+ name="email"
70
+ validators={{
71
+ onBlur: ({ value }) => {
72
+ if (!value || value.trim().length === 0) {
73
+ return 'Email is required'
74
+ }
75
+ if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
76
+ return 'Invalid email address'
77
+ }
78
+ return undefined
79
+ },
80
+ }}
81
+ >
82
+ {(field) => <field.TextField label="Email" />}
83
+ </form.AppField>
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
+ >
96
+ {(field) => <field.TextField label="Street Address" />}
97
+ </form.AppField>
98
+
99
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
100
+ <form.AppField
101
+ name="address.city"
102
+ validators={{
103
+ onBlur: ({ value }) => {
104
+ if (!value || value.trim().length === 0) {
105
+ return 'City is required'
106
+ }
107
+ return undefined
108
+ },
109
+ }}
110
+ >
111
+ {(field) => <field.TextField label="City" />}
112
+ </form.AppField>
113
+ <form.AppField
114
+ name="address.state"
115
+ validators={{
116
+ onBlur: ({ value }) => {
117
+ if (!value || value.trim().length === 0) {
118
+ return 'State is required'
119
+ }
120
+ return undefined
121
+ },
122
+ }}
123
+ >
124
+ {(field) => <field.TextField label="State" />}
125
+ </form.AppField>
126
+ <form.AppField
127
+ name="address.zipCode"
128
+ validators={{
129
+ onBlur: ({ value }) => {
130
+ if (!value || value.trim().length === 0) {
131
+ return 'Zip code is required'
132
+ }
133
+ if (!/^\d{5}(-\d{4})?$/.test(value)) {
134
+ return 'Invalid zip code format'
135
+ }
136
+ return undefined
137
+ },
138
+ }}
139
+ >
140
+ {(field) => <field.TextField label="Zip Code" />}
141
+ </form.AppField>
142
+ </div>
143
+
144
+ <form.AppField
145
+ name="address.country"
146
+ validators={{
147
+ onBlur: ({ value }) => {
148
+ if (!value || value.trim().length === 0) {
149
+ return 'Country is required'
150
+ }
151
+ return undefined
152
+ },
153
+ }}
154
+ >
155
+ {(field) => (
156
+ <field.Select
157
+ label="Country"
158
+ values={[
159
+ { label: 'United States', value: 'US' },
160
+ { label: 'Canada', value: 'CA' },
161
+ { label: 'United Kingdom', value: 'UK' },
162
+ { label: 'Australia', value: 'AU' },
163
+ { label: 'Germany', value: 'DE' },
164
+ { label: 'France', value: 'FR' },
165
+ { label: 'Japan', value: 'JP' },
166
+ ]}
167
+ placeholder="Select a country"
168
+ />
169
+ )}
170
+ </form.AppField>
171
+
172
+ <form.AppField
173
+ name="phone"
174
+ validators={{
175
+ onBlur: ({ value }) => {
176
+ if (!value || value.trim().length === 0) {
177
+ return 'Phone number is required'
178
+ }
179
+ if (
180
+ !/^(\+\d{1,3})?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/.test(
181
+ value,
182
+ )
183
+ ) {
184
+ return 'Invalid phone number format'
185
+ }
186
+ return undefined
187
+ },
188
+ }}
189
+ >
190
+ {(field) => (
191
+ <field.TextField label="Phone" placeholder="123-456-7890" />
192
+ )}
193
+ </form.AppField>
194
+
195
+ <div className="flex justify-end">
196
+ <form.AppForm>
197
+ <form.SubscribeButton label="Submit" />
198
+ </form.AppForm>
199
+ </div>
200
+ </form>
201
+ </div>
202
+ </div>
203
+ )
204
+ }
205
+
206
+
207
+ <% if (codeRouter) { %>
208
+ export default (parentRoute: RootRoute) => createRoute({
209
+ path: '/demo/form/address',
210
+ component: AddressForm,
211
+ getParentRoute: () => parentRoute,
212
+ })
213
+ <% } %>
@@ -0,0 +1,77 @@
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 name="title">
53
+ {(field) => <field.TextField label="Title" />}
54
+ </form.AppField>
55
+
56
+ <form.AppField name="description">
57
+ {(field) => <field.TextArea label="Description" />}
58
+ </form.AppField>
59
+
60
+ <div className="flex justify-end">
61
+ <form.AppForm>
62
+ <form.SubscribeButton label="Submit" />
63
+ </form.AppForm>
64
+ </div>
65
+ </form>
66
+ </div>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ <% if (codeRouter) { %>
72
+ export default (parentRoute: RootRoute) => createRoute({
73
+ path: '/demo/form/simple',
74
+ component: SimpleForm,
75
+ getParentRoute: () => parentRoute,
76
+ })
77
+ <% } %>
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "Form",
3
+ "description": "TanStack Form",
4
+ "phase": "add-on",
5
+ "type": "add-on",
6
+ "modes": ["file-router", "code-router"],
7
+ "link": "https://tanstack.com/form/latest",
8
+ "routes": [
9
+ {
10
+ "url": "/demo/form/simple",
11
+ "name": "Simple Form",
12
+ "path": "src/routes/demo.form.simple.tsx",
13
+ "jsName": "FormSimpleDemo"
14
+ },
15
+ {
16
+ "url": "/demo/form/address",
17
+ "name": "Address Form",
18
+ "path": "src/routes/demo.form.address.tsx",
19
+ "jsName": "FormAddressDemo"
20
+ }
21
+ ],
22
+ "shadcnComponents": [
23
+ "button",
24
+ "select",
25
+ "input",
26
+ "textarea",
27
+ "slider",
28
+ "switch",
29
+ "label"
30
+ ]
31
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/react-form": "^1.0.0",
4
+ "zod": "^3.24.2"
5
+ }
6
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 633 633"><defs><linearGradient id="b" x1="50%" x2="50%" y1="0%" y2="71.65%"><stop offset="0%" stop-color="#6BDAFF"/><stop offset="31.922%" stop-color="#F9FFB5"/><stop offset="70.627%" stop-color="#FFA770"/><stop offset="100%" stop-color="#FF7373"/></linearGradient><linearGradient id="d" x1="43.996%" x2="53.441%" y1="8.54%" y2="93.872%"><stop offset="0%" stop-color="#673800"/><stop offset="100%" stop-color="#B65E00"/></linearGradient><linearGradient id="e" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="f" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="g" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="h" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="i" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="j" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#2F8A00"/><stop offset="100%" stop-color="#90FF57"/></linearGradient><linearGradient id="k" x1="92.9%" x2="8.641%" y1="45.768%" y2="54.892%"><stop offset="0%" stop-color="#EE2700"/><stop offset="100%" stop-color="#FF008E"/></linearGradient><linearGradient id="l" x1="61.109%" x2="43.717%" y1="3.633%" y2="43.072%"><stop offset="0%" stop-color="#FFF400"/><stop offset="100%" stop-color="#3C8700"/></linearGradient><linearGradient id="m" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFDF00"/><stop offset="100%" stop-color="#FF9D00"/></linearGradient><linearGradient id="n" x1="127.279%" x2="0%" y1="49.778%" y2="50.222%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="o" x1="127.279%" x2="0%" y1="47.531%" y2="52.469%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="p" x1="127.279%" x2="0%" y1="46.195%" y2="53.805%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="q" x1="127.279%" x2="0%" y1="35.33%" y2="64.67%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="r" x1="127.279%" x2="0%" y1="4.875%" y2="95.125%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="s" x1="78.334%" x2="31.668%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="t" x1="57.913%" x2="44.88%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><linearGradient id="u" x1="50.495%" x2="49.68%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFA400"/><stop offset="100%" stop-color="#FF5E00"/></linearGradient><circle id="a" cx="308.5" cy="308.5" r="308.5"/><circle id="v" cx="307.5" cy="308.5" r="316.5"/></defs><g fill="none" fill-rule="evenodd" transform="translate(9 8)"><mask id="c" fill="#fff"><use xlink:href="#a"/></mask><use xlink:href="#a" fill="url(#b)"/><ellipse cx="81.5" cy="602.5" fill="#015064" stroke="#00CFE2" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="535.5" cy="602.5" fill="#015064" stroke="#00CFE2" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="81.5" cy="640.5" fill="#015064" stroke="#00A8B8" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="535.5" cy="640.5" fill="#015064" stroke="#00A8B8" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="81.5" cy="676.5" fill="#015064" stroke="#007782" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><ellipse cx="535.5" cy="676.5" fill="#015064" stroke="#007782" stroke-width="25" mask="url(#c)" rx="214.5" ry="185.968"/><g mask="url(#c)"><path fill="url(#d)" stroke="#6E3A00" stroke-width="6.088" d="M98.318 88.007c7.691 37.104 16.643 72.442 26.856 106.013 10.212 33.571 25.57 68.934 46.07 106.088l-51.903 11.67c-10.057-60.01-17.232-99.172-21.525-117.487-4.293-18.315-10.989-51.434-20.089-99.357l20.591-6.927" transform="scale(-1 1) rotate(25 -300.37 -553.013)"/><g stroke="#2F8A00"><path fill="url(#e)" stroke-width="9.343" d="M108.544 66.538s-13.54-21.305-37.417-27.785c-15.917-4.321-33.933.31-54.048 13.892C33.464 65.975 47.24 73.52 58.405 75.28c16.749 2.64 50.14-8.74 50.14-8.74Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#f)" stroke-width="9.343" d="M108.544 67.138s-47.187-5.997-81.077 19.936C4.873 104.362-3.782 137.794 1.502 187.369c28.42-29.22 48.758-50.836 61.016-64.846 18.387-21.016 46.026-55.385 46.026-55.385Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#g)" stroke-width="9.343" d="M108.544 66.538c-1.96-21.705 3.98-38.018 17.82-48.94C140.203 6.674 154.85.808 170.303 0c-4.865 21.527-12.373 36.314-22.524 44.361-10.151 8.048-23.23 15.44-39.236 22.177Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#h)" stroke-width="9.343" d="M108.544 67.138c29.838-31.486 61.061-42.776 93.669-33.869C234.82 42.176 253.749 60.785 259 89.096c-34.898-3.657-59.974-6.343-75.228-8.058-15.254-1.716-40.33-6.349-75.228-13.9Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#i)" stroke-width="9.343" d="M108.544 67.138c34.868-9.381 64.503-3.658 88.905 17.17 24.402 20.829 39.656 46.686 45.762 77.571-39.626-7.574-68.4-20.115-86.322-37.624a395.051 395.051 0 0 1-48.345-57.117Z" transform="rotate(1 -6061.691 5926.397)"/><path fill="url(#j)" stroke-width="9.343" d="M108.544 67.138C76.206 82.6 57.608 105.366 52.75 135.436c-4.858 30.07-.292 62.89 13.698 98.462 24.873-41.418 38.905-71.368 42.096-89.849 3.191-18.48 3.191-44.118 0-76.91Z" transform="rotate(1 -6061.691 5926.397)"/><path stroke-linecap="round" stroke-width="5.91" d="M211.284 173.477c-13.851 21.992-23.291 42.022-28.32 60.093-5.03 18.071-8.175 33.143-9.436 45.216"/><path stroke-linecap="round" stroke-width="5.91" d="M209.814 176.884c-23.982 2.565-42.792 10.469-56.428 23.714-13.639 13.245-23.483 26.136-29.536 38.674M219.045 167.299c29.028-7.723 50.972-10.173 65.831-7.352 14.859 2.822 26.807 7.659 35.842 14.51M211.31 172.618c20.29 9.106 38.353 19.052 54.186 29.837 15.833 10.786 27.714 20.99 35.643 30.617"/></g><path stroke="#830305" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="6.937" d="m409.379 398.157-23.176 18.556M328.04 375.516l-22.313 28.398M312.904 353.698l53.18 59.816"/><path fill="url(#k)" d="M67.585 27.463H5.68C1.893 28.148 0 30.38 0 34.16c0 3.78 1.893 6.211 5.68 7.293h67.17l41.751-30.356c2.488-2.646 2.794-5.315.92-8.006s-4.6-3.626-8.177-2.803l-39.76 27.174Z" transform="scale(-1 1) rotate(-9 2092.128 2856.854)"/><path fill="#D8D8D8" stroke="#FFF" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="4.414" d="m402.861 391.51.471-4.088M382.21 388.752l.472-4.087M361.546 385.404l.485-3.845M337.59 371.883l2.56-2.498M324.276 359.567l2.56-2.497"/></g><ellipse cx="308.5" cy="720.5" fill="url(#l)" mask="url(#c)" rx="266" ry="316.5"/><ellipse cx="308.5" cy="720.5" stroke="#6DA300" stroke-opacity=".502" stroke-width="26" mask="url(#c)" rx="253" ry="303.5"/><g mask="url(#c)"><g transform="translate(389 -32)"><circle cx="168.5" cy="113.5" r="113.5" fill="url(#m)"/><circle cx="168.5" cy="113.5" r="106" stroke="#FFC900" stroke-opacity=".529" stroke-width="15"/><path stroke="url(#n)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="M30 113H0"/><path stroke="url(#o)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="M33.5 79.5 7 74"/><path stroke="url(#p)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m34 146-29 8"/><path stroke="url(#q)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m45 177-24 13"/><path stroke="url(#r)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m67 204-20 19"/><path stroke="url(#s)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m94.373 227-13.834 22.847"/><path stroke="url(#t)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="M127.5 243.5 120 268"/><path stroke="url(#u)" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="12" d="m167.5 252.5.5 24.5"/></g></g><circle cx="307.5" cy="308.5" r="304" stroke="#000" stroke-width="25"/></g></svg>