create-tsrouter-app 0.6.11 → 0.7.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.
Files changed (34) hide show
  1. package/dist/create-app.js +2 -2
  2. package/package.json +1 -1
  3. package/src/create-app.ts +2 -2
  4. package/templates/react/add-on/start/assets/src/router.tsx.ejs +2 -0
  5. package/templates/react/base/README.md.ejs +9 -3
  6. package/templates/react/code-router/src/main.tsx.ejs +1 -0
  7. package/templates/react/example/tanchat/assets/public/example-guitar-dune.jpg +0 -0
  8. package/templates/react/example/tanchat/assets/public/example-guitar-motherboard.jpg +0 -0
  9. package/templates/react/example/tanchat/assets/public/example-guitar-racing.jpg +0 -0
  10. package/templates/react/example/tanchat/assets/public/example-guitar-steamer-trunk.jpg +0 -0
  11. package/templates/react/example/tanchat/assets/public/example-guitar-steampunk.jpg +0 -0
  12. package/templates/react/example/tanchat/assets/public/example-guitar-underwater.jpg +0 -0
  13. package/templates/react/example/tanchat/assets/src/components/example-AIAssistant.tsx +173 -0
  14. package/templates/react/example/tanchat/assets/src/components/example-GuitarRecommendation.tsx +47 -0
  15. package/templates/react/example/tanchat/assets/src/data/example-guitars.ts +73 -0
  16. package/templates/react/example/tanchat/assets/src/integrations/tanchat/header-user.tsx +5 -0
  17. package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx +119 -397
  18. package/templates/react/example/tanchat/assets/src/routes/example.guitars/$guitarId.tsx +50 -0
  19. package/templates/react/example/tanchat/assets/src/routes/example.guitars/index.tsx +54 -0
  20. package/templates/react/example/tanchat/assets/src/store/example-assistant.ts +3 -0
  21. package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +14 -70
  22. package/templates/react/example/tanchat/assets/src/utils/demo.tools.ts +43 -0
  23. package/templates/react/example/tanchat/info.json +4 -0
  24. package/templates/react/example/tanchat/package.json +4 -1
  25. package/templates/react/file-router/src/main.tsx.ejs +2 -1
  26. package/templates/react/file-router/src/routes/__root.tsx.ejs +8 -5
  27. package/templates/solid/file-router/src/main.tsx.ejs +1 -0
  28. package/tests/snapshots/cra/cr-js-npm.json +2 -2
  29. package/tests/snapshots/cra/cr-ts-npm.json +2 -2
  30. package/tests/snapshots/cra/fr-ts-npm.json +2 -2
  31. package/tests/snapshots/cra/fr-ts-tw-npm.json +2 -2
  32. package/templates/react/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +0 -148
  33. package/templates/react/example/tanchat/assets/src/store/demo.hooks.ts +0 -21
  34. package/templates/react/example/tanchat/assets/src/store/demo.store.ts +0 -133
@@ -307,7 +307,7 @@ export async function createApp(options, { silent = false, environment, }) {
307
307
  if (shadcnComponents.size > 0) {
308
308
  s?.start(`Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`);
309
309
  await environment.execute('npx', ['shadcn@canary', 'add', ...shadcnComponents], targetDir);
310
- s?.stop(`Installed shadcn components`);
310
+ s?.stop(`Installed required shadcn components`);
311
311
  }
312
312
  }
313
313
  const integrations = [];
@@ -439,7 +439,7 @@ ${environment.getErrors().join('\n')}`;
439
439
  if (options.packageManager === 'deno') {
440
440
  startCommand = `deno ${isAddOnEnabled('start') ? 'task dev' : 'start'}`;
441
441
  }
442
- outro(`Created your TanStack app in '${basename(targetDir)}'.
442
+ outro(`Your TanStack app is ready in '${basename(targetDir)}'.
443
443
 
444
444
  Use the following commands to start your app:
445
445
  % cd ${options.projectName}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tsrouter-app",
3
- "version": "0.6.11",
3
+ "version": "0.7.0",
4
4
  "description": "Tanstack Application Builder",
5
5
  "bin": "./dist/index.js",
6
6
  "type": "module",
package/src/create-app.ts CHANGED
@@ -490,7 +490,7 @@ export async function createApp(
490
490
  ['shadcn@canary', 'add', ...shadcnComponents],
491
491
  targetDir,
492
492
  )
493
- s?.stop(`Installed shadcn components`)
493
+ s?.stop(`Installed required shadcn components`)
494
494
  }
495
495
  }
496
496
 
@@ -712,7 +712,7 @@ ${environment.getErrors().join('\n')}`
712
712
  startCommand = `deno ${isAddOnEnabled('start') ? 'task dev' : 'start'}`
713
713
  }
