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

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 (66) hide show
  1. package/dist/assets/index-D0-fpgzI.js +223 -0
  2. package/dist/assets/index-D0-fpgzI.js.map +1 -0
  3. package/dist/assets/index-D5brMzJg.css +1 -0
  4. package/dist/favicon.ico +0 -0
  5. package/dist/index.html +13 -0
  6. package/dist/logo192.png +0 -0
  7. package/dist/logo512.png +0 -0
  8. package/dist/manifest.json +25 -0
  9. package/dist/robots.txt +3 -0
  10. package/dist/tailwind.svg +1 -0
  11. package/dist/tanstack.png +0 -0
  12. package/dist/typescript.svg +1 -0
  13. package/index.html +12 -0
  14. package/{src → lib}/engine-handling/add-to-app-wrapper.ts +37 -41
  15. package/{src → lib}/engine-handling/create-app-wrapper.ts +35 -37
  16. package/{src → lib}/engine-handling/generate-initial-payload.ts +27 -1
  17. package/lib/engine-handling/server-environment.ts +37 -0
  18. package/lib/index.ts +140 -34
  19. package/lib/types.d.ts +13 -0
  20. package/lib-dist/engine-handling/add-to-app-wrapper.d.ts +12 -0
  21. package/lib-dist/engine-handling/add-to-app-wrapper.js +73 -0
  22. package/lib-dist/engine-handling/create-app-wrapper.d.ts +13 -0
  23. package/lib-dist/engine-handling/create-app-wrapper.js +68 -0
  24. package/lib-dist/engine-handling/file-helpers.d.ts +2 -0
  25. package/lib-dist/engine-handling/file-helpers.js +20 -0
  26. package/lib-dist/engine-handling/framework-registration.d.ts +1 -0
  27. package/lib-dist/engine-handling/framework-registration.js +10 -0
  28. package/lib-dist/engine-handling/generate-initial-payload.d.ts +40 -0
  29. package/lib-dist/engine-handling/generate-initial-payload.js +93 -0
  30. package/lib-dist/engine-handling/server-environment.d.ts +17 -0
  31. package/lib-dist/engine-handling/server-environment.js +18 -0
  32. package/lib-dist/index.d.ts +3 -7
  33. package/lib-dist/index.js +119 -18
  34. package/package.json +11 -12
  35. package/src/components/sidebar-items/starter.tsx +12 -4
  36. package/src/components/starters-carousel.tsx +45 -0
  37. package/src/components/startup-dialog.tsx +73 -0
  38. package/src/components/ui/carousel.tsx +239 -0
  39. package/src/index.tsx +44 -0
  40. package/src/lib/api.ts +10 -8
  41. package/src/main.tsx +12 -0
  42. package/src/store/project.ts +39 -0
  43. package/src/types.d.ts +15 -0
  44. package/vite.config.ts +16 -0
  45. package/app.config.js +0 -22
  46. package/src/api.ts +0 -6
  47. package/src/client.tsx +0 -8
  48. package/src/engine-handling/server-environment.ts +0 -26
  49. package/src/integrations/tanstack-query/layout.tsx +0 -5
  50. package/src/integrations/tanstack-query/root-provider.tsx +0 -15
  51. package/src/logo.svg +0 -44
  52. package/src/routeTree.gen.ts +0 -88
  53. package/src/router.tsx +0 -32
  54. package/src/routes/__root.tsx +0 -86
  55. package/src/routes/api/add-to-app.ts +0 -21
  56. package/src/routes/api/create-app.ts +0 -21
  57. package/src/routes/api/dry-run-add-to-app.ts +0 -16
  58. package/src/routes/api/dry-run-create-app.ts +0 -16
  59. package/src/routes/api/initial-payload.ts +0 -10
  60. package/src/routes/api/load-remote-add-on.ts +0 -42
  61. package/src/routes/api/load-starter.ts +0 -47
  62. package/src/routes/api/shutdown.ts +0 -11
  63. package/src/routes/index.tsx +0 -15
  64. package/src/ssr.tsx +0 -12
  65. /package/{src → lib}/engine-handling/file-helpers.ts +0 -0
  66. /package/{src → lib}/engine-handling/framework-registration.ts +0 -0
package/lib-dist/index.js CHANGED
@@ -1,23 +1,124 @@
1
1
  import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
