create-react-starter-kit 1.0.2 → 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 (89) hide show
  1. package/dist/generator/dependencies.d.ts.map +1 -1
  2. package/dist/generator/dependencies.js +20 -2
  3. package/dist/generator/dependencies.js.map +1 -1
  4. package/dist/generator/index.d.ts.map +1 -1
  5. package/dist/generator/index.js +10 -2
  6. package/dist/generator/index.js.map +1 -1
  7. package/dist/templates/README.md.ejs +23 -29
  8. package/dist/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
  9. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
  10. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
  11. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
  12. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
  13. package/dist/templates/__when_formatting_prettier__/.prettierignore +28 -0
  14. package/dist/templates/__when_linting_biome__/biome.json +12 -0
  15. package/dist/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
  16. package/dist/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
  17. package/dist/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
  18. package/dist/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
  19. package/dist/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
  20. package/dist/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
  21. package/dist/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
  22. package/dist/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +4 -1
  23. package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js.ejs +40 -0
  24. package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +40 -0
  25. package/dist/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
  26. package/dist/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
  27. package/dist/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
  28. package/dist/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
  29. package/dist/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
  30. package/dist/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
  31. package/dist/templates/docs/architecture.md.ejs +32 -34
  32. package/dist/templates/docs/getting-started.md.ejs +1 -1
  33. package/{templates/src/app → dist/templates/src}/App.tsx.ejs +34 -1
  34. package/dist/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
  35. package/dist/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
  36. package/dist/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
  37. package/dist/templates/src/main.tsx.ejs +2 -2
  38. package/{templates/src/app → dist/templates/src}/providers.tsx.ejs +1 -2
  39. package/dist/templates/src/routes/AboutPage.tsx.ejs +11 -10
  40. package/dist/templates/src/routes/CounterPage.tsx.ejs +17 -0
  41. package/dist/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
  42. package/dist/templates/src/routes/HomePage.tsx.ejs +43 -21
  43. package/dist/templates/src/routes/PostsPage.tsx.ejs +17 -0
  44. package/dist/templates/src/styles/global.css.ejs +14 -33
  45. package/package.json +1 -1
  46. package/templates/README.md.ejs +23 -29
  47. package/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
  48. package/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
  49. package/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
  50. package/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
  51. package/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
  52. package/templates/__when_formatting_prettier__/.prettierignore +28 -0
  53. package/templates/__when_linting_biome__/biome.json +12 -0
  54. package/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
  55. package/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
  56. package/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
  57. package/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
  58. package/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
  59. package/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
  60. package/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
  61. package/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +4 -1
  62. package/templates/__when_visualTesting_storybook__/.storybook/preview.js.ejs +40 -0
  63. package/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +40 -0
  64. package/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
  65. package/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
  66. package/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
  67. package/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
  68. package/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
  69. package/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
  70. package/templates/docs/architecture.md.ejs +32 -34
  71. package/templates/docs/getting-started.md.ejs +1 -1
  72. package/{dist/templates/src/app → templates/src}/App.tsx.ejs +34 -1
  73. package/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
  74. package/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
  75. package/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
  76. package/templates/src/main.tsx.ejs +2 -2
  77. package/{dist/templates/src/app → templates/src}/providers.tsx.ejs +1 -2
  78. package/templates/src/routes/AboutPage.tsx.ejs +11 -10
  79. package/templates/src/routes/CounterPage.tsx.ejs +17 -0
  80. package/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
  81. package/templates/src/routes/HomePage.tsx.ejs +43 -21
  82. package/templates/src/routes/PostsPage.tsx.ejs +17 -0
  83. package/templates/src/styles/global.css.ejs +14 -33
  84. package/dist/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
  85. package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
  86. package/dist/templates/src/components/ui/Toast.tsx.ejs +0 -61
  87. package/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
  88. package/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
  89. package/templates/src/components/ui/Toast.tsx.ejs +0 -61
