@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.
- package/README.md +20 -0
- package/dist/assets/index-DSKioOfX.css +1 -0
- package/dist/assets/index-DWTDdndE.js +213 -0
- package/dist/assets/index-DWTDdndE.js.map +1 -0
- package/dist/index.html +9 -3
- package/dist/logo-color-100w.png +0 -0
- package/index.html +7 -1
- package/lib/engine-handling/add-to-app-wrapper.ts +39 -15
- package/lib/engine-handling/create-app-wrapper.ts +17 -6
- package/lib/engine-handling/file-helpers.ts +4 -2
- package/lib/engine-handling/generate-initial-payload.ts +58 -51
- package/lib/index.ts +11 -1
- package/lib/types.d.ts +18 -11
- package/lib-dist/engine-handling/add-to-app-wrapper.d.ts +2 -0
- package/lib-dist/engine-handling/add-to-app-wrapper.js +14 -9
- package/lib-dist/engine-handling/create-app-wrapper.d.ts +2 -1
- package/lib-dist/engine-handling/create-app-wrapper.js +6 -4
- package/lib-dist/engine-handling/file-helpers.js +3 -2
- package/lib-dist/engine-handling/generate-initial-payload.d.ts +14 -22
- package/lib-dist/engine-handling/generate-initial-payload.js +44 -49
- package/lib-dist/index.d.ts +2 -0
- package/lib-dist/index.js +6 -1
- package/package.json +6 -4
- package/public/logo-color-100w.png +0 -0
- package/src/components/background-animation.tsx +229 -0
- package/src/components/cta-sidebar.tsx +28 -33
- package/src/components/file-navigator.tsx +72 -74
- package/src/components/header.tsx +31 -0
- package/src/components/sidebar-items/add-ons.tsx +48 -45
- package/src/components/sidebar-items/mode-selector.tsx +6 -4
- package/src/components/sidebar-items/project-name.tsx +4 -5
- package/src/components/sidebar-items/typescript-switch.tsx +3 -3
- package/src/components/startup-dialog.tsx +4 -6
- package/src/components/ui/switch.tsx +6 -6
- package/src/hooks/use-mounted.ts +9 -0
- package/src/hooks/use-preferred-reduced-motion.ts +27 -0
- package/src/index.tsx +24 -20
- package/src/store/project.ts +36 -20
- package/src/styles.css +90 -18
- package/src/types.d.ts +1 -1
- package/tailwind.config.cjs +47 -0
- package/dist/assets/index-D0-fpgzI.js +0 -223
- package/dist/assets/index-D0-fpgzI.js.map +0 -1
- 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 {
|
|
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-
|
|
28
|
-
<
|
|
29
|
-
<div className="flex flex-row
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
<
|
|
38
|
+
<Fragment key={type}>
|
|
39
39
|
{sortedAddOns.filter((addOn) => addOn.type === type).length > 0 && (
|
|
40
|
-
<
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
<
|
|
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-
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
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
|
|
2
|
-
import * as SwitchPrimitive from
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as SwitchPrimitive from '@radix-ui/react-switch'
|
|
3
3
|
|
|
4
|
-
import { cn } from
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
-
|
|
15
|
+
useManager()
|
|
18
16
|
|
|
19
17
|
return (
|
|
20
|
-
<main
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<
|
|
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
|
-
<
|
|
38
|
-
|
|
39
|
-
<Content />
|
|
40
|
-
<Toaster />
|
|
41
|
-
</SidebarProvider>
|
|
44
|
+
<Content />
|
|
45
|
+
<Toaster />
|
|
42
46
|
</QueryClientProvider>
|
|
43
47
|
)
|
|
44
48
|
}
|
package/src/store/project.ts
CHANGED
|
@@ -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-
|
|
22
|
-
targetDir: 'my-
|
|
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
|
|
54
|
-
|
|
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]
|
|
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
|
}
|