Skip to content

Commit f454a39

Browse files
committed
chore: lots of UI work on setup
1 parent 3695aff commit f454a39

27 files changed

+781
-81
lines changed

examples/legend-state-add-on/.add-on/info.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
{
22
"id": "legend-state-add-on-add-on",
3-
"name": "legend-state-add-on-add-on",
3+
"name": "Legend State",
44
"version": "0.0.1",
55
"description": "Add-on",
66
"author": "Jane Smith <[email protected]>",
77
"license": "MIT",
88
"link": "https://github.com/jane-smith/legend-state-add-on-add-on",
99
"shadcnComponents": [],
1010
"framework": "react-cra",
11-
"modes": [
12-
"file-router"
13-
],
11+
"modes": ["file-router"],
1412
"routes": [
1513
{
1614
"url": "/demo/legend-state",
@@ -28,4 +26,4 @@
2826
}
2927
},
3028
"dependsOn": []
31-
}
29+
}

examples/legend-state-add-on/add-on.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
{
22
"id": "legend-state-add-on-add-on",
3-
"name": "legend-state-add-on-add-on",
3+
"name": "Legend State",
44
"version": "0.0.1",
55
"description": "Add-on",
66
"author": "Jane Smith <[email protected]>",
77
"license": "MIT",
88
"link": "https://github.com/jane-smith/legend-state-add-on-add-on",
99
"shadcnComponents": [],
1010
"framework": "react-cra",
11-
"modes": [
12-
"file-router"
13-
],
11+
"modes": ["file-router"],
1412
"routes": [
1513
{
1614
"url": "/demo/legend-state",
@@ -33,4 +31,4 @@
3331
"./src/routes/demo.legend-state.tsx.ejs": "import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/react-router';\n\nimport { todos$ } from \"@/lib/demo-legend-state\";\nimport { For, useObservable } from \"@legendapp/state/react\";\nimport { $React } from \"@legendapp/state/react-web\";\n\n<% if (codeRouter) { %>\nimport type { RootRoute } from '@tanstack/react-router'\n<% } else { %>\nexport const Route = createFileRoute('/demo/legend-state')({\n component: LegendStateDemo,\n})\n<% } %>;\n\nfunction TodoForm() {\n const todoText$ = useObservable(\"\");\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (todoText$.peek().trim() === \"\") return;\n todos$.push({\n id: Date.now(),\n text: todoText$.peek().trim(),\n completed: false,\n });\n todoText$.set(\"\");\n };\n return (\n <form onSubmit={handleSubmit}>\n <$React.input\n placeholder=\"Add a todo\"\n type=\"text\"\n $value={todoText$}\n className=\"bg-white/10 rounded-lg px-4 py-2 outline-none border border-white/20 hover:border-white/40 focus:border-white/60 transition-colors duration-200 placeholder-white/40 w-full\"\n />\n </form>\n );\n}\nfunction TodoList() {\n return (\n <ul className=\"space-y-4\">\n <For each={todos$} optimized>\n {(todo) => (\n <li\n key={todo.id.get()}\n className=\"inline-flex items-center gap-2 justify-between w-full\"\n >\n <span className={todo.completed.get() ? \"line-through\" : \"\"}>\n {todo.text.get()}\n </span>\n <span className=\"flex items-center gap-2 justify-center\">\n <$React.input\n type=\"checkbox\"\n $checked={todo.completed}\n className=\"w-6 h-6 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600\"\n />\n <button\n type=\"button\"\n onClick={() =>\n todos$.splice(\n todos$.findIndex((t) => t.id.get() === todo.id.get()),\n 1\n )\n }\n className=\"focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-xs px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900\"\n >\n Remove\n </button>\n </span>\n </li>\n )}\n </For>\n </ul>\n );\n}\n\nfunction LegendStateDemo() {\n return (\n <div\n className=\"min-h-[calc(100vh-32px)] text-white p-8 flex items-center justify-center w-full h-full\"\n style={{\n backgroundImage:\n \"radial-gradient(50% 50% at 80% 80%, #f4a460 0%, #8b4513 70%, #1a0f0a 100%)\",\n }}\n >\n <div className=\"bg-white/10 backdrop-blur-lg rounded-xl p-8 shadow-lg flex flex-col gap-4 text-3xl w-5xl\">\n <h1 className=\"text-4xl font-bold mb-5\">Legend State Example</h1>\n <TodoForm />\n <TodoList />\n </div>\n </div>\n );\n}\n\n<% if (codeRouter) { %>\nexport default (parentRoute: RootRoute) => createRoute({\n path: '/demo/legend-state',\n \n component: LegendStateDemo,\n\n getParentRoute: () => parentRoute,\n})\n<% } %>\n"
3432
},
3533
"deletedFiles": []
36-
}
34+
}

