Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ dist-ssr

.eslintcache

# React Router generated files
.react-router/
build/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
Expand Down
4 changes: 2 additions & 2 deletions .lighthouserc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const ci = {
collect: {
startServerCommand: 'npm run preview:prod',
url: 'http://localhost:5173/',
startServerCommand: 'npm run start',
url: 'http://localhost:3000/',
},
assert: {
// assert options here
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,22 @@ It is intended to be helping, not dictating. If you don't know exactly what to u

→ Good, you're already set up!

Create a production build with `npm run build`, but be aware that a server runtime is needed as this project uses server-side rendering.
## Production

To test the production build locally:

1. Build for production:

```
npm run build
```

2. Start the production server:
```
npm start
```

The production build will be created in the `build/` directory. Note that a server runtime is needed as this project uses server-side rendering.

## How does this work

Expand All @@ -51,7 +66,7 @@ Where documenting inline is not possible or impractical, we have added explanati

- **CSS and SCSS:** Based on Carbon, this project makes extensive use of SCSS. To improve the loading experience and avoid layout shifts and flash of unstyled content, we decided to load the SCSS from the `index.html` file. If you have a case where you load a lot of CSS under very dynamic conditions, you might decide to use an `import` statement inside one of your React components. However, be aware that this should stay as an exception, as the styles will be delayed after the component has been loaded.

- **Server-side rendering:** Server-side rendering is a critical component for most applications to improve their performance. This means here that **the project is separated in two parts - client-side and server-side**.
- **Server-side rendering:** This project uses React Router v7 in Framework mode, which provides built-in server-side rendering (SSR) with automatic code splitting and optimized hydration. The framework handles both client and server rendering seamlessly.

- **Quality and productivity helpers:** This project contains quite a few helpers to help with consistency, productivity, and speed. For example, it has templates for unit and end-to-end testing. It also contains linters so your team doesn't have to lose time on code formatting.
With time, we plan to add more helpers to help you monitor your accessibility and front-end performance.
Expand All @@ -62,11 +77,11 @@ This project comes with a pre-configured testing setup using React Testing Libra

#### Setup

- The test configuration is located in `vite.config.js`, which sets up Vitest with globals, a JSDOM environment, and points to the setup file `src/test/setup.js`.
- The test configuration is located in `vite.config.js`, which sets up Vitest with globals, a JSDOM environment, and points to the setup file `src/test/setup.js`. The React Router plugin is conditionally disabled during tests to prevent conflicts.

- This file handles mocking network requests and manages server startup and teardown. It also handles mocking browser features missing in JSDOM.
- The setup file handles mocking network requests and manages MSW server startup and teardown. It also handles mocking browser features missing in JSDOM.

- The `src/test/server.js` wraps the server with msw's setupServer function and injects networking utils from `src/test/networking.js` that track each outgoing network request and help debugging unit tests.
- The `src/test/server.js` configures MSW (Mock Service Worker) handlers for API endpoints, allowing you to mock external API calls in your tests.

#### Writing Tests

Expand Down
14 changes: 14 additions & 0 deletions app/entry.client.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { HydratedRouter } from 'react-router/dom';

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter />
</StrictMode>,
);
});

// Made with Bob
29 changes: 29 additions & 0 deletions app/entry.client.jsx.backup
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright IBM Corp. 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { HydratedRouter } from 'react-router';

/**
* Client entry point for React Router Framework mode.
*
* This file handles client-side hydration of the server-rendered HTML.
* It's called by the browser to make the static HTML interactive.
*
* @see https://reactrouter.com/start/framework/entry-client
*/
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter />
</StrictMode>,
);
});

// Made with Bob
121 changes: 121 additions & 0 deletions app/navigation.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Copyright IBM Corp. 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { MagicWand, LogoGithub } from '@carbon/icons-react';

/**
* Navigation configuration for Carbon UI Shell components.
* This config is separate from routing to allow independent management
* of navigation structure and route definitions.
*/

/**
* Header navigation items
* These appear in the top navigation bar
*/
export const headerNavigation = [
{
path: '/dashboard',
label: 'Dashboard',
},
{
path: '/link-1',
label: 'Link 1',
},
{
path: '/link-2',
label: 'Link 2',
},
{
path: '/link-3',
label: 'Link 3',
},
{
path: '/link-4',
label: 'Link 4',
subMenu: [
{
path: '/link-4/sub-link-1',
label: 'Sub-link 1',
},
{
path: '/link-4/sub-link-2',
label: 'Sub-link 2',
},
{
path: '/link-4/sub-link-3',
label: 'Sub-link 3',
},
],
},
];

