diff --git a/antd-style/.eslintrc.cjs b/antd-style/.eslintrc.cjs new file mode 100644 index 00000000..4f6f59ee --- /dev/null +++ b/antd-style/.eslintrc.cjs @@ -0,0 +1,84 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + ignorePatterns: ["!**/.server", "!**/.client"], + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + "import/resolver": { + typescript: {}, + }, + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.cjs"], + env: { + node: true, + }, + }, + ], +}; diff --git a/antd-style/.gitignore b/antd-style/.gitignore new file mode 100644 index 00000000..80ec311f --- /dev/null +++ b/antd-style/.gitignore @@ -0,0 +1,5 @@ +node_modules + +/.cache +/build +.env diff --git a/antd-style/README.md b/antd-style/README.md new file mode 100644 index 00000000..284d2142 --- /dev/null +++ b/antd-style/README.md @@ -0,0 +1,20 @@ +# Ant Design Example (antd-style) + +Using [antd-style](https://github.com/ant-design/antd-style) to extract styles and inject them into the HTML can avoid page flicker. + +## Preview + +Open this example on [CodeSandbox](https://codesandbox.com): + +[![Open in CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/remix-run/examples/tree/main/antd-style) + +## Example + +This example shows how to use Ant Design with Remix. + +Check the content of [./app/routes/_index.tsx](./app/routes/_index.tsx) and use `` to implement local theme changes. + +## Related Links + +[Ant Design](https://ant.design) +[antd-style](https://github.com/ant-design/antd-style) diff --git a/antd-style/app/entry.server.tsx b/antd-style/app/entry.server.tsx new file mode 100644 index 00000000..5e61f442 --- /dev/null +++ b/antd-style/app/entry.server.tsx @@ -0,0 +1,37 @@ +import { extractStaticStyle, StyleProvider } from "antd-style"; +import { createCache } from '@ant-design/cssinjs'; +import type { EntryContext } from "@remix-run/node"; +import { RemixServer } from "@remix-run/react"; +import { renderToString } from "react-dom/server"; + + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + + const antdCache = createCache(); + + let html = renderToString( + + + + ) + + const styles = extractStaticStyle(html, { antdCache }) + .map((item) => item.tag); + + html = html.replace("__ANTD__STYLES__", styles.join("\n")); + + responseHeaders.set("Content-Type", "text/html"); + + return new Response(`${html}`, { + status: responseStatusCode, + headers: responseHeaders, + }); +} diff --git a/antd-style/app/root.tsx b/antd-style/app/root.tsx new file mode 100644 index 00000000..792cec99 --- /dev/null +++ b/antd-style/app/root.tsx @@ -0,0 +1,30 @@ +import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react"; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + {typeof document === "undefined" ? "__ANTD__STYLES__" : null} + + + + + {children} + + + + + ); +} + +export default function App() { + return ; +} diff --git a/antd-style/app/routes/_index.tsx b/antd-style/app/routes/_index.tsx new file mode 100644 index 00000000..410a26a5 --- /dev/null +++ b/antd-style/app/routes/_index.tsx @@ -0,0 +1,64 @@ +import { Link } from '@remix-run/react'; +import { Button, Flex, ConfigProvider } from 'antd'; +import type { ThemeConfig } from "antd"; + +const links = [ + { + href: "https://remix.run/docs", + text: "Remix Docs", + }, + { + href: "https://github.com/ant-design/ant-design", + text: "Ant Design Repository" + }, + { + href: "https://ant.design/", + text: "Ant Design Docs", + }, + { + href: "https://github.com/ant-design/antd-style", + text: "antd-style Repository" + }, +]; + +const theme: ThemeConfig = { + token: { + fontSize: 14, + colorPrimary: "#10b981", + }, + components: { + Button: { + fontWeight: 400, + }, + }, +}; + +const Demo = () => ( + + + + +) + +export default function Index() { + return ( + + + + + + + + + { + links.map(({ text, ...rest }) => ( + + )) + } + /About + + + ); +} diff --git a/antd-style/app/routes/about.tsx b/antd-style/app/routes/about.tsx new file mode 100644 index 00000000..4cf1e454 --- /dev/null +++ b/antd-style/app/routes/about.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Alert } from 'antd'; +import { createStyles } from 'antd-style'; + +const useStyle = createStyles(({ token, css }) => ({ + title: css({ + display: 'inline-block', + fontSize: token.fontSizeLG * 2, + background: `linear-gradient(to right, ${token.colorError}, ${token.colorWarning})`, + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + }) +})) + +const Title = (props: React.HTMLAttributes) => ( +
+ ANTD-STYLE +
+) + +const App: React.FC = () => { + const { styles } = useStyle(); + + return ( + } + type="info" + description="css-in-js library with antd v5 token system" + /> + ) +} + +export default App; \ No newline at end of file diff --git a/antd-style/package.json b/antd-style/package.json new file mode 100644 index 00000000..a41c4f3b --- /dev/null +++ b/antd-style/package.json @@ -0,0 +1,42 @@ +{ + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "remix vite:build", + "dev": "remix vite:dev", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", + "start": "remix-serve ./build/server/index.js", + "typecheck": "tsc" + }, + "dependencies": { + "@ant-design/cssinjs": "^1.22.0", + "@remix-run/node": "^2.9.2", + "@remix-run/react": "^2.9.2", + "@remix-run/serve": "^2.9.2", + "antd": "^5.22.1", + "antd-style": "^3.7.1", + "isbot": "^4.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@remix-run/dev": "^2.9.2", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "eslint": "^8.38.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "typescript": "^5.1.6", + "vite": "^5.1.0", + "vite-tsconfig-paths": "^4.2.1" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/antd-style/public/favicon.ico b/antd-style/public/favicon.ico new file mode 100644 index 00000000..8830cf68 Binary files /dev/null and b/antd-style/public/favicon.ico differ diff --git a/antd-style/sandbox.config.json b/antd-style/sandbox.config.json new file mode 100644 index 00000000..f92e0250 --- /dev/null +++ b/antd-style/sandbox.config.json @@ -0,0 +1,7 @@ +{ + "hardReloadOnChange": true, + "template": "remix", + "container": { + "port": 3000 + } +} diff --git a/antd-style/tsconfig.json b/antd-style/tsconfig.json new file mode 100644 index 00000000..9d87dd37 --- /dev/null +++ b/antd-style/tsconfig.json @@ -0,0 +1,32 @@ +{ + "include": [ + "**/*.ts", + "**/*.tsx", + "**/.server/**/*.ts", + "**/.server/**/*.tsx", + "**/.client/**/*.ts", + "**/.client/**/*.tsx" + ], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["@remix-run/node", "vite/client"], + "isolatedModules": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "target": "ES2022", + "strict": true, + "allowJs": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + + // Vite takes care of building everything, not tsc. + "noEmit": true + } +} diff --git a/antd-style/vite.config.ts b/antd-style/vite.config.ts new file mode 100644 index 00000000..54066fb7 --- /dev/null +++ b/antd-style/vite.config.ts @@ -0,0 +1,16 @@ +import { vitePlugin as remix } from "@remix-run/dev"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig({ + plugins: [ + remix({ + future: { + v3_fetcherPersist: true, + v3_relativeSplatPath: true, + v3_throwAbortReason: true, + }, + }), + tsconfigPaths(), + ], +});