@tanstack/cta-ui 0.10.0-alpha.26 → 0.10.0-alpha.27

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 (44) hide show
  1. package/README.md +20 -0
  2. package/dist/assets/index-DSKioOfX.css +1 -0
  3. package/dist/assets/index-DWTDdndE.js +213 -0
  4. package/dist/assets/index-DWTDdndE.js.map +1 -0
  5. package/dist/index.html +9 -3
  6. package/dist/logo-color-100w.png +0 -0
  7. package/index.html +7 -1
  8. package/lib/engine-handling/add-to-app-wrapper.ts +39 -15
  9. package/lib/engine-handling/create-app-wrapper.ts +17 -6
  10. package/lib/engine-handling/file-helpers.ts +4 -2
  11. package/lib/engine-handling/generate-initial-payload.ts +58 -51
  12. package/lib/index.ts +11 -1
  13. package/lib/types.d.ts +18 -11
  14. package/lib-dist/engine-handling/add-to-app-wrapper.d.ts +2 -0
  15. package/lib-dist/engine-handling/add-to-app-wrapper.js +14 -9
  16. package/lib-dist/engine-handling/create-app-wrapper.d.ts +2 -1
  17. package/lib-dist/engine-handling/create-app-wrapper.js +6 -4
  18. package/lib-dist/engine-handling/file-helpers.js +3 -2
  19. package/lib-dist/engine-handling/generate-initial-payload.d.ts +14 -22
  20. package/lib-dist/engine-handling/generate-initial-payload.js +44 -49
  21. package/lib-dist/index.d.ts +2 -0
  22. package/lib-dist/index.js +6 -1
  23. package/package.json +6 -4
  24. package/public/logo-color-100w.png +0 -0
  25. package/src/components/background-animation.tsx +229 -0
  26. package/src/components/cta-sidebar.tsx +28 -33
  27. package/src/components/file-navigator.tsx +72 -74
  28. package/src/components/header.tsx +31 -0
  29. package/src/components/sidebar-items/add-ons.tsx +48 -45
  30. package/src/components/sidebar-items/mode-selector.tsx +6 -4
  31. package/src/components/sidebar-items/project-name.tsx +4 -5
  32. package/src/components/sidebar-items/typescript-switch.tsx +3 -3
  33. package/src/components/startup-dialog.tsx +4 -6
  34. package/src/components/ui/switch.tsx +6 -6
  35. package/src/hooks/use-mounted.ts +9 -0
  36. package/src/hooks/use-preferred-reduced-motion.ts +27 -0
  37. package/src/index.tsx +24 -20
  38. package/src/store/project.ts +36 -20
  39. package/src/styles.css +90 -18
  40. package/src/types.d.ts +1 -1
  41. package/tailwind.config.cjs +47 -0
  42. package/dist/assets/index-D0-fpgzI.js +0 -223
  43. package/dist/assets/index-D0-fpgzI.js.map +0 -1
  44. package/dist/assets/index-D5brMzJg.css +0 -1
@@ -7,7 +7,7 @@ import FileTree from './file-tree'
7
7
  import type { FileTreeItem } from '@/types'
8
8
 
9
9
  import { Label } from '@/components/ui/label'
10
- import { Checkbox } from '@/components/ui/checkbox'
10
+ import { Switch } from '@/components/ui/switch'
11
11
 
