@react-foundry/plop-pack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +42 -0
  3. package/package.json +38 -0
  4. package/skel/app/.dockerignore +9 -0
  5. package/skel/app/Dockerfile +32 -0
  6. package/skel/app/Makefile.hbs +87 -0
  7. package/skel/app/README.md.hbs +91 -0
  8. package/skel/app/aws-lambda-entry.js +3 -0
  9. package/skel/app/cypress.config.mjs +6 -0
  10. package/skel/app/feat/home.spec.js +5 -0
  11. package/skel/app/gitignore +10 -0
  12. package/skel/app/jest.config.cjs +15 -0
  13. package/skel/app/lambda.Dockerfile +23 -0
  14. package/skel/app/package.json.hbs +61 -0
  15. package/skel/app/plopfile.mjs +5 -0
  16. package/skel/app/public/favicon.ico +0 -0
  17. package/skel/app/react-router.config.ts +13 -0
  18. package/skel/app/src/app/app.scss +0 -0
  19. package/skel/app/src/app/config.ts.hbs +1 -0
  20. package/skel/app/src/app/entry.client.tsx +17 -0
  21. package/skel/app/src/app/entry.server.tsx +85 -0
  22. package/skel/app/src/app/root.tsx +141 -0
  23. package/skel/app/src/app/routes/_index.tsx +23 -0
  24. package/skel/app/src/app/routes.ts +6 -0
  25. package/skel/app/src/server/config.ts +59 -0
  26. package/skel/app/src/server/dev.ts +13 -0
  27. package/skel/app/src/server/httpd.ts +49 -0
  28. package/skel/app/src/server/index.ts +33 -0
  29. package/skel/app/src/server/server-build.d.ts +14 -0
  30. package/skel/app/test.Dockerfile +18 -0
  31. package/skel/app/tsconfig.json +19 -0
  32. package/skel/app/vite.config.server.ts +24 -0
  33. package/skel/app/vite.config.ts +20 -0
  34. package/skel/component/README.md.hbs +66 -0
  35. package/skel/component/assets/Component.scss.hbs +9 -0
  36. package/skel/component/gitignore +5 -0
  37. package/skel/component/jest.config.js.hbs +15 -0
  38. package/skel/component/package.json.hbs +72 -0
  39. package/skel/component/spec/Component.mdx.hbs +19 -0
  40. package/skel/component/spec/Component.stories.tsx.hbs +22 -0
  41. package/skel/component/spec/Component.ts.hbs +28 -0
  42. package/skel/component/src/Component.tsx.hbs +32 -0
  43. package/skel/component/tsconfig.json +14 -0
  44. package/skel/lib/README.md.hbs +51 -0
  45. package/skel/lib/gitignore +5 -0
  46. package/skel/lib/jest.config.js.hbs +15 -0
  47. package/skel/lib/package.json.hbs +44 -0
  48. package/skel/lib/src/index.ts.hbs +6 -0
  49. package/skel/lib/tsconfig.json +14 -0
  50. package/src/action-paths.js +39 -0
  51. package/src/actions/copy.js +27 -0
  52. package/src/actions/merge.js +26 -0
  53. package/src/actions/message.js +3 -0
  54. package/src/actions/shell.js +21 -0
  55. package/src/actions/symlink.js +42 -0
  56. package/src/actions/write.js +20 -0
  57. package/src/extend-generator.js +38 -0
  58. package/src/generators/app.js +140 -0
  59. package/src/generators/component.js +73 -0
  60. package/src/generators/lib.js +54 -0
  61. package/src/helpers/eq.js +3 -0
  62. package/src/helpers/md-title.js +6 -0
  63. package/src/index.js +38 -0
  64. package/src/rel-to-skel.js +9 -0
  65. package/src/relative-path.js +5 -0
  66. package/src/run-plop.js +24 -0
