@tanstack/cta-ui-base 0.15.4

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 (172) hide show
  1. package/LICENSE +21 -0
  2. package/components.json +21 -0
  3. package/dist/app.d.ts +1 -0
  4. package/dist/app.js +10 -0
  5. package/dist/components/add-on-info-dialog.d.ts +5 -0
  6. package/dist/components/add-on-info-dialog.js +5 -0
  7. package/dist/components/background-animation.d.ts +1 -0
  8. package/dist/components/background-animation.js +144 -0
  9. package/dist/components/cta-provider.d.ts +3 -0
  10. package/dist/components/cta-provider.js +11 -0
  11. package/dist/components/cta-sidebar.d.ts +1 -0
  12. package/dist/components/cta-sidebar.js +15 -0
  13. package/dist/components/custom-add-on-dialog.d.ts +1 -0
  14. package/dist/components/custom-add-on-dialog.js +38 -0
  15. package/dist/components/file-navigator.d.ts +2 -0
  16. package/dist/components/file-navigator.js +86 -0
  17. package/dist/components/file-tree.d.ts +5 -0
  18. package/dist/components/file-tree.js +14 -0
  19. package/dist/components/file-viewer.d.ts +5 -0
  20. package/dist/components/file-viewer.js +40 -0
  21. package/dist/components/header.d.ts +1 -0
  22. package/dist/components/header.js +5 -0
  23. package/dist/components/icons/tailwind.d.ts +3 -0
  24. package/dist/components/icons/tailwind.js +5 -0
  25. package/dist/components/icons/tanstack.d.ts +3 -0
  26. package/dist/components/icons/tanstack.js +5 -0
  27. package/dist/components/icons/typescript.d.ts +3 -0
  28. package/dist/components/icons/typescript.js +5 -0
  29. package/dist/components/query-provider.d.ts +3 -0
  30. package/dist/components/query-provider.js +7 -0
  31. package/dist/components/sidebar-items/add-ons.d.ts +1 -0
  32. package/dist/components/sidebar-items/add-ons.js +27 -0
  33. package/dist/components/sidebar-items/mode-selector.d.ts +1 -0
  34. package/dist/components/sidebar-items/mode-selector.js +19 -0
  35. package/dist/components/sidebar-items/project-name.d.ts +1 -0
  36. package/dist/components/sidebar-items/project-name.js +12 -0
  37. package/dist/components/sidebar-items/run-add-ons.d.ts +1 -0
  38. package/dist/components/sidebar-items/run-add-ons.js +25 -0
  39. package/dist/components/sidebar-items/run-create-app.d.ts +1 -0
  40. package/dist/components/sidebar-items/run-create-app.js +28 -0
  41. package/dist/components/sidebar-items/sidebar-container.d.ts +3 -0
  42. package/dist/components/sidebar-items/sidebar-container.js +4 -0
  43. package/dist/components/sidebar-items/sidebar-group.d.ts +3 -0
  44. package/dist/components/sidebar-items/sidebar-group.js +4 -0
  45. package/dist/components/sidebar-items/starter.d.ts +1 -0
  46. package/dist/components/sidebar-items/starter.js +42 -0
  47. package/dist/components/sidebar-items/typescript-switch.d.ts +1 -0
  48. package/dist/components/sidebar-items/typescript-switch.js +18 -0
  49. package/dist/components/starters-carousel.d.ts +3 -0
  50. package/dist/components/starters-carousel.js +12 -0
  51. package/dist/components/startup-dialog.d.ts +1 -0
  52. package/dist/components/startup-dialog.js +30 -0
  53. package/dist/components/status-list.d.ts +5 -0
  54. package/dist/components/status-list.js +4 -0
  55. package/dist/components/toaster.d.ts +4 -0
  56. package/dist/components/toaster.js +15 -0
  57. package/dist/components/ui/button.d.ts +10 -0
  58. package/dist/components/ui/button.js +32 -0
  59. package/dist/components/ui/carousel.d.ts +20 -0
  60. package/dist/components/ui/carousel.js +90 -0
  61. package/dist/components/ui/checkbox.d.ts +4 -0
  62. package/dist/components/ui/checkbox.js +9 -0
  63. package/dist/components/ui/dialog.d.ts +15 -0
  64. package/dist/components/ui/dialog.js +36 -0
  65. package/dist/components/ui/dropdown-menu.d.ts +25 -0
  66. package/dist/components/ui/dropdown-menu.js +51 -0
  67. package/dist/components/ui/input.d.ts +3 -0
  68. package/dist/components/ui/input.js +7 -0
  69. package/dist/components/ui/label.d.ts +4 -0
  70. package/dist/components/ui/label.js +8 -0
  71. package/dist/components/ui/popover.d.ts +7 -0
  72. package/dist/components/ui/popover.js +17 -0
  73. package/dist/components/ui/separator.d.ts +4 -0
  74. package/dist/components/ui/separator.js +9 -0
  75. package/dist/components/ui/sheet.d.ts +13 -0
  76. package/dist/components/ui/sheet.js +40 -0
  77. package/dist/components/ui/skeleton.d.ts +2 -0
  78. package/dist/components/ui/skeleton.js +6 -0
  79. package/dist/components/ui/sonner.d.ts +3 -0
  80. package/dist/components/ui/sonner.js +12 -0
  81. package/dist/components/ui/switch.d.ts +4 -0
  82. package/dist/components/ui/switch.js +8 -0
  83. package/dist/components/ui/table.d.ts +10 -0
  84. package/dist/components/ui/table.js +28 -0
  85. package/dist/components/ui/tabs.d.ts +7 -0
  86. package/dist/components/ui/tabs.js +17 -0
  87. package/dist/components/ui/toggle-group.d.ts +7 -0
  88. package/dist/components/ui/toggle-group.js +20 -0
  89. package/dist/components/ui/toggle.d.ts +9 -0
  90. package/dist/components/ui/toggle.js +27 -0
  91. package/dist/components/ui/tooltip.d.ts +7 -0
  92. package/dist/components/ui/tooltip.js +18 -0
  93. package/dist/components/ui/tree-view.d.ts +25 -0
  94. package/dist/components/ui/tree-view.js +151 -0
  95. package/dist/file-classes.d.ts +8 -0
  96. package/dist/file-classes.js +41 -0
  97. package/dist/hooks/use-mounted.d.ts +1 -0
  98. package/dist/hooks/use-mounted.js +8 -0
  99. package/dist/hooks/use-preferred-reduced-motion.d.ts +5 -0
  100. package/dist/hooks/use-preferred-reduced-motion.js +20 -0
  101. package/dist/hooks/use-streaming-status.d.ts +6 -0
  102. package/dist/hooks/use-streaming-status.js +55 -0
  103. package/dist/index.d.ts +20 -0
  104. package/dist/index.js +20 -0
  105. package/dist/lib/api.d.ts +14 -0
  106. package/dist/lib/api.js +74 -0
  107. package/dist/lib/utils.d.ts +2 -0
  108. package/dist/lib/utils.js +5 -0
  109. package/dist/store/add-ons.d.ts +7 -0
  110. package/dist/store/add-ons.js +59 -0
  111. package/dist/store/project.d.ts +76 -0
  112. package/dist/store/project.js +269 -0
  113. package/package.json +50 -0
  114. package/src/app.tsx +28 -0
  115. package/src/components/add-on-info-dialog.tsx +39 -0
  116. package/src/components/background-animation.tsx +224 -0
  117. package/src/components/cta-provider.tsx +22 -0
  118. package/src/components/cta-sidebar.tsx +43 -0
  119. package/src/components/custom-add-on-dialog.tsx +79 -0
  120. package/src/components/file-navigator.tsx +207 -0
  121. package/src/components/file-tree.tsx +35 -0
  122. package/src/components/file-viewer.tsx +67 -0
  123. package/src/components/header.tsx +29 -0
  124. package/src/components/icons/tailwind.tsx +26 -0
  125. package/src/components/icons/tanstack.tsx +338 -0
  126. package/src/components/icons/typescript.tsx +23 -0
  127. package/src/components/query-provider.tsx +10 -0
  128. package/src/components/sidebar-items/add-ons.tsx +94 -0
  129. package/src/components/sidebar-items/mode-selector.tsx +56 -0
  130. package/src/components/sidebar-items/project-name.tsx +32 -0
  131. package/src/components/sidebar-items/run-add-ons.tsx +71 -0
  132. package/src/components/sidebar-items/run-create-app.tsx +82 -0
  133. package/src/components/sidebar-items/sidebar-container.tsx +11 -0
  134. package/src/components/sidebar-items/sidebar-group.tsx +11 -0
  135. package/src/components/sidebar-items/starter.tsx +123 -0
  136. package/src/components/sidebar-items/typescript-switch.tsx +58 -0
  137. package/src/components/starters-carousel.tsx +41 -0
  138. package/src/components/startup-dialog.tsx +72 -0
  139. package/src/components/status-list.tsx +22 -0
  140. package/src/components/toaster.tsx +29 -0
  141. package/src/components/ui/button.tsx +61 -0
  142. package/src/components/ui/carousel.tsx +239 -0
  143. package/src/components/ui/checkbox.tsx +30 -0
  144. package/src/components/ui/dialog.tsx +138 -0
  145. package/src/components/ui/dropdown-menu.tsx +255 -0
  146. package/src/components/ui/input.tsx +21 -0
  147. package/src/components/ui/label.tsx +22 -0
  148. package/src/components/ui/popover.tsx +46 -0
  149. package/src/components/ui/separator.tsx +28 -0
  150. package/src/components/ui/sheet.tsx +137 -0
  151. package/src/components/ui/skeleton.tsx +13 -0
  152. package/src/components/ui/sonner.tsx +24 -0
  153. package/src/components/ui/switch.tsx +29 -0
  154. package/src/components/ui/table.tsx +114 -0
  155. package/src/components/ui/tabs.tsx +64 -0
  156. package/src/components/ui/toggle-group.tsx +72 -0
  157. package/src/components/ui/toggle.tsx +49 -0
  158. package/src/components/ui/tooltip.tsx +61 -0
  159. package/src/components/ui/tree-view.tsx +497 -0
  160. package/src/file-classes.ts +54 -0
  161. package/src/hooks/use-mounted.ts +9 -0
  162. package/src/hooks/use-preferred-reduced-motion.ts +27 -0
  163. package/src/hooks/use-streaming-status.ts +70 -0
  164. package/src/index.ts +44 -0
  165. package/src/lib/api.ts +100 -0
  166. package/src/lib/utils.ts +8 -0
  167. package/src/store/add-ons.ts +81 -0
  168. package/src/store/project.ts +345 -0
  169. package/src/types.d.ts +109 -0
  170. package/tests/store/add-ons.test.ts +222 -0
  171. package/tsconfig.json +25 -0
  172. package/vitest.config.ts +6 -0
