create-react-starter-kit 1.0.1 → 1.0.3

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 (90) hide show
  1. package/README.md +0 -66
  2. package/dist/generator/dependencies.d.ts.map +1 -1
  3. package/dist/generator/dependencies.js +20 -2
  4. package/dist/generator/dependencies.js.map +1 -1
  5. package/dist/generator/index.d.ts.map +1 -1
  6. package/dist/generator/index.js +10 -2
  7. package/dist/generator/index.js.map +1 -1
  8. package/dist/templates/README.md.ejs +23 -29
  9. package/dist/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
  10. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
  11. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
  12. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
  13. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
  14. package/dist/templates/__when_formatting_prettier__/.prettierignore +28 -0
  15. package/dist/templates/__when_linting_biome__/biome.json +12 -0
  16. package/dist/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
  17. package/dist/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
  18. package/dist/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
  19. package/dist/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
  20. package/dist/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
  21. package/dist/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
  22. package/dist/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
  23. package/dist/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +4 -1
  24. package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js.ejs +40 -0
  25. package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +40 -0
  26. package/dist/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
  27. package/dist/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
  28. package/dist/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
  29. package/dist/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
  30. package/dist/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
  31. package/dist/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
  32. package/dist/templates/docs/architecture.md.ejs +32 -34
  33. package/dist/templates/docs/getting-started.md.ejs +1 -1
  34. package/{templates/src/app → dist/templates/src}/App.tsx.ejs +34 -1
  35. package/dist/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
  36. package/dist/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
  37. package/dist/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
  38. package/dist/templates/src/main.tsx.ejs +2 -2
  39. package/{templates/src/app → dist/templates/src}/providers.tsx.ejs +1 -2
  40. package/dist/templates/src/routes/AboutPage.tsx.ejs +11 -10
  41. package/dist/templates/src/routes/CounterPage.tsx.ejs +17 -0
  42. package/dist/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
  43. package/dist/templates/src/routes/HomePage.tsx.ejs +43 -21
  44. package/dist/templates/src/routes/PostsPage.tsx.ejs +17 -0
  45. package/dist/templates/src/styles/global.css.ejs +14 -33
  46. package/package.json +1 -1
  47. package/templates/README.md.ejs +23 -29
  48. package/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
  49. package/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
  50. package/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
  51. package/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
  52. package/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
  53. package/templates/__when_formatting_prettier__/.prettierignore +28 -0
  54. package/templates/__when_linting_biome__/biome.json +12 -0
  55. package/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
  56. package/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
  57. package/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
  58. package/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
  59. package/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
  60. package/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
  61. package/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
  62. package/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +4 -1
  63. package/templates/__when_visualTesting_storybook__/.storybook/preview.js.ejs +40 -0
  64. package/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +40 -0
  65. package/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
  66. package/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
  67. package/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
  68. package/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
  69. package/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
  70. package/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
  71. package/templates/docs/architecture.md.ejs +32 -34
  72. package/templates/docs/getting-started.md.ejs +1 -1
  73. package/{dist/templates/src/app → templates/src}/App.tsx.ejs +34 -1
  74. package/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
  75. package/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
  76. package/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
  77. package/templates/src/main.tsx.ejs +2 -2
  78. package/{dist/templates/src/app → templates/src}/providers.tsx.ejs +1 -2
  79. package/templates/src/routes/AboutPage.tsx.ejs +11 -10
  80. package/templates/src/routes/CounterPage.tsx.ejs +17 -0
  81. package/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
  82. package/templates/src/routes/HomePage.tsx.ejs +43 -21
  83. package/templates/src/routes/PostsPage.tsx.ejs +17 -0
  84. package/templates/src/styles/global.css.ejs +14 -33
  85. package/dist/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
  86. package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
  87. package/dist/templates/src/components/ui/Toast.tsx.ejs +0 -61
  88. package/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
  89. package/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
  90. package/templates/src/components/ui/Toast.tsx.ejs +0 -61
@@ -13,6 +13,14 @@ interface AppLayoutProps {
13
13
  }
14
14
  <% } -%>
15
15
 
16
+ const links = [
17
+ { to: '/', label: 'Home', end: true },
18
+ { to: '/counter', label: 'Counter' },
19
+ { to: '/posts', label: 'Posts' },
20
+ { to: '/feedback', label: 'Feedback' },
21
+ { to: '/about', label: 'About' },
22
+ ] as const;
23
+
16
24
  export function AppLayout(<% if (router === 'tanstack-router') { %>{ children }<% if (isTypeScript) { %>: AppLayoutProps<% } %><% } %>) {
17
25
  <% if (router === 'tanstack-router') { -%>
18
26
  const pathname = useRouterState({ select: (s) => s.location.pathname });
@@ -30,19 +38,34 @@ export function AppLayout(<% if (router === 'tanstack-router') { %>{ children }<
30
38
  </div>
31
39
  <nav className="app-nav" aria-label="Primary">
32
40
  <% 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>
41
+ {links.map((link) => (
42
+ <NavLink
43
+ key={link.to}
44
+ to={link.to}
45
+ end={'end' in link ? link.end : false}
46
+ className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}
47
+ >
48
+ {link.label}
49
+ </NavLink>
50
+ ))}
39
51
  <% } 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>
52
+ {links.map((link) => (
53
+ <Link
54
+ key={link.to}
55
+ to={link.to}
56
+ className={
57
+ link.to === '/'
58
+ ? pathname === '/'
59
+ ? 'nav-link active'
60
+ : 'nav-link'
61
+ : pathname.startsWith(link.to)
62
+ ? 'nav-link active'
63
+ : 'nav-link'
64
+ }
65
+ >
66
+ {link.label}
67
+ </Link>
68
+ ))}
46
69
  <% } -%>
47
70
  </nav>
48
71
  </header>
@@ -13,10 +13,8 @@ import { counterAtom } from '@/store';
13
13
  import { useAppState } from '@/store/context';
14
14
  <% } -%>