12
12
  import {
13
13
  useApplicationMode,
@@ -24,64 +24,62 @@ export function Filters() {
24
24
  const { includedFiles, toggleFilter } = useFilters()
25
25
 
26
26
  return (
27
- <div className="p-2 rounded-md bg-gray-900 file-filters">
28
- <div className="text-center text-sm mb-2">File Filters</div>
29
- <div className="flex flex-row flex-wrap gap-y-2">
30
- <div className="flex flex-row items-center gap-2 w-1/3">
31
- <Checkbox
32
- id="unchanged"
33
- checked={includedFiles.includes('unchanged')}
34
- className="w-4 h-4"
35
- onCheckedChange={() => toggleFilter('unchanged')}
36
- />
37
- <Label htmlFor="unchanged" className={twClasses.unchanged}>
38
- Unchanged
39
- </Label>
40
- </div>
41
- <div className="flex flex-row items-center gap-2 w-1/3">
42
- <Checkbox
43
- id="added"
44
- checked={includedFiles.includes('added')}
45
- className="w-4 h-4"
46
- onCheckedChange={() => toggleFilter('added')}
47
- />
48
- <Label htmlFor="added" className={twClasses.added}>
49
- Added
50
- </Label>
51
- </div>
52
- <div className="flex flex-row items-center gap-2 w-1/3">
53
- <Checkbox
54
- id="modified"
55
- checked={includedFiles.includes('modified')}
56
- className="w-4 h-4"
57
- onCheckedChange={() => toggleFilter('modified')}
58
- />
59
- <Label htmlFor="modified" className={twClasses.modified}>
60
- Modified
61
- </Label>
62
- </div>
63
- <div className="flex flex-row items-center gap-2 w-1/3">
64
- <Checkbox
65
- id="deleted"
66
- checked={includedFiles.includes('deleted')}
67
- className="w-4 h-4"
68
- onCheckedChange={() => toggleFilter('deleted')}
69
- />
70
- <Label htmlFor="deleted" className={twClasses.deleted}>
71
- Deleted
72
- </Label>
73
- </div>
74
- <div className="flex flex-row items-center gap-2 w-1/3">
75
- <Checkbox
76
- id="overwritten"
77
- checked={includedFiles.includes('overwritten')}
78
- className="w-4 h-4"
79
- onCheckedChange={() => toggleFilter('overwritten')}
80
- />
81
- <Label htmlFor="overwritten" className={twClasses.overwritten}>
82
- Overwritten
83
- </Label>
84
- </div>
27
+ <div className="bg-white dark:bg-black/40 shadow-xl p-4 rounded-lg flex flex-row items-center gap-4 mb-2">
28
+ <h3 className="font-medium whitespace-nowrap">File Filters</h3>
29
+ <div className="flex flex-row items-center">
30
+ <Switch
31
+ id="unchanged"
32
+ checked={includedFiles.includes('unchanged')}
33
+ onCheckedChange={() => toggleFilter('unchanged')}
34
+ className="mr-2"
35
+ />
36
+ <Label htmlFor="unchanged" className={twClasses.unchanged}>
37
+ Unchanged
38
+ </Label>
39
+ </div>
40
+ <div className="flex flex-row items-center">
41
+ <Switch
42
+ id="added"
43
+ checked={includedFiles.includes('added')}
44
+ onCheckedChange={() => toggleFilter('added')}
45
+ className="mr-2"
46
+ />
47
+ <Label htmlFor="added" className={twClasses.added}>
48
+ Added
49
+ </Label>
50
+ </div>
51
+ <div className="flex flex-row items-center">
52
+ <Switch
53
+ id="modified"
54
+ checked={includedFiles.includes('modified')}
55
+ onCheckedChange={() => toggleFilter('modified')}
56
+ className="mr-2"
57
+ />
58
+ <Label htmlFor="modified" className={twClasses.modified}>
59
+ Modified
60
+ </Label>
61
+ </div>
62
+ <div className="flex flex-row items-center">
63
+ <Switch
64
+ id="deleted"
65
+ checked={includedFiles.includes('deleted')}
66
+ onCheckedChange={() => toggleFilter('deleted')}
67
+ className="mr-2"
68
+ />
69
+ <Label htmlFor="deleted" className={twClasses.deleted}>
70
+ Deleted
71
+ </Label>
72
+ </div>
73
+ <div className="flex flex-row items-center">
74
+ <Switch
75
+ id="overwritten"
76
+ checked={includedFiles.includes('overwritten')}
77
+ onCheckedChange={() => toggleFilter('overwritten')}
78
+ className="mr-2"
79
+ />
80
+ <Label htmlFor="overwritten" className={twClasses.overwritten}>
81
+ Overwritten
82
+ </Label>
85
83
  </div>
86
84
  </div>
87
85
  )
@@ -184,21 +182,21 @@ export default function FileNavigator() {
184
182
  }
185
183
 
186
184
  return (
187
- <div
188
- className={`flex flex-row border-1 rounded-md mr-10 p-2 inset-shadow-gray-600 inset-shadow-sm`}
189
- >
190
- <div className="w-1/4 max-w-1/4 pr-2">
191
- {mode === 'add' && <Filters />}
192
- <FileTree selectedFile={selectedFile} tree={fileTree} />
193
- </div>
194
- <div className="max-w-3/4 w-3/4 pl-2">
195
- {selectedFile && modifiedFileContents ? (
196
- <FileViewer
197
- filePath={selectedFile}
198
- originalFile={originalFileContents}
199
- modifiedFile={modifiedFileContents}
200
- />
201
- ) : null}
185
+ <div className="bg-white dark:bg-black/50 rounded-lg p-2 sm:p-4">
186
+ {mode === 'add' && <Filters />}
187
+ <div className="flex flex-row">
188
+ <div className="w-1/4 max-w-1/4 bg-gray-500/10 rounded-l-lg">
189
+ <FileTree selectedFile={selectedFile} tree={fileTree} />
190
+ </div>
191
+ <div className="max-w-3/4 w-3/4">
192
+ {selectedFile && modifiedFileContents ? (
193
+ <FileViewer
194
+ filePath={selectedFile}
195
+ originalFile={originalFileContents}
196
+ modifiedFile={modifiedFileContents}
197
+ />
198
+ ) : null}
199
+ </div>
202
200
  </div>
203
201
  </div>
204
202
  )
@@ -0,0 +1,31 @@
1
+ export function AppHeader() {
2
+ return (
3
+ <div className="bg-white dark:bg-black/50 rounded-lg p-2 sm:p-4 flex items-center gap-2 text-lg sm:text-xl shadow-xl">
4
+ <div className="flex items-center gap-1.5">
5
+ <img
6
+ src="/logo-color-100w.png"
7
+ alt="TanStack Logo"
8
+ className="w-[30px] rounded-full overflow-hidden border-2 border-black dark:border-none"
9
+ />
10
+ <div className="font-black text-xl uppercase">TanStack</div>
11
+ </div>
12
+ <svg
13
+ stroke="currentColor"
14
+ fill="currentColor"
15
+ stroke-width="0"
16
+ viewBox="0 0 256 512"
17
+ height="1em"
18
+ width="1em"
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ >
21
+ <path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"></path>
22
+ </svg>
23
+ <div className="hover:text-blue-500 flex items-center gap-2">
24
+ Create TanStack App{' '}
25
+ <span className="bg-gradient-to-r from-blue-500 to-cyan-500 text-white text-xs font-bold px-2 py-0.5 rounded">
26
+ ALPHA
27
+ </span>
28
+ </div>
29
+ </div>
30
+ )
31
+ }
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'react'
1
+ import { useMemo, useState, Fragment } from 'react'
2
2
  import { InfoIcon } from 'lucide-react'
3
3
 
4
4
  import type { AddOnInfo } from '@/types'
@@ -35,53 +35,56 @@ export default function SelectedAddOns() {
35
35
  onClose={() => setInfoAddOn(undefined)}
36
36
  />
37
37
  {Object.keys(addOnTypeLabels).map((type) => (
38
- <div key={type}>
38
+ <Fragment key={type}>
39
39
  {sortedAddOns.filter((addOn) => addOn.type === type).length > 0 && (
40
- <h1 className="text-sm text-center border-b-2">
41
- {addOnTypeLabels[type]}
42
- </h1>
43
- )}
44
- <div className="flex flex-row flex-wrap">
45
- {sortedAddOns
46
- .filter((addOn) => addOn.type === type)
47
- .map((addOn) => (
48
- <div
49
- key={addOn.id}
50
- className="w-1/2 flex flex-row justify-between pr-4"
51
- >
52
- <div className="p-1 flex flex-row items-center">
53
- <Switch
54
- id={addOn.id}
55
- checked={addOnState[addOn.id].selected}
56
- disabled={!addOnState[addOn.id].enabled}
57
- onCheckedChange={() => {
58
- toggleAddOn(addOn.id)
59
- }}
60
- />
61
- <Label
62
- htmlFor={addOn.id}
63
- className="pl-2 font-semibold text-gray-300"
40
+ <div
41
+ key={`${type}-add-ons`}
42
+ className="block p-4 bg-gray-500/10 hover:bg-gray-500/20 rounded-lg transition-colors space-y-4 active"
43
+ >
44
+ <h3 className="font-medium">{addOnTypeLabels[type]}</h3>
45
+ <div className="flex flex-row flex-wrap">
46
+ {sortedAddOns
47
+ .filter((addOn) => addOn.type === type)
48
+ .map((addOn) => (
49
+ <div
50
+ key={addOn.id}
51
+ className="w-1/2 flex flex-row justify-between pr-4"
64
52
  >
65
- {addOn.smallLogo && (
66
- <img
67
- src={`data:image/svg+xml,${encodeURIComponent(
68
- addOn.smallLogo,
69
- )}`}
70
- alt={addOn.name}
71
- className="w-5"
53
+ <div className="p-1 flex flex-row items-center">
54
+ <Switch
55
+ id={addOn.id}
56
+ checked={addOnState[addOn.id].selected}
57
+ disabled={!addOnState[addOn.id].enabled}
58
+ onCheckedChange={() => {
59
+ toggleAddOn(addOn.id)
60
+ }}
61
+ />
62
+ <Label
63
+ htmlFor={addOn.id}
64
+ className="pl-2 font-semibold text-gray-300"
65
+ >
66
+ {addOn.smallLogo && (
67
+ <img
68
+ src={`data:image/svg+xml,${encodeURIComponent(
69
+ addOn.smallLogo,
70
+ )}`}
71
+ alt={addOn.name}
72
+ className="w-5"
73
+ />
74
+ )}
75
+ {addOn.name}
76
+ </Label>
77
+ <InfoIcon
78
+ className="ml-2 w-4 text-gray-600"
79
+ onClick={() => setInfoAddOn(addOn)}
72
80
  />
73
- )}
74
- {addOn.name}
75
- </Label>
76
- <InfoIcon
77
- className="ml-2 w-4 text-gray-600"
78
- onClick={() => setInfoAddOn(addOn)}
79
- />
80
- </div>
81
- </div>
82
- ))}
83
- </div>
84
- </div>
81
+ </div>
82
+ </div>
83
+ ))}
84
+ </div>
85
+ </div>
86
+ )}
87
+ </Fragment>
85
88
  ))}
86
89
  <div className="mt-4">
87
90
  <ImportCustomAddOn />
@@ -21,8 +21,9 @@ export default function ModeSelector() {
21
21
  }
22
22
 
23
23
  return (
24
- <>
25
- <div className="flex flex-row justify-center items-center mt-4">
24
+ <div className="flex flex-row gap-2 items-center">
25
+ <h3 className="font-medium whitespace-nowrap">Router Mode</h3>
26
+ <div className="flex flex-row justify-center items-center">
26
27
  <ToggleGroup
27
28
  type="single"
28
29
  value={routerMode}
@@ -31,10 +32,11 @@ export default function ModeSelector() {
31
32
  setRouterMode(v as Mode)
32
33
  }
33
34
  }}