/**
* Side navigation items
* These appear in the side panel navigation
*/
export const sideNavigation = [
{
path: '/getting-started',
label: 'Getting Started',
icon: MagicWand,
href: 'https://github.com/carbon-design-system/carbon-react-router-starter?tab=readme-ov-file#get-started',
subMenu: [
{
path: '/getting-started/how',
label: 'How does this work',
href: 'https://github.com/carbon-design-system/carbon-react-router-starter?tab=readme-ov-file#how-does-this-work',
},
{
path: '/getting-started/up-to-date',
label: 'Keeping this up to date',
href: 'https://github.com/carbon-design-system/carbon-react-router-starter?tab=readme-ov-file#keeping-this-up-to-date',
},
{
path: '/getting-started/report',
label: 'Report problems',
href: 'https://github.com/carbon-design-system/carbon-react-router-starter?tab=readme-ov-file#report-problems',
},
],
},
{
path: '/github',
label: 'GitHub',
icon: LogoGithub,
href: 'https://github.com/carbon-design-system/carbon-react-router-starter',
},
];

/**
* Helper function to get all navigation items (header + side)
*/
export const getAllNavigationItems = () => {
return [...headerNavigation, ...sideNavigation];
};

/**
* Helper function to find a navigation item by path
*/
export const findNavigationItem = (path) => {
const allItems = getAllNavigationItems();

for (const item of allItems) {
if (item.path === path) {
return item;
}
if (item.subMenu) {
const subItem = item.subMenu.find((sub) => sub.path === path);
if (subItem) {
return subItem;
}
}
}

return null;
};

// Made with Bob
145 changes: 145 additions & 0 deletions app/root.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Copyright IBM Corp. 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
isRouteErrorResponse,
} from 'react-router';
import { GlobalTheme, Theme } from '@carbon/react';
import { ThemeProvider, useThemeContext } from '../src/context/ThemeContext';

// Import global styles
import '../src/index.scss';

/**
* Root layout component that wraps the entire application.
* Provides document structure, theme management, and error boundaries.
*
* @see https://reactrouter.com/start/framework/routing#root-layout
*/
export function Layout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

/**
* Theme wrapper component that applies Carbon Design System themes.
* Uses the ThemeContext to determine which theme to apply.
* Waits for theme to be ready before rendering to prevent flash.
*/
function ThemedApp() {
const { themeMenu, ready } = useThemeContext();

// Don't render until theme is ready to prevent flash
if (!ready) {
return null;
}

return (
<GlobalTheme theme={themeMenu}>
<Theme theme={themeMenu}>
<Outlet />
</Theme>
</GlobalTheme>
);
}

/**
* Root component that provides theme context to the entire application.
* This is the entry point for all routes.
*
* @see https://reactrouter.com/start/framework/routing#root-route
*/
export default function Root() {
return (
<ThemeProvider>
<ThemedApp />
</ThemeProvider>
);
}

/**
* Error boundary for the root route.
* Handles both route errors and unexpected errors.
*
* @see https://reactrouter.com/start/framework/route-module#errorboundary
*/
export function ErrorBoundary({ error }) {
let errorMessage = 'An unexpected error occurred';
let errorDetails = null;

if (isRouteErrorResponse(error)) {
// Handle route-specific errors (404, etc.)
errorMessage = `${error.status} ${error.statusText}`;
errorDetails = error.data;
} else if (error instanceof Error) {
// Handle JavaScript errors
errorMessage = error.message;
errorDetails = error.stack;
}

return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Error - Carbon React Router</title>
<Meta />
<Links />
</head>
<body>
<div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
<h1 style={{ color: '#da1e28' }}>Application Error</h1>
<p style={{ fontSize: '1.25rem', marginTop: '1rem' }}>
{errorMessage}
</p>
{errorDetails && (
<details style={{ marginTop: '2rem' }}>
<summary style={{ cursor: 'pointer', fontWeight: 'bold' }}>
Error Details
</summary>
<pre
style={{
marginTop: '1rem',
padding: '1rem',
backgroundColor: '#f4f4f4',
borderRadius: '4px',
overflow: 'auto',
}}
>
{typeof errorDetails === 'string'
? errorDetails
: JSON.stringify(errorDetails, null, 2)}
</pre>
</details>
)}
</div>
<Scripts />
</body>
</html>
);
}

// Made with Bob
Loading
Loading