examples/resume-example/starter-info.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "resume-example-starter",
3-
"name": "resume-example-starter",
3+
"name": "Resume Starter",
44
"version": "0.0.1",
55
"description": "Project starter",
66
"author": "Jane Smith <[email protected]>",
@@ -25,10 +25,7 @@
2525
"@content-collections/vinxi": "^0.1.0"
2626
}
2727
},
28-
"dependsOn": [
29-
"start",
30-
"shadcn"
31-
],
28+
"dependsOn": ["start", "shadcn"],
3229
"typescript": true,
3330
"tailwind": true
34-
}
31+
}

examples/resume-example/starter.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "resume-example-starter",
3-
"name": "resume-example-starter",
3+
"name": "Resume Starter",
44
"version": "0.0.1",
55
"description": "Project starter",
66
"author": "Jane Smith <[email protected]>",
@@ -25,10 +25,7 @@
2525
"@content-collections/vinxi": "^0.1.0"
2626
}
2727
},
28-
"dependsOn": [
29-
"start",
30-
"shadcn"
31-
],
28+
"dependsOn": ["start", "shadcn"],
3229
"typescript": true,
3330
"tailwind": true,
3431
"files": {
@@ -57,4 +54,4 @@
5754
"./src/routes/demo.start.api-request.tsx",
5855
"./src/routes/demo.start.server-funcs.tsx"
5956
]
60-
}
57+
}

packages/cta-cli/src/cli.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,25 @@ export function cli({
269269
)
270270
}
271271