package/src/lib/api.ts ADDED
@@ -0,0 +1,100 @@
1
+ import type { SerializedOptions } from '@tanstack/cta-engine'
2
+
3
+ import type {
4
+ AddOnInfo,
5
+ DryRunOutput,
6
+ InitialData,
7
+ StarterInfo,
8
+ } from '../types'
9
+
10
+ // @ts-ignore - import.meta.env is not available in the browser
11
+ const baseUrl = import.meta.env.VITE_API_BASE_URL || ''
12
+
13
+ export async function createAppStreaming(
14
+ options: SerializedOptions,
15
+ chosenAddOns: Array<string>,
16
+ projectStarter?: StarterInfo,
17
+ ) {
18
+ return await fetch(`${baseUrl}/api/create-app`, {
19
+ method: 'POST',
20
+ body: JSON.stringify({
21
+ options: {
22
+ ...options,
23
+ chosenAddOns,
24
+ starter: projectStarter?.url || undefined,
25
+ },
26
+ }),
27
+ headers: {
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ })
31
+ }
32
+
33
+ export async function addToAppStreaming(chosenAddOns: Array<string>) {
34
+ return await fetch(`${baseUrl}/api/add-to-app`, {
35
+ method: 'POST',
36
+ body: JSON.stringify({
37
+ addOns: chosenAddOns,
38
+ }),
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ },
42
+ })
43
+ }
44
+
45
+ export function shutdown() {
46
+ return fetch(`${baseUrl}/api/shutdown`, {
47
+ method: 'POST',
48
+ })
49
+ }
50
+
51
+ export async function loadRemoteAddOn(url: string) {
52
+ const response = await fetch(`${baseUrl}/api/load-remote-add-on?url=${url}`)
53
+ return (await response.json()) as AddOnInfo | { error: string }
54
+ }
55
+
56
+ export async function loadRemoteStarter(url: string) {
57
+ const response = await fetch(`${baseUrl}/api/load-starter?url=${url}`)
58
+ return (await response.json()) as StarterInfo | { error: string }
59
+ }
60
+
61
+ const initialDataRequest = fetch(`${baseUrl}/api/initial-payload`)
62
+
63
+ export async function loadInitialData() {
64
+ const payloadReq = await initialDataRequest
65
+ return (await payloadReq.json()) as InitialData
66
+ }
67
+
68
+ export async function dryRunCreateApp(
69
+ options: SerializedOptions,
70
+ chosenAddOns: Array<string>,
71
+ projectStarter?: StarterInfo,
72
+ ) {
73
+ const outputReq = await fetch(`${baseUrl}/api/dry-run-create-app`, {
74
+ method: 'POST',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ },
78
+ body: JSON.stringify({
79
+ options: {
80
+ ...options,
81
+ chosenAddOns: chosenAddOns,
82
+ starter: projectStarter?.url,
83
+ },
84
+ }),
85
+ })
86
+ return outputReq.json() as Promise<DryRunOutput>
87
+ }
88
+
89
+ export async function dryRunAddToApp(addOns: Array<string>) {
90
+ const outputReq = await fetch(`${baseUrl}/api/dry-run-add-to-app`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ },
95
+ body: JSON.stringify({
96
+ addOns,
97
+ }),
98
+ })
99
+ return outputReq.json() as Promise<DryRunOutput>
100
+ }
@@ -0,0 +1,8 @@
1
+ import { clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ import type { ClassValue } from 'clsx'
5
+
6
+ export function cn(...inputs: Array<ClassValue>) {
7
+ return twMerge(clsx(inputs))
8
+ }
@@ -0,0 +1,81 @@
1
+ import type { AddOnInfo } from '../types'
2
+
3
+ export function getAddOnStatus(
4
+ availableAddOns: Array<AddOnInfo>,
5
+ chosenAddOns: Array<string>,
6
+ originalAddOns: Array<string>,
7
+ ) {
8
+ const addOnMap = new Map<
9
+ string,
10
+ {
11
+ enabled: boolean
12
+ selected: boolean
13
+ dependedUpon: boolean
14
+ }
15
+ >()
16
+
17
+ for (const addOn of availableAddOns) {
18
+ addOnMap.set(addOn.id, {
19
+ selected: false,
20
+ enabled: true,
21
+ dependedUpon: false,
22
+ })
23
+ }
24
+
25
+ // Guard against cycles in the dependency graph. The results won't be great. But it won't crash.
26
+ function cycleGuardedSelectAndDisableDependsOn(startingAddOnId: string) {
27
+ const visited = new Set<string>()
28
+ function selectAndDisableDependsOn(addOnId: string) {
29
+ if (visited.has(addOnId)) {
30
+ return
31
+ }
32
+ visited.add(addOnId)
33
+ const selectedAddOn = availableAddOns.find(
34
+ (addOn) => addOn.id === addOnId,
35
+ )
36
+ if (selectedAddOn) {
37
+ for (const dependsOnId of selectedAddOn.dependsOn || []) {
38
+ const dependsOnAddOn = addOnMap.get(dependsOnId)
39
+ if (dependsOnAddOn) {
40
+ dependsOnAddOn.selected = true
41
+ dependsOnAddOn.enabled = false
42
+ dependsOnAddOn.dependedUpon = true
43
+ selectAndDisableDependsOn(dependsOnId)
44
+ }
45
+ }
46
+ const addOn = addOnMap.get(addOnId)
47
+ if (addOn) {
48
+ addOn.selected = true
49
+ if (!addOn.dependedUpon) {
50
+ addOn.enabled = true
51
+ }
52
+ }
53
+ }
54
+ }
55
+ selectAndDisableDependsOn(startingAddOnId)
56
+ }
57
+
58
+ for (const addOn of originalAddOns) {
59
+ const addOnInfo = addOnMap.get(addOn)
60
+ if (addOnInfo) {
61
+ addOnInfo.selected = true
62
+ addOnInfo.enabled = false
63
+ addOnInfo.dependedUpon = true
64
+ }
65
+ cycleGuardedSelectAndDisableDependsOn(addOn)
66
+ }
67
+
68
+ for (const addOnId of chosenAddOns) {
69
+ cycleGuardedSelectAndDisableDependsOn(addOnId)
70
+ }
71
+
72
+ return Object.fromEntries(
73
+ Array.from(addOnMap.entries()).map(([v, addOn]) => [
74
+ v,
75
+ {
76
+ enabled: addOn.enabled,
77
+ selected: addOn.selected,
78
+ },
79
+ ]),
80
+ )
81
+ }
@@ -0,0 +1,345 @@
1
+ import { useCallback, useEffect, useMemo } from 'react'
2
+ import { create } from 'zustand'
3
+ import { persist } from 'zustand/middleware'
4
+ import { useQuery } from '@tanstack/react-query'
5
+
6
+ import { dryRunAddToApp, dryRunCreateApp, loadInitialData } from '../lib/api'
7
+
8
+ import { getAddOnStatus } from './add-ons'
9
+
10
+ import type { SerializedOptions } from '@tanstack/cta-engine'
11
+
12
+ import type { AddOnInfo, DryRunOutput, StarterInfo } from '../types'
13
+
14
+ export const useProjectOptions = create<
15
+ SerializedOptions & { initialized: boolean }
16
+ >(() => ({
17
+ initialized: false,
18
+ framework: '',
19
+ mode: '',
20
+ projectName: '',
21
+ targetDir: '',
22
+ typescript: true,
23
+ tailwind: true,
24
+ git: true,
25
+ chosenAddOns: [],
26
+ packageManager: 'pnpm',
27
+ }))
28
+
29
+ const useInitialData = () =>
30
+ useQuery({
31
+ queryKey: ['initial-data'],
32
+ queryFn: loadInitialData,
33
+ })
34
+
35
+ export const useReady = () => {
36
+ const { data } = useInitialData()
37
+ return data !== undefined
38
+ }
39
+
40
+ const useForcedRouterMode = () => useInitialData().data?.forcedRouterMode
41
+ const useForcedAddOns = () => useInitialData().data?.forcedAddOns
42
+
43
+ export const useRegistry = () => useInitialData().data?.registry
44
+ export const useProjectLocalFiles = () => useInitialData().data?.localFiles
45
+ export const useOriginalOutput = () => useInitialData().data?.output
46
+ export const useOriginalOptions = () => useInitialData().data?.options
47
+ export const useOriginalSelectedAddOns = () =>
48
+ useOriginalOptions()?.chosenAddOns
49
+ export const useApplicationMode = () => useInitialData().data?.applicationMode
50
+ export const useAddOnsByMode = () => useInitialData().data?.addOns
51
+ export const useSupportedModes = () => useInitialData().data?.supportedModes
52
+
53
+ const useApplicationSettings = create<{
54
+ includeFiles: Array<string>
55
+ }>(() => ({
56
+ includeFiles: ['unchanged', 'added', 'modified', 'deleted', 'overwritten'],
57
+ }))
58
+
59
+ const useMutableAddOns = create<{
60
+ userSelectedAddOns: Array<string>
61
+ customAddOns: Array<AddOnInfo>
62
+ }>(() => ({
63
+ userSelectedAddOns: [],
64
+ customAddOns: [],
65
+ }))
66
+
67
+ export const useProjectStarter = create<{
68
+ projectStarter: StarterInfo | undefined
69
+ }>(() => ({
70
+ projectStarter: undefined,
71
+ }))
72
+
73
+ export function addCustomAddOn(addOn: AddOnInfo) {
74
+ useMutableAddOns.setState((state) => ({
75
+ customAddOns: [...state.customAddOns, addOn],
76
+ }))
77
+ if (addOn.modes.includes(useProjectOptions.getState().mode)) {
78
+ useMutableAddOns.setState((state) => ({
79
+ userSelectedAddOns: [...state.userSelectedAddOns, addOn.id],
80
+ }))
81
+ }
82
+ }
83
+
84
+ export function useAddOns() {
85
+ const ready = useReady()
86
+
87
+ const routerMode = useRouterMode()
88
+ const originalSelectedAddOns = useOriginalSelectedAddOns()
89
+ const addOnsByMode = useAddOnsByMode()
90
+ const forcedAddOns = useForcedAddOns()
91
+ const { userSelectedAddOns, customAddOns } = useMutableAddOns()
92
+ const projectStarter = useProjectStarter().projectStarter
93
+
94
+ const availableAddOns = useMemo(() => {
95
+ if (!ready) return []
96
+ const baseAddOns = addOnsByMode?.[routerMode] || []
97
+ return [
98
+ ...baseAddOns,
99
+ ...customAddOns.filter((addOn) => addOn.modes.includes(routerMode)),
100
+ ]
101
+ }, [ready, routerMode, addOnsByMode, customAddOns])
102
+
103
+ const addOnState = useMemo(() => {
104
+ if (!ready) return {}
105
+ const originalAddOns: Set<string> = new Set()
106
+ for (const addOn of projectStarter?.dependsOn || []) {
107
+ originalAddOns.add(addOn)
108
+ }
109
+ for (const addOn of originalSelectedAddOns) {
110
+ originalAddOns.add(addOn)
111
+ }
112
+ for (const addOn of forcedAddOns || []) {
113
+ originalAddOns.add(addOn)
114
+ }
115
+ return getAddOnStatus(
116
+ availableAddOns,
117
+ userSelectedAddOns,
118
+ Array.from(originalAddOns),
119
+ )
120
+ }, [
121
+ ready,
122
+ availableAddOns,
123
+ userSelectedAddOns,
124
+ originalSelectedAddOns,
125
+ projectStarter?.dependsOn,
126
+ forcedAddOns,
127
+ ])
128
+
129
+ const chosenAddOns = useMemo(() => {
130
+ if (!ready) return []
131
+ const addOns = new Set(
132
+ Object.keys(addOnState).filter((addOn) => addOnState[addOn].selected),
133
+ )
134
+ for (const addOn of forcedAddOns || []) {
135
+ addOns.add(addOn)
136
+ }
137
+ return Array.from(addOns)
138
+ }, [ready, addOnState, forcedAddOns])
139
+
140
+ const toggleAddOn = useCallback(
141
+ (addOnId: string) => {
142
+ if (!ready) return
143
+ if (addOnState[addOnId].enabled) {
144
+ if (addOnState[addOnId].selected) {
145
+ useMutableAddOns.setState((state) => ({
146
+ userSelectedAddOns: state.userSelectedAddOns.filter(
147
+ (addOn) => addOn !== addOnId,
148
+ ),
149
+ }))
150
+ } else {
151
+ useMutableAddOns.setState((state) => ({
152
+ userSelectedAddOns: [...state.userSelectedAddOns, addOnId],
153
+ }))
154
+ }
155
+ }
156
+ },
157
+ [ready, addOnState],
158
+ )
159
+
160
+ return {
161
+ toggleAddOn,
162
+ chosenAddOns,
163
+ availableAddOns,
164
+ userSelectedAddOns,
165
+ originalSelectedAddOns,
166
+ addOnState,
167
+ }
168
+ }
169
+
170
+ const useHasProjectStarter = () =>
171
+ useProjectStarter((state) => state.projectStarter === undefined)
172
+
173
+ export const useModeEditable = () => {
174
+ const ready = useReady()
175
+ const forcedRouterMode = useForcedRouterMode()
176
+ const hasProjectStarter = useHasProjectStarter()
177
+ return ready ? !forcedRouterMode && hasProjectStarter : false
178
+ }
179
+
180
+ export const useTypeScriptEditable = () => {
181
+ const ready = useReady()
182
+ const hasProjectStarter = useHasProjectStarter()
183
+ const routerMode = useRouterMode()
184
+ return ready ? hasProjectStarter && routerMode === 'code-router' : false
185
+ }
186
+
187
+ export const useTailwindEditable = () => {
188
+ const ready = useReady()
189
+ const hasProjectStarter = useHasProjectStarter()
190
+ const routerMode = useRouterMode()
191
+ return ready ? hasProjectStarter && routerMode === 'code-router' : false
192
+ }
193
+
194
+ export const useProjectName = () =>
195
+ useProjectOptions((state) => state.projectName)
196
+
197
+ export const useRouterMode = () => {
198
+ const ready = useReady()
199
+ const forcedRouterMode = useForcedRouterMode()
200
+ const userMode = useProjectOptions((state) => state.mode)
201
+ return ready ? forcedRouterMode || userMode : 'file-router'
202
+ }
203
+
204
+ export function useFilters() {
205
+ const ready = useReady()
206
+ const includedFiles = useApplicationSettings((state) => state.includeFiles)
207
+
208
+ const toggleFilter = useCallback(
209
+ (filter: string) => {
210
+ if (!ready) return
211
+ useApplicationSettings.setState((state) => ({
212
+ includeFiles: state.includeFiles.includes(filter)
213
+ ? state.includeFiles.filter((f) => f !== filter)
214
+ : [...state.includeFiles, filter],
215
+ }))
216
+ },
217
+ [ready],
218
+ )
219
+
220
+ return {
221
+ includedFiles,
222
+ toggleFilter,
223
+ }
224
+ }
225
+
226
+ export function useDryRun() {
227
+ const ready = useReady()
228
+ const applicationMode = useApplicationMode()
229
+ const { initialized, ...projectOptions } = useProjectOptions()
230
+ const { userSelectedAddOns, chosenAddOns } = useAddOns()
231
+ const projectStarter = useProjectStarter().projectStarter
232
+
233
+ const { data: dryRunOutput } = useQuery<DryRunOutput>({
234
+ queryKey: [
235
+ 'dry-run',
236
+ applicationMode,
237
+ JSON.stringify(projectOptions),
238
+ JSON.stringify(userSelectedAddOns),
239
+ projectStarter?.url,
240
+ ],
241
+ queryFn: async () => {
242
+ if (applicationMode === 'none' || !ready || !initialized) {
243
+ return {
244
+ files: {},
245
+ commands: [],
246
+ deletedFiles: [],
247
+ }
248
+ } else if (applicationMode === 'setup') {
249
+ return dryRunCreateApp(projectOptions, chosenAddOns, projectStarter)
250
+ } else {
251
+ return dryRunAddToApp(userSelectedAddOns)
252
+ }
253
+ },
254
+ enabled: ready,
255
+ initialData: {
256
+ files: {},
257
+ commands: [],
258
+ deletedFiles: [],
259
+ },
260
+ })
261
+
262
+ return dryRunOutput
263
+ }
264
+
265
+ type StartupDialogState = {
266
+ open: boolean
267
+ dontShowAgain: boolean
268
+ setOpen: (open: boolean) => void
269
+ setDontShowAgain: (dontShowAgain: boolean) => void
270
+ }
271
+
272
+ export const useStartupDialog = create<StartupDialogState>()(
273
+ persist(
274
+ (set) => ({
275
+ open: false,
276
+ dontShowAgain: false,
277
+ setOpen: (open) => set({ open }),
278
+ setDontShowAgain: (dontShowAgain) => set({ dontShowAgain }),
279
+ }),
280
+ {
281
+ name: 'startup-dialog',
282
+ partialize: (state) => ({
283
+ dontShowAgain: state.dontShowAgain,
284
+ }),
285
+ merge: (persistedState: unknown, currentState) => {
286
+ if (
287
+ persistedState &&
288
+ (persistedState as { dontShowAgain?: boolean }).dontShowAgain
289
+ ) {
290
+ currentState.open = false
291
+ } else {
292
+ currentState.open = true
293
+ }
294
+ return currentState
295
+ },
296
+ },
297
+ ),
298
+ )
299
+
300
+ export const setProjectName = (projectName: string) =>
301
+ useProjectOptions.setState({
302
+ projectName,
303
+ })
304
+
305
+ export const setRouterMode = (mode: string) =>
306
+ useProjectOptions.setState({
307
+ mode,
308
+ })
309
+
310
+ export function setTypeScript(typescript: boolean) {
311
+ useProjectOptions.setState({
312
+ typescript,
313
+ })
314
+ }
315
+
316
+ export function setTailwind(tailwind: boolean) {
317
+ useProjectOptions.setState({
318
+ tailwind,
319
+ })
320
+ }
321
+
322
+ export function setProjectStarter(starter: StarterInfo | undefined) {
323
+ useProjectStarter.setState(() => ({
324
+ projectStarter: starter,
325
+ }))
326
+ if (starter) {
327
+ useProjectOptions.setState({
328
+ mode: starter.mode,
329
+ })
330
+ }
331
+ }
332
+
333
+ export function useManager() {
334
+ const ready = useReady()
335
+ const originalOptions = useOriginalOptions()
336
+
337
+ useEffect(() => {
338
+ if (ready) {
339
+ useProjectOptions.setState({
340
+ ...originalOptions,
341
+ initialized: true,
342
+ })
343
+ }
344
+ }, [ready])
345
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,109 @@
1
+ import type { StatusStepType } from '@tanstack/cta-engine'
2
+
3
+ export type ApplicationMode = 'add' | 'setup' | 'none'
4
+
5
+ export type StarterInfo = {
6
+ url: string
7
+ id: string
8
+ name: string
9
+ description: string
10
+ version: string
11
+ author: string
12
+ license: string
13
+ mode: string
14
+ typescript: boolean
15
+ tailwind: boolean
16
+ banner?: string
17
+ dependsOn?: Array<string>
18
+ }
19
+
20
+ // Files
21
+
22
+ export type DryRunOutput = {
23
+ files: Record<string, string>
24
+ commands: Array<{
25
+ command: string
26
+ args: Array<string>
27
+ }>
28
+ deletedFiles: Array<string>
29
+ }
30
+
31
+ export type AddOnInfo = {
32
+ id: string
33
+ name: string
34
+ description: string
35
+ type: 'add-on' | 'example' | 'starter' | 'toolchain'
36
+ modes: Array<string>
37
+ smallLogo?: string
38
+ logo?: string
39
+ link: string
40
+ dependsOn?: Array<string>
41
+ }
42
+
43
+ export type FileClass =
44
+ | 'unchanged'
45
+ | 'added'
46
+ | 'modified'
47
+ | 'deleted'
48
+ | 'overwritten'
49
+
50
+ export type FileTreeItem = TreeDataItem & {
51
+ contents: string
52
+ fullPath: string
53
+ fileClass: FileClass | undefined
54
+ originalFile?: string
55
+ modifiedFile?: string
56
+ }
57
+
58
+ export type Registry = {
59
+ starters: Array<{
60
+ name: string
61
+ description: string
62
+ url: string
63
+ banner?: string
64
+ }>
65
+ 'add-ons': Array<{
66
+ name: string
67
+ description: string
68
+ url: string
69
+ }>
70
+ }
71
+
72
+ export type InitialData = {
73
+ supportedModes: Record<
74
+ string,
75
+ {
76
+ displayName: string
77
+ description: string
78
+ forceTypescript: boolean
79
+ }
80
+ >
81
+ options: SerializedOptions
82
+ output: GeneratorOutput
83
+ localFiles: Record<string, string>
84
+ addOns: Record<string, Array<AddOnInfo>>
85
+ applicationMode: ApplicationMode
86
+ forcedRouterMode?: string
87
+ forcedAddOns?: Array<string>
88
+ registry?: Registry | undefined
89
+ }
90
+
91
+ export type EventItem = {
92
+ msgType: 'start'
93
+ id: string
94
+ type: StatusStepType
95
+ message: string
96
+ }
97
+ export type EventFinish = {
98
+ msgType: 'finish'
99
+ id: string
100
+ message: string
101
+ }
102
+
103
+ export type StreamEvent = EventItem | EventFinish
104
+
105
+ export type StreamItem = {
106
+ id: string
107
+ icon: typeof FileIcon
108
+ message: string
109
+ }