create-react-starter-kit 1.0.0
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/LICENSE +21 -0
- package/README.md +119 -0
- package/bin/create-react-starter-kit.js +7 -0
- package/dist/cli-args.d.ts +15 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +78 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/generator/dependencies.d.ts +8 -0
- package/dist/generator/dependencies.d.ts.map +1 -0
- package/dist/generator/dependencies.js +356 -0
- package/dist/generator/dependencies.js.map +1 -0
- package/dist/generator/index.d.ts +5 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +358 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +267 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/templates/.env.example.ejs +21 -0
- package/dist/templates/.husky/commit-msg +2 -0
- package/dist/templates/.husky/pre-commit +2 -0
- package/dist/templates/README.md.ejs +169 -0
- package/dist/templates/__js__/jsconfig.json +9 -0
- package/dist/templates/__ts__/src/vite-env.d.ts +16 -0
- package/dist/templates/__ts__/tsconfig.json +24 -0
- package/dist/templates/__when_buildTool_esbuild__/scripts/esbuild.build.mjs.ejs +23 -0
- package/dist/templates/__when_buildTool_esbuild__/scripts/esbuild.dev.mjs.ejs +48 -0
- package/dist/templates/__when_buildTool_parcel__/.parcelrc +9 -0
- package/dist/templates/__when_buildTool_rollup__/rollup.config.js.ejs +33 -0
- package/dist/templates/__when_buildTool_rspack__/rspack.config.js.ejs +36 -0
- package/dist/templates/__when_buildTool_vite__/vite.config.ts.ejs +37 -0
- package/dist/templates/__when_buildTool_webpack__/webpack.config.js.ejs +68 -0
- package/dist/templates/__when_e2eTesting_cypress__/cypress/e2e/home.cy.ts.ejs +6 -0
- package/dist/templates/__when_e2eTesting_cypress__/cypress.config.ts.ejs +8 -0
- package/dist/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +16 -0
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +6 -0
- package/dist/templates/__when_formatting_prettier__/.prettierrc +7 -0
- package/dist/templates/__when_formatting_stylelint__/.stylelintrc.json +3 -0
- package/dist/templates/__when_linting_biome__/biome.json +19 -0
- package/dist/templates/__when_linting_eslint__/eslint.config.js.ejs +42 -0
- package/dist/templates/__when_serverState_rtk-query__/src/store/postsApi.ts.ejs +19 -0
- package/dist/templates/__when_stateManagement_context__/src/store/context.tsx.ejs +57 -0
- package/dist/templates/__when_styling_tailwind__/postcss.config.js +6 -0
- package/dist/templates/__when_styling_tailwind__/tailwind.config.js.ejs +23 -0
- package/dist/templates/__when_unitTesting_jest__/jest.config.json.ejs +11 -0
- package/dist/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +13 -0
- package/dist/templates/__when_unitTesting_jest__/tests/setup.ts.ejs +1 -0
- package/dist/templates/__when_unitTesting_jest__/tests/styleMock.ts.ejs +1 -0
- package/dist/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +13 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +16 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/setup.ts.ejs +1 -0
- package/dist/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +11 -0
- package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js +8 -0
- package/dist/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +15 -0
- package/dist/templates/commitlint.config.js +3 -0
- package/dist/templates/docs/architecture.md.ejs +74 -0
- package/dist/templates/docs/best-practices.md.ejs +56 -0
- package/dist/templates/docs/examples/README.md.ejs +14 -0
- package/dist/templates/docs/examples/api.md.ejs +17 -0
- package/dist/templates/docs/examples/data-fetching.md.ejs +18 -0
- package/dist/templates/docs/examples/forms.md.ejs +18 -0
- package/dist/templates/docs/examples/state.md.ejs +17 -0
- package/dist/templates/docs/examples/styling.md.ejs +23 -0
- package/dist/templates/docs/examples/testing.md.ejs +27 -0
- package/dist/templates/docs/getting-started.md.ejs +55 -0
- package/dist/templates/docs/selected-stack.md.ejs +55 -0
- package/dist/templates/index.html.ejs +19 -0
- package/dist/templates/public/vite.svg +4 -0
- package/dist/templates/src/app/App.tsx.ejs +71 -0
- package/dist/templates/src/app/providers.tsx.ejs +93 -0
- package/dist/templates/src/assets/.gitkeep +0 -0
- package/dist/templates/src/components/StackBadge.tsx.ejs +15 -0
- package/dist/templates/src/components/layout/AppLayout.tsx.ejs +64 -0
- package/dist/templates/src/components/ui/Toast.tsx.ejs +61 -0
- package/dist/templates/src/components/ui/UiButton.tsx.ejs +168 -0
- package/dist/templates/src/config/env.ts.ejs +18 -0
- package/dist/templates/src/features/counter/CounterPanel.tsx.ejs +66 -0
- package/dist/templates/src/features/feedback/FeedbackForm.tsx.ejs +373 -0
- package/dist/templates/src/features/posts/PostsPanel.tsx.ejs +91 -0
- package/dist/templates/src/hooks/useMounted.ts.ejs +15 -0
- package/dist/templates/src/lib/cn.ts.ejs +12 -0
- package/dist/templates/src/lib/monitoring.ts.ejs +66 -0
- package/dist/templates/src/lib/validation.ts.ejs +81 -0
- package/dist/templates/src/main.tsx.ejs +22 -0
- package/dist/templates/src/routes/AboutPage.tsx.ejs +30 -0
- package/dist/templates/src/routes/HomePage.tsx.ejs +47 -0
- package/dist/templates/src/services/api.ts.ejs +84 -0
- package/dist/templates/src/store/index.ts.ejs +80 -0
- package/dist/templates/src/styles/global.css.ejs +457 -0
- package/dist/templates/src/types/index.ts.ejs +7 -0
- package/dist/templates/src/utils/format.ts.ejs +3 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +31 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/validate.d.ts +3 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +24 -0
- package/dist/utils/validate.js.map +1 -0
- package/package.json +62 -0
- package/templates/.env.example.ejs +21 -0
- package/templates/.husky/commit-msg +2 -0
- package/templates/.husky/pre-commit +2 -0
- package/templates/README.md.ejs +169 -0
- package/templates/__js__/jsconfig.json +9 -0
- package/templates/__ts__/src/vite-env.d.ts +16 -0
- package/templates/__ts__/tsconfig.json +24 -0
- package/templates/__when_buildTool_esbuild__/scripts/esbuild.build.mjs.ejs +23 -0
- package/templates/__when_buildTool_esbuild__/scripts/esbuild.dev.mjs.ejs +48 -0
- package/templates/__when_buildTool_parcel__/.parcelrc +9 -0
- package/templates/__when_buildTool_rollup__/rollup.config.js.ejs +33 -0
- package/templates/__when_buildTool_rspack__/rspack.config.js.ejs +36 -0
- package/templates/__when_buildTool_vite__/vite.config.ts.ejs +37 -0
- package/templates/__when_buildTool_webpack__/webpack.config.js.ejs +68 -0
- package/templates/__when_e2eTesting_cypress__/cypress/e2e/home.cy.ts.ejs +6 -0
- package/templates/__when_e2eTesting_cypress__/cypress.config.ts.ejs +8 -0
- package/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +16 -0
- package/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +6 -0
- package/templates/__when_formatting_prettier__/.prettierrc +7 -0
- package/templates/__when_formatting_stylelint__/.stylelintrc.json +3 -0
- package/templates/__when_linting_biome__/biome.json +19 -0
- package/templates/__when_linting_eslint__/eslint.config.js.ejs +42 -0
- package/templates/__when_serverState_rtk-query__/src/store/postsApi.ts.ejs +19 -0
- package/templates/__when_stateManagement_context__/src/store/context.tsx.ejs +57 -0
- package/templates/__when_styling_tailwind__/postcss.config.js +6 -0
- package/templates/__when_styling_tailwind__/tailwind.config.js.ejs +23 -0
- package/templates/__when_unitTesting_jest__/jest.config.json.ejs +11 -0
- package/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +13 -0
- package/templates/__when_unitTesting_jest__/tests/setup.ts.ejs +1 -0
- package/templates/__when_unitTesting_jest__/tests/styleMock.ts.ejs +1 -0
- package/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +13 -0
- package/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +16 -0
- package/templates/__when_unitTesting_vitest__/tests/setup.ts.ejs +1 -0
- package/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +11 -0
- package/templates/__when_visualTesting_storybook__/.storybook/preview.js +8 -0
- package/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +15 -0
- package/templates/commitlint.config.js +3 -0
- package/templates/docs/architecture.md.ejs +74 -0
- package/templates/docs/best-practices.md.ejs +56 -0
- package/templates/docs/examples/README.md.ejs +14 -0
- package/templates/docs/examples/api.md.ejs +17 -0
- package/templates/docs/examples/data-fetching.md.ejs +18 -0
- package/templates/docs/examples/forms.md.ejs +18 -0
- package/templates/docs/examples/state.md.ejs +17 -0
- package/templates/docs/examples/styling.md.ejs +23 -0
- package/templates/docs/examples/testing.md.ejs +27 -0
- package/templates/docs/getting-started.md.ejs +55 -0
- package/templates/docs/selected-stack.md.ejs +55 -0
- package/templates/index.html.ejs +19 -0
- package/templates/public/vite.svg +4 -0
- package/templates/src/app/App.tsx.ejs +71 -0
- package/templates/src/app/providers.tsx.ejs +93 -0
- package/templates/src/assets/.gitkeep +0 -0
- package/templates/src/components/StackBadge.tsx.ejs +15 -0
- package/templates/src/components/layout/AppLayout.tsx.ejs +64 -0
- package/templates/src/components/ui/Toast.tsx.ejs +61 -0
- package/templates/src/components/ui/UiButton.tsx.ejs +168 -0
- package/templates/src/config/env.ts.ejs +18 -0
- package/templates/src/features/counter/CounterPanel.tsx.ejs +66 -0
- package/templates/src/features/feedback/FeedbackForm.tsx.ejs +373 -0
- package/templates/src/features/posts/PostsPanel.tsx.ejs +91 -0
- package/templates/src/hooks/useMounted.ts.ejs +15 -0
- package/templates/src/lib/cn.ts.ejs +12 -0
- package/templates/src/lib/monitoring.ts.ejs +66 -0
- package/templates/src/lib/validation.ts.ejs +81 -0
- package/templates/src/main.tsx.ejs +22 -0
- package/templates/src/routes/AboutPage.tsx.ejs +30 -0
- package/templates/src/routes/HomePage.tsx.ejs +47 -0
- package/templates/src/services/api.ts.ejs +84 -0
- package/templates/src/store/index.ts.ejs +80 -0
- package/templates/src/styles/global.css.ejs +457 -0
- package/templates/src/types/index.ts.ejs +7 -0
- package/templates/src/utils/format.ts.ejs +3 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title><%= projectName %></title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<% if (buildTool === 'vite') { -%>
|
|
12
|
+
<script type="module" src="/src/main.<%= jsxExt %>"></script>
|
|
13
|
+
<% } else if (buildTool === 'parcel') { -%>
|
|
14
|
+
<script type="module" src="./src/main.<%= jsxExt %>"></script>
|
|
15
|
+
<% } else if (buildTool === 'esbuild' || buildTool === 'rollup') { -%>
|
|
16
|
+
<script type="module" src="/dist/main.js"></script>
|
|
17
|
+
<% } -%>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<% if (router === 'react-router') { -%>
|
|
2
|
+
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
|
3
|
+
import { AppLayout } from '@/components/layout/AppLayout';
|
|
4
|
+
import { HomePage } from '@/routes/HomePage';
|
|
5
|
+
import { AboutPage } from '@/routes/AboutPage';
|
|
6
|
+
|
|
7
|
+
export function App() {
|
|
8
|
+
return (
|
|
9
|
+
<BrowserRouter>
|
|
10
|
+
<Routes>
|
|
11
|
+
<Route element={<AppLayout />}>
|
|
12
|
+
<Route path="/" element={<HomePage />} />
|
|
13
|
+
<Route path="/about" element={<AboutPage />} />
|
|
14
|
+
<Route path="*" element={<Navigate to="/" replace />} />
|
|
15
|
+
</Route>
|
|
16
|
+
</Routes>
|
|
17
|
+
</BrowserRouter>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
<% } else { -%>
|
|
21
|
+
import {
|
|
22
|
+
Outlet,
|
|
23
|
+
RouterProvider,
|
|
24
|
+
createRootRoute,
|
|
25
|
+
createRoute,
|
|
26
|
+
createRouter,
|
|
27
|
+
} from '@tanstack/react-router';
|
|
28
|
+
import { AppLayout } from '@/components/layout/AppLayout';
|
|
29
|
+
import { HomePage } from '@/routes/HomePage';
|
|
30
|
+
import { AboutPage } from '@/routes/AboutPage';
|
|
31
|
+
|
|
32
|
+
function RootComponent() {
|
|
33
|
+
return (
|
|
34
|
+
<AppLayout>
|
|
35
|
+
<Outlet />
|
|
36
|
+
</AppLayout>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const rootRoute = createRootRoute({
|
|
41
|
+
component: RootComponent,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const indexRoute = createRoute({
|
|
45
|
+
getParentRoute: () => rootRoute,
|
|
46
|
+
path: '/',
|
|
47
|
+
component: HomePage,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const aboutRoute = createRoute({
|
|
51
|
+
getParentRoute: () => rootRoute,
|
|
52
|
+
path: '/about',
|
|
53
|
+
component: AboutPage,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]);
|
|
57
|
+
|
|
58
|
+
const router = createRouter({ routeTree });
|
|
59
|
+
|
|
60
|
+
<% if (isTypeScript) { -%>
|
|
61
|
+
declare module '@tanstack/react-router' {
|
|
62
|
+
interface Register {
|
|
63
|
+
router: typeof router;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
<% } -%>
|
|
67
|
+
|
|
68
|
+
export function App() {
|
|
69
|
+
return <RouterProvider router={router} />;
|
|
70
|
+
}
|
|
71
|
+
<% } -%>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<% if (isTypeScript) { -%>
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
<% } -%>
|
|
4
|
+
<% if (serverState === 'tanstack-query') { -%>
|
|
5
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
6
|
+
<% } -%>
|
|
7
|
+
<% if (serverState === 'swr') { -%>
|
|
8
|
+
import { SWRConfig } from 'swr';
|
|
9
|
+
import { apiGet } from '@/services/api';
|
|
10
|
+
<% } -%>
|
|
11
|
+
<% if (stateManagement === 'redux') { -%>
|
|
12
|
+
import { Provider } from 'react-redux';
|
|
13
|
+
import { store } from '@/store';
|
|
14
|
+
<% } -%>
|
|
15
|
+
<% if (stateManagement === 'jotai') { -%>
|
|
16
|
+
import { Provider as JotaiProvider } from 'jotai';
|
|
17
|
+
<% } -%>
|
|
18
|
+
<% if (stateManagement === 'context') { -%>
|
|
19
|
+
import { AppStateProvider } from '@/store/context';
|
|
20
|
+
<% } -%>
|
|
21
|
+
<% if (uiLibrary === 'chakra') { -%>
|
|
22
|
+
import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
|
|
23
|
+
<% } -%>
|
|
24
|
+
<% if (uiLibrary === 'mui') { -%>
|
|
25
|
+
import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
|
|
26
|
+
<% } -%>
|
|
27
|
+
import { ToastProvider } from '@/components/ui/Toast';
|
|
28
|
+
|
|
29
|
+
<% if (serverState === 'tanstack-query') { -%>
|
|
30
|
+
const queryClient = new QueryClient({
|
|
31
|
+
defaultOptions: {
|
|
32
|
+
queries: {
|
|
33
|
+
staleTime: 30_000,
|
|
34
|
+
retry: 1,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
<% } -%>
|
|
39
|
+
<% if (uiLibrary === 'mui') { -%>
|
|
40
|
+
const theme = createTheme({
|
|
41
|
+
palette: {
|
|
42
|
+
primary: { main: '#1d4ed8' },
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
<% } -%>
|
|
46
|
+
|
|
47
|
+
<% if (isTypeScript) { -%>
|
|
48
|
+
interface AppProvidersProps {
|
|
49
|
+
children: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
<% } -%>
|
|
52
|
+
|
|
53
|
+
export function AppProviders({ children }<% if (isTypeScript) { %>: AppProvidersProps<% } %>) {
|
|
54
|
+
let tree = children;
|
|
55
|
+
|
|
56
|
+
<% if (stateManagement === 'context') { -%>
|
|
57
|
+
tree = <AppStateProvider>{tree}</AppStateProvider>;
|
|
58
|
+
<% } -%>
|
|
59
|
+
<% if (stateManagement === 'jotai') { -%>
|
|
60
|
+
tree = <JotaiProvider>{tree}</JotaiProvider>;
|
|
61
|
+
<% } -%>
|
|
62
|
+
<% if (stateManagement === 'redux') { -%>
|
|
63
|
+
tree = <Provider store={store}>{tree}</Provider>;
|
|
64
|
+
<% } -%>
|
|
65
|
+
<% if (serverState === 'tanstack-query') { -%>
|
|
66
|
+
tree = <QueryClientProvider client={queryClient}>{tree}</QueryClientProvider>;
|
|
67
|
+
<% } -%>
|
|
68
|
+
<% if (serverState === 'swr') { -%>
|
|
69
|
+
tree = (
|
|
70
|
+
<SWRConfig
|
|
71
|
+
value={{
|
|
72
|
+
fetcher: (url<% if (isTypeScript) { %>: string<% } %>) => apiGet(url),
|
|
73
|
+
revalidateOnFocus: false,
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
{tree}
|
|
77
|
+
</SWRConfig>
|
|
78
|
+
);
|
|
79
|
+
<% } -%>
|
|
80
|
+
<% if (uiLibrary === 'mui') { -%>
|
|
81
|
+
tree = (
|
|
82
|
+
<ThemeProvider theme={theme}>
|
|
83
|
+
<CssBaseline />
|
|
84
|
+
{tree}
|
|
85
|
+
</ThemeProvider>
|
|
86
|
+
);
|
|
87
|
+
<% } -%>
|
|
88
|
+
<% if (uiLibrary === 'chakra') { -%>
|
|
89
|
+
tree = <ChakraProvider value={defaultSystem}>{tree}</ChakraProvider>;
|
|
90
|
+
<% } -%>
|
|
91
|
+
|
|
92
|
+
return <ToastProvider>{tree}</ToastProvider>;
|
|
93
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<% if (isTypeScript) { -%>
|
|
2
|
+
interface StackBadgeProps {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
<% } -%>
|
|
7
|
+
|
|
8
|
+
export function StackBadge({ label, value }<% if (isTypeScript) { %>: StackBadgeProps<% } %>) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="stack-badge">
|
|
11
|
+
<span className="stack-badge-label">{label}</span>
|
|
12
|
+
<span className="stack-badge-value">{value}</span>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<% if (isTypeScript) { -%>
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
<% } -%>
|
|
4
|
+
<% if (router === 'react-router') { -%>
|
|
5
|
+
import { NavLink, Outlet } from 'react-router-dom';
|
|
6
|
+
<% } else { -%>
|
|
7
|
+
import { Link, useRouterState } from '@tanstack/react-router';
|
|
8
|
+
<% } -%>
|
|
9
|
+
|
|
10
|
+
<% if (isTypeScript && router === 'tanstack-router') { -%>
|
|
11
|
+
interface AppLayoutProps {
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
<% } -%>
|
|
15
|
+
|
|
16
|
+
export function AppLayout(<% if (router === 'tanstack-router') { %>{ children }<% if (isTypeScript) { %>: AppLayoutProps<% } %><% } %>) {
|
|
17
|
+
<% if (router === 'tanstack-router') { -%>
|
|
18
|
+
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
19
|
+
<% } -%>
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="app-shell">
|
|
23
|
+
<header className="app-header">
|
|
24
|
+
<div className="brand">
|
|
25
|
+
<span className="brand-mark" aria-hidden="true" />
|
|
26
|
+
<div>
|
|
27
|
+
<p className="brand-name"><%= projectName %></p>
|
|
28
|
+
<p className="brand-tagline">Production React starter</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<nav className="app-nav" aria-label="Primary">
|
|
32
|
+
<% if (router === 'react-router') { -%>
|
|
33
|
+
<NavLink to="/" end className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}>
|
|
34
|
+
Home
|
|
35
|
+
</NavLink>
|
|
36
|
+
<NavLink to="/about" className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}>
|
|
37
|
+
About
|
|
38
|
+
</NavLink>
|
|
39
|
+
<% } else { -%>
|
|
40
|
+
<Link to="/" className={pathname === '/' ? 'nav-link active' : 'nav-link'}>
|
|
41
|
+
Home
|
|
42
|
+
</Link>
|
|
43
|
+
<Link to="/about" className={pathname === '/about' ? 'nav-link active' : 'nav-link'}>
|
|
44
|
+
About
|
|
45
|
+
</Link>
|
|
46
|
+
<% } -%>
|
|
47
|
+
</nav>
|
|
48
|
+
</header>
|
|
49
|
+
<main className="app-main">
|
|
50
|
+
<% if (router === 'react-router') { -%>
|
|
51
|
+
<Outlet />
|
|
52
|
+
<% } else { -%>
|
|
53
|
+
{children}
|
|
54
|
+
<% } -%>
|
|
55
|
+
</main>
|
|
56
|
+
<footer className="app-footer">
|
|
57
|
+
<p>
|
|
58
|
+
Scaffolded with create-react-starter-kit · <%= stackLabels.buildTool %> ·{' '}
|
|
59
|
+
<%= stackLabels.language %>
|
|
60
|
+
</p>
|
|
61
|
+
</footer>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
<% if (isTypeScript) { -%>
|
|
8
|
+
type ReactNode,
|
|
9
|
+
<% } -%>
|
|
10
|
+
} from 'react';
|
|
11
|
+
|
|
12
|
+
<% if (isTypeScript) { -%>
|
|
13
|
+
type ToastTone = 'info' | 'success' | 'error';
|
|
14
|
+
|
|
15
|
+
interface ToastItem {
|
|
16
|
+
id: string;
|
|
17
|
+
message: string;
|
|
18
|
+
tone: ToastTone;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ToastContextValue {
|
|
22
|
+
push: (message: string, tone?: ToastTone) => void;
|
|
23
|
+
}
|
|
24
|
+
<% } -%>
|
|
25
|
+
|
|
26
|
+
const ToastContext = createContext<% if (isTypeScript) { %><ToastContextValue | null><% } %>(null);
|
|
27
|
+
|
|
28
|
+
export function ToastProvider({ children }<% if (isTypeScript) { %>: { children: ReactNode }<% } %>) {
|
|
29
|
+
const [items, setItems] = useState<% if (isTypeScript) { %><ToastItem[]><% } %>([]);
|
|
30
|
+
|
|
31
|
+
const push = useCallback((message<% if (isTypeScript) { %>: string<% } %>, tone<% if (isTypeScript) { %>: ToastTone<% } %> = 'info') => {
|
|
32
|
+
const id = crypto.randomUUID();
|
|
33
|
+
setItems((prev) => [...prev, { id, message, tone }]);
|
|
34
|
+
window.setTimeout(() => {
|
|
35
|
+
setItems((prev) => prev.filter((item) => item.id !== id));
|
|
36
|
+
}, 3200);
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const value = useMemo(() => ({ push }), [push]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ToastContext.Provider value={value}>
|
|
43
|
+
{children}
|
|
44
|
+
<div className="toast-viewport" aria-live="polite">
|
|
45
|
+
{items.map((item) => (
|
|
46
|
+
<div key={item.id} className={`toast toast-${item.tone}`}>
|
|
47
|
+
{item.message}
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</ToastContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useToast() {
|
|
56
|
+
const ctx = useContext(ToastContext);
|
|
57
|
+
if (!ctx) {
|
|
58
|
+
throw new Error('useToast must be used within ToastProvider');
|
|
59
|
+
}
|
|
60
|
+
return ctx;
|
|
61
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<% if (isTypeScript) { -%>
|
|
2
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
<% } -%>
|
|
4
|
+
import { cn } from '@/lib/cn';
|
|
5
|
+
<% if (uiLibrary === 'mui') { -%>
|
|
6
|
+
import Button from '@mui/material/Button';
|
|
7
|
+
<% } else if (uiLibrary === 'antd') { -%>
|
|
8
|
+
import { Button as AntButton } from 'antd';
|
|
9
|
+
<% } else if (uiLibrary === 'chakra') { -%>
|
|
10
|
+
import { Button as ChakraButton } from '@chakra-ui/react';
|
|
11
|
+
<% } else if (uiLibrary === 'headless-ui') { -%>
|
|
12
|
+
import { Button as HeadlessButton } from '@headlessui/react';
|
|
13
|
+
<% } else if (uiLibrary === 'shadcn') { -%>
|
|
14
|
+
import { cva<% if (isTypeScript) { %>, type VariantProps<% } %> } from 'class-variance-authority';
|
|
15
|
+
<% } -%>
|
|
16
|
+
|
|
17
|
+
<% if (uiLibrary === 'shadcn') { -%>
|
|
18
|
+
const buttonVariants = cva(
|
|
19
|
+
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 disabled:opacity-50',
|
|
20
|
+
{
|
|
21
|
+
variants: {
|
|
22
|
+
variant: {
|
|
23
|
+
default: 'bg-brand-600 text-white hover:bg-brand-700',
|
|
24
|
+
secondary: 'bg-slate-100 text-slate-900 hover:bg-slate-200',
|
|
25
|
+
outline: 'border border-slate-300 bg-white hover:bg-slate-50',
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
default: 'h-10 px-4 py-2',
|
|
29
|
+
sm: 'h-8 px-3',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: 'default',
|
|
34
|
+
size: 'default',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
<% if (isTypeScript) { -%>
|
|
40
|
+
export interface UiButtonProps
|
|
41
|
+
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
42
|
+
VariantProps<typeof buttonVariants> {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}
|
|
45
|
+
<% } -%>
|
|
46
|
+
|
|
47
|
+
export function UiButton({
|
|
48
|
+
className,
|
|
49
|
+
variant,
|
|
50
|
+
size,
|
|
51
|
+
children,
|
|
52
|
+
...props
|
|
53
|
+
}<% if (isTypeScript) { %>: UiButtonProps<% } %>) {
|
|
54
|
+
return (
|
|
55
|
+
<button className={cn(buttonVariants({ variant, size }), className)} {...props}>
|
|
56
|
+
{children}
|
|
57
|
+
</button>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
<% } else if (uiLibrary === 'mui') { -%>
|
|
61
|
+
<% if (isTypeScript) { -%>
|
|
62
|
+
interface UiButtonProps {
|
|
63
|
+
children: ReactNode;
|
|
64
|
+
onClick?: () => void;
|
|
65
|
+
variant?: 'contained' | 'outlined' | 'text';
|
|
66
|
+
type?: 'button' | 'submit' | 'reset';
|
|
67
|
+
disabled?: boolean;
|
|
68
|
+
}
|
|
69
|
+
<% } -%>
|
|
70
|
+
|
|
71
|
+
export function UiButton({
|
|
72
|
+
children,
|
|
73
|
+
onClick,
|
|
74
|
+
variant = 'contained',
|
|
75
|
+
type = 'button',
|
|
76
|
+
disabled,
|
|
77
|
+
}<% if (isTypeScript) { %>: UiButtonProps<% } %>) {
|
|
78
|
+
return (
|
|
79
|
+
<Button variant={variant} onClick={onClick} type={type} disabled={disabled}>
|
|
80
|
+
{children}
|
|
81
|
+
</Button>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
<% } else if (uiLibrary === 'antd') { -%>
|
|
85
|
+
<% if (isTypeScript) { -%>
|
|
86
|
+
interface UiButtonProps {
|
|
87
|
+
children: ReactNode;
|
|
88
|
+
onClick?: () => void;
|
|
89
|
+
type?: 'default' | 'primary' | 'dashed' | 'link' | 'text';
|
|
90
|
+
htmlType?: 'button' | 'submit' | 'reset';
|
|
91
|
+
disabled?: boolean;
|
|
92
|
+
}
|
|
93
|
+
<% } -%>
|
|
94
|
+
|
|
95
|
+
export function UiButton({
|
|
96
|
+
children,
|
|
97
|
+
onClick,
|
|
98
|
+
type = 'primary',
|
|
99
|
+
htmlType = 'button',
|
|
100
|
+
disabled,
|
|
101
|
+
}<% if (isTypeScript) { %>: UiButtonProps<% } %>) {
|
|
102
|
+
return (
|
|
103
|
+
<AntButton type={type} onClick={onClick} htmlType={htmlType} disabled={disabled}>
|
|
104
|
+
{children}
|
|
105
|
+
</AntButton>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
<% } else if (uiLibrary === 'chakra') { -%>
|
|
109
|
+
<% if (isTypeScript) { -%>
|
|
110
|
+
interface UiButtonProps {
|
|
111
|
+
children: ReactNode;
|
|
112
|
+
onClick?: () => void;
|
|
113
|
+
type?: 'button' | 'submit' | 'reset';
|
|
114
|
+
disabled?: boolean;
|
|
115
|
+
}
|
|
116
|
+
<% } -%>
|
|
117
|
+
|
|
118
|
+
export function UiButton({ children, onClick, type = 'button', disabled }<% if (isTypeScript) { %>: UiButtonProps<% } %>) {
|
|
119
|
+
return (
|
|
120
|
+
<ChakraButton colorPalette="blue" onClick={onClick} type={type} disabled={disabled}>
|
|
121
|
+
{children}
|
|
122
|
+
</ChakraButton>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
<% } else if (uiLibrary === 'headless-ui') { -%>
|
|
126
|
+
<% if (isTypeScript) { -%>
|
|
127
|
+
interface UiButtonProps {
|
|
128
|
+
children: ReactNode;
|
|
129
|
+
onClick?: () => void;
|
|
130
|
+
type?: 'button' | 'submit' | 'reset';
|
|
131
|
+
disabled?: boolean;
|
|
132
|
+
className?: string;
|
|
133
|
+
}
|
|
134
|
+
<% } -%>
|
|
135
|
+
|
|
136
|
+
export function UiButton({
|
|
137
|
+
children,
|
|
138
|
+
onClick,
|
|
139
|
+
type = 'button',
|
|
140
|
+
disabled,
|
|
141
|
+
className,
|
|
142
|
+
}<% if (isTypeScript) { %>: UiButtonProps<% } %>) {
|
|
143
|
+
return (
|
|
144
|
+
<HeadlessButton
|
|
145
|
+
type={type}
|
|
146
|
+
onClick={onClick}
|
|
147
|
+
disabled={disabled}
|
|
148
|
+
className={cn('ui-button', className)}
|
|
149
|
+
>
|
|
150
|
+
{children}
|
|
151
|
+
</HeadlessButton>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
<% } else { -%>
|
|
155
|
+
<% if (isTypeScript) { -%>
|
|
156
|
+
interface UiButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
157
|
+
children: ReactNode;
|
|
158
|
+
}
|
|
159
|
+
<% } -%>
|
|
160
|
+
|
|
161
|
+
export function UiButton({ children, className, ...props }<% if (isTypeScript) { %>: UiButtonProps<% } %>) {
|
|
162
|
+
return (
|
|
163
|
+
<button className={cn('ui-button', className)} {...props}>
|
|
164
|
+
{children}
|
|
165
|
+
</button>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
<% } -%>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% if (isTypeScript) { -%>
|
|
2
|
+
/**
|
|
3
|
+
* Central place for reading public env vars.
|
|
4
|
+
* Only expose values that are safe for the browser bundle.
|
|
5
|
+
*/
|
|
6
|
+
export const env = {
|
|
7
|
+
apiBaseUrl:
|
|
8
|
+
import.meta.env?.VITE_API_BASE_URL ?? 'https://jsonplaceholder.typicode.com',
|
|
9
|
+
mode: import.meta.env?.MODE ?? 'development',
|
|
10
|
+
} as const;
|
|
11
|
+
<% } else { -%>
|
|
12
|
+
/** Central place for reading public env vars. */
|
|
13
|
+
export const env = {
|
|
14
|
+
apiBaseUrl:
|
|
15
|
+
import.meta.env?.VITE_API_BASE_URL ?? 'https://jsonplaceholder.typicode.com',
|
|
16
|
+
mode: import.meta.env?.MODE ?? 'development',
|
|
17
|
+
};
|
|
18
|
+
<% } -%>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<% if (stateManagement === 'redux') { -%>
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
3
|
+
import { decrement, increment } from '@/store';
|
|
4
|
+
<% if (isTypeScript) { -%>
|
|
5
|
+
import type { RootState } from '@/store';
|
|
6
|
+
<% } -%>
|
|
7
|
+
<% } else if (stateManagement === 'zustand') { -%>
|
|
8
|
+
import { useCounterStore } from '@/store';
|
|
9
|
+
<% } else if (stateManagement === 'jotai') { -%>
|
|
10
|
+
import { useAtom } from 'jotai';
|
|
11
|
+
import { counterAtom } from '@/store';
|
|
12
|
+
<% } else if (stateManagement === 'context') { -%>
|
|
13
|
+
import { useAppState } from '@/store/context';
|
|
14
|
+
<% } -%>
|
|
15
|
+
import { UiButton } from '@/components/ui/UiButton';
|
|
16
|
+
import { useToast } from '@/components/ui/Toast';
|
|
17
|
+
|
|
18
|
+
export function CounterPanel() {
|
|
19
|
+
const { push } = useToast();
|
|
20
|
+
<% if (stateManagement === 'redux') { -%>
|
|
21
|
+
const value = useSelector((state<% if (isTypeScript) { %>: RootState<% } %>) => state.counter.value);
|
|
22
|
+
const dispatch = useDispatch();
|
|
23
|
+
const onInc = () => dispatch(increment());
|
|
24
|
+
const onDec = () => dispatch(decrement());
|
|
25
|
+
<% } else if (stateManagement === 'zustand') { -%>
|
|
26
|
+
const value = useCounterStore((s) => s.value);
|
|
27
|
+
const onInc = useCounterStore((s) => s.increment);
|
|
28
|
+
const onDec = useCounterStore((s) => s.decrement);
|
|
29
|
+
<% } else if (stateManagement === 'jotai') { -%>
|
|
30
|
+
const [value, setValue] = useAtom(counterAtom);
|
|
31
|
+
const onInc = () => setValue((v) => v + 1);
|
|
32
|
+
const onDec = () => setValue((v) => v - 1);
|
|
33
|
+
<% } else if (stateManagement === 'context') { -%>
|
|
34
|
+
const { state, dispatch } = useAppState();
|
|
35
|
+
const value = state.value;
|
|
36
|
+
const onInc = () => dispatch({ type: 'increment' });
|
|
37
|
+
const onDec = () => dispatch({ type: 'decrement' });
|
|
38
|
+
<% } else { -%>
|
|
39
|
+
const value = 0;
|
|
40
|
+
const onInc = () => undefined;
|
|
41
|
+
const onDec = () => undefined;
|
|
42
|
+
<% } -%>
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<article className="panel">
|
|
46
|
+
<header className="panel-header">
|
|
47
|
+
<h2>Client state</h2>
|
|
48
|
+
<p><%= stackLabels.stateManagement %> example — local interactive counter.</p>
|
|
49
|
+
</header>
|
|
50
|
+
<% if (stateManagement === 'none') { -%>
|
|
51
|
+
<p className="muted">Client state library was set to None. Add one later under <code>src/store</code>.</p>
|
|
52
|
+
<% } else { -%>
|
|
53
|
+
<div className="counter">
|
|
54
|
+
<UiButton onClick={() => { onDec(); push('Decremented', 'info'); }}>−</UiButton>
|
|
55
|
+
<strong className="counter-value" aria-live="polite">{value}</strong>
|
|
56
|
+
<UiButton onClick={() => { onInc(); push('Incremented', 'success'); }}>+</UiButton>
|
|
57
|
+
</div>
|
|
58
|
+
<% } -%>
|
|
59
|
+
<p className="panel-note">
|
|
60
|
+
<strong>When to use:</strong> shared UI state that is not server cache.
|
|
61
|
+
<br />
|
|
62
|
+
<strong>Best practice:</strong> keep slices/stores small and colocated with features.
|
|
63
|
+
</p>
|
|
64
|
+
</article>
|
|
65
|
+
);
|
|
66
|
+
}
|