init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
node_modules
/.cache
/build
/public/build
.env

View File

@@ -0,0 +1,3 @@
{
"startCommand": "npm run dev"
}

View File

@@ -0,0 +1,42 @@
# Material UI - Remix example in TypeScript
## How to use
Download the example [or clone the repo](https://github.com/mui/material-ui):
<!-- #target-branch-reference -->
```bash
curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/material-ui-remix-ts
cd material-ui-remix-ts
```
Install it and run:
```bash
npm install
npm run dev
```
or:
<!-- #target-branch-reference -->
[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/material-ui-remix-ts)
[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/github/mui/material-ui/tree/master/examples/material-ui-remix-ts)
## The idea behind the example
<!-- #host-reference -->
The project uses [Remix](https://remix.run/), which is a full-stack web framework.
It includes `@mui/material` and its peer dependencies, including [Emotion](https://emotion.sh/docs/introduction), the default style engine in Material UI.
If you prefer, you can [use styled-components instead](https://mui.com/material-ui/integrations/interoperability/#styled-components).
## What's next?
<!-- #host-reference -->
You now have a working example project.
You can head back to the documentation and continue by browsing the [templates](https://mui.com/material-ui/getting-started/templates/) section.

View File

@@ -0,0 +1,54 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { RemixBrowser } from '@remix-run/react';
import { CacheProvider } from '@emotion/react';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import ClientStyleContext from './src/ClientStyleContext';
import createEmotionCache from './src/createEmotionCache';
import theme from './src/theme';
interface ClientCacheProviderProps {
children: React.ReactNode;
}
function ClientCacheProvider({ children }: ClientCacheProviderProps) {
const [cache, setCache] = React.useState(createEmotionCache());
const clientStyleContextValue = React.useMemo(
() => ({
reset() {
setCache(createEmotionCache());
},
}),
[],
);
return (
<ClientStyleContext.Provider value={clientStyleContextValue}>
<CacheProvider value={cache}>{children}</CacheProvider>
</ClientStyleContext.Provider>
);
}
const hydrate = () => {
React.startTransition(() => {
ReactDOM.hydrateRoot(
document,
<ClientCacheProvider>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<RemixBrowser />
</ThemeProvider>
</ClientCacheProvider>,
);
});
};
if (window.requestIdleCallback) {
window.requestIdleCallback(hydrate);
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
setTimeout(hydrate, 1);
}

View File

@@ -0,0 +1,59 @@
import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';
import { RemixServer } from '@remix-run/react';
import type { EntryContext } from '@remix-run/node';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import theme from './src/theme';
import createEmotionCache from './src/createEmotionCache';
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
function MuiRemixServer() {
return (
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<RemixServer context={remixContext} url={request.url} />
</ThemeProvider>
</CacheProvider>
);
}
// Render the component to a string.
const html = ReactDOMServer.renderToString(<MuiRemixServer />);
// Grab the CSS from emotion
const { styles } = extractCriticalToChunks(html);
let stylesHTML = '';
styles.forEach(({ key, ids, css }) => {
const emotionKey = `${key} ${ids.join(' ')}`;
const newStyleTag = `<style data-emotion="${emotionKey}">${css}</style>`;
stylesHTML = `${stylesHTML}${newStyleTag}`;
});
// Add the Emotion style tags after the insertion point meta tag
const markup = html.replace(
/<meta(\s)*name="emotion-insertion-point"(\s)*content="emotion-insertion-point"(\s)*\/>/,
`<meta name="emotion-insertion-point" content="emotion-insertion-point"/>${stylesHTML}`,
);
responseHeaders.set('Content-Type', 'text/html');
return new Response(`<!DOCTYPE html>${markup}`, {
status: responseStatusCode,
headers: responseHeaders,
});
}

View File

@@ -0,0 +1,128 @@
import * as React from 'react';
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useRouteError,
isRouteErrorResponse,
} from '@remix-run/react';
import { withEmotionCache } from '@emotion/react';
import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/material';
import theme from './src/theme';
import ClientStyleContext from './src/ClientStyleContext';
import Layout from './src/Layout';
interface DocumentProps {
children: React.ReactNode;
title?: string;
}
const Document = withEmotionCache(({ children, title }: DocumentProps, emotionCache) => {
const clientStyleData = React.useContext(ClientStyleContext);
// Only executed on client
useEnhancedEffect(() => {
// re-link sheet container
emotionCache.sheet.container = document.head;
// re-inject tags
const tags = emotionCache.sheet.tags;
emotionCache.sheet.flush();
tags.forEach((tag) => {
// eslint-disable-next-line no-underscore-dangle
(emotionCache.sheet as any)._insertTag(tag);
});
// reset cache to reapply global styles
clientStyleData.reset();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content={theme.palette.primary.main} />
{title ? <title>{title}</title> : null}
<Meta />
<Links />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
/>
<meta name="emotion-insertion-point" content="emotion-insertion-point" />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
});
// https://remix.run/docs/en/main/route/component
// https://remix.run/docs/en/main/file-conventions/routes
export default function App() {
return (
<Document>
<Layout>
<Outlet />
</Layout>
</Document>
);
}
// https://remix.run/docs/en/main/route/error-boundary
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
let message;
switch (error.status) {
case 401:
message = <p>Oops! Looks like you tried to visit a page that you do not have access to.</p>;
break;
case 404:
message = <p>Oops! Looks like you tried to visit a page that does not exist.</p>;
break;
default:
throw new Error(error.data || error.statusText);
}
return (
<Document title={`${error.status} ${error.statusText}`}>
<Layout>
<h1>
{error.status}: {error.statusText}
</h1>
{message}
</Layout>
</Document>
);
}
if (error instanceof Error) {
console.error(error);
return (
<Document title="Error!">
<Layout>
<div>
<h1>There was an error</h1>
<p>{error.message}</p>
<hr />
<p>Hey, developer, you should replace this with what you want your users to see.</p>
</div>
</Layout>
</Document>
);
}
return <h1>Unknown Error</h1>;
}

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import type { MetaFunction } from '@remix-run/node';
import { Link as RemixLink } from '@remix-run/react';
import Typography from '@mui/material/Typography';
import Link from '@mui/material/Link';
// https://remix.run/docs/en/main/route/meta
export const meta: MetaFunction = () => [
{ title: 'Remix Starter' },
{ name: 'description', content: 'Welcome to remix!' },
];
// https://remix.run/docs/en/main/file-conventions/routes#basic-routes
export default function Index() {
return (
<React.Fragment>
<Typography variant="h4" component="h1" sx={{ mb: 2 }}>
Material UI Remix in TypeScript example
</Typography>
<Link to="/about" color="secondary" component={RemixLink}>
Go to the about page
</Link>
</React.Fragment>
);
}

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import { Link } from '@remix-run/react';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
export default function About() {
return (
<React.Fragment>
<Typography variant="h4" component="h1" sx={{ mb: 2 }}>
Material UI Remix in TypeScript example
</Typography>
<Button variant="contained" component={Link} to="/">
Go to the main page
</Button>
</React.Fragment>
);
}

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
export interface ClientStyleContextData {
reset: () => void;
}
export default React.createContext<ClientStyleContextData>({
reset: () => {},
});

View File

@@ -0,0 +1,21 @@
import * as React from 'react';
import Typography from '@mui/material/Typography';
import Link from '@mui/material/Link';
export default function Copyright() {
return (
<Typography
variant="body2"
align="center"
sx={{
color: 'text.secondary',
}}
>
{'Copyright © '}
<Link color="inherit" href="https://mui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}.
</Typography>
);
}

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import Container from '@mui/material/Container';
import Box from '@mui/material/Box';
import ProTip from './ProTip';
import Copyright from './Copyright';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<Container maxWidth="sm">
<Box sx={{ my: 4 }}>
{children}
<ProTip />
<Copyright />
</Box>
</Container>
);
}

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import Link from '@mui/material/Link';
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
import Typography from '@mui/material/Typography';
function LightBulbIcon(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z" />
</SvgIcon>
);
}
export default function ProTip() {
return (
<Typography sx={{ mt: 6, mb: 3, color: 'text.secondary' }}>
<LightBulbIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
{'Pro tip: See more '}
<Link href="https://mui.com/material-ui/getting-started/templates/">templates</Link>
{' in the Material UI documentation.'}
</Typography>
);
}

