create-react-adam 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -20
- package/bin/index.js +264 -94
- package/package.json +8 -3
- package/template/.github/workflows/check.yml +1 -1
- package/template/.github/workflows/e2e.yml +3 -3
- package/template/.github/workflows/lighthouse.yml +33 -0
- package/template/.nvmrc +1 -1
- package/template/README.md +133 -31
- package/template/e2e/gitignore +13 -0
- package/template/e2e/package.json +2 -3
- package/template/e2e/playwright.config.ts +0 -1
- package/template/eslint-rules/prefer-webp-images.js +66 -0
- package/template/eslint.config.js +3 -0
- package/template/eslint.config.no-webp.js +54 -0
- package/template/gitignore +26 -0
- package/template/index.html +19 -0
- package/template/lighthouserc.json +17 -0
- package/template/npmrc +2 -0
- package/template/package.json +18 -18
- package/template/public/fonts/InterVariable.woff2 +0 -0
- package/template/public/robots.txt +5 -0
- package/template/src/App.tsx +25 -11
- package/template/src/app.css +27 -9
- package/template/src/components/Button.tsx +27 -0
- package/template/src/pages/About/index.no-utils.tsx +5 -0
- package/template/src/pages/About/index.tsx +22 -12
- package/template/src/pages/Home/index.no-utils.tsx +37 -1
- package/template/src/pages/Home/index.tsx +22 -12
- package/template/src/pages/NotFound/index.tsx +25 -18
- package/template/src/types/ui.ts +1 -0
- package/template/src/utils/classNames.ts +23 -2
- package/template/src/utils/helpers.ts +22 -0
- package/template/src/utils/useUrlState.ts +17 -4
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
- name: Setup Node.js
|
|
18
18
|
uses: actions/setup-node@v6
|
|
19
19
|
with:
|
|
20
|
-
node-version: "24.
|
|
20
|
+
node-version: "24.18.0"
|
|
21
21
|
cache: "npm"
|
|
22
22
|
|
|
23
23
|
- name: Install dependencies
|
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
run: npm run test:e2e
|
|
37
37
|
|
|
38
38
|
- name: Upload Playwright report
|
|
39
|
-
uses: actions/upload-artifact@
|
|
39
|
+
uses: actions/upload-artifact@v6
|
|
40
40
|
if: always()
|
|
41
41
|
with:
|
|
42
42
|
name: playwright-report
|
|
@@ -44,7 +44,7 @@ jobs:
|
|
|
44
44
|
retention-days: 30
|
|
45
45
|
|
|
46
46
|
- name: Upload Allure results
|
|
47
|
-
uses: actions/upload-artifact@
|
|
47
|
+
uses: actions/upload-artifact@v6
|
|
48
48
|
if: always()
|
|
49
49
|
with:
|
|
50
50
|
name: allure-results
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Lighthouse CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lighthouse:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
|
|
17
|
+
- name: Setup Node.js
|
|
18
|
+
uses: actions/setup-node@v6
|
|
19
|
+
with:
|
|
20
|
+
node-version: "24.18.0"
|
|
21
|
+
cache: "npm"
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm install
|
|
25
|
+
|
|
26
|
+
- name: Build
|
|
27
|
+
run: npm run build
|
|
28
|
+
|
|
29
|
+
- name: Run Lighthouse CI
|
|
30
|
+
uses: treosh/lighthouse-ci-action@v12
|
|
31
|
+
with:
|
|
32
|
+
configPath: ./lighthouserc.json
|
|
33
|
+
uploadArtifacts: true
|
package/template/.nvmrc
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
24.
|
|
1
|
+
24.18.0
|
|
2
2
|
|
package/template/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
-
A React application created with create-react-adam.
|
|
3
|
+
A React application created with [create-react-adam](https://github.com/adamjsturge/create-react-adam).
|
|
4
4
|
|
|
5
5
|
## Getting Started
|
|
6
6
|
|
|
@@ -12,14 +12,6 @@ This will start the development server at `http://localhost:5173`.
|
|
|
12
12
|
|
|
13
13
|
## Available Scripts
|
|
14
14
|
|
|
15
|
-
### `npm run itDoesNotWork`
|
|
16
|
-
|
|
17
|
-
Runs basic troubleshooting checks:
|
|
18
|
-
|
|
19
|
-
- Installs dependencies
|
|
20
|
-
- Checks if Vite dev server is running
|
|
21
|
-
- Provides helpful diagnostic information
|
|
22
|
-
|
|
23
15
|
### `npm run dev`
|
|
24
16
|
|
|
25
17
|
Starts the development server with hot module replacement.
|
|
@@ -32,49 +24,159 @@ Builds the app for production. The build artifacts will be stored in the `dist/`
|
|
|
32
24
|
|
|
33
25
|
Previews the production build locally.
|
|
34
26
|
|
|
35
|
-
### `npm run lint`
|
|
27
|
+
### `npm run lint` / `npm run lint:check`
|
|
28
|
+
|
|
29
|
+
Runs ESLint with auto-fix / check-only (for CI).
|
|
30
|
+
|
|
31
|
+
### `npm run format` / `npm run format:check`
|
|
32
|
+
|
|
33
|
+
Formats all files with Prettier (organizes imports, sorts Tailwind classes) / check-only (for CI).
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
### `npm run setFormatToPrecommitHook`
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
Installs a git pre-commit hook that formats and lints staged files. If this
|
|
38
|
+
project was scaffolded outside an existing repository, the hook was already
|
|
39
|
+
installed for you.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
### `npm run test:e2e` (if E2E was included)
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Runs Playwright E2E tests from the `e2e/` directory. `test:e2e:ui` opens
|
|
44
|
+
Playwright UI mode and `test:e2e:report` generates an Allure report.
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **Tailwind CSS** - Utility-first CSS framework
|
|
50
|
-
- **ESLint & Prettier** - Code quality and formatting
|
|
46
|
+
### `npm run itDoesNotWork`
|
|
47
|
+
|
|
48
|
+
Runs basic troubleshooting checks: installs dependencies, checks whether the
|
|
49
|
+
Vite dev server is running, and prints diagnostic information.
|
|
51
50
|
|
|
52
51
|
## Project Structure
|
|
53
52
|
|
|
54
53
|
```
|
|
55
54
|
src/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
├── pages/ # Route pages (Home, About, NotFound)
|
|
56
|
+
├── components/ # Reusable components
|
|
57
|
+
├── types/ # Shared TypeScript types
|
|
58
|
+
├── utils/ # Utility functions (if included)
|
|
59
|
+
├── App.tsx # Routing, skip link, <main> landmark
|
|
60
|
+
├── main.tsx # Entry point
|
|
61
|
+
└── app.css # Tailwind theme + base styles
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
Routing uses [Wouter](https://github.com/molefrog/wouter). Pages are
|
|
65
|
+
code-split with `React.lazy`, and each page sets its own `document.title`.
|
|
66
|
+
|
|
64
67
|
## Adding New Pages
|
|
65
68
|
|
|
66
69
|
1. Create a new component in `src/pages/`
|
|
67
|
-
2.
|
|
68
|
-
|
|
69
|
-
Example:
|
|
70
|
+
2. Add a lazy import and route in `src/App.tsx`
|
|
70
71
|
|
|
71
72
|
```tsx
|
|
72
|
-
|
|
73
|
+
const NewPage = lazy(() => import("./pages/NewPage"));
|
|
73
74
|
|
|
74
75
|
// In the Switch component:
|
|
75
76
|
<Route path="/new" component={NewPage} />;
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
## Performance, SEO, and Accessibility
|
|
80
|
+
|
|
81
|
+
- **Fonts**: Inter is self-hosted from `public/fonts/` (variable `woff2`,
|
|
82
|
+
`font-display: swap`, preloaded in `index.html`). No third-party requests.
|
|
83
|
+
- **SEO**: `index.html` ships meta description, Open Graph, and Twitter card
|
|
84
|
+
tags — update them as your app takes shape. `public/robots.txt` is included.
|
|
85
|
+
- **Accessibility**: a skip-to-content link, `<main>` landmark, and visible
|
|
86
|
+
`:focus-visible` styles are wired into `App.tsx` / `app.css`.
|
|
87
|
+
|
|
88
|
+
## ESLint: prefer-webp-images (if included)
|
|
89
|
+
|
|
90
|
+
A local rule in `eslint-rules/prefer-webp-images.js` errors on references to
|
|
91
|
+
`.png`, `.jpg`, `.jpeg`, `.gif`, or `.bmp` images in imports, JSX attributes,
|
|
92
|
+
and string literals. Convert images to WebP at
|
|
93
|
+
[tools.sturge.dev/webp](https://tools.sturge.dev/webp).
|
|
94
|
+
|
|
95
|
+
For the rare image that must stay in another format:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
// eslint-disable-next-line adam/prefer-webp-images
|
|
99
|
+
import legacyLogo from "./legacy-logo.png";
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Utilities (if included)
|
|
103
|
+
|
|
104
|
+
### `classNames`
|
|
105
|
+
|
|
106
|
+
Joins class names, skipping falsy values, with clsx-style conditional objects:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import classNames from "./utils/classNames";
|
|
110
|
+
|
|
111
|
+
classNames("btn", isActive && "btn-active", { hidden: !isOpen });
|
|
112
|
+
// => "btn btn-active hidden" depending on the conditions
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `storage` and `useReactPersist`
|
|
116
|
+
|
|
117
|
+
`localStorage` wrapper with JSON serialization and optional expiry, plus a
|
|
118
|
+
`useState`-compatible hook that persists across reloads:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import storage, { useReactPersist } from "./utils/Storage";
|
|
122
|
+
|
|
123
|
+
storage.save("token", "abc123", { secondsTillExpiry: 3600 });
|
|
124
|
+
storage.load("token", null);
|
|
125
|
+
|
|
126
|
+
const [count, setCount] = useReactPersist("counter", 0);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `useUrlState`
|
|
130
|
+
|
|
131
|
+
`useState`-compatible hook that syncs a value with a URL query parameter.
|
|
132
|
+
Supports string and number values (the type follows the default value):
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { useUrlState } from "./utils/useUrlState";
|
|
136
|
+
|
|
137
|
+
const [page, setPage] = useUrlState("page", 1);
|
|
138
|
+
const [tab, setTab] = useUrlState("tab", "overview");
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `useInternetConnected`
|
|
142
|
+
|
|
143
|
+
Runs a callback when the browser is online and every time the connection
|
|
144
|
+
comes back:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { useInternetConnected } from "./utils/Internet";
|
|
148
|
+
|
|
149
|
+
useInternetConnected(() => refetch(), [refetch]);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `safeTimeout`
|
|
153
|
+
|
|
154
|
+
Drop-in `setTimeout` replacement that clamps delays above the 32-bit signed
|
|
155
|
+
integer maximum (~24.8 days) instead of letting them overflow and fire
|
|
156
|
+
immediately:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { safeTimeout } from "./utils/helpers";
|
|
160
|
+
|
|
161
|
+
safeTimeout(() => expireSession(), thirtyDaysInMs);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## CI Workflows
|
|
165
|
+
|
|
166
|
+
- **Code Checks** (`.github/workflows/check.yml`): type checking, formatting,
|
|
167
|
+
linting, and a production build on every push and PR to `main`.
|
|
168
|
+
- **E2E Tests** (`.github/workflows/e2e.yml`, if included): Playwright tests
|
|
169
|
+
with report artifacts.
|
|
170
|
+
- **Lighthouse CI** (`.github/workflows/lighthouse.yml`, if included): audits
|
|
171
|
+
the production build and fails below accessibility ≥ 95, SEO ≥ 95, and
|
|
172
|
+
performance ≥ 85. Budgets live in `lighthouserc.json`.
|
|
173
|
+
|
|
174
|
+
## Dependency Management
|
|
175
|
+
|
|
176
|
+
Dependencies are pinned to exact versions and `.npmrc` sets
|
|
177
|
+
`save-exact=true`, so future installs stay pinned. The repo is
|
|
178
|
+
Renovate-friendly.
|
|
179
|
+
|
|
78
180
|
## Learn More
|
|
79
181
|
|
|
80
182
|
- [React Documentation](https://react.dev)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const NON_WEBP_IMAGE_PATTERN = /\.(png|jpe?g|gif|bmp)$/i;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Flags references to non-WebP raster images (.png, .jpg, .jpeg, .gif, .bmp)
|
|
5
|
+
* in imports, JSX attributes, and string literals. WebP/AVIF/SVG are allowed.
|
|
6
|
+
*/
|
|
7
|
+
function findNonWebpImage(value) {
|
|
8
|
+
if (typeof value !== "string") {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Handle srcSet-style values ("a.png 1x, b.png 2x") and Vite suffixes
|
|
13
|
+
// ("./a.png?url") by checking each path-like token.
|
|
14
|
+
for (const token of value.split(/[\s,]+/)) {
|
|
15
|
+
const path = token.split(/[?#]/)[0];
|
|
16
|
+
if (NON_WEBP_IMAGE_PATTERN.test(path)) {
|
|
17
|
+
return token;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const preferWebpImages = {
|
|
25
|
+
meta: {
|
|
26
|
+
type: "suggestion",
|
|
27
|
+
docs: {
|
|
28
|
+
description:
|
|
29
|
+
"Prefer WebP images over PNG/JPEG/GIF/BMP for smaller file sizes and faster page loads",
|
|
30
|
+
},
|
|
31
|
+
messages: {
|
|
32
|
+
preferWebp:
|
|
33
|
+
'Image "{{path}}" is not WebP. Convert it at https://tools.sturge.dev/webp',
|
|
34
|
+
},
|
|
35
|
+
schema: [],
|
|
36
|
+
},
|
|
37
|
+
create(context) {
|
|
38
|
+
function check(node, value) {
|
|
39
|
+
const match = findNonWebpImage(value);
|
|
40
|
+
if (match) {
|
|
41
|
+
context.report({
|
|
42
|
+
node,
|
|
43
|
+
messageId: "preferWebp",
|
|
44
|
+
data: { path: match },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
Literal(node) {
|
|
51
|
+
check(node, node.value);
|
|
52
|
+
},
|
|
53
|
+
TemplateLiteral(node) {
|
|
54
|
+
if (node.quasis.length === 1) {
|
|
55
|
+
check(node, node.quasis[0].value.cooked);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default {
|
|
63
|
+
rules: {
|
|
64
|
+
"prefer-webp-images": preferWebpImages,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -8,6 +8,7 @@ import reactHooks from "eslint-plugin-react-hooks";
|
|
|
8
8
|
import reactRefresh from "eslint-plugin-react-refresh";
|
|
9
9
|
import unicorn from "eslint-plugin-unicorn";
|
|
10
10
|
import tseslint from "typescript-eslint";
|
|
11
|
+
import adam from "./eslint-rules/prefer-webp-images.js";
|
|
11
12
|
|
|
12
13
|
export default tseslint.config({
|
|
13
14
|
extends: [
|
|
@@ -25,6 +26,7 @@ export default tseslint.config({
|
|
|
25
26
|
"jsx-a11y": jsxA11y,
|
|
26
27
|
"react-hooks": reactHooks,
|
|
27
28
|
"react-refresh": reactRefresh,
|
|
29
|
+
adam,
|
|
28
30
|
},
|
|
29
31
|
languageOptions: {
|
|
30
32
|
parserOptions: {
|
|
@@ -48,6 +50,7 @@ export default tseslint.config({
|
|
|
48
50
|
"warn",
|
|
49
51
|
{ allowConstantExport: true },
|
|
50
52
|
],
|
|
53
|
+
"adam/prefer-webp-images": "error",
|
|
51
54
|
},
|
|
52
55
|
files: ["**/*.{ts,tsx}"],
|
|
53
56
|
ignores: ["dist"],
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import prettier from "eslint-config-prettier";
|
|
3
|
+
import deMorgan from "eslint-plugin-de-morgan";
|
|
4
|
+
import importPlugin from "eslint-plugin-import";
|
|
5
|
+
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
6
|
+
import promisePlugin from "eslint-plugin-promise";
|
|
7
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
8
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
9
|
+
import unicorn from "eslint-plugin-unicorn";
|
|
10
|
+
import tseslint from "typescript-eslint";
|
|
11
|
+
|
|
12
|
+
export default tseslint.config({
|
|
13
|
+
extends: [
|
|
14
|
+
eslint.configs.recommended,
|
|
15
|
+
...tseslint.configs.recommended,
|
|
16
|
+
...tseslint.configs.strict,
|
|
17
|
+
prettier,
|
|
18
|
+
promisePlugin.configs["flat/recommended"],
|
|
19
|
+
importPlugin.flatConfigs.recommended,
|
|
20
|
+
importPlugin.flatConfigs.typescript,
|
|
21
|
+
unicorn.configs["unopinionated"],
|
|
22
|
+
deMorgan.configs.recommended,
|
|
23
|
+
],
|
|
24
|
+
plugins: {
|
|
25
|
+
"jsx-a11y": jsxA11y,
|
|
26
|
+
"react-hooks": reactHooks,
|
|
27
|
+
"react-refresh": reactRefresh,
|
|
28
|
+
},
|
|
29
|
+
languageOptions: {
|
|
30
|
+
parserOptions: {
|
|
31
|
+
ecmaFeatures: {
|
|
32
|
+
jsx: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
rules: {
|
|
37
|
+
"@typescript-eslint/no-unused-vars": [
|
|
38
|
+
"error",
|
|
39
|
+
{
|
|
40
|
+
argsIgnorePattern: "^_",
|
|
41
|
+
varsIgnorePattern: "^_",
|
|
42
|
+
caughtErrorsIgnorePattern: "^_",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
...jsxA11y.flatConfigs.strict.rules,
|
|
46
|
+
...reactHooks.configs.recommended.rules,
|
|
47
|
+
"react-refresh/only-export-components": [
|
|
48
|
+
"warn",
|
|
49
|
+
{ allowConstantExport: true },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
files: ["**/*.{ts,tsx}"],
|
|
53
|
+
ignores: ["dist"],
|
|
54
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Editor directories and files
|
|
16
|
+
.vscode/*
|
|
17
|
+
!.vscode/extensions.json
|
|
18
|
+
.idea
|
|
19
|
+
.DS_Store
|
|
20
|
+
*.suo
|
|
21
|
+
*.ntvs*
|
|
22
|
+
*.njsproj
|
|
23
|
+
*.sln
|
|
24
|
+
*.sw?
|
|
25
|
+
|
|
26
|
+
# E2E tests handled in e2e/.gitignore
|
package/template/index.html
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta
|
|
7
|
+
name="description"
|
|
8
|
+
content="__PROJECT_NAME__ — a React app built with create-react-adam"
|
|
9
|
+
/>
|
|
10
|
+
<meta name="theme-color" content="#2563eb" />
|
|
11
|
+
<meta property="og:title" content="__PROJECT_NAME__" />
|
|
12
|
+
<meta
|
|
13
|
+
property="og:description"
|
|
14
|
+
content="__PROJECT_NAME__ — a React app built with create-react-adam"
|
|
15
|
+
/>
|
|
16
|
+
<meta property="og:type" content="website" />
|
|
17
|
+
<meta name="twitter:card" content="summary" />
|
|
18
|
+
<link
|
|
19
|
+
rel="preload"
|
|
20
|
+
href="/fonts/InterVariable.woff2"
|
|
21
|
+
as="font"
|
|
22
|
+
type="font/woff2"
|
|
23
|
+
crossorigin
|
|
24
|
+
/>
|
|
6
25
|
<title>__PROJECT_NAME__</title>
|
|
7
26
|
</head>
|
|
8
27
|
<body>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ci": {
|
|
3
|
+
"collect": {
|
|
4
|
+
"startServerCommand": "npm run preview",
|
|
5
|
+
"startServerReadyPattern": "Local",
|
|
6
|
+
"url": ["http://localhost:4173/"],
|
|
7
|
+
"numberOfRuns": 3
|
|
8
|
+
},
|
|
9
|
+
"assert": {
|
|
10
|
+
"assertions": {
|
|
11
|
+
"categories:accessibility": ["error", { "minScore": 0.95 }],
|
|
12
|
+
"categories:seo": ["error", { "minScore": 0.95 }],
|
|
13
|
+
"categories:performance": ["error", { "minScore": 0.85 }]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
package/template/npmrc
ADDED
package/template/package.json
CHANGED
|
@@ -19,32 +19,32 @@
|
|
|
19
19
|
"test:e2e:report": "cd e2e && allure generate ./allure-results --clean && allure open"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"react": "19.2.
|
|
23
|
-
"react-dom": "19.2.
|
|
24
|
-
"react-icons": "5.
|
|
25
|
-
"wouter": "3.
|
|
22
|
+
"react": "19.2.7",
|
|
23
|
+
"react-dom": "19.2.7",
|
|
24
|
+
"react-icons": "5.7.0",
|
|
25
|
+
"wouter": "3.10.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@eslint/js": "9.39.
|
|
29
|
-
"@tailwindcss/vite": "4.
|
|
30
|
-
"@types/react": "19.2.
|
|
28
|
+
"@eslint/js": "9.39.4",
|
|
29
|
+
"@tailwindcss/vite": "4.3.2",
|
|
30
|
+
"@types/react": "19.2.17",
|
|
31
31
|
"@types/react-dom": "19.2.3",
|
|
32
|
-
"@vitejs/plugin-react": "5.
|
|
33
|
-
"eslint": "9.39.
|
|
32
|
+
"@vitejs/plugin-react": "5.2.0",
|
|
33
|
+
"eslint": "9.39.4",
|
|
34
34
|
"eslint-config-prettier": "10.1.8",
|
|
35
|
-
"eslint-plugin-de-morgan": "2.
|
|
35
|
+
"eslint-plugin-de-morgan": "2.1.2",
|
|
36
36
|
"eslint-plugin-import": "2.32.0",
|
|
37
37
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
|
38
|
-
"eslint-plugin-promise": "7.
|
|
39
|
-
"eslint-plugin-react-hooks": "7.
|
|
40
|
-
"eslint-plugin-react-refresh": "0.
|
|
38
|
+
"eslint-plugin-promise": "7.3.0",
|
|
39
|
+
"eslint-plugin-react-hooks": "7.1.1",
|
|
40
|
+
"eslint-plugin-react-refresh": "0.5.3",
|
|
41
41
|
"eslint-plugin-unicorn": "62.0.0",
|
|
42
|
-
"prettier": "3.
|
|
42
|
+
"prettier": "3.9.5",
|
|
43
43
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
44
|
-
"prettier-plugin-tailwindcss": "0.
|
|
45
|
-
"tailwindcss": "4.
|
|
44
|
+
"prettier-plugin-tailwindcss": "0.8.0",
|
|
45
|
+
"tailwindcss": "4.3.2",
|
|
46
46
|
"typescript": "5.9.3",
|
|
47
|
-
"typescript-eslint": "8.
|
|
48
|
-
"vite": "7.
|
|
47
|
+
"typescript-eslint": "8.63.0",
|
|
48
|
+
"vite": "7.3.6"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
Binary file
|
package/template/src/App.tsx
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
|
+
import { lazy, Suspense } from "react";
|
|
1
2
|
import { Redirect, Route, Switch } from "wouter";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
const Home = lazy(() => import("./pages/Home"));
|
|
5
|
+
const About = lazy(() => import("./pages/About"));
|
|
6
|
+
const NotFound = lazy(() => import("./pages/NotFound"));
|
|
5
7
|
|
|
6
8
|
const App = () => (
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
<>
|
|
10
|
+
<a
|
|
11
|
+
href="#main-content"
|
|
12
|
+
className="bg-brand-primary text-brand-white sr-only z-50 rounded-lg px-4 py-2 focus:not-sr-only focus:fixed focus:top-4 focus:left-4"
|
|
13
|
+
>
|
|
14
|
+
Skip to main content
|
|
15
|
+
</a>
|
|
16
|
+
<main id="main-content">
|
|
17
|
+
<Suspense fallback={null}>
|
|
18
|
+
<Switch>
|
|
19
|
+
<Route path="/" component={Home} />
|
|
20
|
+
<Route path="/about" component={About} />
|
|
21
|
+
<Route path="/home">
|
|
22
|
+
<Redirect to="/" />
|
|
23
|
+
</Route>
|
|
24
|
+
<Route component={NotFound} />
|
|
25
|
+
</Switch>
|
|
26
|
+
</Suspense>
|
|
27
|
+
</main>
|
|
28
|
+
</>
|
|
15
29
|
);
|
|
16
30
|
|
|
17
31
|
export default App;
|
package/template/src/app.css
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
|
+
@font-face {
|
|
4
|
+
font-family: "Inter";
|
|
5
|
+
font-style: normal;
|
|
6
|
+
font-weight: 100 900;
|
|
7
|
+
font-display: swap;
|
|
8
|
+
src: url("/fonts/InterVariable.woff2") format("woff2");
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
@theme {
|
|
4
|
-
--font-sans:
|
|
12
|
+
--font-sans:
|
|
13
|
+
"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
5
14
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
--color-brand-
|
|
15
|
+
/* Brand colors are chosen to meet WCAG AA contrast (>= 4.5:1) with white
|
|
16
|
+
text; keep that in mind when changing them. */
|
|
17
|
+
--color-brand-primary: #2563eb;
|
|
18
|
+
--color-brand-primaryHover: #1d4ed8;
|
|
19
|
+
--color-brand-primaryActive: #1e40af;
|
|
9
20
|
|
|
10
|
-
--color-brand-secondary: #
|
|
11
|
-
--color-brand-secondaryHover: #
|
|
12
|
-
--color-brand-secondaryActive: #
|
|
21
|
+
--color-brand-secondary: #7c3aed;
|
|
22
|
+
--color-brand-secondaryHover: #6d28d9;
|
|
23
|
+
--color-brand-secondaryActive: #5b21b6;
|
|
13
24
|
|
|
14
|
-
--color-brand-success: #
|
|
25
|
+
--color-brand-success: #15803d;
|
|
15
26
|
--color-brand-warning: #f59e0b;
|
|
16
|
-
--color-brand-danger: #
|
|
27
|
+
--color-brand-danger: #dc2626;
|
|
17
28
|
|
|
18
29
|
--color-brand-black: #0f172a;
|
|
19
30
|
--color-brand-white: #ffffff;
|
|
@@ -27,3 +38,10 @@
|
|
|
27
38
|
|
|
28
39
|
--color-brand-disabled: #94a3b8;
|
|
29
40
|
}
|
|
41
|
+
|
|
42
|
+
@layer base {
|
|
43
|
+
:focus-visible {
|
|
44
|
+
outline: 2px solid var(--color-brand-primary);
|
|
45
|
+
outline-offset: 2px;
|
|
46
|
+
}
|
|
47
|
+
}
|