Skip to content

Commit f55b432

Browse files
committed
fix: add registry support
1 parent 22a2de5 commit f55b432

File tree

12 files changed

+518
-4
lines changed

12 files changed

+518
-4
lines changed

examples/react-cra/registry.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"starters": [
3+
{
4+
"name": "e-Commerce",
5+
"description": "A starter project for React with CRA",
6+
"url": "./ecommerce-starter/starter.json",
7+
"banner": "./ecommerce-starter/.starter/banner.png"
8+
},
9+
{
10+
"name": "Blog",
11+
"description": "A blog starter for Tanstack React Start",
12+
"url": "./blog-starter/starter.json",
13+
"banner": "./blog-starter/.starter/banner.png"
14+
},
15+
{
16+
"name": "Resume",
17+
"description": "A resume starter for Tanstack React Start",
18+
"url": "./resume-starter/starter.json",
19+
"banner": "./resume-starter/.starter/banner.png"
20+
}
21+
],
22+
"add-ons": [
23+
{
24+
"name": "Legend State",
25+
"description": "A Legend State add-on for Tanstack React Start",
26+
"url": "./legend-state-add-on/add-on.json"
27+
},
28+
{
29+
"name": "MUI",
30+
"description": "A MUI add-on for Tanstack React Start",
31+
"url": "./mui-add-on/add-on.json"
32+
}
33+
]
34+
}

packages/cta-ui/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@uiw/react-codemirror": "^4.23.10",
4545
"class-variance-authority": "^0.7.1",
4646
"clsx": "^2.1.1",
47+
"embla-carousel-react": "^8.6.0",
4748
"execa": "^9.5.2",
4849
"jotai-tanstack-query": "^0.9.0",
4950
"lucide-react": "^0.476.0",

packages/cta-ui/src/components/sidebar-items/starter.tsx

+12-4
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import {
1212
DialogHeader,
1313
DialogTitle,
1414
} from '@/components/ui/dialog'
15-
1615
import {
1716
setProjectStarter,
1817
useApplicationMode,
1918
useProjectStarter,
19+
useRegistry,
2020
} from '@/store/project'
2121
import { loadRemoteStarter } from '@/lib/api'
22+
import { StartersCarousel } from '@/components/starters-carousel'
2223

2324
export default function Starter() {
2425
const [url, setUrl] = useState('')
@@ -32,8 +33,8 @@ export default function Starter() {
3233
return null
3334
}
3435

35-
async function onImport() {
36-
const data = await loadRemoteStarter(url)
36+
async function onImport(registryUrl?: string) {
37+
const data = await loadRemoteStarter(registryUrl || url)
3738

3839
if ('error' in data) {
3940
toast.error('Failed to load starter', {
@@ -45,6 +46,8 @@ export default function Starter() {
4546
}
4647
}
4748

49+
const registry = useRegistry()
50+
4851
return (
4952
<>
5053
{projectStarter?.banner && (
@@ -86,11 +89,16 @@ export default function Starter() {
8689
<FileBoxIcon className="w-4 h-4" />
8790
Set Project Starter
8891
</Button>
89-
<Dialog modal open={open}>
92+
<Dialog modal open={open} onOpenChange={setOpen}>
9093
<DialogContent className="sm:min-w-[425px] sm:max-w-fit">
9194
<DialogHeader>
9295
<DialogTitle>Project Starter URL</DialogTitle>
9396
</DialogHeader>
97+
{registry?.starters && (
98+
<div>
99+
<StartersCarousel onImport={onImport} />
100+
</div>
101+
)}
94102
<div>
95103
<Input
96104
value={url}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
Carousel,
3+
CarouselContent,
4+
CarouselItem,
5+
} from '@/components/ui/carousel'
6+
7+
import { useRegistry } from '@/store/project'
8+
9+
export function StartersCarousel({
10+
onImport,
11+
}: {
12+
onImport: (url: string) => void
13+
}) {
14+
const registry = useRegistry()
15+
16+
if (!registry) {
17+
return null
18+
}
19+
20+
return (
21+
<div>
22+
<Carousel>
23+
<CarouselContent>
24+
{registry.starters.map((starter) => (
25+
<CarouselItem className="basis-1/3" key={starter.url}>
26+
<div
27+
className="p-2 flex flex-col items-center hover:cursor-pointer hover:bg-gray-700/50 hover:text-white rounded-lg"
28+
onClick={() => {
29+
onImport(starter.url)
30+
}}
31+
>
32+
<img
33+
src={starter.banner}
34+
alt={starter.name}
35+
className="w-100 max-w-full"
36+
/>
37+
<div className="text-md font-bold">{starter.name}</div>
38+
</div>
39+
</CarouselItem>
40+
))}
41+
</CarouselContent>
42+
</Carousel>
43+
</div>
44+
)
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { toast } from 'sonner'
2+
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogFooter,
7+
DialogHeader,
8+
DialogTitle,
9+
} from '@/components/ui/dialog'
10+
import { StartersCarousel } from '@/components/starters-carousel'
11+
import { Button } from '@/components/ui/button'
12+
import { Switch } from '@/components/ui/switch'
13+
import { Label } from '@/components/ui/label'
14+
import {
15+
setProjectStarter,
16+
useApplicationMode,
17+
useRegistry,
18+
useStartupDialog,
19+
} from '@/store/project'
20+
import { loadRemoteStarter } from '@/lib/api'
21+
22+
export default function StartupDialog() {
23+
const mode = useApplicationMode()
24+
const registry = useRegistry()
25+
const { open, setOpen, dontShowAgain, setDontShowAgain } = useStartupDialog()
26+
27+
if (mode !== 'setup' || !registry) {
28+
return null
29+
}
30+
31+
async function onImport(registryUrl: string) {
32+
const data = await loadRemoteStarter(registryUrl)
33+
34+
if ('error' in data) {
35+
toast.error('Failed to load starter', {
36+
description: data.error,
37+
})
38+
} else {
39+
setProjectStarter(data)
40+
setOpen(false)
41+
}
42+
}
43+
44+
return (
45+
<Dialog modal open={open} onOpenChange={setOpen}>
46+
<DialogContent className="sm:min-w-[425px] sm:max-w-fit">
47+
<DialogHeader>
48+
<DialogTitle className="text-center text-2xl font-bold">
49+
Would you like to use a starter project?
50+
</DialogTitle>
51+
</DialogHeader>
52+
{registry?.starters && (
53+
<div>
54+
<StartersCarousel onImport={onImport} />
55+
</div>
56+
)}
57+
<DialogFooter className="flex sm:justify-between w-full">
58+
<div className="flex items-center gap-2">
59+
<Switch
60+
id="show-startup-dialog"
61+
checked={dontShowAgain}
62+
onCheckedChange={setDontShowAgain}
63+
/>
64+
<Label htmlFor="show-startup-dialog">Don't show this again</Label>
65+
</div>
66+
<Button onClick={() => setOpen(false)}>
67+
No, I want to start from scratch
68+
</Button>
69+
</DialogFooter>
70+
</DialogContent>
71+
</Dialog>
72+
)
73+
}

0 commit comments

Comments
 (0)