@@ -0,0 +1,141 @@
1
+ import { useEffect } from 'react';
2
+ import {
3
+ Links,
4
+ Meta,
5
+ Outlet,
6
+ Scripts,
7
+ ScrollRestoration,
8
+ isRouteErrorResponse,
9
+ useRouteLoaderData
10
+ } from 'react-router';
11
+ import type { Route } from './+types/root';
12
+ import { cspNonceContext, sanitiseUserInfo, userInfoContext } from '@react-foundry/react-router-context';
13
+ import { UserInfoContext } from '@react-foundry/user-info';
14
+ import { siteTitle } from './config';
15
+
16
+ import './app.scss';
17
+
18
+ export const links: Route.LinksFunction = () => [
19
+ ];
20
+
21
+ export const loader = async ({ context }: Route.LoaderArgs) => {
22
+ const nonce = context.get(cspNonceContext);
23
+ const user = context.get(userInfoContext);
24
+
25
+ return {
26
+ nonce,
27
+ user: user && sanitiseUserInfo(user)
28
+ };
29
+ };
30
+
31
+ export function Layout({ children }: { children: React.ReactNode }) {
32
+ const data = useRouteLoaderData('root');
33
+ const nonce = data?.nonce;
34
+ const userInfo = data?.user;
35
+ const sign = (
36
+ userInfo && userInfo.username
37
+ ? {
38
+ href: '/auth/sign-out',
39
+ text: 'Sign out'
40
+ }
41
+ : {
42
+ href: '/auth/sign-in',
43
+ text: 'Sign in'
44
+ }
45
+ );
46
+
47
+ return (
48
+ <html lang="en">
49
+ <head>
50
+ <meta charSet="utf-8" />
51
+ <Meta />
52
+ <Links />
53
+ </head>
54
+ <body>
55
+ <UserInfoContext.Provider value={userInfo}>
56
+ <h1>{siteTitle}</h1>
57
+ {children}
58
+ <ul>
59
+ <a href={sign.href}>{sign.text}</a>
60
+ </ul>
61
+ </UserInfoContext.Provider>
62
+ <ScrollRestoration nonce={nonce} />
63
+ <Scripts nonce={nonce} />
64
+ </body>
65
+ </html>
66
+ );
67
+ }
68
+
69
+ export default function App() {
70
+ useEffect(() => {
71
+ document.body.classList.add('js-enabled');
72
+ }, []);
73
+
74
+ return <Outlet />;
75
+ }
76
+
77
+ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
78
+ const statusToMessage: Record<number, string> = {
79
+ 400: 'Bad request',
80
+ 401: 'Unauthorised',
81
+ 402: 'Payment required',
82
+ 403: 'Forbidden',
83
+ 404: 'Page not found',
84
+ 405: 'Method not allowed',
85
+ 406: 'Not acceptable',
86
+ 407: 'Proxy authentication required',
87
+ 408: 'Request timeout',
88
+ 409: 'Conflict',
89
+ 410: 'Gone',
90
+ 418: 'I am a teapot',
91
+ 500: 'Internal server error',
92
+ 501: 'Not implemented',
93
+ 502: 'Bad gateway',
94
+ 503: 'Service unavailable',
95
+ 504: 'Gateway timeout',
96
+ 505: 'HTTP version not supported',
97
+ };
98
+
99
+ const defaultMessage = 'Something went wrong';
100
+ const defaultDetails = 'An unexpected error occurred.';
101
+
102
+ const { message, details, stack } = (
103
+ isRouteErrorResponse(error)
104
+ ? {
105
+ message: statusToMessage[error.status] || error.statusText || defaultMessage,
106
+ details: (
107
+ error.status !== 404
108
+ ? error.statusText
109
+ : (
110
+ 'If you typed the web address, check it is correct.\n' +
111
+ 'If you pasted the web address, check you copied the entire address.'
112
+ )
113
+ )
114
+ }
115
+ : (
116
+ import.meta.env.DEV && error && error instanceof Error
117
+ ? {
118
+ message: defaultMessage,
119
+ details: error.message,
120
+ stack: error.stack
121
+ }
122
+ : {
123
+ message: defaultMessage,
124
+ details: defaultDetails
125
+ }
126
+ )
127
+ );
128
+
129
+ return (
130
+ <main>
131
+ <title>{message}</title>
132
+ <h1>{message}</h1>
133
+ <p>{details}</p>
134
+ {stack && (
135
+ <pre>
136
+ <code>{stack}</code>
137
+ </pre>
138
+ )}
139
+ </main>
140
+ );
141
+ }
@@ -0,0 +1,23 @@
1
+ import type { Route } from "./+types/_index";
2
+ import { siteTitle } from '../config';
3
+
4
+ export const title = 'Home';
5
+ const description = 'Our homepage';
6
+
7
+ export function meta({}: Route.MetaArgs) {
8
+ return [
9
+ { title: `${title} - ${siteTitle}` },
10
+ { name: 'description', content: description },
11
+ { name: 'og:title', content: title },
12
+ { name: 'og:description', content: description },
13
+ ];
14
+ }
15
+
16
+ export default function Home() {
17
+ return (
18
+ <>
19
+ <h1>{title}</h1>
20
+ <p>This is the home page.</p>
21
+ </>
22
+ );
23
+ }
@@ -0,0 +1,6 @@
1
+ import type { RouteConfig } from '@react-router/dev/routes';
2
+ import { flatRoutes } from '@react-router/fs-routes';
3
+
4
+ export default flatRoutes({
5
+ rootDirectory: 'routes'
6
+ }) satisfies RouteConfig;
@@ -0,0 +1,59 @@
1
+ import { AuthMethod, Mode, NodeEnv, SessionStore, defaultsTrue, defaultsFalse } from '@react-foundry/fastify';
2
+
3
+ const env = process.env.NODE_ENV as NodeEnv;
4
+ const devMode = env === NodeEnv.Development;
5
+ const standardRoles: string[] = [];
6
+
7
+ const serverConfig = {
8
+ auth: {
9
+ method: (process.env.AUTH_METHOD || ( devMode ? AuthMethod.Dummy : AuthMethod.Basic )) as AuthMethod,
10
+ dummy: {
11
+ username: 'TestUser',
12
+ groups: [],
13
+ roles: standardRoles
14
+ },
15
+ headers: {
16
+ usernameHeader: process.env.AUTH_HEADER_USERNAME || 'x-auth-username',
17
+ groupsHeader: process.env.AUTH_HEADER_GROUPS || 'x-auth-groups',
18
+ rolesHeader: process.env.AUTH_HEADER_ROLES || 'x-auth-roles'
19
+ },
20
+ basic: {
21
+ username: process.env.AUTH_USERNAME || 'guest',
22
+ password: process.env.AUTH_PASSWORD || 'password',
23
+ roles: standardRoles,
24
+ },
25
+ oidc: {
26
+ issuer: process.env.OIDC_ISSUER || 'http://localhost:8001/realms/demo/',
27
+ clientId: process.env.OIDC_CLIENT_ID || 'app',
28
+ clientSecret: process.env.OIDC_CLIENT_SECRET || 'app-secret',
29
+ redirectUri: process.env.OIDC_REDIRECT_URI || 'http://localhost:8080'
30
+ }
31
+ },
32
+ contentSecurityPolicy: {
33
+ formAction: process.env.FORM_ACTION?.split(','),
34
+ frameAncestors: process.env.FRAME_ANCESTORS?.split(',')
35
+ },
36
+ cookies: {
37
+ secret: process.env.COOKIES_SECRET || 'changeme',
38
+ secure: ( devMode ? defaultsFalse : defaultsTrue )(process.env.COOKIES_SECURE)
39
+ },
40
+ devMode,
41
+ env,
42
+ formAction: [],
43
+ frameAncestors: [],
44
+ logger: {
45
+ destination: process.env.LOG_DESTINATION,
46
+ level: process.env.LOG_LEVEL || ( devMode ? 'debug' : 'info' )
47
+ },
48
+ httpd: {
49
+ host: process.env.LISTEN_HOST || '::',
50
+ port: Number(process.env.PORT) || Number(process.env.LISTEN_PORT) || 8080
51
+ },
52
+ mode: (process.env.MODE || 'server') as Mode,
53
+ privacy: defaultsFalse(process.env.PRIVACY),
54
+ session: {
55
+ store: process.env.SESSION_STORE as SessionStore
56
+ }
57
+ };
58
+
59
+ export default serverConfig;
@@ -0,0 +1,13 @@
1
+ import { Mode } from '@react-foundry/fastify';
2
+ import fastifyReactRouterDev from '@react-foundry/fastify-react-router/dev';
3
+ import { createServer, reactRouterOptions } from './httpd';
4
+ import config from './config';
5
+
6
+ const httpd = createServer();
7
+
8
+ await httpd.register(fastifyReactRouterDev, reactRouterOptions);
9
+
10
+ await httpd.listen({
11
+ host: '::1',
12
+ port: config.httpd.port
13
+ });
@@ -0,0 +1,49 @@
1
+ import type { FastifyInstance, IsFunction, OnClose } from '@react-foundry/fastify';
2
+
3
+ import { AuthMethod, Fastify, Mode, SessionStore } from '@react-foundry/fastify';
4
+ import config from './config';
5
+
6
+ type Server = FastifyInstance;
7
+
8
+ const isReady: IsFunction = async () => true;
9
+ const onClose: OnClose = async () => undefined;
10
+
11
+ export const reactRouterOptions = {
12
+ stream: config.mode === Mode.Server
13
+ };
14
+
15
+ export const createServer = (): Server => {
16
+ const httpd = Fastify({
17
+ auth: config.auth && {
18
+ ...(
19
+ ( config.auth.method === AuthMethod.None && { method: AuthMethod.None } )
20
+ || ( config.auth.method === AuthMethod.Dummy && { method: AuthMethod.Dummy, ...config.auth.dummy } )
21
+ || ( config.auth.method === AuthMethod.Headers && { method: AuthMethod.Headers, ...config.auth.headers } )
22
+ || ( config.auth.method === AuthMethod.Basic && { method: AuthMethod.Basic, ...config.auth.basic } )
23
+ || ( config.auth.method === AuthMethod.OIDC && { method: AuthMethod.OIDC, ...config.auth.oidc } )
24
+ ),
25
+ privacy: config.privacy,
26
+ } || undefined,
27
+ contentSecurityPolicy: {
28
+ formAction: config.formAction,
29
+ frameAncestors: config.frameAncestors
30
+ },
31
+ cookies: {
32
+ secret: config.cookies.secret,
33
+ secure: config.cookies.secure
34
+ },
35
+ dev: config.devMode,
36
+ isReady,
37
+ logger: {
38
+ file: config.logger.destination,
39
+ level: config.logger.level
40
+ },
41
+ onClose,
42
+ session: config.session && (
43
+ ( config.session.store === SessionStore.Cookie && { store: SessionStore.Cookie } )
44
+ || ( config.session.store === SessionStore.Memory && { store: SessionStore.Memory } )
45
+ ) || undefined
46
+ });
47
+
48
+ return httpd;
49
+ };
@@ -0,0 +1,33 @@
1
+ /// <reference path="./server-build.d.ts" />
2
+
3
+ import type { Application } from 'serverless-http';
4
+
5
+ import { join } from 'node:path';
6
+ import serverless from 'serverless-http';
7
+ import { Mode } from '@react-foundry/fastify';
8
+ import fastifyReactRouter from '@react-foundry/fastify-react-router';
9
+ import { createServer, reactRouterOptions } from './httpd';
10
+ import config from './config';
11
+ import * as serverBuild from '../../dist/app/server/index.js';
12
+
13
+ const assets = join(import.meta.dirname, '..', '..', 'dist', 'app', 'client');
14
+ const httpd = createServer();
15
+
16
+ await httpd.register(fastifyReactRouter, {
17
+ ...reactRouterOptions,
18
+ assets,
19
+ serverBuild
20
+ });
21
+
22
+ export const handler = (
23
+ config.mode !== Mode.Serverless
24
+ ? undefined
25
+ : serverless(httpd as unknown as Application)
26
+ );
27
+
28
+ if (config.mode === Mode.Server) {
29
+ await httpd.listen({
30
+ host: config.httpd.host,
31
+ port: config.httpd.port
32
+ });
33
+ }
@@ -0,0 +1,14 @@
1
+ declare module '*/dist/app/server/index.js' {
2
+ import type { ServerBuild } from './httpd';
3
+
4
+ export declare const entry: ServerBuild['entry'];
5
+ export declare const routes: ServerBuild['routes'];
6
+ export declare const assets: ServerBuild['assets'];
7
+ export declare const publicPath: ServerBuild['publicPath'];
8
+ export declare const assetsBuildDirectory: ServerBuild['assetsBuildDirectory'];
9
+ export declare const future: ServerBuild['future'];
10
+ export declare const ssr: ServerBuild['ssr'];
11
+ export declare const isSpaMode: ServerBuild['isSpaMode'];
12
+ export declare const prerender: ServerBuild['prerender'];
13
+ export declare const routeDiscovery: ServerBuild['routeDiscovery'];
14
+ }
@@ -0,0 +1,18 @@
1
+ FROM cypress/included:15.9.0
2
+
3
+ RUN mv /root/.cache /home/node/.cache && \
4
+ mkdir -p /cypress && \
5
+ chown -R node:node /home/node/.cache && \
6
+ chown -R node:node /cypress/
7
+ ENV CYPRESS_CACHE_FOLDER=/home/node/.cache/Cypress
8
+
9
+ COPY package.json fixtures/* /cypress/fixtures/
10
+ RUN echo '{ "pluginsFile": false }' > '/cypress.json' && \
11
+ rm /cypress/fixtures/package.json
12
+ COPY feat/ /cypress/integration/
13
+
14
+ USER node
15
+ ENV CYPRESS_BASE_URL="http://localhost:8080" \
16
+ CYPRESS_PROJECT_ID="" \
17
+ CYPRESS_RECORD_KEY=""
18
+ HEALTHCHECK NONE
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "../../tsconfig.vite.json",
3
+ "compilerOptions": {
4
+ "types": ["node", "vite/client", "mdx", "@catalyse/vite-html-react"],
5
+ "rootDirs": [".", ".react-router/types"],
6
+ "outDir": "dist",
7
+ "paths": {
8
+ "~/*": ["./src/app/*"]
9
+ }
10
+ },
11
+ "include": [
12
+ "src",
13
+ ".react-router/types"
14
+ ],
15
+ "exclude": [
16
+ "dist",
17
+ "node_modules"
18
+ ]
19
+ }
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ build: {
5
+ commonjsOptions: {
6
+ defaultIsModuleExports: true // Mimics Node.js
7
+ },
8
+ copyPublicDir: false,
9
+ minify: false,
10
+ modulePreload: false,
11
+ rollupOptions: {
12
+ output: {
13
+ manualChunks: null,
14
+ inlineDynamicImports: true
15
+ }
16
+ },
17
+ ssr: 'src/server/index.ts',
18
+ outDir: 'dist/server',
19
+ target: 'node24'
20
+ },
21
+ ssr: {
22
+ noExternal: true
23
+ }
24
+ });
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vite';
2
+ import mdx from '@mdx-js/rollup'
3
+ import html from '@react-foundry/vite-html-react';
4
+ import { reactRouter } from '@react-router/dev/vite';
5
+
6
+ export default defineConfig({
7
+ build: {
8
+ commonjsOptions: {
9
+ defaultIsModuleExports: true // Mimics Node.js, aligns dev and prod
10
+ }
11
+ },
12
+ plugins: [
13
+ html(),
14
+ mdx(),
15
+ reactRouter(),
16
+ ],
17
+ ssr: {
18
+ noExternal: /\.mdx$/
19
+ }
20
+ });
@@ -0,0 +1,66 @@
1
+ {{#mdTitle}}{{{titleCase (pkg 'name')}}} - {{{titleCase name}}}{{/mdTitle}}
2
+
3
+ {{{description}}}
4
+
5
+
6
+ Using this package
7
+ ------------------
8
+
9
+ First install the package into your project:
10
+
11
+ ```shell
12
+ npm install -S @{{{pkg 'name'}}}/{{{dashCase name}}}
13
+ ```
14
+
15
+ Then use it in your code as follows:
16
+
17
+ ```js
18
+ import React, { createElement as h } from 'react';
19
+ import {{{properCase name}}} from '@{{{pkg 'name'}}}/{{{dashCase name}}}';
20
+
21
+ export const MyComponent = props => (
22
+ <{{{properCase name}}}
23
+ // WRITEME
24
+ />
25
+ );
26
+
27
+ export default MyComponent;
28
+ ```
29
+
30
+
31
+ Working on this package
32
+ -----------------------
33
+
34
+ Before working on this package you must install its dependencies using
35
+ the following command:
36
+
37
+ ```shell
38
+ pnpm install
39
+ ```
40
+
41
+
42
+ ### Testing
43
+
44
+ Run the unit tests.
45
+
46
+ ```shell
47
+ npm test
48
+ ```
49
+
50
+
51
+ ### Building
52
+
53
+ Build the package by compiling the TypeScript source code.
54
+
55
+ ```shell
56
+ npm run build
57
+ ```
58
+
59
+
60
+ ### Clean-up
61
+
62
+ Remove any previously built files.
63
+
64
+ ```shell
65
+ npm run clean
66
+ ```
@@ -0,0 +1,9 @@
1
+ .{{{dashCase (pkg 'name')}}}-{{{dashCase name}}} {
2
+ /* Write base styles for {{{properCase name}}} here */
3
+ background-color: beige;
4
+
5
+ &__heading {
6
+ /* Write styles for 'heading' element within {{{properCase name}}} here */
7
+ color: navy;
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ dist/
2
+ node_modules/
3
+ package-lock.json
4
+ pnpm-lock.yaml
5
+ tsconfig.tsbuildinfo
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const baseConfig = require('../../jest.config.base');
4
+
5
+ const config = {
6
+ ...baseConfig,
7
+ collectCoverageFrom: [
8
+ '<rootDir>/src/**.{ts,tsx}',
9
+ ],
10
+ testMatch: [
11
+ '<rootDir>/spec/**/{!(*.stories),}.{ts,tsx}'
12
+ ]
13
+ };
14
+
15
+ module.exports = config;
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@{{{pkg 'name'}}}/{{{dashCase name}}}",
3
+ "version": "{{{pkg 'version'}}}",
4
+ "description": "{{{description}}}",
5
+ "main": "src/{{{properCase name}}}.tsx",
6
+ "sass": "assets/{{{properCase name}}}.scss",
7
+ "exports": {
8
+ ".": {
9
+ "sass": "./assets/{{{properCase name}}}.scss",
10
+ "default": "./src/{{{properCase name}}}.tsx"
11
+ }
12
+ },
13
+ "publishConfig": {
14
+ "main": "dist/{{{properCase name}}}.js",
15
+ "module": "dist/{{{properCase name}}}.mjs",
16
+ "typings": "dist/{{{properCase name}}}.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "sass": "./assets/{{{properCase name}}}.scss",
20
+ "types": "./dist/{{{properCase name}}}.d.ts",
21
+ "import": "./dist/{{{properCase name}}}.mjs",
22
+ "require": "./dist/{{{properCase name}}}.js",
23
+ "default": "./dist/{{{properCase name}}}.mjs"
24
+ }
25
+ }
26
+ },
27
+ "publishConfig": {
28
+ "main": "dist/{{{properCase name}}}.js",
29
+ "typings": "dist/{{{properCase name}}}.d.ts"
30
+ },
31
+ "files": [
32
+ "/assets",
33
+ "/dist"
34
+ ],
35
+ "scripts": {
36
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
37
+ "prepublishOnly": "npm run clean && npm run build",
38
+ "build": "npm run build:esm && npm run build:cjs",
39
+ "build:esm": "tsc -m es2022 && find dist -name '*.js' -exec sh -c 'mv \"$0\" \"${0%.js}.mjs\"' {} \\;",
40
+ "build:cjs": "tsc",
41
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
42
+ },
43
+ "author": "{{{pkg 'author'}}}",
44
+ "license": "{{{pkg 'license'}}}",
45
+ "keywords": [
46
+ "react-components"
47
+ ],
48
+ "dependencies": {
49
+ "@react-foundry/component-helpers": "{{#if (eq (pkg 'name')'react-foundry')}}workspace:^0.1.0"
50
+ },
51
+ "peerDependencies": {
52
+ "@react-foundry/docs-components": "^0.1.0",
53
+ "@storybook/addon-docs": "^9.1.17",
54
+ "react": "^19.2.3"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@react-foundry/docs-components": {
58
+ "optional": true
59
+ },
60
+ "@storybook/addon-docs": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "devDependencies": {
65
+ "@react-foundry/component-test-helpers": "{{#if (eq (pkg 'name')'react-foundry')}}workspace:*{{else}}0.1.0{{/if}}",
66
+ "@types/react": "19.2.8",
67
+ "jest": "30.2.0",
68
+ "jest-environment-jsdom": "30.2.0",
69
+ "ts-jest": "29.4.6",
70
+ "typescript": "5.9.3"
71
+ }
72
+ }
@@ -0,0 +1,19 @@
1
+ import { Canvas, Controls, Meta, Primary } from '@storybook/addon-docs/blocks';
2
+ import * as Stories from './{{{properCase name}}}.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # {{{name}}}
7
+
8
+ {{{description}}}
9
+ WRITE MORE DESCRIPTION HERE.
10
+
11
+ <Primary />
12
+ <Controls />
13
+
14
+ ## Stories
15
+ ### Standard
16
+
17
+ A standard {{{name}}}.
18
+
19
+ <Canvas of={Stories.Primary} />
@@ -0,0 +1,22 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { {{{properCase name}}} } from '../src/{{{properCase name}}}';
4
+
5
+ const meta = {
6
+ title: '{{{name}}}',
7
+ component: {{{properCase name}}},
8
+ parameters: {
9
+ chromatic: { viewports: [768, 360] },
10
+ description: '{{{description}}}'
11
+ },
12
+ args: {
13
+ }
14
+ } satisfies Meta<typeof {{{properCase name}}}>;
15
+
16
+ export default meta;
17
+ type Story = StoryObj<typeof meta>;
18
+
19
+ export const Primary: Story = {
20
+ args: {
21
+ }
22
+ };
@@ -0,0 +1,28 @@
1
+ import { createElement as h } from 'react';
2
+ import { render, screen } from '@react-foundry/component-test-helpers';
3
+ import {{{properCase name}}} from '../src/{{{properCase name}}}';
4
+
5
+ describe('{{{properCase name}}}', () => {
6
+ const minimalProps = {
7
+ };
8
+
9
+ describe('when given minimal valid props', () => {
10
+ beforeEach(async () => {
11
+ render(h({{{properCase name}}}, minimalProps));
12
+ });
13
+
14
+ it('renders an element', async () => expect(screen.getByRole('generic')).toBeInTheDocument());
15
+ });
16
+
17
+ describe('when given all valid props', () => {
18
+ const props = {
19
+ ...minimalProps
20
+ };
21
+
22
+ beforeEach(async () => {
23
+ render(h({{{properCase name}}}, props));
24
+ });
25
+
26
+ it('renders an element', async () => expect(screen.getByRole('generic')).toBeInTheDocument());
27
+ });
28
+ });