View File

@@ -0,0 +1,5 @@
import createCache from '@emotion/cache';
export default function createEmotionCache() {
return createCache({ key: 'css' });
}

View File

@@ -0,0 +1,20 @@
import { createTheme } from '@mui/material/styles';
import { red } from '@mui/material/colors';
// Create a theme instance.
const theme = createTheme({
cssVariables: true,
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
},
});
export default theme;

View File

@@ -0,0 +1,35 @@
{
"name": "material-ui-remix-ts",
"version": "7.0.0",
"private": true,
"scripts": {
"build": "remix build",
"dev": "remix dev --manual",
"start": "remix-serve ./build/index.js",
"typecheck": "tsc"
},
"dependencies": {
"@emotion/cache": "latest",
"@emotion/react": "latest",
"@emotion/server": "latest",
"@emotion/styled": "latest",
"@mui/material": "latest",
"@remix-run/css-bundle": "latest",
"@remix-run/node": "latest",
"@remix-run/react": "latest",
"@remix-run/serve": "latest",
"@remix-run/server-runtime": "latest",
"react": "latest",
"react-dom": "latest"
},
"devDependencies": {
"@remix-run/dev": "latest",
"@types/react": "latest",
"@types/react-dom": "latest",
"typescript": "latest"
},
"engines": {
"node": ">=14.0.0"
},
"sideEffects": false
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,11 @@
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
appDirectory: 'app',
browserBuildDirectory: 'public/build',
publicPath: '/build/',
serverBuildDirectory: 'build',
devServerPort: 8002,
// TODO: when mui has esm support, remove this (default is esm)
// check it https://github.com/mui/material-ui/issues/30671
serverModuleFormat: 'cjs',
};

View File

@@ -0,0 +1,2 @@
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node/globals" />

View File

@@ -0,0 +1,21 @@
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
"target": "ES2022",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"~/*": ["./app/*"]
},
// Remix takes care of building everything in `remix build`.
"noEmit": true
}
}