- export function launchUI({ mode, addOns, options, forcedMode, forcedAddOns, }) {
4
- const projectPath = process.cwd();
5
- delete process.env.NODE_ENV;
6
- process.env.CTA_ADD_ONS = addOns?.join(',') || '';
7
- process.env.CTA_PROJECT_PATH = projectPath;
8
- process.env.CTA_OPTIONS = options ? JSON.stringify(options) : '';
9
- process.env.CTA_MODE = mode;
10
- if (forcedMode) {
11
- process.env.CTA_FORCED_ROUTER_MODE = forcedMode;
3
+ import express from 'express';
4
+ import cors from 'cors';
5
+ import { AddOnCompiledSchema, StarterCompiledSchema, } from '@tanstack/cta-engine';
6
+ import { addToAppWrapper } from './engine-handling/add-to-app-wrapper.js';
7
+ import { createAppWrapper } from './engine-handling/create-app-wrapper.js';
8
+ import { generateInitialPayload } from './engine-handling/generate-initial-payload.js';
9
+ import { setServerEnvironment } from './engine-handling/server-environment.js';
10
+ export function launchUI(options) {
11
+ const { port: requestedPort, ...rest } = options;
12
+ setServerEnvironment(rest);
13
+ const app = express();
14
+ app.use(cors());
15
+ app.use(express.json());
16
+ app.use(express.urlencoded({ extended: true }));
17
+ const launchUI = !process.env.CTA_DISABLE_UI;
18
+ if (launchUI) {
19
+ const packagePath = resolve(dirname(fileURLToPath(import.meta.url)), '..');
20
+ app.use(express.static(resolve(packagePath, 'dist')));
12
21
  }
13
- if (forcedAddOns) {
14
- process.env.CTA_FORCED_ADD_ONS = forcedAddOns.join(',');
15
- }
16
- const developerPath = resolve(dirname(fileURLToPath(import.meta.url)), '..');
17
- const configPath = resolve(developerPath, './app.config.js');
18
- process.chdir(developerPath);
19
- import(configPath).then(async (config) => {
20
- const out = await config.default;
21
- await out.dev();
22
+ app.post('/api/add-to-app', async (req, res) => {
23
+ await addToAppWrapper(req.body.addOns, {
24
+ response: res,
25
+ });
26
+ });
27
+ app.post('/api/create-app', async (req, res) => {
28
+ await createAppWrapper(req.body.options, {
29
+ response: res,
30
+ });
31
+ });
32
+ app.post('/api/dry-run-add-to-app', async (req, res) => {
33
+ res.send(await addToAppWrapper(req.body.addOns, {
34
+ dryRun: true,
35
+ }));
36
+ });
37
+ app.post('/api/dry-run-create-app', async (req, res) => {
38
+ res.send(await createAppWrapper(req.body.options, {
39
+ dryRun: true,
40
+ }));
41
+ });
42
+ app.get('/api/initial-payload', async (_req, res) => {
43
+ res.send(await generateInitialPayload());
44
+ });
45
+ app.get('/api/load-remote-add-on', async (req, res) => {
46
+ const { url } = req.query;
47
+ if (!url) {
48
+ res.status(400).send('URL is required');
49
+ return;
50
+ }
51
+ try {
52
+ const response = await fetch(url);
53
+ const data = await response.json();
54
+ const parsed = AddOnCompiledSchema.safeParse(data);
55
+ if (!parsed.success) {
56
+ res.status(400).json({ error: 'Invalid add-on data' });
57
+ }
58
+ else {
59
+ res.json({
60
+ id: url,
61
+ name: parsed.data.name,
62
+ description: parsed.data.description,
63
+ version: parsed.data.version,
64
+ author: parsed.data.author,
65
+ license: parsed.data.license,
66
+ link: parsed.data.link,
67
+ smallLogo: parsed.data.smallLogo,
68
+ logo: parsed.data.logo,
69
+ type: parsed.data.type,
70
+ modes: parsed.data.modes,
71
+ });
72
+ }
73
+ }
74
+ catch {
75
+ res.status(500).send('Failed to load add-on');
76
+ }
77
+ });
78
+ app.get('/api/load-starter', async (req, res) => {
79
+ const { url } = req.query;
80
+ if (!url) {
81
+ res.status(400).send('URL is required');
82
+ return;
83
+ }
84
+ try {
85
+ const response = await fetch(url);
86
+ const data = await response.json();
87
+ const parsed = StarterCompiledSchema.safeParse(data);
88
+ if (!parsed.success) {
89
+ res.status(400).json({ error: 'Invalid starter data' });
90
+ }
91
+ else {
92
+ res.json({
93
+ url,
94
+ id: parsed.data.id,
95
+ name: parsed.data.name,
96
+ description: parsed.data.description,
97
+ version: parsed.data.version,
98
+ author: parsed.data.author,
99
+ license: parsed.data.license,
100
+ dependsOn: parsed.data.dependsOn,
101
+ mode: parsed.data.mode,
102
+ typescript: parsed.data.typescript,
103
+ tailwind: parsed.data.tailwind,
104
+ banner: parsed.data.banner
105
+ ? url.replace('starter.json', parsed.data.banner)
106
+ : undefined,
107
+ });
108
+ }
109
+ }
110
+ catch {
111
+ res.status(500).send('Failed to load starter');
112
+ }
113
+ });
114
+ app.post('/api/shutdown', (_req, res) => {
115
+ setTimeout(() => {
116
+ process.exit(0);
117
+ }, 50);
118
+ res.send({ shutdown: true });
119
+ });
120
+ const port = requestedPort || process.env.PORT || 8080;
121
+ app.listen(port, () => {
122
+ console.log(`Create TanStack ${launchUI ? 'App' : 'API'} is running on http://localhost:${port}`);
22
123
  });
23
124
  }
package/package.json CHANGED
@@ -24,16 +24,14 @@
24
24
  "@tailwindcss/vite": "^4.0.6",
25
25
  "@tanstack/react-query": "^5.66.5",
26
26
  "@tanstack/react-query-devtools": "^5.66.5",
27
- "@tanstack/react-router": "^1.114.3",
28
- "@tanstack/react-router-devtools": "^1.114.3",
29
- "@tanstack/react-router-with-query": "^1.114.3",
30
- "@tanstack/react-start": "^1.114.3",
31
- "@tanstack/router-plugin": "^1.114.3",
32
27
  "@uiw/codemirror-theme-github": "^4.23.10",
33
28
  "@uiw/react-codemirror": "^4.23.10",
34
29
  "class-variance-authority": "^0.7.1",
35
30
  "clsx": "^2.1.1",
31
+ "cors": "^2.8.5",
32
+ "embla-carousel-react": "^8.6.0",
36
33
  "execa": "^9.5.2",
34
+ "express": "^4.21.2",
37
35
  "jotai-tanstack-query": "^0.9.0",
38
36
  "lucide-react": "^0.476.0",
39
37
  "next-themes": "^0.4.6",
@@ -44,27 +42,28 @@
44
42
  "tailwind-merge": "^3.0.2",
45
43
  "tailwindcss": "^4.0.6",
46
44
  "tailwindcss-animate": "^1.0.7",
47
- "vinxi": "^0.5.3",
48
45
  "vite-tsconfig-paths": "^5.1.4",
49
46
  "zustand": "^5.0.3",
50
- "@tanstack/cta-engine": "0.10.0-alpha.21",
51
- "@tanstack/cta-framework-react-cra": "0.10.0-alpha.21",
52
- "@tanstack/cta-framework-solid": "0.10.0-alpha.21"
47
+ "@tanstack/cta-engine": "0.10.0-alpha.26",
48
+ "@tanstack/cta-framework-solid": "0.10.0-alpha.26",
49
+ "@tanstack/cta-framework-react-cra": "0.10.0-alpha.26"
53
50
  },
54
51
  "devDependencies": {
55
52
  "@testing-library/dom": "^10.4.0",
56
53
  "@testing-library/react": "^16.2.0",
54
+ "@types/cors": "^2.8.17",
55
+ "@types/express": "^5.0.1",
57
56
  "@types/node": "^22.14.1",
58
57
  "@types/react": "^19.0.8",
59
58
  "@types/react-dom": "^19.0.3",
60
- "@vitejs/plugin-react": "^4.3.4",
59
+ "@vitejs/plugin-react": "^4.4.1",
61
60
  "@vitest/coverage-v8": "3.1.1",
62
61
  "jsdom": "^26.0.0",
63
62
  "typescript": "^5.7.2",
64
- "vite": "^6.1.0",
63
+ "vite": "^6.3.3",
65
64
  "vitest": "^3.0.5",
66
65
  "web-vitals": "^4.2.4"
67
66
  },
68
- "version": "0.10.0-alpha.23",
67
+ "version": "0.10.0-alpha.26",
69
68
  "scripts": {}
70
69
  }
@@ -12,13 +12,14 @@ import {
12
12
  DialogHeader,
13
13
  DialogTitle,
14
14
  } from '@/components/ui/dialog'
15
-
16
15
  import {
17
16
  setProjectStarter,
18
17
  useApplicationMode,
19
18
  useProjectStarter,
19
+ useRegistry,
20
20
  } from '@/store/project'
21
21
  import { loadRemoteStarter } from '@/lib/api'
22
+ import { StartersCarousel } from '@/components/starters-carousel'
22
23
 
23
24
  export default function Starter() {
24
25
  const [url, setUrl] = useState('')
@@ -32,8 +33,8 @@ export default function Starter() {
32
33
  return null
33
34
  }
34
35
 
35
- async function onImport() {
36
- const data = await loadRemoteStarter(url)
36
+ async function onImport(registryUrl?: string) {
37
+ const data = await loadRemoteStarter(registryUrl || url)
37
38
 
38
39
  if ('error' in data) {
39
40
  toast.error('Failed to load starter', {
@@ -45,6 +46,8 @@ export default function Starter() {
45
46
  }
46
47
  }
47
48
 
49
+ const registry = useRegistry()
50
+
48
51
  return (
49
52
  <>
50
53
  {projectStarter?.banner && (
@@ -86,11 +89,16 @@ export default function Starter() {
86
89
  <FileBoxIcon className="w-4 h-4" />
87
90
  Set Project Starter
88
91
  </Button>
89
- <Dialog modal open={open}>
92
+ <Dialog modal open={open} onOpenChange={setOpen}>
90
93
  <DialogContent className="sm:min-w-[425px] sm:max-w-fit">
91
94
  <DialogHeader>
92
95
  <DialogTitle>Project Starter URL</DialogTitle>
93
96
  </DialogHeader>
97
+ {registry?.starters && (
98
+ <div>
99
+ <StartersCarousel onImport={onImport} />
100
+ </div>
101
+ )}
94
102
  <div>
95
103
  <Input
96
104
  value={url}
@@ -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
+ }
@@ -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,0 +1,239 @@
1
+ import * as React from "react"
2
+ import useEmblaCarousel, {
3
+ type UseEmblaCarouselType,
4
+ } from "embla-carousel-react"
5
+ import { ArrowLeft, ArrowRight } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { Button } from "@/components/ui/button"
9
+
10
+ type CarouselApi = UseEmblaCarouselType[1]
11
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
12
+ type CarouselOptions = UseCarouselParameters[0]
13
+ type CarouselPlugin = UseCarouselParameters[1]
14
+
15
+ type CarouselProps = {
16
+ opts?: CarouselOptions
17
+ plugins?: CarouselPlugin
18
+ orientation?: "horizontal" | "vertical"
19
+ setApi?: (api: CarouselApi) => void
20
+ }
21
+
22
+ type CarouselContextProps = {
23
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
24
+ api: ReturnType<typeof useEmblaCarousel>[1]
25
+ scrollPrev: () => void
26
+ scrollNext: () => void
27
+ canScrollPrev: boolean
28
+ canScrollNext: boolean
29
+ } & CarouselProps
30
+
31
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
32
+
33
+ function useCarousel() {
34
+ const context = React.useContext(CarouselContext)
35
+
36
+ if (!context) {
37
+ throw new Error("useCarousel must be used within a <Carousel />")
38
+ }
39
+
40
+ return context
41
+ }
42
+
43
+ function Carousel({
44
+ orientation = "horizontal",
45
+ opts,
46
+ setApi,
47
+ plugins,
48
+ className,
49
+ children,
50
+ ...props
51
+ }: React.ComponentProps<"div"> & CarouselProps) {
52
+ const [carouselRef, api] = useEmblaCarousel(
53
+ {
54
+ ...opts,
55
+ axis: orientation === "horizontal" ? "x" : "y",
56
+ },
57
+ plugins
58
+ )
59
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
60
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
61
+
62
+ const onSelect = React.useCallback((api: CarouselApi) => {
63
+ if (!api) return
64
+ setCanScrollPrev(api.canScrollPrev())
65
+ setCanScrollNext(api.canScrollNext())
66
+ }, [])
67
+
68
+ const scrollPrev = React.useCallback(() => {
69
+ api?.scrollPrev()
70
+ }, [api])
71
+
72
+ const scrollNext = React.useCallback(() => {
73
+ api?.scrollNext()
74
+ }, [api])
75
+
76
+ const handleKeyDown = React.useCallback(
77
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
78
+ if (event.key === "ArrowLeft") {
79
+ event.preventDefault()
80
+ scrollPrev()
81
+ } else if (event.key === "ArrowRight") {
82
+ event.preventDefault()
83
+ scrollNext()
84
+ }
85
+ },
86
+ [scrollPrev, scrollNext]
87
+ )
88
+
89
+ React.useEffect(() => {
90
+ if (!api || !setApi) return
91
+ setApi(api)
92
+ }, [api, setApi])
93
+
94
+ React.useEffect(() => {
95
+ if (!api) return
96
+ onSelect(api)
97
+ api.on("reInit", onSelect)
98
+ api.on("select", onSelect)
99
+
100
+ return () => {
101
+ api?.off("select", onSelect)
102
+ }
103
+ }, [api, onSelect])
104
+
105
+ return (
106
+ <CarouselContext.Provider
107
+ value={{
108
+ carouselRef,
109
+ api: api,
110
+ opts,
111
+ orientation:
112
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
113
+ scrollPrev,
114
+ scrollNext,
115
+ canScrollPrev,
116
+ canScrollNext,
117
+ }}
118
+ >
119
+ <div
120
+ onKeyDownCapture={handleKeyDown}
121
+ className={cn("relative", className)}
122
+ role="region"
123
+ aria-roledescription="carousel"
124
+ data-slot="carousel"
125
+ {...props}
126
+ >
127
+ {children}
128
+ </div>
129
+ </CarouselContext.Provider>
130
+ )
131
+ }
132
+
133
+ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
134
+ const { carouselRef, orientation } = useCarousel()
135
+
136
+ return (
137
+ <div
138
+ ref={carouselRef}
139
+ className="overflow-hidden"
140
+ data-slot="carousel-content"
141
+ >
142
+ <div
143
+ className={cn(
144
+ "flex",
145
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
146
+ className
147
+ )}
148
+ {...props}
149
+ />
150
+ </div>
151
+ )
152
+ }
153
+
154
+ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
155
+ const { orientation } = useCarousel()
156
+
157
+ return (
158
+ <div
159
+ role="group"
160
+ aria-roledescription="slide"
161
+ data-slot="carousel-item"
162
+ className={cn(
163
+ "min-w-0 shrink-0 grow-0 basis-full",
164
+ orientation === "horizontal" ? "pl-4" : "pt-4",
165
+ className
166
+ )}
167
+ {...props}
168
+ />
169
+ )
170
+ }
171
+
172
+ function CarouselPrevious({
173
+ className,
174
+ variant = "outline",
175
+ size = "icon",
176
+ ...props
177
+ }: React.ComponentProps<typeof Button>) {
178
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
179
+
180
+ return (
181
+ <Button
182
+ data-slot="carousel-previous"
183
+ variant={variant}
184
+ size={size}
185
+ className={cn(
186
+ "absolute size-8 rounded-full",
187
+ orientation === "horizontal"
188
+ ? "top-1/2 -left-12 -translate-y-1/2"
189
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
190
+ className
191
+ )}
192
+ disabled={!canScrollPrev}
193
+ onClick={scrollPrev}
194
+ {...props}
195
+ >
196
+ <ArrowLeft />
197
+ <span className="sr-only">Previous slide</span>
198
+ </Button>
199
+ )
200
+ }
201
+
202
+ function CarouselNext({
203
+ className,
204
+ variant = "outline",
205
+ size = "icon",
206
+ ...props
207
+ }: React.ComponentProps<typeof Button>) {
208
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
209
+
210
+ return (
211
+ <Button
212
+ data-slot="carousel-next"
213
+ variant={variant}
214
+ size={size}
215
+ className={cn(
216
+ "absolute size-8 rounded-full",
217
+ orientation === "horizontal"
218
+ ? "top-1/2 -right-12 -translate-y-1/2"
219
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
220
+ className
221
+ )}
222
+ disabled={!canScrollNext}
223
+ onClick={scrollNext}
224
+ {...props}
225
+ >
226
+ <ArrowRight />
227
+ <span className="sr-only">Next slide</span>
228
+ </Button>
229
+ )
230
+ }
231
+
232
+ export {
233
+ type CarouselApi,
234
+ Carousel,
235
+ CarouselContent,
236
+ CarouselItem,
237
+ CarouselPrevious,
238
+ CarouselNext,
239
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,44 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
+ import FileNavigator from '@/components/file-navigator'
3
+ import StartupDialog from '@/components/startup-dialog'
4
+
5
+ import {
6
+ SidebarProvider,
7
+ SidebarTrigger,
8
+ useSidebar,
9
+ } from '@/components/ui/sidebar'
10
+ import { Toaster } from '@/components/toaster'
11
+
12
+ import { AppSidebar } from '@/components/cta-sidebar'
13
+
14
+ const queryClient = new QueryClient()
15
+
16
+ function Content() {
17
+ const { open } = useSidebar()
18
+
19
+ 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 />
29
+ </div>
30
+ </main>
31
+ )
32
+ }
33
+
34
+ export default function RootComponent() {
35
+ return (
36
+ <QueryClientProvider client={queryClient}>
37
+ <SidebarProvider>
38
+ <AppSidebar />
39
+ <Content />
40
+ <Toaster />
41
+ </SidebarProvider>
42
+ </QueryClientProvider>
43
+ )
44
+ }