35
+ className="rounded-md border-2 border-gray-500/10"
34
36
  >
35
37
  <ToggleGroupItem
36
38
  value="code-router"
37
- className="px-8"
39
+ className="px-4"
38
40
  disabled={!enableMode}
39
41
  >
40
42
  <CodeIcon className="w-4 h-4" />
@@ -50,6 +52,6 @@ export default function ModeSelector() {
50
52
  </ToggleGroupItem>
51
53
  </ToggleGroup>
52
54
  </div>
53
- </>
55
+ </div>
54
56
  )
55
57
  }
@@ -1,5 +1,4 @@
1
1
  import { Input } from '@/components/ui/input'
2
- import { SidebarGroupLabel } from '@/components/ui/sidebar'
3
2
 
4
3
  import {
5
4
  setProjectName,
@@ -16,14 +15,14 @@ export default function ProjectName() {
16
15
  }
17
16
 
18
17
  return (
19
- <>
20
- <SidebarGroupLabel>Project Name</SidebarGroupLabel>
18
+ <div className="flex flex-row gap-2 items-center">
19
+ <h3 className="font-medium whitespace-nowrap">Project Name</h3>
21
20
  <Input
22
21
  value={name}
23
22
  placeholder="my-app"
24
23
  onChange={(e) => setProjectName(e.target.value)}
25
- className="w-full"
24
+ className="w-full bg-gray-500/10 rounded-md px-2 py-1 min-w-[200px] text-sm"
26
25
  />
27
- </>
26
+ </div>
28
27
  )
29
28
  }