272+
if (options.ui) {
273+
const defaultOptions: Options = {
274+
framework: getFrameworkByName(cliOptions.framework || 'react')!,
275+
mode: 'file-router',
276+
chosenAddOns: [],
277+
packageManager: 'pnpm',
278+
projectName: projectName || 'my-app',
279+
targetDir: resolve(process.cwd(), projectName || 'my-app'),
280+
typescript: true,
281+
tailwind: true,
282+
git: true,
283+
}
284+
launchUI({
285+
mode: 'setup',
286+
options: createSerializedOptions(finalOptions || defaultOptions),
287+
})
288+
return
289+
}
290+
272291
if (finalOptions) {
273292
intro(`Creating a new ${appName} app in ${projectName}...`)
274293
} else {
@@ -286,14 +305,7 @@ export function cli({
286305
finalOptions.targetDir =
287306
options.targetDir || resolve(process.cwd(), finalOptions.projectName)
288307

289-
if (options.ui) {
290-
launchUI({
291-
mode: 'setup',
292-
options: createSerializedOptions(finalOptions),
293-
})
294-
} else {
295-
await createApp(environment, finalOptions)
296-
}
308+
await createApp(environment, finalOptions)
297309
} catch (error) {
298310
log.error(
299311
error instanceof Error ? error.message : 'An unknown error occurred',

packages/cta-engine/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ export {
4141

4242
export { createSerializedOptions } from './options.js'
4343

44+
export {
45+
StarterCompiledSchema,
46+
AddOnCompiledSchema,
47+
AddOnInfoSchema,
48+
IntegrationSchema,
49+
} from './types.js'
50+
4451
export type {
4552
AddOn,
4653
Environment,

packages/cta-ui/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@radix-ui/react-popover": "^1.1.10",
2222
"@radix-ui/react-separator": "^1.1.4",
2323
"@radix-ui/react-slot": "^1.2.0",
24+
"@radix-ui/react-switch": "^1.2.2",
2425
"@radix-ui/react-tabs": "^1.1.3",
2526
"@radix-ui/react-toggle": "^1.1.2",
2627
"@radix-ui/react-toggle-group": "^1.1.2",
@@ -44,9 +45,11 @@
4445
"clsx": "^2.1.1",
4546
"execa": "^9.5.2",
4647
"lucide-react": "^0.476.0",
48+
"next-themes": "^0.4.6",
4749
"react": "^19.0.0",
4850
"react-codemirror-merge": "^4.23.10",
4951
"react-dom": "^19.0.0",
52+
"sonner": "^2.0.3",
5053
"tailwind-merge": "^3.0.2",
5154
"tailwindcss": "^4.0.6",
5255
"tailwindcss-animate": "^1.0.7",

packages/cta-ui/public/tanstack.png

65.3 KB
Loading

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,37 @@ import {
33
SidebarContent,
44
SidebarFooter,
55
SidebarGroup,
6+
SidebarGroupLabel,
67
SidebarHeader,
78
} from '@/components/ui/sidebar'
89

9-
import { SelectedAddOns } from '@/components/sidebar-items/add-ons'
10+
import SelectedAddOns from '@/components/sidebar-items/add-ons'
1011
import RunAddOns from '@/components/sidebar-items/run-add-ons'
1112
import RunCreateApp from '@/components/sidebar-items/run-create-app'
13+
import ProjectName from '@/components/sidebar-items/project-name'
14+
import ModeSelector from '@/components/sidebar-items/mode-selector'
15+
import TypescriptSwitch from '@/components/sidebar-items/typescript-switch'
16+
import StarterDialog from '@/components/sidebar-items/starter'
1217

1318
export function AppSidebar() {
1419
return (
1520
<Sidebar>
16-
<SidebarHeader />
21+
<SidebarHeader className="flex justify-center items-center">
22+
<img src="/tanstack.png" className="w-3/5" />
23+
</SidebarHeader>
1724
<SidebarContent>
1825
<SidebarGroup>
26+
<ProjectName />
27+
<ModeSelector />
28+
<TypescriptSwitch />
29+
</SidebarGroup>
30+
<SidebarGroup>
31+
<SidebarGroupLabel>Add-ons</SidebarGroupLabel>
1932
<SelectedAddOns />
2033
</SidebarGroup>
21-
<SidebarGroup />
34+
<SidebarGroup>
35+
<StarterDialog />
36+
</SidebarGroup>
2237
</SidebarContent>
2338
<SidebarFooter>
2439
<RunAddOns />
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { useState } from 'react'
2+
import { useStore } from '@tanstack/react-store'
3+
import { toast } from 'sonner'
4+
5+
import { Button } from '@/components/ui/button'
6+
import { Input } from '@/components/ui/input'
7+
import {
8+
Dialog,
9+
DialogContent,
10+
DialogFooter,
11+
DialogHeader,
12+
DialogTitle,
13+
} from '@/components/ui/dialog'
14+
15+
import { customAddOns, projectOptions, selectedAddOns } from '@/store/project'
16+
17+
export default function CustomAddOnDialog() {
18+
const [url, setUrl] = useState('')
19+
const [open, setOpen] = useState(false)
20+
21+
const mode = useStore(projectOptions).mode
22+
23+
async function onImport() {
24+
const response = await fetch(`/api/load-remote-add-on?url=${url}`)
25+
const data = await response.json()
26+
27+
if (!data.error) {
28+
customAddOns.setState((state) => [...state, data])
29+
if (data.modes.includes(mode)) {
30+
selectedAddOns.setState((state) => [...state, data])
31+
}
32+
setOpen(false)
33+
} else {
34+
toast.error('Failed to load add-on', {
35+
description: data.error,
36+
})
37+
}
38+
}
39+
40+
return (
41+
<div>
42+
<Button
43+
variant="secondary"
44+
className="w-full"
45+
onClick={() => {
46+
setUrl('')
47+
setOpen(true)
48+
}}
49+
>
50+
Import Custom Add-On
51+
</Button>
52+
<Dialog modal open={open}>
53+
<DialogContent className="sm:min-w-[425px] sm:max-w-fit">
54+
<DialogHeader>
55+
<DialogTitle>Import Custom Add-On</DialogTitle>
56+
</DialogHeader>
57+
<div>
58+
<Input
59+
value={url}
60+
onChange={(e) => setUrl(e.target.value)}
61+
placeholder="https://github.com/myorg/myproject/add-on.json"
62+
className="min-w-lg w-full"
63+
/>
64+
</div>
65+
<DialogFooter>
66+
<Button onClick={onImport}>Import</Button>
67+
</DialogFooter>
68+
</DialogContent>
69+
</Dialog>
70+
</div>
71+
)
72+
}

packages/cta-ui/src/components/file-navigator.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ import {
1414
PopoverTrigger,
1515
} from '@/components/ui/popover'
1616

17-
import { projectFiles, projectLocalFiles } from '@/store/project'
17+
import {
18+
projectFiles,
19+
projectLocalFiles,
20+
applicationMode,
21+
} from '@/store/project'
1822

23+
// TODO: Add file filters
1924
export function DropdownMenuDemo() {
2025
return (
2126
<Popover>
@@ -52,27 +57,33 @@ export default function FileNavigator() {
5257
const { output, originalOutput } = useStore(projectFiles)
5358
const localFiles = useStore(projectLocalFiles)
5459

60+
const mode = useStore(applicationMode)
61+
5562
return (
5663
<div className="flex flex-row w-[calc(100vw-450px)]">
5764
<div className="w-1/4 max-w-1/4 pr-2">
5865
<DropdownMenuDemo />
5966
<FileTree
6067
prefix="./"
6168
tree={output.files}
62-
originalTree={originalOutput.files}
69+
originalTree={mode === 'setup' ? output.files : originalOutput.files}
6370
localTree={localFiles}
6471
onFileSelected={(file) => {
6572
setSelectedFile(file)
66-
if (localFiles[file]) {
67-
if (!output.files[file]) {
68-
setOriginalFileContents(undefined)
69-
setModifiedFileContents(localFiles[file])
73+
if (mode === 'add') {
74+
if (localFiles[file]) {
75+
if (!output.files[file]) {
76+
setOriginalFileContents(undefined)
77+
setModifiedFileContents(localFiles[file])
78+
} else {
79+
setOriginalFileContents(localFiles[file])
80+
setModifiedFileContents(output.files[file])
81+
}
7082
} else {
71-
setOriginalFileContents(localFiles[file])
83+
setOriginalFileContents(originalOutput.files[file])
7284
setModifiedFileContents(output.files[file])
7385
}
7486
} else {
75-
setOriginalFileContents(originalOutput.files[file])
7687
setModifiedFileContents(output.files[file])
7788
}
7889
}}

0 commit comments

Comments
 (0)