@@ -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/
@@ -1,5 +1,17 @@
1
1
  {
2
2
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
+ "files": {
4
+ "ignore": [
5
+ "node_modules",
6
+ "dist",
7
+ "build",
8
+ "coverage",
9
+ "storybook-static",
10
+ ".parcel-cache",
11
+ "playwright-report",
12
+ "test-results"
13
+ ]
14
+ },
3
15
  "organizeImports": { "enabled": true },
4
16
  "linter": {
5
17
  "enabled": true,
@@ -1,11 +1,20 @@
1
1
  import { render, screen } from '@testing-library/react';
2
- import { AppProviders } from '@/app/providers';
2
+ <% if (router === 'react-router') { -%>
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ <% } -%>
5
+ import { AppProviders } from '@/providers';
3
6
  import { HomePage } from '@/routes/HomePage';
4
7
 
5
8
  test('renders the project hero', () => {
6
9
  render(
7
10
  <AppProviders>
11
+ <% if (router === 'react-router') { -%>
12
+ <MemoryRouter>
13
+ <HomePage />
14
+ </MemoryRouter>
15
+ <% } else { -%>
8
16
  <HomePage />
17
+ <% } -%>
9
18
  </AppProviders>,
10
19
  );
11
20
 
@@ -1,11 +1,20 @@
1
1
  import { render, screen } from '@testing-library/react';
2
- import { AppProviders } from '@/app/providers';
2
+ <% if (router === 'react-router') { -%>
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ <% } -%>
5
+ import { AppProviders } from '@/providers';
3
6
  import { HomePage } from '@/routes/HomePage';
4
7
 
5
8
  test('renders the project hero', () => {
6
9
  render(
7
10
  <AppProviders>
11
+ <% if (router === 'react-router') { -%>
12
+ <MemoryRouter>
13
+ <HomePage />
14
+ </MemoryRouter>
15
+ <% } else { -%>
8
16
  <HomePage />
17
+ <% } -%>
9
18
  </AppProviders>,
10
19
  );
11
20
 
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { CounterPanel } from '@/features/counter/CounterPanel';
5
+ import { renderWithProviders } from './test-utils';
6
+
7
+ describe('CounterPanel', () => {
8
+ <% if (stateManagement === 'none') { -%>
9
+ it('shows guidance when no state library is selected', () => {
10
+ renderWithProviders(<CounterPanel />);
11
+ expect(screen.getByText(/Client state library was set to None/i)).toBeInTheDocument();
12
+ });
13
+ <% } else { -%>
14
+ it('starts at 0 and updates when increment/decrement are clicked', async () => {
15
+ const user = userEvent.setup();
16
+ renderWithProviders(<CounterPanel />);
17
+
18
+ expect(screen.getByTestId('counter-value')).toHaveTextContent('0');
19
+
20
+ await user.click(screen.getByRole('button', { name: 'Increment' }));
21
+ expect(screen.getByTestId('counter-value')).toHaveTextContent('1');
22
+
23
+ await user.click(screen.getByRole('button', { name: 'Increment' }));
24
+ expect(screen.getByTestId('counter-value')).toHaveTextContent('2');
25
+
26
+ await user.click(screen.getByRole('button', { name: 'Decrement' }));
27
+ expect(screen.getByTestId('counter-value')).toHaveTextContent('1');
28
+ });
29
+
30
+ it('exposes accessible button labels', () => {
31
+ renderWithProviders(<CounterPanel />);
32
+ expect(screen.getByRole('button', { name: 'Increment' })).toBeInTheDocument();
33
+ expect(screen.getByRole('button', { name: 'Decrement' })).toBeInTheDocument();
34
+ });
35
+ <% } -%>
36
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { FeedbackForm } from '@/features/feedback/FeedbackForm';
5
+ import { renderWithProviders } from './test-utils';
6
+
7
+ vi.mock('@/services/api', async (importOriginal) => {
8
+ const actual = await importOriginal<% if (isTypeScript) { %><typeof import('@/services/api')><% } %>();
9
+ return {
10
+ ...actual,
11
+ createFeedback: vi.fn(async () => ({ id: 101 })),
12
+ };
13
+ });
14
+
15
+ describe('FeedbackForm', () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+
20
+ it('shows validation errors when submitting empty values', async () => {
21
+ const user = userEvent.setup();
22
+ renderWithProviders(<FeedbackForm />);
23
+
24
+ await user.click(screen.getByRole('button', { name: /Send feedback/i }));
25
+
26
+ await waitFor(() => {
27
+ expect(screen.getAllByText(/at least|valid email|required/i).length).toBeGreaterThan(0);
28
+ });
29
+ });
30
+
31
+ it('submits valid feedback and shows success status', async () => {
32
+ const user = userEvent.setup();
33
+ const { createFeedback } = await import('@/services/api');
34
+ renderWithProviders(<FeedbackForm />);
35
+
36
+ await user.type(screen.getByPlaceholderText('Ada Lovelace'), 'Ada Lovelace');
37
+ await user.type(screen.getByPlaceholderText('ada@example.com'), 'ada@example.com');
38
+ await user.type(
39
+ screen.getByPlaceholderText('What should we improve?'),
40
+ 'Please add more routing examples.',
41
+ );
42
+ await user.click(screen.getByRole('button', { name: /Send feedback/i }));
43
+
44
+ await waitFor(() => {
45
+ expect(createFeedback).toHaveBeenCalled();
46
+ expect(screen.getByRole('status')).toHaveTextContent(/submitted successfully/i);
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { screen } from '@testing-library/react';
3
+ import { HomePage } from '@/routes/HomePage';
4
+ import { AboutPage } from '@/routes/AboutPage';
5
+ import { renderWithProviders } from './test-utils';
6
+
7
+ describe('HomePage', () => {
8
+ it('renders the project hero and demo cards', () => {
9
+ renderWithProviders(<HomePage />);
10
+
11
+ expect(screen.getByRole('heading', { name: /<%= projectName %>/i })).toBeInTheDocument();
12
+ expect(screen.getByRole('link', { name: /Go to counter/i })).toBeInTheDocument();
13
+ expect(screen.getByRole('link', { name: /Go to posts/i })).toBeInTheDocument();
14
+ expect(screen.getByRole('link', { name: /Go to feedback/i })).toBeInTheDocument();
15
+ });
16
+ });
17
+
18
+ describe('AboutPage', () => {
19
+ it('explains routes and project layout', () => {
20
+ renderWithProviders(<AboutPage />);
21
+
22
+ expect(screen.getByRole('heading', { name: /About this starter/i })).toBeInTheDocument();
23
+ expect(screen.getByText(/\/counter/)).toBeInTheDocument();
24
+ expect(screen.getByText(/feature-based/i)).toBeInTheDocument();
25
+ });
26
+ });
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { PostsPanel } from '@/features/posts/PostsPanel';
5
+ import { renderWithProviders } from './test-utils';
6
+
7
+ vi.mock('@/services/api', async (importOriginal) => {
8
+ const actual = await importOriginal<% if (isTypeScript) { %><typeof import('@/services/api')><% } %>();
9
+ return {
10
+ ...actual,
11
+ fetchPosts: vi.fn(async () => [
12
+ { userId: 1, id: 1, title: 'Alpha post', body: 'body-1' },
13
+ { userId: 1, id: 2, title: 'Beta post', body: 'body-2' },
14
+ ]),
15
+ };
16
+ });
17
+
18
+ <% if (serverState === 'rtk-query') { -%>
19
+ // RTK Query uses its own endpoint; mock still covers the transport used by other modes.
20
+ <% } -%>
21
+
22
+ describe('PostsPanel', () => {
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ });
26
+
27
+ <% if (serverState === 'rtk-query') { -%>
28
+ it('renders the posts panel chrome', async () => {
29
+ renderWithProviders(<PostsPanel />);
30
+ expect(screen.getByRole('heading', { name: 'Server state' })).toBeInTheDocument();
31
+ expect(screen.getByRole('button', { name: /Refresh/i })).toBeInTheDocument();
32
+ });
33
+ <% } else { -%>
34
+ it('renders fetched posts', async () => {
35
+ renderWithProviders(<PostsPanel />);
36
+
37
+ await waitFor(() => {
38
+ expect(screen.getByText(/Alpha post/i)).toBeInTheDocument();
39
+ });
40
+ expect(screen.getByText(/Beta post/i)).toBeInTheDocument();
41
+ });
42
+
43
+ it('refreshes posts when Refresh is clicked', async () => {
44
+ const user = userEvent.setup();
45
+ const { fetchPosts } = await import('@/services/api');
46
+ renderWithProviders(<PostsPanel />);
47
+
48
+ await waitFor(() => {
49
+ expect(screen.getByText(/Alpha post/i)).toBeInTheDocument();
50
+ });
51
+
52
+ await user.click(screen.getByRole('button', { name: /Refresh/i }));
53
+ await waitFor(() => {
54
+ expect(fetchPosts).toHaveBeenCalled();
55
+ });
56
+ });
57
+ <% } -%>
58
+ });
@@ -0,0 +1,50 @@
1
+ <% if (isTypeScript) { -%>
2
+ import type { ReactElement, ReactNode } from 'react';
3
+ <% } -%>
4
+ import { render } from '@testing-library/react';
5
+ <% if (router === 'react-router') { -%>
6
+ import { MemoryRouter } from 'react-router-dom';
7
+ <% } -%>
8
+ import { AppProviders } from '@/providers';
9
+
10
+ <% if (isTypeScript) { -%>
11
+ interface Options {
12
+ route?: string;
13
+ }
14
+
15
+ function Wrapper({ children, route = '/' }: { children: ReactNode; route?: string }) {
16
+ <% if (router === 'react-router') { -%>
17
+ return (
18
+ <AppProviders>
19
+ <MemoryRouter initialEntries={[route]}>{children}</MemoryRouter>
20
+ </AppProviders>
21
+ );
22
+ <% } else { -%>
23
+ return <AppProviders>{children}</AppProviders>;
24
+ <% } -%>
25
+ }
26
+
27
+ export function renderWithProviders(ui: ReactElement, options: Options = {}) {
28
+ return render(ui, {
29
+ wrapper: ({ children }) => <Wrapper route={options.route}>{children}</Wrapper>,
30
+ });
31
+ }
32
+ <% } else { -%>
33
+ function Wrapper({ children, route = '/' }) {
34
+ <% if (router === 'react-router') { -%>
35
+ return (
36
+ <AppProviders>
37
+ <MemoryRouter initialEntries={[route]}>{children}</MemoryRouter>
38
+ </AppProviders>
39
+ );
40
+ <% } else { -%>
41
+ return <AppProviders>{children}</AppProviders>;
42
+ <% } -%>
43
+ }
44
+
45
+ export function renderWithProviders(ui, options = {}) {
46
+ return render(ui, {
47
+ wrapper: ({ children }) => <Wrapper route={options.route}>{children}</Wrapper>,
48
+ });
49
+ }
50
+ <% } -%>
@@ -1,11 +1,14 @@
1
1
  /** @type { import('@storybook/<%= buildTool === 'vite' || buildTool === 'esbuild' ? 'react-vite' : 'react-webpack5' %>').StorybookConfig } */
2
2
  const config = {
3
3
  stories: ['../src/**/*.stories.@(<%= isTypeScript ? 'ts|tsx' : 'js|jsx' %>)'],
4
- addons: [],
4
+ addons: ['@storybook/addon-essentials'],
5
5
  framework: {
6
6
  name: '@storybook/<%= buildTool === 'vite' || buildTool === 'esbuild' ? 'react-vite' : 'react-webpack5' %>',
7
7
  options: {},
8
8
  },
9
+ docs: {
10
+ autodocs: 'tag',
11
+ },
9
12
  };
10
13
 
11
14
  export default config;
@@ -0,0 +1,40 @@
1
+ import { AppProviders } from '../src/providers';
2
+ <% if (router === 'react-router') { -%>
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ <% } -%>
5
+ import '../src/styles/global.<%= styling === 'sass' && uiLibrary !== 'shadcn' ? 'scss' : 'css' %>';
6
+
7
+ /** @type { import('@storybook/react').Preview } */
8
+ const preview = {
9
+ parameters: {
10
+ layout: 'padded',
11
+ controls: {
12
+ matchers: {
13
+ color: /(background|color)$/i,
14
+ date: /Date$/i,
15
+ },
16
+ },
17
+ docs: {
18
+ toc: true,
19
+ },
20
+ },
21
+ decorators: [
22
+ (Story) => (
23
+ <AppProviders>
24
+ <% if (router === 'react-router') { -%>
25
+ <MemoryRouter>
26
+ <div style={{ maxWidth: 960, margin: '0 auto' }}>
27
+ <Story />
28
+ </div>
29
+ </MemoryRouter>
30
+ <% } else { -%>
31
+ <div style={{ maxWidth: 960, margin: '0 auto' }}>
32
+ <Story />
33
+ </div>
34
+ <% } -%>
35
+ </AppProviders>
36
+ ),
37
+ ],
38
+ };
39
+
40
+ export default preview;
@@ -0,0 +1,40 @@
1
+ import { AppProviders } from '../src/providers';
2
+ <% if (router === 'react-router') { -%>
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ <% } -%>
5
+ import '../src/styles/global.<%= styling === 'sass' && uiLibrary !== 'shadcn' ? 'scss' : 'css' %>';
6
+
7
+ /** @type { import('@storybook/react').Preview } */
8
+ const preview = {
9
+ parameters: {
10
+ layout: 'padded',
11
+ controls: {
12
+ matchers: {
13
+ color: /(background|color)$/i,
14
+ date: /Date$/i,
15
+ },
16
+ },
17
+ docs: {
18
+ toc: true,
19
+ },
20
+ },
21
+ decorators: [
22
+ (Story) => (
23
+ <AppProviders>
24
+ <% if (router === 'react-router') { -%>
25
+ <MemoryRouter>
26
+ <div style={{ maxWidth: 960, margin: '0 auto' }}>
27
+ <Story />
28
+ </div>
29
+ </MemoryRouter>
30
+ <% } else { -%>
31
+ <div style={{ maxWidth: 960, margin: '0 auto' }}>
32
+ <Story />
33
+ </div>
34
+ <% } -%>
35
+ </AppProviders>
36
+ ),
37
+ ],
38
+ };
39
+
40
+ export default preview;
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { StackBadge } from '@/components/StackBadge';
3
+
4
+ const meta = {
5
+ title: 'Components/StackBadge',
6
+ component: StackBadge,
7
+ tags: ['autodocs'],
8
+ args: {
9
+ label: 'Build',
10
+ value: '<%= stackLabels.buildTool %>',
11
+ },
12
+ } satisfies Meta<typeof StackBadge>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {};
18
+
19
+ export const Language: Story = {
20
+ args: {
21
+ label: 'Language',
22
+ value: '<%= stackLabels.language %>',
23
+ },
24
+ };
25
+
26
+ export const Row: Story = {
27
+ render: () => (
28
+ <div className="stack-row">
29
+ <StackBadge label="Build" value="<%= stackLabels.buildTool %>" />
30
+ <StackBadge label="Router" value="<%= stackLabels.router %>" />
31
+ <StackBadge label="State" value="<%= stackLabels.stateManagement %>" />
32
+ <StackBadge label="Data" value="<%= stackLabels.serverState %>" />
33
+ </div>
34
+ ),
35
+ };
@@ -1,15 +1,38 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { UiButton } from '@/components/ui/UiButton';
3
3
 
4
+ /**
5
+ * Shared button adapter used across demos.
6
+ * Prefer this over raw HTML buttons so UI library swaps stay localized.
7
+ */
4
8
  const meta = {
5
- title: 'UI/UiButton',
9
+ title: 'Components/UiButton',
6
10
  component: UiButton,
11
+ tags: ['autodocs'],
7
12
  args: {
8
13
  children: 'Click me',
9
14
  },
15
+ argTypes: {
16
+ onClick: { action: 'clicked' },
17
+ children: { control: 'text' },
18
+ },
10
19
  } satisfies Meta<typeof UiButton>;
11
20
 
12
21
  export default meta;
13
22
  type Story = StoryObj<typeof meta>;
14
23
 
15
24
  export const Primary: Story = {};
25
+
26
+ export const SubmitLabel: Story = {
27
+ args: {
28
+ children: 'Send feedback',
29
+ type: 'submit',
30
+ },
31
+ };
32
+
33
+ export const Disabled: Story = {
34
+ args: {
35
+ children: 'Disabled',
36
+ disabled: true,
37
+ },
38
+ };
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { CounterPanel } from '@/features/counter/CounterPanel';
3
+
4
+ /**
5
+ * Client-state demo used on `/counter`.
6
+ * Interact with +/- to verify store wiring in isolation.
7
+ */
8
+ const meta = {
9
+ title: 'Features/CounterPanel',
10
+ component: CounterPanel,
11
+ tags: ['autodocs'],
12
+ parameters: {
13
+ docs: {
14
+ description: {
15
+ component:
16
+ 'Demonstrates <%= stackLabels.stateManagement %> for ephemeral UI state. Prefer this over putting server entities into client stores.',
17
+ },
18
+ },
19
+ },
20
+ } satisfies Meta<typeof CounterPanel>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Default: Story = {};
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { FeedbackForm } from '@/features/feedback/FeedbackForm';
3
+
4
+ /**
5
+ * Forms demo used on `/feedback`.
6
+ * Try empty submit for validation, then a valid payload for success status.
7
+ */
8
+ const meta = {
9
+ title: 'Features/FeedbackForm',
10
+ component: FeedbackForm,
11
+ tags: ['autodocs'],
12
+ parameters: {
13
+ docs: {
14
+ description: {
15
+ component:
16
+ 'Built with <%= stackLabels.forms %><% if (validation !== 'none') { %> and <%= stackLabels.validation %><% } %>. Submits to the demo REST API.',
17
+ },
18
+ },
19
+ },
20
+ } satisfies Meta<typeof FeedbackForm>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Empty: Story = {};
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { PostsPanel } from '@/features/posts/PostsPanel';
3
+
4
+ /**
5
+ * Server-state demo used on `/posts`.
6
+ * Needs network access to JSONPlaceholder (or your `VITE_API_BASE_URL`).
7
+ */
8
+ const meta = {
9
+ title: 'Features/PostsPanel',
10
+ component: PostsPanel,
11
+ tags: ['autodocs'],
12
+ parameters: {
13
+ docs: {
14
+ description: {
15
+ component:
16
+ 'Fetches posts with <%= stackLabels.serverState === 'None' ? stackLabels.apiLayer : stackLabels.serverState %>. Use Refresh to exercise refetch behavior.',
17
+ },
18
+ },
19
+ },
20
+ } satisfies Meta<typeof PostsPanel>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Default: Story = {};
@@ -0,0 +1,42 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { HomePage } from '@/routes/HomePage';
3
+ import { AboutPage } from '@/routes/AboutPage';
4
+ import { CounterPage } from '@/routes/CounterPage';
5
+ import { PostsPage } from '@/routes/PostsPage';
6
+ import { FeedbackPage } from '@/routes/FeedbackPage';
7
+
8
+ const meta = {
9
+ title: 'Pages/Routes',
10
+ parameters: {
11
+ layout: 'fullscreen',
12
+ docs: {
13
+ description: {
14
+ component:
15
+ 'Route-level pages composed from feature panels. Useful for visual review of layout and copy.',
16
+ },
17
+ },
18
+ },
19
+ } satisfies Meta;
20
+
21
+ export default meta;
22
+ type Story = StoryObj;
23
+
24
+ export const Home: Story = {
25
+ render: () => <HomePage />,
26
+ };
27
+
28
+ export const About: Story = {
29
+ render: () => <AboutPage />,
30
+ };
31
+
32
+ export const Counter: Story = {
33
+ render: () => <CounterPage />,
34
+ };
35
+
36
+ export const Posts: Story = {
37
+ render: () => <PostsPage />,
38
+ };
39
+
40
+ export const Feedback: Story = {
41
+ render: () => <FeedbackPage />,
42
+ };