├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── setup
└── vitest.ts
├── src
├── components
│ ├── App
│ │ ├── App.css
│ │ ├── App.test.tsx
│ │ └── App.tsx
│ └── Button.css
│ │ ├── Button.module.css
│ │ ├── Button.test.tsx
│ │ └── Button.tsx
├── index.css
├── main.tsx
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2022: true },
4 | extends: [
5 | "eslint:recommended",
6 | "plugin:@typescript-eslint/strict-type-checked",
7 | "plugin:react-hooks/recommended",
8 | ],
9 | parserOptions: {
10 | project: true,
11 | tsconfigRootDir: __dirname,
12 | },
13 | ignorePatterns: ["dist", ".eslintrc.cjs"],
14 | parser: "@typescript-eslint/parser",
15 | plugins: ["react-refresh"],
16 | };
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node artifact files
2 | node_modules/
3 | dist/
4 | coverage/
5 |
6 | # Log files
7 | *.log
8 |
9 | # JetBrains IDE
10 | .idea/
11 |
12 | # Visual Studio Code
13 | .vscode/
14 |
15 | # Generated by MacOS
16 | .DS_Store
17 |
18 | # Generated by Windows
19 | Thumbs.db
20 |
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Boilerplate: React single page application
2 |
3 | An enterprise SPA Web project template
4 | based on modern approaches and the latest version of React library.
5 |
6 | ## Features:
7 | - Quickly setup and configure your application.
8 | - [React](https://react.dev/) library lets you build user interfaces out of individual pieces called components.
9 | - **Typescript**: up-to-date version.
10 | - [Vite](https://vitejs.dev/) provides a modern development environment.
11 | - **Linter**: eslint + prettier = ❤️
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Valletta Software
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-react-typescript-starter",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview",
10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
11 | "test": "vitest",
12 | "coverage": "vitest run --coverage",
13 | "update": "npx npm-check-updates -i"
14 | },
15 | "dependencies": {
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0"
18 | },
19 | "devDependencies": {
20 | "@testing-library/jest-dom": "^6.2.0",
21 | "@testing-library/react": "^14.1.2",
22 | "@testing-library/user-event": "^14.5.2",
23 | "@types/react": "^18.2.48",
24 | "@types/react-dom": "^18.2.18",
25 | "@typescript-eslint/eslint-plugin": "^6.19.0",
26 | "@typescript-eslint/parser": "^6.19.0",
27 | "@vitejs/plugin-react": "^4.2.1",
28 | "@vitest/coverage-v8": "^1.2.1",
29 | "eslint": "^8.56.0",
30 | "eslint-plugin-react-hooks": "^4.6.0",
31 | "eslint-plugin-react-refresh": "^0.4.5",
32 | "jsdom": "^24.0.0",
33 | "prettier": "^3.2.4",
34 | "typescript": "^5.3.3",
35 | "vite": "^5.0.12",
36 | "vitest": "^1.2.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/setup/vitest.ts:
--------------------------------------------------------------------------------
1 | import { afterEach } from "vitest";
2 | import { cleanup } from "@testing-library/react";
3 | import "@testing-library/jest-dom/vitest";
4 |
5 | afterEach(() => {
6 | cleanup();
7 | });
8 |
--------------------------------------------------------------------------------
/src/components/App/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .App {
9 | display: flex;
10 | flex-direction: column;
11 | place-items: center;
12 | gap: 1rem;
13 | }
14 |
15 | .App__header {
16 | font-size: 3rem;
17 | font-weight: 700;
18 | margin: 0;
19 | }
20 |
21 | .App__count {
22 | font-size: 2rem;
23 | margin: 0;
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/App/App.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@testing-library/react";
2 | import user from "@testing-library/user-event";
3 | import { expect, test, describe } from "vitest";
4 | import { App } from "./App";
5 |
6 | describe("Render the app correctly", () => {
7 | test("should render the title", async () => {
8 | render();
9 |
10 | const header = await screen.findByText("Valletta Software");
11 |
12 | expect(header).toBeInTheDocument();
13 | });
14 |
15 | test("should have a functioning button that increments the counter", async () => {
16 | render();
17 |
18 | const button = await screen.findByRole("button");
19 | const counter = await screen.findByText("0");
20 |
21 | expect(counter.innerHTML).toBe("0");
22 |
23 | await user.click(button);
24 | await user.click(button);
25 |
26 | expect(counter.innerHTML).toBe("2");
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/components/App/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Button } from "../Button.css/Button.tsx";
3 | import "./App.css";
4 |
5 | export function App() {
6 | const [count, setCount] = useState(0);
7 |
8 | const handleClick = () => {
9 | setCount(count + 1);
10 | };
11 |
12 | return (
13 |
14 |
React Boilerplate
15 |
by Valletta Software Development
16 |
{count}
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Button.css/Button.module.css:
--------------------------------------------------------------------------------
1 | .button {
2 | background-color: var(--Cyan-700);
3 | border: 0.25rem solid var(--Cyan-300);
4 | transition: border 0.2s ease-in-out;
5 | padding: 1rem 2rem;
6 | border-radius: 0.5rem;
7 | width: max-content;
8 | }
9 |
10 | .button:hover {
11 | border: 0.25rem solid var(--Cyan-500);
12 | }
13 |
14 | .text {
15 | color: var(--Cyan-300);
16 | font-size: 1.5rem;
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Button.css/Button.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@testing-library/react";
2 | import user from "@testing-library/user-event";
3 | import { expect, test, describe, vi } from "vitest";
4 | import { Button } from "./Button";
5 |
6 | describe("Render the button correctly", () => {
7 | test("should render the button with the correct text", async () => {
8 | render();
9 |
10 | const button = await screen.findByRole("button");
11 |
12 | expect(button.firstChild).toHaveTextContent("Click me!");
13 | });
14 |
15 | test("should call the onClick function when clicked", async () => {
16 | const onClick = vi.fn();
17 | render();
18 |
19 | const button = await screen.findByRole("button");
20 |
21 | await user.click(button);
22 |
23 | expect(onClick).toHaveBeenCalledOnce();
24 | });
25 |
26 | test("should have a type of button by default", async () => {
27 | render();
28 |
29 | const button = await screen.findByRole("button");
30 |
31 | expect(button).toHaveAttribute("type", "button");
32 | });
33 |
34 | test("should have a type of submit when specified", async () => {
35 | render();
36 |
37 | const button = await screen.findByRole("button");
38 |
39 | expect(button).toHaveAttribute("type", "submit");
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/Button.css/Button.tsx:
--------------------------------------------------------------------------------
1 | import { MouseEvent } from "react";
2 | import classes from "./Button.module.css";
3 |
4 | interface ButtonProps {
5 | text: string;
6 | onClick?: (event: MouseEvent) => void;
7 | type?: "button" | "submit" | "reset";
8 | }
9 |
10 | export function Button({ text, onClick, type = "button" }: ButtonProps) {
11 | return (
12 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: system-ui, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | --Rose-800: #7d314b;
7 | --Rose-800: color(display-p3 0.4235 0.2235 0.2941);
8 |
9 | --Rose-200: #ffd9e4;
10 | --Rose-200: color(display-p3 0.9765 0.8627 0.8941);
11 |
12 | --Cyan-700: #007b67;
13 | --Cyan-700: color(display-p3 0.0902 0.4667 0.4078);
14 |
15 | --Cyan-500: #00c1a2;
16 | --Cyan-500: color(display-p3 0.2235 0.7294 0.6431);
17 |
18 | --Cyan-300: #77dfce;
19 | --Cyan-300: color(display-p3 0.6353 0.8588 0.8078);
20 | }
21 |
22 | body {
23 | margin: 0;
24 | padding: 0;
25 | box-sizing: border-box;
26 | display: flex;
27 | place-items: center;
28 | min-width: 320px;
29 | min-height: 100vh;
30 | color: var(--Rose-800);
31 | background: var(--Rose-200);
32 | }
33 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import { App } from "./components/App/App.tsx";
4 | import "./index.css";
5 |
6 | createRoot(document.getElementById("root") as Element).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2022", "DOM"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 | },
23 | "include": ["src", "setup/vitest.ts", "vite.config.ts", "vitest.config.ts"],
24 | "references": [{ "path": "./tsconfig.node.json" }],
25 | }
26 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | });
8 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, mergeConfig } from "vitest/config";
2 | import viteConfig from "./vite.config";
3 |
4 | export default mergeConfig(
5 | viteConfig,
6 | defineConfig({
7 | test: {
8 | environment: "jsdom",
9 | setupFiles: "./setup/vitest.ts",
10 | include: ["**/*.test.tsx"],
11 | },
12 | })
13 | );
14 |
--------------------------------------------------------------------------------