create-react-starter-kit 1.0.2 → 1.0.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.
- package/dist/generator/dependencies.d.ts.map +1 -1
- package/dist/generator/dependencies.js +20 -2
- package/dist/generator/dependencies.js.map +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +10 -2
- package/dist/generator/index.js.map +1 -1
- package/dist/templates/README.md.ejs +23 -29
- package/dist/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
- package/dist/templates/__when_formatting_prettier__/.prettierignore +28 -0
- package/dist/templates/__when_linting_biome__/biome.json +12 -0
- package/dist/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
- package/dist/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
- package/dist/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
- package/dist/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +32 -1
- package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +42 -0
- package/dist/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
- package/dist/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
- package/dist/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
- package/dist/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
- package/dist/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
- package/dist/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
- package/dist/templates/docs/architecture.md.ejs +32 -34
- package/dist/templates/docs/getting-started.md.ejs +1 -1
- package/{templates/src/app → dist/templates/src}/App.tsx.ejs +34 -1
- package/dist/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
- package/dist/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
- package/dist/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
- package/dist/templates/src/main.tsx.ejs +2 -2
- package/{templates/src/app → dist/templates/src}/providers.tsx.ejs +1 -2
- package/dist/templates/src/routes/AboutPage.tsx.ejs +11 -10
- package/dist/templates/src/routes/CounterPage.tsx.ejs +17 -0
- package/dist/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
- package/dist/templates/src/routes/HomePage.tsx.ejs +43 -21
- package/dist/templates/src/routes/PostsPage.tsx.ejs +17 -0
- package/dist/templates/src/styles/global.css.ejs +14 -33
- package/package.json +1 -1
- package/templates/README.md.ejs +23 -29
- package/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
- package/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
- package/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
- package/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
- package/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
- package/templates/__when_formatting_prettier__/.prettierignore +28 -0
- package/templates/__when_linting_biome__/biome.json +12 -0
- package/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
- package/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
- package/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
- package/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
- package/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
- package/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
- package/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
- package/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +32 -1
- package/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +42 -0
- package/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
- package/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
- package/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
- package/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
- package/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
- package/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
- package/templates/docs/architecture.md.ejs +32 -34
- package/templates/docs/getting-started.md.ejs +1 -1
- package/{dist/templates/src/app → templates/src}/App.tsx.ejs +34 -1
- package/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
- package/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
- package/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
- package/templates/src/main.tsx.ejs +2 -2
- package/{dist/templates/src/app → templates/src}/providers.tsx.ejs +1 -2
- package/templates/src/routes/AboutPage.tsx.ejs +11 -10
- package/templates/src/routes/CounterPage.tsx.ejs +17 -0
- package/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
- package/templates/src/routes/HomePage.tsx.ejs +43 -21
- package/templates/src/routes/PostsPage.tsx.ejs +17 -0
- package/templates/src/styles/global.css.ejs +14 -33
- package/dist/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
- package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
- package/dist/templates/src/components/ui/Toast.tsx.ejs +0 -61
- package/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
- package/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
- package/templates/src/components/ui/Toast.tsx.ejs +0 -61
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
<% if (forms === 'react-hook-form') { -%>
|
|
2
3
|
import { useForm } from 'react-hook-form';
|
|
3
4
|
<% if (validation === 'zod') { -%>
|
|
@@ -25,7 +26,6 @@ import { feedbackSchema } from '@/lib/validation';
|
|
|
25
26
|
import type { FeedbackValues } from '@/lib/validation';
|
|
26
27
|
<% } -%>
|
|
27
28
|
<% } else { -%>
|
|
28
|
-
import { useState } from 'react';
|
|
29
29
|
<% if (validation === 'none') { -%>
|
|
30
30
|
import { validateFeedback<% if (isTypeScript) { %>, type FeedbackValues<% } %> } from '@/lib/validation';
|
|
31
31
|
<% } else { -%>
|
|
@@ -33,7 +33,6 @@ import { feedbackSchema<% if (isTypeScript) { %>, type FeedbackValues<% } %> } f
|
|
|
33
33
|
<% } -%>
|
|
34
34
|
<% } -%>
|
|
35
35
|
import { createFeedback } from '@/services/api';
|
|
36
|
-
import { useToast } from '@/components/ui/Toast';
|
|
37
36
|
import { UiButton } from '@/components/ui/UiButton';
|
|
38
37
|
|
|
39
38
|
const defaults<% if (isTypeScript) { %>: FeedbackValues<% } %> = {
|
|
@@ -43,7 +42,7 @@ const defaults<% if (isTypeScript) { %>: FeedbackValues<% } %> = {
|
|
|
43
42
|
};
|
|
44
43
|
|
|
45
44
|
export function FeedbackForm() {
|
|
46
|
-
const {
|
|
45
|
+
const [status, setStatus] = useState<% if (isTypeScript) { %><string | null><% } %>(null);
|
|
47
46
|
|
|
48
47
|
<% if (forms === 'react-hook-form') { -%>
|
|
49
48
|
const {
|
|
@@ -71,7 +70,7 @@ export function FeedbackForm() {
|
|
|
71
70
|
}
|
|
72
71
|
<% } -%>
|
|
73
72
|
await createFeedback(values);
|
|
74
|
-
|
|
73
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
75
74
|
reset();
|
|
76
75
|
};
|
|
77
76
|
|
|
@@ -107,6 +106,11 @@ export function FeedbackForm() {
|
|
|
107
106
|
{isSubmitting ? 'Sending…' : 'Send feedback'}
|
|
108
107
|
</UiButton>
|
|
109
108
|
</form>
|
|
109
|
+
{status && (
|
|
110
|
+
<p className="success-text" role="status">
|
|
111
|
+
{status}
|
|
112
|
+
</p>
|
|
113
|
+
)}
|
|
110
114
|
<p className="panel-note">
|
|
111
115
|
<strong>When to use:</strong> complex forms with validation and async submit.
|
|
112
116
|
<br />
|
|
@@ -137,7 +141,7 @@ export function FeedbackForm() {
|
|
|
137
141
|
feedbackSchema.parse(values);
|
|
138
142
|
<% } -%>
|
|
139
143
|
await createFeedback(values);
|
|
140
|
-
|
|
144
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
141
145
|
helpers.resetForm();
|
|
142
146
|
}}
|
|
143
147
|
>
|
|
@@ -164,6 +168,11 @@ export function FeedbackForm() {
|
|
|
164
168
|
</Form>
|
|
165
169
|
)}
|
|
166
170
|
</Formik>
|
|
171
|
+
{status && (
|
|
172
|
+
<p className="success-text" role="status">
|
|
173
|
+
{status}
|
|
174
|
+
</p>
|
|
175
|
+
)}
|
|
167
176
|
<p className="panel-note">
|
|
168
177
|
<strong>When to use:</strong> form-heavy screens with Formik's render/props model.
|
|
169
178
|
<br />
|
|
@@ -187,7 +196,7 @@ export function FeedbackForm() {
|
|
|
187
196
|
if (result.error) throw result.error;
|
|
188
197
|
<% } -%>
|
|
189
198
|
await createFeedback(value);
|
|
190
|
-
|
|
199
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
191
200
|
form.reset();
|
|
192
201
|
},
|
|
193
202
|
});
|
|
@@ -255,6 +264,11 @@ export function FeedbackForm() {
|
|
|
255
264
|
</form.Field>
|
|
256
265
|
<UiButton type="submit">Send feedback</UiButton>
|
|
257
266
|
</form>
|
|
267
|
+
{status && (
|
|
268
|
+
<p className="success-text" role="status">
|
|
269
|
+
{status}
|
|
270
|
+
</p>
|
|
271
|
+
)}
|
|
258
272
|
<p className="panel-note">
|
|
259
273
|
<strong>When to use:</strong> headless, type-safe forms with fine-grained reactivity.
|
|
260
274
|
<br />
|
|
@@ -315,7 +329,7 @@ export function FeedbackForm() {
|
|
|
315
329
|
setIsSubmitting(true);
|
|
316
330
|
try {
|
|
317
331
|
await createFeedback(values);
|
|
318
|
-
|
|
332
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
319
333
|
setValues(defaults);
|
|
320
334
|
} finally {
|
|
321
335
|
setIsSubmitting(false);
|
|
@@ -362,6 +376,11 @@ export function FeedbackForm() {
|
|
|
362
376
|
{isSubmitting ? 'Sending…' : 'Send feedback'}
|
|
363
377
|
</UiButton>
|
|
364
378
|
</form>
|
|
379
|
+
{status && (
|
|
380
|
+
<p className="success-text" role="status">
|
|
381
|
+
{status}
|
|
382
|
+
</p>
|
|
383
|
+
)}
|
|
365
384
|
<p className="panel-note">
|
|
366
385
|
<strong>When to use:</strong> simple forms without a dedicated form library.
|
|
367
386
|
<br />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { AppProviders } from '@/
|
|
4
|
-
import { App } from '@/
|
|
3
|
+
import { AppProviders } from '@/providers';
|
|
4
|
+
import { App } from '@/App';
|
|
5
5
|
import { initMonitoring } from '@/lib/monitoring';
|
|
6
6
|
import '@/styles/global.<%= styling === 'sass' && uiLibrary !== 'shadcn' ? 'scss' : 'css' %>';
|
|
7
7
|
|
|
@@ -24,7 +24,6 @@ import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
|
|
|
24
24
|
<% if (uiLibrary === 'mui') { -%>
|
|
25
25
|
import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
|
|
26
26
|
<% } -%>
|
|
27
|
-
import { ToastProvider } from '@/components/ui/Toast';
|
|
28
27
|
|
|
29
28
|
<% if (serverState === 'tanstack-query') { -%>
|
|
30
29
|
const queryClient = new QueryClient({
|
|
@@ -89,5 +88,5 @@ export function AppProviders({ children }<% if (isTypeScript) { %>: AppProviders
|
|
|
89
88
|
tree = <ChakraProvider value={defaultSystem}>{tree}</ChakraProvider>;
|
|
90
89
|
<% } -%>
|
|
91
90
|
|
|
92
|
-
return
|
|
91
|
+
return <>{tree}</>;
|
|
93
92
|
}
|
|
@@ -4,26 +4,27 @@ export function AboutPage() {
|
|
|
4
4
|
<h1>About this starter</h1>
|
|
5
5
|
<p>
|
|
6
6
|
Generated with <strong>create-react-starter-kit</strong> using a scalable{' '}
|
|
7
|
-
<strong>feature-based</strong> layout (domain modules under <code>src/features</code>)
|
|
8
|
-
the same organizational approach used across large production React codebases.
|
|
7
|
+
<strong>feature-based</strong> layout (domain modules under <code>src/features</code>).
|
|
9
8
|
</p>
|
|
10
9
|
<ul className="about-list">
|
|
11
10
|
<li>
|
|
12
|
-
<strong>
|
|
13
|
-
|
|
11
|
+
<strong>Routes:</strong> <code>/</code>, <code>/counter</code>, <code>/posts</code>,{' '}
|
|
12
|
+
<code>/feedback</code>, <code>/about</code>
|
|
14
13
|
</li>
|
|
15
14
|
<li>
|
|
16
|
-
<strong>
|
|
17
|
-
<code>src/lib/</code>. Prefer imports via the <code>@/</code> path alias.
|
|
15
|
+
<strong>Extend features</strong> under <code>src/features/<name>/</code>.
|
|
18
16
|
</li>
|
|
19
17
|
<li>
|
|
20
|
-
<strong>
|
|
21
|
-
|
|
18
|
+
<strong>Shared UI</strong> lives in <code>src/components/</code>. Prefer the{' '}
|
|
19
|
+
<code>@/</code> path alias.
|
|
20
|
+
</li>
|
|
21
|
+
<li>
|
|
22
|
+
<strong>Bootstrap</strong> is in <code>src/main.<%= jsxExt %></code> with providers in{' '}
|
|
23
|
+
<code>src/providers.<%= jsxExt %></code> and routing in <code>src/App.<%= jsxExt %></code>.
|
|
22
24
|
</li>
|
|
23
25
|
</ul>
|
|
24
26
|
<p>
|
|
25
|
-
|
|
26
|
-
<code>docs/selected-stack.md</code> for library-specific guidance.
|
|
27
|
+
Read <code>docs/getting-started.md</code> next, then <code>docs/selected-stack.md</code>.
|
|
27
28
|
</p>
|
|
28
29
|
</div>
|
|
29
30
|
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CounterPanel } from '@/features/counter/CounterPanel';
|
|
2
|
+
|
|
3
|
+
export function CounterPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="page">
|
|
6
|
+
<header className="page-intro">
|
|
7
|
+
<p className="eyebrow">Client state</p>
|
|
8
|
+
<h1>Counter</h1>
|
|
9
|
+
<p className="lede">
|
|
10
|
+
Interactive counter wired to <strong><%= stackLabels.stateManagement %></strong>. Use this
|
|
11
|
+
pattern for UI state that is not server cache.
|
|
12
|
+
</p>
|
|
13
|
+
</header>
|
|
14
|
+
<CounterPanel />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FeedbackForm } from '@/features/feedback/FeedbackForm';
|
|
2
|
+
|
|
3
|
+
export function FeedbackPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="page">
|
|
6
|
+
<header className="page-intro">
|
|
7
|
+
<p className="eyebrow">Forms</p>
|
|
8
|
+
<h1>Feedback</h1>
|
|
9
|
+
<p className="lede">
|
|
10
|
+
Submit demo feedback with <strong><%= stackLabels.forms %></strong>
|
|
11
|
+
<% if (validation !== 'none') { -%>
|
|
12
|
+
{' '}
|
|
13
|
+
and <strong><%= stackLabels.validation %></strong> validation
|
|
14
|
+
<% } -%>
|
|
15
|
+
.
|
|
16
|
+
</p>
|
|
17
|
+
</header>
|
|
18
|
+
<FeedbackForm />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -1,29 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
<% if (router === 'react-router') { -%>
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
<% } else { -%>
|
|
4
|
+
import { Link } from '@tanstack/react-router';
|
|
5
|
+
<% } -%>
|
|
4
6
|
import { StackBadge } from '@/components/StackBadge';
|
|
5
|
-
import { useToast } from '@/components/ui/Toast';
|
|
6
|
-
import { UiButton } from '@/components/ui/UiButton';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const demos = [
|
|
9
|
+
{
|
|
10
|
+
to: '/counter',
|
|
11
|
+
title: 'Counter',
|
|
12
|
+
body: 'Client state demo with <%= stackLabels.stateManagement %>.',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
to: '/posts',
|
|
16
|
+
title: 'Posts',
|
|
17
|
+
body: 'Server state / data fetching with <%= stackLabels.serverState %>.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
to: '/feedback',
|
|
21
|
+
title: 'Feedback',
|
|
22
|
+
body: 'Forms and validation with <%= stackLabels.forms %><% if (validation !== 'none') { %> + <%= stackLabels.validation %><% } %>.',
|
|
23
|
+
},
|
|
24
|
+
] as const;
|
|
10
25
|
|
|
26
|
+
export function HomePage() {
|
|
11
27
|
return (
|
|
12
28
|
<div className="page home-page">
|
|
13
29
|
<section className="hero">
|
|
14
30
|
<p className="eyebrow">Starter workspace</p>
|
|
15
31
|
<h1><%= projectName %></h1>
|
|
16
32
|
<p className="lede">
|
|
17
|
-
A
|
|
18
|
-
|
|
33
|
+
A production-shaped React starter. Open each demo route to explore client state, server
|
|
34
|
+
data, and forms — each page is a real working example for your selected stack.
|
|
19
35
|
</p>
|
|
20
36
|
<div className="hero-actions">
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
</
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
</
|
|
37
|
+
<Link className="ui-button" to="/counter">
|
|
38
|
+
Open counter demo
|
|
39
|
+
</Link>
|
|
40
|
+
<Link className="text-link" to="/about">
|
|
41
|
+
About this starter →
|
|
42
|
+
</Link>
|
|
27
43
|
</div>
|
|
28
44
|
<div className="stack-row">
|
|
29
45
|
<StackBadge label="Build" value="<%= stackLabels.buildTool %>" />
|
|
@@ -35,12 +51,18 @@ export function HomePage() {
|
|
|
35
51
|
</div>
|
|
36
52
|
</section>
|
|
37
53
|
|
|
38
|
-
<section className="panels">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
<section className="panels" aria-label="Demo routes">
|
|
55
|
+
{demos.map((demo) => (
|
|
56
|
+
<article key={demo.to} className="panel">
|
|
57
|
+
<header className="panel-header">
|
|
58
|
+
<h2>{demo.title}</h2>
|
|
59
|
+
<p>{demo.body}</p>
|
|
60
|
+
</header>
|
|
61
|
+
<Link className="text-link" to={demo.to}>
|
|
62
|
+
Go to {demo.title.toLowerCase()} →
|
|
63
|
+
</Link>
|
|
64
|
+
</article>
|
|
65
|
+
))}
|
|
44
66
|
</section>
|
|
45
67
|
</div>
|
|
46
68
|
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PostsPanel } from '@/features/posts/PostsPanel';
|
|
2
|
+
|
|
3
|
+
export function PostsPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="page">
|
|
6
|
+
<header className="page-intro">
|
|
7
|
+
<p className="eyebrow">Server state</p>
|
|
8
|
+
<h1>Posts</h1>
|
|
9
|
+
<p className="lede">
|
|
10
|
+
Fetches sample posts via <strong><%= stackLabels.serverState === 'None' ? stackLabels.apiLayer : stackLabels.serverState %></strong>{' '}
|
|
11
|
+
and <strong><%= stackLabels.apiLayer %></strong>.
|
|
12
|
+
</p>
|
|
13
|
+
</header>
|
|
14
|
+
<PostsPanel />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -90,10 +90,23 @@ body {
|
|
|
90
90
|
|
|
91
91
|
.hero h1,
|
|
92
92
|
.about-page h1,
|
|
93
|
+
.page-intro h1,
|
|
93
94
|
.panel-header h2 {
|
|
94
95
|
@apply m-0 font-display tracking-tight text-slate-900;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
.page-intro {
|
|
99
|
+
@apply mb-6;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.page-intro h1 {
|
|
103
|
+
@apply mt-2 text-3xl sm:text-4xl;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.success-text {
|
|
107
|
+
@apply text-sm text-[var(--ok)];
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
.hero h1 {
|
|
98
111
|
@apply mt-2 text-4xl sm:text-5xl;
|
|
99
112
|
}
|
|
@@ -181,27 +194,7 @@ body {
|
|
|
181
194
|
}
|
|
182
195
|
|
|
183
196
|
.ui-button {
|
|
184
|
-
@apply inline-flex items-center justify-center rounded-md bg-brand-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-brand-700 disabled:opacity-50;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.toast-viewport {
|
|
188
|
-
@apply pointer-events-none fixed bottom-4 right-4 z-50 flex w-80 flex-col gap-2;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.toast {
|
|
192
|
-
@apply rounded-xl border px-4 py-3 text-sm shadow-lg;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.toast-info {
|
|
196
|
-
@apply border-slate-200 bg-white text-slate-800;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
.toast-success {
|
|
200
|
-
@apply border-green-200 bg-green-50 text-[var(--ok)];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.toast-error {
|
|
204
|
-
@apply border-red-200 bg-red-50 text-[var(--bad)];
|
|
197
|
+
@apply inline-flex items-center justify-center rounded-md bg-brand-600 px-4 py-2 text-sm font-medium text-white no-underline transition hover:bg-brand-700 disabled:opacity-50;
|
|
205
198
|
}
|
|
206
199
|
|
|
207
200
|
.about-list {
|
|
@@ -265,10 +258,6 @@ body { margin: 0; min-height: 100vh; background: linear-gradient(180deg, #f8fafc
|
|
|
265
258
|
.panel-note { margin-top: 1rem; padding-top: 0.75rem; border-top: 1px dashed #e2e8f0; font-size: 0.75rem; line-height: 1.5; }
|
|
266
259
|
.ui-button { appearance: none; border: 0; background: var(--brand); color: #fff; border-radius: 0.5rem; padding: 0.55rem 1rem; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
|
|
267
260
|
.ui-button:disabled { opacity: 0.5; }
|
|
268
|
-
.toast-viewport { position: fixed; right: 1rem; bottom: 1rem; width: 20rem; display: grid; gap: 0.5rem; z-index: 50; }
|
|
269
|
-
.toast { border-radius: 0.75rem; padding: 0.75rem 1rem; font-size: 0.875rem; box-shadow: var(--shadow); background: #fff; border: 1px solid var(--line); }
|
|
270
|
-
.toast-success { background: #f0fdf4; border-color: #bbf7d0; color: var(--ok); }
|
|
271
|
-
.toast-error { background: #fef2f2; border-color: #fecaca; color: var(--bad); }
|
|
272
261
|
.about-list { margin: 1rem 0; padding-left: 1.25rem; }
|
|
273
262
|
<% } else if (styling === 'styled-components') { -%>
|
|
274
263
|
/* Global reset — most styling lives in styled components; keep layout tokens here */
|
|
@@ -335,10 +324,6 @@ body {
|
|
|
335
324
|
.panel-note { margin-top: 1rem; padding-top: 0.75rem; border-top: 1px dashed #e2e8f0; font-size: 0.75rem; line-height: 1.5; }
|
|
336
325
|
.ui-button { appearance: none; border: 0; background: var(--brand); color: #fff; border-radius: 0.5rem; padding: 0.55rem 1rem; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
|
|
337
326
|
.ui-button:disabled { opacity: 0.5; }
|
|
338
|
-
.toast-viewport { position: fixed; right: 1rem; bottom: 1rem; width: 20rem; display: grid; gap: 0.5rem; z-index: 50; }
|
|
339
|
-
.toast { border-radius: 0.75rem; padding: 0.75rem 1rem; font-size: 0.875rem; box-shadow: var(--shadow); background: #fff; border: 1px solid var(--line); }
|
|
340
|
-
.toast-success { background: #f0fdf4; border-color: #bbf7d0; color: var(--ok); }
|
|
341
|
-
.toast-error { background: #fef2f2; border-color: #fecaca; color: var(--bad); }
|
|
342
327
|
.about-list { margin: 1rem 0; padding-left: 1.25rem; }
|
|
343
328
|
<% } else { -%>
|
|
344
329
|
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@500;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap');
|
|
@@ -449,9 +434,5 @@ body {
|
|
|
449
434
|
padding: 0.55rem 1rem; font-size: 0.875rem; font-weight: 500; cursor: pointer;
|
|
450
435
|
&:disabled { opacity: 0.5; }
|
|
451
436
|
}
|
|
452
|
-
.toast-viewport { position: fixed; right: 1rem; bottom: 1rem; width: 20rem; display: grid; gap: 0.5rem; z-index: 50; }
|
|
453
|
-
.toast { border-radius: 0.75rem; padding: 0.75rem 1rem; font-size: 0.875rem; box-shadow: $shadow; background: #fff; border: 1px solid $line; }
|
|
454
|
-
.toast-success { background: #f0fdf4; border-color: #bbf7d0; color: $ok; }
|
|
455
|
-
.toast-error { background: #fef2f2; border-color: #fecaca; color: $bad; }
|
|
456
437
|
.about-list { margin: 1rem 0; padding-left: 1.25rem; }
|
|
457
438
|
<% } -%>
|
package/package.json
CHANGED
package/templates/README.md.ejs
CHANGED
|
@@ -74,21 +74,23 @@ Open the URL printed in the terminal (typically `http://localhost:5173`).
|
|
|
74
74
|
|
|
75
75
|
```text
|
|
76
76
|
src/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
App.tsx # Route tree
|
|
78
|
+
main.tsx # Bootstrap / render
|
|
79
|
+
providers.tsx # App-wide providers
|
|
80
|
+
assets/ # Images / media imported from code
|
|
81
|
+
components/ # Shared UI (layout/, ui/)
|
|
82
|
+
config/ # Env helpers / app config
|
|
83
|
+
features/ # Domain modules (scalable default)
|
|
84
|
+
hooks/ # Shared hooks
|
|
85
|
+
lib/ # Third-party wrappers / helpers
|
|
86
|
+
routes/ # Route-level pages
|
|
87
|
+
store/ # Client state
|
|
88
|
+
services/ # API clients
|
|
89
|
+
styles/ # Global styles
|
|
90
|
+
types/ # Shared types
|
|
91
|
+
utils/ # Pure helpers
|
|
92
|
+
docs/ # Architecture & usage docs
|
|
93
|
+
tests/ # Unit + e2e helpers
|
|
92
94
|
```
|
|
93
95
|
|
|
94
96
|
Organization: **feature-based / domain-driven** with `@/` path aliases. See `docs/architecture.md`.
|
|
@@ -123,21 +125,13 @@ Start with `docs/selected-stack.md` and `docs/getting-started.md`.
|
|
|
123
125
|
|
|
124
126
|
## Example usage
|
|
125
127
|
|
|
126
|
-
The demo
|
|
128
|
+
The demo pages include:
|
|
127
129
|
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
<% }
|
|
132
|
-
|
|
133
|
-
- data fetching (<%= stackLabels.serverState %>)
|
|
134
|
-
<% } -%>
|
|
135
|
-
<% if (forms !== 'none') { -%>
|
|
136
|
-
- forms (<%= stackLabels.forms %>)
|
|
137
|
-
<% } -%>
|
|
138
|
-
<% if (validation !== 'none') { -%>
|
|
139
|
-
- validation (<%= stackLabels.validation %>)
|
|
140
|
-
<% } -%>
|
|
130
|
+
- `/` — overview + links to demos
|
|
131
|
+
- `/counter` — client state (<%= stackLabels.stateManagement %>)
|
|
132
|
+
- `/posts` — data fetching (<%= stackLabels.serverState %>)
|
|
133
|
+
- `/feedback` — forms<% if (validation !== 'none') { %> + validation (<%= stackLabels.forms %><% if (validation !== 'none') { %> / <%= stackLabels.validation %><% } %>)<% } else { %> (<%= stackLabels.forms %>)<% } %>
|
|
134
|
+
- `/about` — starter orientation
|
|
141
135
|
- styling (<%= stackLabels.styling %>)
|
|
142
136
|
<% if (uiLibrary !== 'none') { -%>
|
|
143
137
|
- UI primitives (<%= stackLabels.uiLibrary %>)
|
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import { defineConfig, devices } from '@playwright/test';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Run once before first e2e: `<%= packageManager === 'npm' ? 'npm run' : packageManager %> test:e2e:install`
|
|
5
|
+
*/
|
|
3
6
|
export default defineConfig({
|
|
4
7
|
testDir: './tests/e2e',
|
|
5
8
|
fullyParallel: true,
|
|
9
|
+
forbidOnly: !!process.env.CI,
|
|
10
|
+
retries: process.env.CI ? 1 : 0,
|
|
11
|
+
reporter: process.env.CI ? 'github' : 'list',
|
|
6
12
|
use: {
|
|
7
13
|
baseURL: 'http://127.0.0.1:5173',
|
|
8
14
|
trace: 'on-first-retry',
|
|
15
|
+
screenshot: 'only-on-failure',
|
|
9
16
|
},
|
|
10
17
|
webServer: {
|
|
11
|
-
|
|
18
|
+
// Prefer the local binary so e2e works even if a global package manager is missing.
|
|
19
|
+
command:
|
|
20
|
+
'<%= buildTool === 'vite'
|
|
21
|
+
? 'npx vite --host 127.0.0.1 --port 5173'
|
|
22
|
+
: packageManager === 'npm'
|
|
23
|
+
? 'npm run dev -- --host 127.0.0.1 --port 5173'
|
|
24
|
+
: packageManager === 'yarn'
|
|
25
|
+
? 'yarn dev --host 127.0.0.1 --port 5173'
|
|
26
|
+
: 'pnpm exec vite --host 127.0.0.1 --port 5173' %>',
|
|
12
27
|
url: 'http://127.0.0.1:5173',
|
|
13
28
|
reuseExistingServer: !process.env.CI,
|
|
29
|
+
timeout: 120_000,
|
|
14
30
|
},
|
|
15
31
|
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
|
16
32
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('Counter page', () => {
|
|
4
|
+
<% if (stateManagement === 'none') { -%>
|
|
5
|
+
test('shows empty-state message when no state library is selected', async ({ page }) => {
|
|
6
|
+
await page.goto('/counter');
|
|
7
|
+
await expect(page.getByText(/Client state library was set to None/i)).toBeVisible();
|
|
8
|
+
});
|
|
9
|
+
<% } else { -%>
|
|
10
|
+
test('increments and decrements the counter', async ({ page }) => {
|
|
11
|
+
await page.goto('/counter');
|
|
12
|
+
const value = page.getByTestId('counter-value');
|
|
13
|
+
await expect(value).toHaveText('0');
|
|
14
|
+
|
|
15
|
+
await page.getByRole('button', { name: 'Increment' }).click();
|
|
16
|
+
await expect(value).toHaveText('1');
|
|
17
|
+
|
|
18
|
+
await page.getByRole('button', { name: 'Increment' }).click();
|
|
19
|
+
await expect(value).toHaveText('2');
|
|
20
|
+
|
|
21
|
+
await page.getByRole('button', { name: 'Decrement' }).click();
|
|
22
|
+
await expect(value).toHaveText('1');
|
|
23
|
+
});
|
|
24
|
+
<% } -%>
|
|
25
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('Feedback page', () => {
|
|
4
|
+
test('shows validation errors for empty submit', async ({ page }) => {
|
|
5
|
+
await page.goto('/feedback');
|
|
6
|
+
await page.getByRole('button', { name: /Send feedback/i }).click();
|
|
7
|
+
await expect(page.locator('.field-error').first()).toBeVisible({ timeout: 5_000 });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('submits valid feedback', async ({ page }) => {
|
|
11
|
+
await page.goto('/feedback');
|
|
12
|
+
await page.getByPlaceholder('Ada Lovelace').fill('Ada Lovelace');
|
|
13
|
+
await page.getByPlaceholder('ada@example.com').fill('ada@example.com');
|
|
14
|
+
await page.getByPlaceholder('What should we improve?').fill('Please add more examples for routing.');
|
|
15
|
+
await page.getByRole('button', { name: /Send feedback/i }).click();
|
|
16
|
+
await expect(page.getByRole('status')).toContainText(/submitted successfully/i, {
|
|
17
|
+
timeout: 15_000,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { test, expect } from '@playwright/test';
|
|
2
2
|
|
|
3
|
-
test('
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
test.describe('Home', () => {
|
|
4
|
+
test('loads hero and demo links', async ({ page }) => {
|
|
5
|
+
await page.goto('/');
|
|
6
|
+
await expect(page.getByRole('heading', { name: /<%= projectName %>/i })).toBeVisible();
|
|
7
|
+
await expect(page.getByRole('navigation', { name: 'Primary' })).toBeVisible();
|
|
8
|
+
await expect(page.getByRole('link', { name: /Go to counter/i })).toBeVisible();
|
|
9
|
+
await expect(page.getByRole('link', { name: /Go to posts/i })).toBeVisible();
|
|
10
|
+
await expect(page.getByRole('link', { name: /Go to feedback/i })).toBeVisible();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test.describe('Navigation', () => {
|
|
15
|
+
test('can visit each primary route', async ({ page }) => {
|
|
16
|
+
await page.goto('/');
|
|
17
|
+
await page.getByRole('link', { name: 'Counter', exact: true }).click();
|
|
18
|
+
await expect(page).toHaveURL(/\/counter/);
|
|
19
|
+
await expect(page.getByRole('heading', { name: 'Counter' })).toBeVisible();
|
|
20
|
+
|
|
21
|
+
await page.getByRole('link', { name: 'Posts', exact: true }).click();
|
|
22
|
+
await expect(page).toHaveURL(/\/posts/);
|
|
23
|
+
await expect(page.getByRole('heading', { name: 'Posts' })).toBeVisible();
|
|
24
|
+
|
|
25
|
+
await page.getByRole('link', { name: 'Feedback', exact: true }).click();
|
|
26
|
+
await expect(page).toHaveURL(/\/feedback/);
|
|
27
|
+
await expect(page.getByRole('heading', { name: 'Feedback' })).toBeVisible();
|
|
28
|
+
|
|
29
|
+
await page.getByRole('link', { name: 'About', exact: true }).click();
|
|
30
|
+
await expect(page).toHaveURL(/\/about/);
|
|
31
|
+
await expect(page.getByRole('heading', { name: /About/i })).toBeVisible();
|
|
32
|
+
});
|
|
6
33
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('Posts page', () => {
|
|
4
|
+
test('loads posts from the demo API', async ({ page }) => {
|
|
5
|
+
await page.goto('/posts');
|
|
6
|
+
await expect(page.getByRole('heading', { name: 'Posts' })).toBeVisible();
|
|
7
|
+
await expect(page.getByRole('list')).toBeVisible({ timeout: 15_000 });
|
|
8
|
+
await expect(page.getByRole('listitem').first()).toBeVisible();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('refresh button reloads posts', async ({ page }) => {
|
|
12
|
+
await page.goto('/posts');
|
|
13
|
+
await expect(page.getByRole('listitem').first()).toBeVisible({ timeout: 15_000 });
|
|
14
|
+
await page.getByRole('button', { name: /Refresh/i }).click();
|
|
15
|
+
await expect(page.getByRole('listitem').first()).toBeVisible({ timeout: 15_000 });
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build outputs
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
out/
|
|
8
|
+
.cache/
|
|
9
|
+
.parcel-cache/
|
|
10
|
+
.rspack-profile-*/
|
|
11
|
+
storybook-static/
|
|
12
|
+
coverage/
|
|
13
|
+
playwright-report/
|
|
14
|
+
test-results/
|
|
15
|
+
cypress/videos/
|
|
16
|
+
cypress/screenshots/
|
|
17
|
+
|
|
18
|
+
# Generated / lockfiles
|
|
19
|
+
package-lock.json
|
|
20
|
+
pnpm-lock.yaml
|
|
21
|
+
yarn.lock
|
|
22
|
+
*.tgz
|
|
23
|
+
*.tsbuildinfo
|
|
24
|
+
|
|
25
|
+
# Env / misc
|
|
26
|
+
.env
|
|
27
|
+
.env.*
|
|
28
|
+
public/
|