15
15
  import { UiButton } from '@/components/ui/UiButton';
16
- import { useToast } from '@/components/ui/Toast';
17
16
 
18
17
  export function CounterPanel() {
19
- const { push } = useToast();
20
18
  <% if (stateManagement === 'redux') { -%>
21
19
  const value = useSelector((state<% if (isTypeScript) { %>: RootState<% } %>) => state.counter.value);
22
20
  const dispatch = useDispatch();
@@ -48,12 +46,20 @@ export function CounterPanel() {
48
46
  <p><%= stackLabels.stateManagement %> example — local interactive counter.</p>
49
47
  </header>
50
48
  <% if (stateManagement === 'none') { -%>
51
- <p className="muted">Client state library was set to None. Add one later under <code>src/store</code>.</p>
49
+ <p className="muted">
50
+ Client state library was set to None. Add one later under <code>src/store</code>.
51
+ </p>
52
52
  <% } else { -%>
53
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>
54
+ <UiButton aria-label="Decrement" onClick={onDec}>
55
+
56
+ </UiButton>
57
+ <strong className="counter-value" data-testid="counter-value" aria-live="polite">
58
+ {value}
59
+ </strong>
60
+ <UiButton aria-label="Increment" onClick={onInc}>
61
+ +
62
+ </UiButton>
57
63
  </div>
58
64
  <% } -%>
59
65
  <p className="panel-note">
@@ -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 { push } = useToast();
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
- push('Feedback submitted (demo API)', 'success');
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
- push('Feedback submitted (demo API)', 'success');
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
- push('Feedback submitted (demo API)', 'success');
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
- push('Feedback submitted (demo API)', 'success');
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 '@/app/providers';
4
- import { App } from '@/app/App';
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 <ToastProvider>{tree}</ToastProvider>;
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>Extend features</strong> under <code>src/features/&lt;name&gt;/</code> keep
13
- domain logic close to the UI that owns it.
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>Shared primitives</strong> live in <code>src/components/</code> and{' '}
17
- <code>src/lib/</code>. Prefer imports via the <code>@/</code> path alias.
15
+ <strong>Extend features</strong> under <code>src/features/&lt;name&gt;/</code>.
18
16
  </li>
19
17
  <li>
20
- <strong>Docs</strong> in <code>docs/</code> explain architecture, testing, and production
21
- habits for this stack.
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
- Prefer reading <code>docs/getting-started.md</code> next, then{' '}
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
- import { CounterPanel } from '@/features/counter/CounterPanel';
2
- import { PostsPanel } from '@/features/posts/PostsPanel';
3
- import { FeedbackForm } from '@/features/feedback/FeedbackForm';
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
- export function HomePage() {
9
- const { push } = useToast();
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 minimal, production-shaped React app wired for your selected stack. Explore the panels
18
- below — each one is a real usage example, not a placeholder.
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
- <UiButton onClick={() => push('Toast system is ready', 'success')}>
22
- Show toast
23
- </UiButton>
24
- <a className="text-link" href="/about">
25
- Read about the stack
26
- </a>
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
- <% if (stateManagement !== 'none') { -%>
40
- <CounterPanel />
41
- <% } -%>
42
- <PostsPanel />
43
- <FeedbackForm />
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-react-starter-kit",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Interactive CLI to scaffold a production-ready React app with your preferred stack",
5
5
  "keywords": [
6
6
  "react",
@@ -74,21 +74,23 @@ Open the URL printed in the terminal (typically `http://localhost:5173`).
74
74
 
75
75
  ```text
76
76
  src/
77
- app/ # App shell, providers, bootstrap
78
- assets/ # Images / media imported from code
79
- components/ # Shared UI (layout/, ui/)
80
- config/ # Env helpers / app config
81
- features/ # Domain modules (scalable default)
82
- hooks/ # Shared hooks
83
- lib/ # Third-party wrappers / helpers
84
- routes/ # Route-level pages
85
- store/ # Client state
86
- services/ # API clients
87
- styles/ # Global styles
88
- types/ # Shared types
89
- utils/ # Pure helpers
90
- docs/ # Architecture & usage docs
91
- tests/ # Extra test helpers / e2e
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 page (`/`) shows:
128
+ The demo pages include:
127
129
 
128
- - navigation + layout
129
- <% if (stateManagement !== 'none') { -%>
130
- - client state (<%= stackLabels.stateManagement %>)
131
- <% } -%>
132
- <% if (serverState !== 'none') { -%>
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
- command: '<%= packageManager === 'npm' ? 'npm run dev' : packageManager + ' dev' %>',
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
+ });