714
714
 
715
- outro(`Created your TanStack app in '${basename(targetDir)}'.
715
+ outro(`Your TanStack app is ready in '${basename(targetDir)}'.
716
716
 
717
717
  Use the following commands to start your app:
718
718
  % cd ${options.projectName}
@@ -23,11 +23,13 @@ export const createRouter = () => {
23
23
  <% } %>
24
24
  },
25
25
  scrollRestoration: true,
26
+ defaultPreloadStaleTime: 0,
26
27
  }), TanstackQuery.getContext().queryClient)
27
28
  <% } else { %>
28
29
  const router = createTanstackRouter({
29
30
  routeTree,
30
31
  scrollRestoration: true,
32
+ defaultPreloadStaleTime: 0,
31
33
  })
32
34
  <% } %>
33
35
  return router
@@ -175,7 +175,7 @@ export const Route = createRootRoute({
175
175
  <% } %>
176
176
  The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.
177
177
 
178
- More information on layouts can be found in the [Layouts documentation](hthttps://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
178
+ More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
179
179
 
180
180
  <% if (codeRouter) { %>
181
181
  ### Migrating To File Base Routing
@@ -316,7 +316,13 @@ import "./styles.css";
316
316
  import reportWebVitals from "./reportWebVitals.<%= js %>";
317
317
 
318
318
  // Create a new router instance
319
- const router = createRouter({ routeTree, defaultPreload: "intent", scrollRestoration: true, defaultStructuralSharing: true });
319
+ const router = createRouter({
320
+ routeTree,
321
+ defaultPreload: "intent",
322
+ defaultPreloadStaleTime: 0,
323
+ scrollRestoration: true,
324
+ defaultStructuralSharing: true
325
+ });
320
326
  <% if (typescript) { %>
321
327
  // Register the router instance for type safety
322
328
  declare module "@tanstack/react-router" {
@@ -394,7 +400,7 @@ First add your dependencies:
394
400
  <%= getPackageManagerAddScript("@tanstack/react-query @tanstack/react-query-devtools") %>
395
401
  ```
396
402
 
397
- Next we'll need to creata query client and provider. We recommend putting those in `main.<%= jsx %>`.
403
+ Next we'll need to create a query client and provider. We recommend putting those in `main.<%= jsx %>`.
398
404
 
399
405
  ```tsx
400
406
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -59,6 +59,7 @@ const router = createRouter({
59
59
  defaultPreload: "intent",
60
60
  scrollRestoration: true,
61
61
  defaultStructuralSharing: true,
62
+ defaultPreloadStaleTime: 0,
62
63
  });
63
64
  <% if (typescript) { %>
64
65
  declare module "@tanstack/react-router" {
@@ -0,0 +1,173 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { useStore } from '@tanstack/react-store'
3
+ import { Send, X } from 'lucide-react'
4
+ import ReactMarkdown from 'react-markdown'
5
+ import rehypeRaw from 'rehype-raw'
6
+ import rehypeSanitize from 'rehype-sanitize'
7
+ import rehypeHighlight from 'rehype-highlight'
8
+ import remarkGfm from 'remark-gfm'
9
+ import { useChat } from '@ai-sdk/react'
10
+ import { genAIResponse } from '../utils/demo.ai'
11
+
12
+ import { showAIAssistant } from '../store/example-assistant'
13
+ import GuitarRecommendation from './example-GuitarRecommendation'
14
+
15
+ import type { UIMessage } from 'ai'
16
+
17
+ function Messages({ messages }: { messages: Array<UIMessage> }) {
18
+ const messagesContainerRef = useRef<HTMLDivElement>(null)
19
+
20
+ useEffect(() => {
21
+ if (messagesContainerRef.current) {
22
+ messagesContainerRef.current.scrollTop =
23
+ messagesContainerRef.current.scrollHeight
24
+ }
25
+ }, [messages])
26
+
27
+ if (!messages.length) {
28
+ return (
29
+ <div className="flex-1 flex items-center justify-center text-gray-400 text-sm">
30
+ Ask me anything! I'm here to help.
31
+ </div>
32
+ )
33
+ }
34
+
35
+ return (
36
+ <div ref={messagesContainerRef} className="flex-1 overflow-y-auto">
37
+ {messages.map(({ id, role, content, parts }) => (
38
+ <div
39
+ key={id}
40
+ className={`py-3 ${
41
+ role === 'assistant'
42
+ ? 'bg-gradient-to-r from-orange-500/5 to-red-600/5'
43
+ : 'bg-transparent'
44
+ }`}
45
+ >
46
+ {content.length > 0 && (
47
+ <div className="flex items-start gap-2 px-4">
48
+ {role === 'assistant' ? (
49
+ <div className="w-6 h-6 rounded-lg bg-gradient-to-r from-orange-500 to-red-600 flex items-center justify-center text-xs font-medium text-white flex-shrink-0">
50
+ AI
51
+ </div>
52
+ ) : (
53
+ <div className="w-6 h-6 rounded-lg bg-gray-700 flex items-center justify-center text-xs font-medium text-white flex-shrink-0">
54
+ Y
55
+ </div>
56
+ )}
57
+ <div className="flex-1 min-w-0">
58
+ <ReactMarkdown
59
+ className="prose dark:prose-invert max-w-none prose-sm"
60
+ rehypePlugins={[
61
+ rehypeRaw,
62
+ rehypeSanitize,
63
+ rehypeHighlight,
64
+ remarkGfm,
65
+ ]}
66
+ >
67
+ {content}
68
+ </ReactMarkdown>
69
+ </div>
70
+ </div>
71
+ )}
72
+ {parts
73
+ .filter((part) => part.type === 'tool-invocation')
74
+ .filter(
75
+ (part) => part.toolInvocation.toolName === 'recommendGuitar',
76
+ )
77
+ .map((toolCall) => (
78
+ <div
79
+ key={toolCall.toolInvocation.toolName}
80
+ className="max-w-[80%] mx-auto"
81
+ >
82
+ <GuitarRecommendation id={toolCall.toolInvocation.args.id} />
83
+ </div>
84
+ ))}
85
+ </div>
86
+ ))}
87
+ </div>
88
+ )
89
+ }
90
+
91
+ export default function AIAssistant() {
92
+ const isOpen = useStore(showAIAssistant)
93
+ const { messages, input, handleInputChange, handleSubmit } = useChat({
94
+ initialMessages: [],
95
+ fetch: (_url, options) => {
96
+ const { messages } = JSON.parse(options!.body! as string)
97
+ return genAIResponse({
98
+ data: {
99
+ messages,
100
+ },
101
+ })
102
+ },
103
+ onToolCall: (call) => {
104
+ if (call.toolCall.toolName === 'recommendGuitar') {
105
+ return 'Handled by the UI'
106
+ }
107
+ },
108
+ })
109
+
110
+ return (
111
+ <div className="relative z-50">
112
+ <button
113
+ onClick={() => showAIAssistant.setState((state) => !state)}
114
+ className="flex items-center gap-2 px-3 py-1 rounded-lg bg-gradient-to-r from-orange-500 to-red-600 text-white hover:opacity-90 transition-opacity"
115
+ >
116
+ <div className="w-5 h-5 rounded-lg bg-white/20 flex items-center justify-center text-xs font-medium">
117
+ AI
118
+ </div>
119
+ AI Assistant
120
+ </button>
121
+
122
+ {isOpen && (
123
+ <div className="absolute top-full right-0 mt-2 w-[700px] h-[600px] bg-gray-900 rounded-lg shadow-xl border border-orange-500/20 flex flex-col">
124
+ <div className="flex items-center justify-between p-3 border-b border-orange-500/20">
125
+ <h3 className="font-semibold text-white">AI Assistant</h3>
126
+ <button
127
+ onClick={() => showAIAssistant.setState((state) => !state)}
128
+ className="text-gray-400 hover:text-white transition-colors"
129
+ >
130
+ <X className="w-4 h-4" />
131
+ </button>
132
+ </div>
133
+
134
+ <Messages messages={messages} />
135
+
136
+ <div className="p-3 border-t border-orange-500/20">
137
+ <form onSubmit={handleSubmit}>
138
+ <div className="relative">
139
+ <textarea
140
+ value={input}
141
+ onChange={handleInputChange}
142
+ placeholder="Type your message..."
143
+ className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 pl-3 pr-10 py-2 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:border-transparent resize-none overflow-hidden"
144
+ rows={1}
145
+ style={{ minHeight: '36px', maxHeight: '120px' }}
146
+ onInput={(e) => {
147
+ const target = e.target as HTMLTextAreaElement
148
+ target.style.height = 'auto'
149
+ target.style.height =
150
+ Math.min(target.scrollHeight, 120) + 'px'
151
+ }}
152
+ onKeyDown={(e) => {
153
+ if (e.key === 'Enter' && !e.shiftKey) {
154
+ e.preventDefault()
155
+ handleSubmit(e)
156
+ }
157
+ }}
158
+ />
159
+ <button
160
+ type="submit"
161
+ disabled={!input.trim()}
162
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-orange-500 hover:text-orange-400 disabled:text-gray-500 transition-colors focus:outline-none"
163
+ >
164
+ <Send className="w-4 h-4" />
165
+ </button>
166
+ </div>
167
+ </form>
168
+ </div>
169
+ </div>
170
+ )}
171
+ </div>
172
+ )
173
+ }
@@ -0,0 +1,47 @@
1
+ import { useNavigate } from '@tanstack/react-router'
2
+
3
+ import { showAIAssistant } from '../store/example-assistant'
4
+
5
+ import guitars from '../data/example-guitars'
6
+
7
+ export default function GuitarRecommendation({ id }: { id: string }) {
8
+ const navigate = useNavigate()
9
+ const guitar = guitars.find((guitar) => guitar.id === +id)
10
+ if (!guitar) {
11
+ return null
12
+ }
13
+ return (
14
+ <div className="my-4 rounded-lg overflow-hidden border border-orange-500/20 bg-gray-800/50">
15
+ <div className="aspect-[4/3] relative overflow-hidden">
16
+ <img
17
+ src={guitar.image}
18
+ alt={guitar.name}
19
+ className="w-full h-full object-cover"
20
+ />
21
+ </div>
22
+ <div className="p-4">
23
+ <h3 className="text-lg font-semibold text-white mb-2">{guitar.name}</h3>
24
+ <p className="text-sm text-gray-300 mb-3 line-clamp-2">
25
+ {guitar.shortDescription}
26
+ </p>
27
+ <div className="flex items-center justify-between">
28
+ <div className="text-lg font-bold text-emerald-400">
29
+ ${guitar.price}
30
+ </div>
31
+ <button
32
+ onClick={() => {
33
+ navigate({
34
+ to: '/example/guitars/$guitarId',
35
+ params: { guitarId: guitar.id.toString() },
36
+ })
37
+ showAIAssistant.setState(() => false)
38
+ }}
39
+ className="bg-gradient-to-r from-orange-500 to-red-600 text-white px-4 py-1.5 rounded-lg text-sm hover:opacity-90 transition-opacity"
40
+ >
41
+ View Details
42
+ </button>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ )
47
+ }
@@ -0,0 +1,73 @@
1
+ export interface Guitar {
2
+ id: number
3
+ name: string
4
+ image: string
5
+ description: string
6
+ shortDescription: string
7
+ price: number
8
+ }
9
+
10
+ const guitars: Array<Guitar> = [
11
+ {
12
+ id: 1,
13
+ name: 'Dune Guitar',
14
+ image: '/example-guitar-dune.jpg',
15
+ description:
16
+ 'Inspired by the desert, this guitar will transport you to a world of sand and adventure. Its warm, amber-toned finish mimics the endless dunes of Arrakis, while the custom fretboard inlays resemble ancient desert glyphs. The resonant hollow body produces tones that range from whispered sandstorm hushes to the booming echo of desert thunder. Perfect for musicians seeking an instrument with both visual impact and sonic versatility, the Dune Guitar carries the mystique of distant worlds in every note.',
17
+ shortDescription:
18
+ 'A desert-inspired hollow body guitar with warm tones and custom desert glyph inlays.',
19
+ price: 599,
20
+ },
21
+ {
22
+ id: 2,
23
+ name: 'Motherboard Guitar',
24
+ image: '/example-guitar-motherboard.jpg',
25
+ description:
26
+ "This guitar is a tribute to the motherboard of a computer. It's a unique and stylish instrument that will make you feel like a hacker. The intricate circuit-inspired design features actual LED lights that pulse with your playing intensity, while the neck is inlaid with binary code patterns that glow under stage lights. Each pickup has been custom-wound to produce tones ranging from clean digital precision to glitched-out distortion, perfect for electronic music fusion. The Motherboard Guitar seamlessly bridges the gap between traditional craftsmanship and cutting-edge technology, making it the ultimate instrument for the digital age musician.",
27
+ shortDescription:
28
+ 'A tech-inspired electric guitar featuring LED lights and binary code inlays that glow under stage lights.',
29
+ price: 649,
30
+ },
31
+ {
32
+ id: 3,
33
+ name: 'Racing Guitar',
34
+ image: '/example-guitar-racing.jpg',
35
+ description:
36
+ "Engineered for speed and precision, the Racing Guitar embodies the spirit of motorsport in every curve and contour. Its aerodynamic body, painted in classic racing stripes and high-gloss finish, is crafted from lightweight materials that allow for effortless play during extended performances. The custom low-action setup and streamlined neck profile enable lightning-fast fretwork, while specially designed pickups deliver a high-octane tone that cuts through any mix. Built with performance-grade hardware including racing-inspired control knobs and checkered flag inlays, this guitar isn't just played—it's driven to the limits of musical possibility.",
37
+ shortDescription:
38
+ 'A lightweight, aerodynamic guitar with racing stripes and a low-action setup designed for speed and precision.',
39
+ price: 679,
40
+ },
41
+ {
42
+ id: 4,
43
+ name: 'Steamer Trunk Guitar',
44
+ image: '/example-guitar-steamer-trunk.jpg',
45
+ description:
46
+ "The Steamer Trunk Guitar carries the nostalgic essence of vintage travel in its unique design. Crafted with reclaimed wood from authentic antique luggage, each instrument tells a story of journeys past through its weathered finish and brass hardware accents. The body features decorative leather straps and corner protectors reminiscent of classic travel trunks, while the neck is inlaid with miniature world map markers denoting famous destinations. Its warm, rich tone has a distinctive aged quality that can't be replicated, producing sounds that evoke distant shores and adventures waiting to be had. Perfect for the musician whose playing is a journey unto itself.",
47
+ shortDescription:
48
+ 'A nostalgic guitar crafted from reclaimed antique luggage wood with brass accents and world map inlays.',
49
+ price: 629,
50
+ },
51
+ {
52
+ id: 5,
53
+ name: 'Steampunk Guitar',
54
+ image: '/example-guitar-steampunk.jpg',
55
+ description:
56
+ "The Steampunk Guitar is a magnificent fusion of Victorian aesthetics and industrial innovation, featuring an array of functional brass gears, pressure gauges, and copper tubing that adorn its mahogany body. Each component has been meticulously hand-crafted by master artisans, creating not just an instrument but a work of mechanical art. The fretboard is inlaid with vintage clockwork designs that seem to move as you play, while the custom-wound pickups are housed in polished brass casings that enhance the guitar's warm, slightly overdriven tone. Steam-powered tremolo effects can be activated via the special valve system, producing otherworldly sounds that transport listeners to an alternate history where steam and music power the world.",
57
+ shortDescription:
58
+ 'A Victorian-inspired masterpiece featuring functional brass gears, pressure gauges, and copper tubing on a mahogany body.',
59
+ price: 699,
60
+ },
61
+ {
62
+ id: 6,
63
+ name: 'Underwater Guitar',
64
+ image: '/example-guitar-underwater.jpg',
65
+ description:
66
+ 'Dive into the depths of sonic exploration with the Underwater Guitar, an instrument designed to capture the mysterious beauty of the ocean. Its translucent blue-green finish creates the illusion of being submerged, with mother-of-pearl inlays resembling bubbles rising along the fretboard. The body is contoured like ocean waves, featuring hand-painted coral reef details and iridescent abalone accents that shimmer like sunlight through water. Specially designed pickups produce ethereal, fluid tones with extended sustain that mimics the endless nature of the sea. Water-resistant components make this guitar surprisingly practical for beachside performances, while its unique resonant chamber creates haunting harmonics reminiscent of whale songs and the gentle lapping of waves.',
67
+ shortDescription:
68
+ 'An ocean-themed guitar with a translucent blue-green finish, bubble-like pearl inlays, and ethereal tones with extended sustain.',
69
+ price: 499,
70
+ },
71
+ ]
72
+
73
+ export default guitars
@@ -0,0 +1,5 @@
1
+ import AIAssistant from '../../components/example-AIAssistant'
2
+
3
+ export default function HeaderAIAssistant() {
4
+ return <AIAssistant />
5
+ }