@@ -22,8 +22,8 @@ export default function TypescriptSwitch() {
22
22
  }
23
23
 
24
24
  return (
25
- <div className="flex mt-4">
26
- <div className="w-1/2 flex flex-row items-center">
25
+ <div className="flex">
26
+ <div className="w-1/2 flex flex-row items-center justify-center">
27
27
  <Switch
28
28
  id="typescript-switch"
29
29
  checked={typescript}
@@ -35,7 +35,7 @@ export default function TypescriptSwitch() {
35
35
  TypeScript
36
36
  </Label>
37
37
  </div>
38
- <div className="w-1/2 flex flex-row items-center">
38
+ <div className="w-1/2 flex flex-row items-center justify-center">
39
39
  <Switch
40
40
  id="tailwind-switch"
41
41
  checked={tailwind}
@@ -24,7 +24,7 @@ export default function StartupDialog() {
24
24
  const registry = useRegistry()
25
25
  const { open, setOpen, dontShowAgain, setDontShowAgain } = useStartupDialog()
26
26
 
27
- if (mode !== 'setup' || !registry) {
27
+ if (mode !== 'setup' || !registry || registry?.starters?.length === 0) {
28
28
  return null
29
29
  }
30
30
 
@@ -49,11 +49,9 @@ export default function StartupDialog() {
49
49
  Would you like to use a starter project?
50
50
  </DialogTitle>
51
51
  </DialogHeader>
52
- {registry?.starters && (
53
- <div>
54
- <StartersCarousel onImport={onImport} />
55
- </div>
56
- )}
52
+ <div>
53
+ <StartersCarousel onImport={onImport} />
54
+ </div>
57
55
  <DialogFooter className="flex sm:justify-between w-full">
58
56
  <div className="flex items-center gap-2">
59
57
  <Switch
@@ -1,7 +1,7 @@
1
- import * as React from "react"
2
- import * as SwitchPrimitive from "@radix-ui/react-switch"
1
+ import * as React from 'react'
2
+ import * as SwitchPrimitive from '@radix-ui/react-switch'
3
3
 
4
- import { cn } from "@/lib/utils"
4
+ import { cn } from '@/lib/utils'
5
5
 
6
6
  function Switch({
7
7
  className,
@@ -11,15 +11,15 @@ function Switch({
11
11
  <SwitchPrimitive.Root
12
12
  data-slot="switch"
13
13
  className={cn(
14
- "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
15
- className
14
+ 'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 border-1 border-gray-500/30',
15
+ className,
16
16
  )}
17
17
  {...props}
18
18
  >
19
19
  <SwitchPrimitive.Thumb
20
20
  data-slot="switch-thumb"
21
21
  className={cn(
22
- "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
22
+ 'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',
23
23
  )}
24
24
  />
25
25
  </SwitchPrimitive.Root>
@@ -0,0 +1,9 @@
1
+ import * as React from 'react'
2
+
3
+ export function useMounted() {
4
+ const [mounted, setMounted] = React.useState(false)
5
+ React.useEffect(() => {
6
+ setMounted(true)
7
+ }, [])
8
+ return mounted
9
+ }
@@ -0,0 +1,27 @@
1
+ import * as React from 'react'
2
+
3
+ /**
4
+ * Hook that returns the user's preference for reduced motion.
5
+ * @returns null if the value is not yet determined or the user's preference for reduced motion.
6
+ */
7
+ export const usePrefersReducedMotion = () => {
8
+ const [prefersReducedMotion, setPrefersReducedMotion] = React.useState<
9
+ boolean | null
10
+ >(null)
11
+
12
+ React.useEffect(() => {
13
+ const mediaQueryList = window.matchMedia('(prefers-reduced-motion: reduce)')
14
+ setPrefersReducedMotion(mediaQueryList.matches)
15
+
16
+ const listener = (event: MediaQueryListEvent) => {
17
+ setPrefersReducedMotion(event.matches)
18
+ }
19
+
20
+ mediaQueryList.addEventListener('change', listener)
21
+ return () => {
22
+ mediaQueryList.removeEventListener('change', listener)
23
+ }
24
+ }, [])
25
+
26
+ return prefersReducedMotion
27
+ }
package/src/index.tsx CHANGED
@@ -2,31 +2,38 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
2
  import FileNavigator from '@/components/file-navigator'
3
3
  import StartupDialog from '@/components/startup-dialog'
4
4
 
5
- import {
6
- SidebarProvider,
7
- SidebarTrigger,
8
- useSidebar,
9
- } from '@/components/ui/sidebar'
10
5
  import { Toaster } from '@/components/toaster'
11
6
 
12
7
  import { AppSidebar } from '@/components/cta-sidebar'
8
+ import { AppHeader } from '@/components/header'
9
+ import { BackgroundAnimation } from '@/components/background-animation'
10
+ import { useManager } from '@/store/project'
13
11
 
14
12
  const queryClient = new QueryClient()
15
13
 
16
14
  function Content() {
17
- const { open } = useSidebar()
15
+ useManager()
18
16
 
19
17
  return (
20
- <main
21
- className={
22
- open ? 'w-full max-w-[calc(100%-370px)]' : 'w-full max-w-[100%]'
23
- }
24
- >
25
- <SidebarTrigger className="m-2" />
26
- <div className="pl-3">
27
- <FileNavigator />
28
- <StartupDialog />
18
+ <main>
19
+ <BackgroundAnimation />
20
+ <div
21
+ className="min-h-dvh p-2 sm:p-4 space-y-2 sm:space-y-4"
22
+ style={{
23
+ background: `radial-gradient(closest-side, rgba(0,10,40,0.2) 0%, rgba(0,0,0,0) 100%)`,
24
+ }}
25
+ >
26
+ <AppHeader />
27
+ <div className="flex flex-row">
28
+ <div className="w-full sm:w-1/3 lg:w-1/4 pr-1 sm:pr-2">
29
+ <AppSidebar />
30
+ </div>
31
+ <div className="w-full sm:w-2/3 lg:w-3/4 pl-1 sm:pl-2">
32
+ <FileNavigator />
33
+ </div>
34
+ </div>
29
35
  </div>
36
+ <StartupDialog />
30
37
  </main>
31
38
  )
32
39
  }
@@ -34,11 +41,8 @@ function Content() {
34
41
  export default function RootComponent() {
35
42
  return (
36
43
  <QueryClientProvider client={queryClient}>
37
- <SidebarProvider>
38
- <AppSidebar />
39
- <Content />
40
- <Toaster />
41
- </SidebarProvider>
44
+ <Content />
45
+ <Toaster />
42
46
  </QueryClientProvider>
43
47
  )
44
48
  }
@@ -1,4 +1,4 @@
1
- import { useCallback, useMemo } from 'react'
1
+ import { useCallback, useEffect, useMemo } from 'react'
2
2
  import { create } from 'zustand'
3
3
  import { persist } from 'zustand/middleware'
4
4
  import { useQuery } from '@tanstack/react-query'
@@ -10,6 +10,18 @@ import type { Mode, SerializedOptions } from '@tanstack/cta-engine'
10
10
  import type { AddOnInfo, DryRunOutput, StarterInfo } from '@/types.js'
11
11
  import { dryRunAddToApp, dryRunCreateApp, loadInitialData } from '@/lib/api'
12
12
 
13
+ export const useProjectOptions = create<SerializedOptions>(() => ({
14
+ framework: 'react-cra',
15
+ mode: 'file-router',
16
+ projectName: 'my-app',
17
+ targetDir: 'my-app',
18
+ typescript: true,
19
+ tailwind: true,
20
+ git: true,
21
+ chosenAddOns: [],
22
+ packageManager: 'pnpm',
23
+ }))
24
+
13
25
  const useInitialData = () =>
14
26
  useQuery({
15
27
  queryKey: ['initial-data'],
@@ -18,8 +30,8 @@ const useInitialData = () =>
18
30
  options: {
19
31
  framework: 'react-cra',
20
32
  mode: 'file-router',
21
- projectName: 'my-application',
22
- targetDir: 'my-application',
33
+ projectName: 'my-app',
34
+ targetDir: 'my-app',
23
35
  typescript: true,
24
36
  tailwind: true,
25
37
  git: true,
@@ -50,8 +62,8 @@ export const useRegistry = () => useInitialData().data.registry
50
62
 
51
63
  export const useProjectLocalFiles = () => useInitialData().data.localFiles
52
64
  export const useOriginalOutput = () => useInitialData().data.output
53
- export const useOriginalSelectedAddOns = () =>
54
- useInitialData().data.options.chosenAddOns
65
+ export const useOriginalOptions = () => useInitialData().data.options
66
+ export const useOriginalSelectedAddOns = () => useOriginalOptions().chosenAddOns
55
67
  export const useApplicationMode = () => useInitialData().data.applicationMode
56
68
  export const useReady = () => useInitialData().isFetched
57
69
  export const useCodeRouterAddOns = () =>
@@ -59,18 +71,6 @@ export const useCodeRouterAddOns = () =>
59
71
  export const useFileRouterAddOns = () =>
60
72
  useInitialData().data.addOns['file-router']
61
73
 
62
- export const useProjectOptions = create<SerializedOptions>(() => ({
63
- framework: 'react-cra',
64
- mode: 'file-router',
65
- projectName: 'my-app',
66
- targetDir: 'my-app',
67
- typescript: true,
68
- tailwind: true,
69
- git: true,
70
- chosenAddOns: [],
71
- packageManager: 'pnpm',
72
- }))
73
-
74
74
  const useApplicationSettings = create<{
75
75
  includeFiles: Array<string>
76
76
  }>(() => ({
@@ -128,7 +128,7 @@ export function useAddOns() {
128
128
  for (const addOn of originalSelectedAddOns) {
129
129
  originalAddOns.add(addOn)
130
130
  }
131
- for (const addOn of forcedAddOns) {
131
+ for (const addOn of forcedAddOns || []) {
132
132
  originalAddOns.add(addOn)
133
133
  }
134
134
  return getAddOnStatus(
@@ -148,7 +148,7 @@ export function useAddOns() {
148
148
  const addOns = new Set(
149
149
  Object.keys(addOnState).filter((addOn) => addOnState[addOn].selected),
150
150
  )
151
- for (const addOn of forcedAddOns) {
151
+ for (const addOn of forcedAddOns || []) {
152
152
  addOns.add(addOn)
153
153
  }
154
154
  return Array.from(addOns)
@@ -156,7 +156,7 @@ export function useAddOns() {
156
156
 
157
157
  const toggleAddOn = useCallback(
158
158
  (addOnId: string) => {
159
- if (addOnState[addOnId] && addOnState[addOnId].enabled) {
159
+ if (addOnState[addOnId].enabled) {
160
160
  if (addOnState[addOnId].selected) {
161
161
  useMutableAddOns.setState((state) => ({
162
162
  userSelectedAddOns: state.userSelectedAddOns.filter(
@@ -328,4 +328,20 @@ export function setProjectStarter(starter: StarterInfo | undefined) {
328
328
  useProjectStarter.setState(() => ({
329
329
  projectStarter: starter,
330
330
  }))
331
+ if (starter) {
332
+ useProjectOptions.setState({
333
+ mode: starter.mode,
334
+ })
335
+ }
336
+ }
337
+
338
+ export function useManager() {
339
+ const ready = useReady()
340
+ const originalOptions = useOriginalOptions()
341
+
342
+ useEffect(() => {
343
+ if (ready) {
344
+ useProjectOptions.setState(originalOptions)
345
+ }
346
+ }, [ready])
331
347
  }