├── .changelogrc.js
├── .dumi
├── theme
│ └── builtins
│ │ └── Features
│ │ └── index.tsx
└── tsconfig.json
├── .dumirc.ts
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .fatherrc.ts
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
└── workflows
│ ├── preview.yml
│ ├── release.yml
│ ├── static.yml
│ └── test.yml
├── .gitignore
├── .gitpod.yml
├── .husky
└── pre-commit
├── .i18nrc.js
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .releaserc.js
├── .stylelintrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README.zh-CN.md
├── commitlint.config.js
├── docs
├── api
│ ├── create-instance.en-US.md
│ ├── create-instance.zh-CN.md
│ ├── create-styles.en-US.md
│ ├── create-styles.zh-CN.md
│ ├── create-stylish.en-US.md
│ ├── create-stylish.zh-CN.md
│ ├── global-styles.en-US.md
│ ├── global-styles.zh-CN.md
│ ├── setup-styled.en-US.md
│ ├── setup-styled.zh-CN.md
│ ├── style-provider.en-US.md
│ ├── style-provider.zh-CN.md
│ ├── theme-provider.en-US.md
│ ├── theme-provider.zh-CN.md
│ ├── use-responsive.en-US.md
│ ├── use-responsive.zh-CN.md
│ ├── use-theme-mode.en-US.md
│ ├── use-theme-mode.zh-CN.md
│ ├── use-theme.en-US.md
│ └── use-theme.zh-CN.md
├── best-practice
│ ├── antd-based-components.en-US.md
│ ├── antd-based-components.zh-CN.md
│ ├── antd-override.en-US.md
│ ├── antd-override.zh-CN.md
│ ├── clay.en-US.md
│ ├── clay.zh-CN.md
│ ├── custom-token-types.en-US.md
│ ├── custom-token-types.zh-CN.md
│ ├── demos
│ │ ├── ConfigProviderOverride.tsx
│ │ ├── DefaultOverride.tsx
│ │ ├── InputclassNames.tsx
│ │ ├── NestElements.tsx
│ │ ├── OverrideWeight.tsx
│ │ ├── StaticMethod
│ │ │ ├── index.tsx
│ │ │ ├── layout.tsx
│ │ │ └── request.ts
│ │ └── tsconfig.json
│ ├── fix-switch-theme-fouc.en-US.md
│ ├── fix-switch-theme-fouc.zh-CN.md
│ ├── index.en-US.md
│ ├── index.zh-CN.md
│ ├── mac-select.en-US.md
│ ├── mac-select.zh-CN.md
│ ├── mirgration-less-global-style.en-US.md
│ ├── mirgration-less-global-style.zh-CN.md
│ ├── nest-element-style.en-US.md
│ ├── nest-element-style.zh-CN.md
│ ├── static-message.en-US.md
│ ├── static-message.zh-CN.md
│ ├── styled.en-US.md
│ └── styled.zh-CN.md
├── changelog.en-US.md
├── changelog.zh-CN.md
├── demos
│ ├── StyleProvider
│ │ ├── customContainer.tsx
│ │ ├── insertpoint.tsx
│ │ └── speedy.tsx
│ ├── ThemeProvider
│ │ ├── AppGlobalStyle.tsx
│ │ ├── SwitchTheme.tsx
│ │ ├── TSSupport.tsx
│ │ ├── WithApp.tsx
│ │ ├── WithProvider.tsx
│ │ ├── _app.tsx
│ │ ├── customToken.tsx
│ │ ├── default.tsx
│ │ ├── demo.tsx
│ │ ├── nested-prefixCls.tsx
│ │ ├── staticMethod.tsx
│ │ └── style.ts
│ ├── api
│ │ ├── createInstance
│ │ │ ├── withContainer.tsx
│ │ │ └── withStyleProviderContainer.tsx
│ │ ├── createStyles
│ │ │ ├── Responsive.tsx
│ │ │ ├── default.tsx
│ │ │ ├── label.tsx
│ │ │ └── with-antd-cp.tsx
│ │ ├── useResponsive
│ │ │ ├── Demo
│ │ │ │ ├── DisplayTag.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── custom.tsx
│ │ │ ├── default.tsx
│ │ │ └── style.ts
│ │ └── useTheme.tsx
│ ├── cases
│ │ ├── HeroButton
│ │ │ ├── Shape.tsx
│ │ │ ├── index.tsx
│ │ │ ├── settings.ts
│ │ │ └── style.ts
│ │ ├── MacSelect
│ │ │ ├── ScrollArrow
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── Select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── SelectItem
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── data.ts
│ │ │ └── index.tsx
│ │ ├── Typography
│ │ │ └── default.tsx
│ │ └── clay
│ │ │ ├── default.tsx
│ │ │ └── theme.tsx
│ ├── common
│ │ ├── ThemeController.tsx
│ │ ├── Typography
│ │ │ ├── ActionPanel.tsx
│ │ │ ├── Avatar.tsx
│ │ │ ├── GroupCollapse.tsx
│ │ │ ├── GroupTitle.tsx
│ │ │ ├── SubTitle.tsx
│ │ │ ├── Title.tsx
│ │ │ └── index.ts
│ │ └── demo.tsx
│ ├── createStyles
│ │ ├── AntdToken.tsx
│ │ ├── Command
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Keyframes.tsx
│ │ ├── SimpleObject.tsx
│ │ ├── default.tsx
│ │ └── withProps.tsx
│ ├── createStylish
│ │ ├── commonStylish.ts
│ │ └── default.tsx
│ ├── globalStyles
│ │ ├── AntdToken.tsx
│ │ └── default.tsx
│ ├── guide
│ │ ├── component-usage
│ │ │ ├── Button
│ │ │ │ ├── Default.tsx
│ │ │ │ └── WithWhere.tsx
│ │ │ ├── CustomInstance.tsx
│ │ │ ├── CustomTheme
│ │ │ │ ├── Button.tsx
│ │ │ │ └── styles.ts
│ │ │ └── demo.tsx
│ │ ├── custom-theme
│ │ │ └── CustomAppearance.tsx
│ │ ├── styled
│ │ │ ├── EmotionStyledProps.tsx
│ │ │ ├── SetupStyled
│ │ │ │ ├── App.tsx
│ │ │ │ └── index.tsx
│ │ │ └── StyledComponentsProps.tsx
│ │ └── switch-theme
│ │ │ ├── AntdTheme
│ │ │ ├── CustomDark.tsx
│ │ │ └── index.tsx
│ │ │ ├── AutoSwitch.tsx
│ │ │ ├── ControlledSwitch
│ │ │ ├── Controller.tsx
│ │ │ ├── index.tsx
│ │ │ └── useStore.ts
│ │ │ ├── GlobalSwitch
│ │ │ ├── Controller.tsx
│ │ │ └── index.tsx
│ │ │ └── default.tsx
│ ├── migration
│ │ ├── CSSinJSMode
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── LessMode
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ │ └── ProComponentsStatic
│ │ │ ├── CSSinJS.tsx
│ │ │ ├── CSSinJSComponent
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ │ ├── LessComponent
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ │ └── less.tsx
│ ├── styled
│ │ └── basic.tsx
│ ├── tsconfig.json
│ └── typings.d.ts
├── guide
│ ├── babel-plugin.en-US.md
│ ├── babel-plugin.zh-CN.md
│ ├── compare.en-US.md
│ ├── compare.zh-CN.md
│ ├── components-usage.en-US.md
│ ├── components-usage.zh-CN.md
│ ├── create-styles.en-US.md
│ ├── create-styles.zh-CN.md
│ ├── css-in-js-intro.en-US.md
│ ├── css-in-js-intro.zh-CN.md
│ ├── cssinjs-compiler-difference.en-US.md
│ ├── cssinjs-compiler-difference.zh-CN.md
│ ├── custom-theme.en-US.md
│ ├── custom-theme.zh-CN.md
│ ├── demos
│ │ ├── benchmark
│ │ │ ├── dynamic-value.tsx
│ │ │ └── large-content.tsx
│ │ └── tsconfig.json
│ ├── index.en-US.md
│ ├── index.zh-CN.md
│ ├── migrate-less-application.en-US.md
│ ├── migrate-less-application.zh-CN.md
│ ├── migrate-less-codemod.en-US.md
│ ├── migrate-less-codemod.zh-CN.md
│ ├── migrate-less-component.en-US.md
│ ├── migrate-less-component.zh-CN.md
│ ├── performance-comparsion.en-US.md
│ ├── performance-comparsion.zh-CN.md
│ ├── ssr.en-US.md
│ ├── ssr.zh-CN.md
│ ├── strategy.en-US.md
│ ├── strategy.zh-CN.md
│ ├── styled.en-US.md
│ ├── styled.zh-CN.md
│ ├── stylish.en-US.md
│ ├── stylish.zh-CN.md
│ ├── switch-theme.en-US.md
│ └── switch-theme.zh-CN.md
├── index.md
└── index.zh-CN.md
├── package.json
├── public
└── fonts
│ └── RocherColorGX.woff2
├── src
├── context
│ ├── ThemeModeContext.test.ts
│ ├── ThemeModeContext.ts
│ └── index.ts
├── core
│ ├── CacheManager.ts
│ ├── createCSS.ts
│ ├── createEmotion.ts
│ ├── createSerializeStyles.ts
│ ├── index.ts
│ └── insertStyles.ts
├── factories
│ ├── createEmotionContext.ts
│ ├── createGlobalStyle.tsx
│ ├── createStyish.ts
│ ├── createStyleProvider
│ │ └── index.tsx
│ ├── createStyledThemeProvider.tsx
│ ├── createStyles
│ │ ├── index.ts
│ │ ├── response.ts
│ │ └── types.ts
│ ├── createThemeProvider
│ │ ├── AntdProvider.tsx
│ │ ├── ThemeSwitcher.test.tsx
│ │ ├── ThemeSwitcher.tsx
│ │ ├── TokenContainer.tsx
│ │ ├── index.tsx
│ │ └── type.ts
│ └── createUseTheme.ts
├── functions
│ ├── createInstance.ts
│ ├── extractStaticStyle.tsx
│ ├── index.ts
│ ├── setupStyled.test.ts
│ └── setupStyled.ts
├── hooks
│ ├── index.ts
│ ├── useAntdStylish.ts
│ ├── useAntdTheme.ts
│ ├── useAntdToken.ts
│ ├── useResponsive.ts
│ └── useThemeMode.ts
├── index.ts
├── stylish
│ └── button.ts
├── types
│ ├── appearance.ts
│ ├── css.ts
│ ├── function.ts
│ ├── genericUtils.ts
│ ├── index.ts
│ ├── response.ts
│ ├── styleManager.ts
│ ├── styled.ts
│ └── theme.ts
└── utils
│ ├── convertStylish.ts
│ ├── createEmotionServer
│ └── index.ts
│ ├── css.test.ts
│ ├── css.ts
│ ├── index.ts
│ ├── matchBrowserPrefers.test.ts
│ ├── matchBrowserPrefers.ts
│ ├── responsive.ts
│ └── safeStartTransition.ts
├── tests
├── components
│ ├── StyledProvider.test.tsx
│ ├── ThemeProvider.test.tsx
│ └── __snapshots__
│ │ └── ThemeProvider.test.tsx.snap
├── functions
│ ├── __snapshots__
│ │ ├── createInstance.test.tsx.snap
│ │ ├── createStyish.test.tsx.snap
│ │ ├── createStyles.test.tsx.snap
│ │ └── styled.test.tsx.snap
│ ├── createGlobalStyle.test.tsx
│ ├── createInstance.test.tsx
│ ├── createStyish.test.tsx
│ ├── createStyles.test.tsx
│ ├── extractStaticStyle.test.tsx
│ └── styled.test.tsx
├── hooks
│ ├── __snapshots__
│ │ ├── useAntdStylish.test.tsx.snap
│ │ └── useTheme.test.tsx.snap
│ ├── useAntdStylish.test.tsx
│ ├── useResponsive.test.ts
│ ├── useTheme.test.tsx
│ ├── useThemeMode.test.tsx
│ └── useToken.test.ts
├── test-setup.ts
└── tsconfig.json
├── tsconfig-check.json
├── tsconfig.json
└── vitest.config.ts
/.changelogrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayTypes: ['feat', 'fix', 'styles', 'pref'],
3 | };
4 |
--------------------------------------------------------------------------------
/.dumi/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "esModuleInterop": true,
6 | "resolveJsonModule": true,
7 | "baseUrl": "..",
8 | "paths": {
9 | "@@/*": ["./.dumi/tmp/*"],
10 | "dumi/theme-original/builtins/*": ["./.dumi/tmp/dumi/theme/builtins/*"],
11 | "antd-style": ["es"]
12 | }
13 | },
14 | "include": ["./**/*"]
15 | }
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /lambda/mock/**
2 | /scripts
3 | /config
4 | /example
5 | _test_
6 | __test__
7 |
8 | /node_modules
9 | jest*
10 | /es
11 | /lib
12 | /docs
13 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@umijs/lint/dist/config/eslint');
2 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | esm: { output: 'es' },
5 | cjs: { output: 'lib', platform: 'browser' },
6 | });
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '报告Bug 🐛'
3 | about: 报告 antd-style 的 bug
4 | title: '🐛[BUG]'
5 | labels: '🐛 BUG'
6 | assignees: ''
7 | ---
8 |
9 | ### 🐛 bug 描述
10 |
11 |
14 |
15 | ### 📷 复现步骤
16 |
17 |
20 |
21 | ### 🏞 期望结果
22 |
23 |
26 |
27 | ### 💻 复现代码
28 |
29 |
33 |
34 | [可复现 demo](https://codesandbox.io/s/html2ksetch-demo-m53be?file=/src/Demo.tsx)
35 |
36 | ### © 版本信息
37 |
38 | - antd-style 版本: [e.g. 1.0.0]
39 | - 浏览器环境
40 | - 开发环境 [e.g. mac OS]
41 |
42 | ### 🚑 其他信息
43 |
44 |
47 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '功能需求 ✨'
3 | about: 对 antd-style 的需求或建议
4 | title: '👑 [需求]'
5 | labels: '👑 Feature'
6 | assignees: ''
7 | ---
8 |
9 | ### 🥰 需求描述
10 |
11 |
14 |
15 | ### 🧐 解决方案
16 |
17 |
20 |
21 | ### 🚑 其他信息
22 |
23 |
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '疑问或需要帮助 ❓'
3 | about: 对 antd-style 使用的疑问或需要帮助
4 | title: '🧐[问题]'
5 | labels: '🧐 Question'
6 | assignees: ''
7 | ---
8 |
9 | ### 🧐 问题描述
10 |
11 |
14 |
15 | ### 💻 示例代码
16 |
17 |
20 |
21 | ### 🚑 其他信息
22 |
23 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/preview.yml:
--------------------------------------------------------------------------------
1 | name: Surge PR Preview
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | preview:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 |
11 | - name: Install pnpm
12 | uses: pnpm/action-setup@v2
13 | with:
14 | version: 7
15 |
16 | - uses: afc163/surge-preview@v1
17 | with:
18 | surge_token: ${{ secrets.SURGE_TOKEN }}
19 | github_token: ${{ secrets.GITHUB_TOKEN }}
20 | build: |
21 | pnpm i
22 | pnpm run docs:preview
23 | dist: dist
24 |
25 | - name: Get the preview_url
26 | run: echo "url => ${{ steps.preview_step.outputs.preview_url }}"
27 |
28 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - alpha
7 | - beta
8 | - rc
9 |
10 | jobs:
11 | test:
12 | name: Test
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - name: Install pnpm
18 | uses: pnpm/action-setup@v2
19 | with:
20 | version: 7
21 |
22 | - name: Setup Node.js environment
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version: '16'
26 |
27 | - name: Install deps
28 | run: pnpm install
29 |
30 | - name: Test
31 | run: pnpm run test
32 |
33 | release:
34 | needs: test
35 | name: Release
36 | runs-on: ubuntu-latest
37 | steps:
38 | - uses: actions/checkout@v3
39 |
40 | - name: Install pnpm
41 | uses: pnpm/action-setup@v2
42 | with:
43 | version: 7
44 |
45 | - name: Setup Node.js environment
46 | uses: actions/setup-node@v3
47 | with:
48 | node-version: '16'
49 |
50 | - name: Install deps
51 | run: pnpm install
52 |
53 | - name: build
54 | run: pnpm run build
55 |
56 | - name: release
57 | run: pnpm run release
58 | env:
59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
61 |
--------------------------------------------------------------------------------
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ['master']
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow one concurrent deployment
19 | concurrency:
20 | group: 'pages'
21 | cancel-in-progress: true
22 |
23 | jobs:
24 | # Single deploy job since we're just deploying
25 | deploy:
26 | environment:
27 | name: github-pages
28 | url: ${{ steps.deployment.outputs.page_url }}
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v3
33 | - name: Install pnpm
34 | uses: pnpm/action-setup@v2
35 | with:
36 | version: 7
37 |
38 | - name: Setup Node.js environment
39 | uses: actions/setup-node@v3
40 | with:
41 | node-version: '16'
42 |
43 | - name: Install deps
44 | run: pnpm install
45 |
46 | - name: Build Pages
47 | run: pnpm run docs:build
48 |
49 | - name: Setup Pages
50 | uses: actions/configure-pages@v3
51 |
52 | - name: Upload artifact
53 | uses: actions/upload-pages-artifact@v1
54 | with:
55 | # Upload entire repository
56 | path: 'dist'
57 |
58 | - name: Deploy to GitHub Pages
59 | id: deployment
60 | uses: actions/deploy-pages@v1
61 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test CI
2 | on: [push, pull_request]
3 | jobs:
4 | test:
5 | runs-on: ubuntu-latest
6 |
7 | steps:
8 | - uses: actions/checkout@v3
9 |
10 | - name: Install pnpm
11 | uses: pnpm/action-setup@v2
12 | with:
13 | version: 8
14 |
15 | - name: Setup Node.js environment
16 | uses: actions/setup-node@v3
17 | with:
18 | node-version: '18'
19 |
20 | - name: Install deps
21 | run: pnpm install
22 |
23 | - name: lint
24 | run: pnpm run ci
25 |
26 | - name: Test and coverage
27 | run: pnpm run test:coverage
28 |
29 | - name: Upload coverage to Codecov
30 | uses: codecov/codecov-action@v3
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | **/node_modules
5 | # roadhog-api-doc ignore
6 | /src/utils/request-temp.js
7 | _roadhog-api-doc
8 |
9 | # production
10 | **/dist
11 | /.vscode
12 | /es
13 | /lib
14 |
15 | # misc
16 | .DS_Store
17 | storybook-static
18 | npm-debug.log*
19 | yarn-error.log
20 |
21 | /coverage
22 | .idea
23 | package-lock.json
24 | *bak
25 | .vscode
26 |
27 | # visual studio code
28 | .history
29 | *.log
30 | functions/*
31 | lambda/mock/index.js
32 | .temp/**
33 |
34 | # umi
35 | .dumi/tmp*
36 |
37 | # screenshot
38 | screenshot
39 | .firebase
40 | example/.temp/*
41 | .eslintcache
42 | techUI*
43 | server
44 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - init: pnpm install
3 | command: pnpm run start
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged
5 |
--------------------------------------------------------------------------------
/.i18nrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import("@lobehub/i18n-cli").Config}
3 | */
4 | module.exports = {
5 | markdown: {
6 | entry: ['docs/**/**.md', './README.zh-CN.md'],
7 | entryLocale: 'zh-CN',
8 | entryExtension: '.zh-CN.md',
9 | outputLocales: ['en-US'],
10 | },
11 | modelName: 'gpt-3.5-turbo-1106',
12 | };
13 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | lockfile=false
2 | resolution-mode=highest
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.svg
2 | .umi
3 | .umi-production
4 | /dist
5 | .dockerignore
6 | .DS_Store
7 | .eslintignore
8 | *.png
9 | *.toml
10 | docker
11 | .editorconfig
12 | Dockerfile*
13 | .gitignore
14 | .prettierignore
15 | LICENSE
16 | .eslintcache
17 | *.lock
18 | yarn-error.log
19 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pluginSearchDirs: false,
3 | plugins: [
4 | require.resolve('prettier-plugin-organize-imports'),
5 | require.resolve('prettier-plugin-packagejson'),
6 | ],
7 | printWidth: 100,
8 | proseWrap: 'never',
9 | singleQuote: true,
10 | trailingComma: 'all',
11 | overrides: [
12 | {
13 | files: '*.md',
14 | options: {
15 | proseWrap: 'preserve',
16 | },
17 | },
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/.releaserc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['semantic-release-config-gitmoji'],
3 | };
4 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@umijs/lint/dist/config/stylelint');
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-current Arvin Xu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['gitmoji'],
3 | };
4 |
--------------------------------------------------------------------------------
/docs/api/create-stylish.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 createStylish
3 | order: 10
4 | group:
5 | title: Creating Styles
6 | order: 0
7 | ---
8 |
9 | ## Introduction
10 |
11 | For general styling needs, basic requirements can be met through [createStyles](/usage/create-styles), while `createStylish` is considered an advanced usage.
12 |
13 | In a complex business system, there may be some common styles with finer granularity that are not enough to form a component. However, repeatedly writing these styles is not only time-consuming but also results in a lot of duplicate code. Moreover, if a designer requests a unified adjustment of the design style, the cost of making multiple modifications is extremely high. To address this issue, `createStylish` was developed.
14 |
15 | `createStylish` can create reusable styles. In concept, it is similar to the popular tailwindcss in recent years, but it is more flexible and practical. When combined with Ant Design V5 Token System, it can achieve a highly efficient and flexible user experience.
16 |
17 | :::success{title=Applicable Scenarios}
18 | Used for organizing reusable styles in batches.
19 | :::
20 |
21 | ## Typical Example
22 |
23 |
24 |
25 | ## Detailed Introduction
26 |
27 | ## API
28 |
--------------------------------------------------------------------------------
/docs/api/create-stylish.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 createStylish
3 | order: 10
4 | group:
5 | title: 创建样式
6 | order: 0
7 | ---
8 |
9 | ## 简介
10 |
11 | 对于一般的样式诉求,通过 [createStyles](/zh-CN/usage/create-styles) 就可以基本满足,`createStylish` 属于进阶用法。
12 |
13 | 在一个复杂的业务系统中,可能会存在一些通用的样式,这些样式颗粒度更细,并不足以形成一个组件。但是如果每次都重复书写即耗费精力,又会有很多重复的代码,一旦设计师要求统一调整设计风格,多次修改的成本极高。为了解决这个问题,`createStylish` 应运而生。
14 |
15 | `createStylish` 可以创建一个可以被复用的的样式。它在理念上会和近年来比较流行的 tailwindcss 比较接近,但是具有更加好的灵活度和实用性。 同时结合 Ant Design V5 Token System,能达到非常高效与灵活的使用体验。
16 |
17 | :::success{title=适用场景}
18 | 用于批量组织可复用样式。
19 | :::
20 |
21 | ## 典型示例
22 |
23 |
24 |
25 | ## 详细介绍
26 |
27 | ## API
28 |
--------------------------------------------------------------------------------
/docs/api/global-styles.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: createGlobalStyle
3 | description: Create global styles
4 | order: 10
5 | group: Creating Styles
6 | ---
7 |
8 | ## Introduction
9 |
10 | Using `createGlobalStyle` allows you to create styles that are injected globally. The usage of this method is almost identical to `styled-component`, but it is implemented based on `@emotion/react` and `@emotion/serialize`.
11 |
12 | ## Default Usage
13 |
14 |
15 |
16 | ## Using with antd tokens
17 |
18 | By utilizing the token system in antd v5, we can organize and implement a Button style that does not exist in Ant Design.
19 |
20 |
21 |
--------------------------------------------------------------------------------
/docs/api/global-styles.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: createGlobalStyle
3 | description: 创建全局样式
4 | order: 10
5 | group: 创建样式
6 | ---
7 |
8 | ## 简介
9 |
10 | 使用 `createGlobalStyle` 可以创建注入到全局的样式。 该方法的使用和 `styled-component` 基本没有区别,但实现上是基于 `@emotion/react` 和 `@emotion/serialize` 做的封装。
11 |
12 | ## 默认用法
13 |
14 |
15 |
16 | ## 结合 antd 的 token 使用
17 |
18 | 利用 antd v5 的 token 系统,我们可以自行组织实现一个在 Ant Design 中并不存在的 Button 样式。
19 |
20 |
21 |
--------------------------------------------------------------------------------
/docs/api/setup-styled.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 setupStyled
3 | description: Set up global Styled data container
4 | group: Advanced Settings
5 | ---
6 |
7 | TBD
8 |
--------------------------------------------------------------------------------
/docs/api/setup-styled.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 setupStyled
3 | description: 设置全局 Styled 数据容器
4 | group: 高级设置
5 | ---
6 |
7 | TBD
8 |
--------------------------------------------------------------------------------
/docs/api/style-provider.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: StyleProvider
3 | description: 用于全局管理样式插入相关的配置
4 | order: 2
5 | group: 容器组件
6 | demo:
7 | tocDepth: 4
8 | ---
9 |
10 | ## 修改 container
11 |
12 | 指定 `container` 即可使得所有生成的样式(antd、antd-style)均插入到该节点下。
13 |
14 |
15 |
16 | ## 修改样式注入点
17 |
18 | 一般情况下不太需要用到,如果你需要兼容组件覆写样式的需求时,可以考虑设定组件样式的注入点,使得其在该节点之后注入。
19 |
20 |
21 |
22 | ## 开启 speedy 极速模式
23 |
24 | 开启 emotion 的 speedy 模式。建议独立应用可以开启。
25 |
26 |
27 |
28 | :::info{title=Speedy模式}
29 |
30 | 早期的 cssinjs 方案中,样式的插入是一个 style 标签对应一个样式,浏览器解析较慢,但便于修改与调试。
31 |
32 | 目前 emotion 默认使用现代化的 CSSOM api 插入样式,会把一堆 css 放到一个 标签里,插入后移除相应的内容。这种方式性能很好,支持万级别的样式插入。但与微应用(qiankun)兼容性较差。
33 |
34 | antd-style 中默认关闭了 speedy 模式,如果需要,配置 `speedy` 为 `true` 即可。
35 | :::
36 |
37 | ## API
38 |
39 | 继承 `ant-design/cssinjs` 的 [StyleProvider](https://github.com/ant-design/cssinjs#styleprovider) ,其余 API 如下:
40 |
41 | | 属性名 | 类型 | 描述 |
42 | | --------------- | -------------------------------------- | -------------------------------------------------------------------- |
43 | | prefix | `string` | emotion 样式前缀,默认值为 acss |
44 | | nonce | `string` | 随机数,用于 CSP |
45 | | stylisPlugins | `StylisPlugin[]` | Stylis 插件数组 |
46 | | container | `Element` | 渲染样式的容器 |
47 | | speedy | `boolean` | 是否开启极速模式,极速模式下不会插入真实的样式 style,默认为 `false` |
48 | | insertionPoint | `HTMLElement` | 样式插入点,用于控制第一个样式的插入位置 |
49 | | getStyleManager | `(styleManager: StyleManager) => void` | 获取到 styleManager 实例的回调函数 |
50 | | children | `ReactNode` | 子组件 |
51 |
--------------------------------------------------------------------------------
/docs/api/use-responsive.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useResponsive
3 | order: 4
4 | group: Hooks
5 | ---
6 |
7 | ## Introduction
8 |
9 | Get the result of responsive media queries. It is encapsulated based on antd's [Grid.useBreakpoint](https://ant.design/components/grid) .
10 |
11 | ## Usage
12 |
13 | ```tsx | pure
14 | import { useResponsive } from 'antd-style';
15 |
16 | function Theme() {
17 | const { mobile } = useResponsive();
18 |
19 | // Use JavaScript to distinguish between mobile and desktop
20 | return mobile ?
mobile
: desktop
;
21 | }
22 | ```
23 |
24 | ## Example
25 |
26 |
27 |
28 | ## Custom Breakpoints
29 |
30 | Customize responsive breakpoints by passing in antd's breakpoint configuration.
31 |
32 |
33 |
--------------------------------------------------------------------------------
/docs/api/use-responsive.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useResponsive
3 | order: 4
4 | group: Hooks
5 | ---
6 |
7 | ## 简介
8 |
9 | 获取响应式媒体查询的结果。基于 antd 的 [Grid.useBreakpoint](https://ant.design/components/grid-cn#components-grid-demo-usebreakpoint) 封装。
10 |
11 | ## 用法
12 |
13 | ```tsx | pure
14 | import { useResponsive } from 'antd-style';
15 |
16 | function Theme() {
17 | const { mobile } = useResponsive();
18 |
19 | // 使用 js 来区分显示移动端
20 | return mobile ? mobile
: desktop
;
21 | }
22 | ```
23 |
24 | ## 示例
25 |
26 |
27 |
28 | ## 自定义断点
29 |
30 | 通过传入 antd 的断点配置,来自定义响应断点。
31 |
32 |
33 |
--------------------------------------------------------------------------------
/docs/api/use-theme-mode.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useThemeMode
3 | order: 2
4 | group:
5 | title: Hooks
6 | order: 3
7 | ---
8 |
9 | ## Introduction
10 |
11 | Obtain the theme appearance mode under `ThemeProvider`.
12 |
13 | ## Usage
14 |
15 | ```tsx
16 | /**
17 | * inherit: true
18 | * defaultShowCode: true
19 | */
20 | import { Divider, Typography } from 'antd';
21 | import { useThemeMode } from 'antd-style';
22 | import { Flexbox } from 'react-layout-kit';
23 | const { Text } = Typography;
24 |
25 | export default () => {
26 | const { themeMode, appearance, browserPrefers } = useThemeMode();
27 |
28 | return (
29 |
30 | Theme Mode:
31 | {themeMode}
32 |
33 | Appearance Mode:
34 | {appearance}
35 |
36 | Browser Appearance:
37 | {browserPrefers}
38 |
39 | );
40 | };
41 | ```
42 |
43 | ## API
44 |
45 | ### Typescript
46 |
47 | ```ts
48 | useThemeMode: () => ThemeContextState;
49 | ```
50 |
51 | ### ThemeContextState
52 |
53 | | Parameter | Type | Default | Description |
54 | | -------------- | ------------------------------------- | ------- | ----------------------- |
55 | | browserPrefers | [`BrowserPrefers`](#themeappearance) | `light` | Browser appearance |
56 | | themeMode | [`ThemeMode`](#thememode) | `light` | Theme mode |
57 | | appearance | [`ThemeAppearance`](#themeappearance) | `light` | Display appearance |
58 | | isDarkMode | `boolean` | `false` | Whether it is dark mode |
59 |
60 | ### BrowserPrefers
61 |
62 | Browser appearance, only `dark` or `light`.
63 |
64 | ### ThemeAppearance
65 |
66 | Appearance mode, can be `dark`, `light`, or a custom string.
67 |
68 | ### ThemeMode
69 |
70 | Theme mode, can be `dark`, `light`, or `auto`.
71 |
--------------------------------------------------------------------------------
/docs/api/use-theme-mode.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useThemeMode
3 | order: 2
4 | group:
5 | title: Hooks
6 | order: 3
7 | ---
8 |
9 | ## 简介
10 |
11 | 可以获取到 `ThemeProvider` 下的主题外观模式。
12 |
13 | ## 用法
14 |
15 | ```tsx
16 | /**
17 | * inherit: true
18 | * defaultShowCode: true
19 | */
20 | import { Divider, Typography } from 'antd';
21 | import { useThemeMode } from 'antd-style';
22 | import { Flexbox } from 'react-layout-kit';
23 | const { Text } = Typography;
24 |
25 | export default () => {
26 | const { themeMode, appearance, browserPrefers } = useThemeMode();
27 |
28 | return (
29 |
30 | 主题模式:
31 | {themeMode}
32 |
33 | 外观模式:
34 | {appearance}
35 |
36 | 浏览器外观:
37 | {browserPrefers}
38 |
39 | );
40 | };
41 | ```
42 |
43 | ## API
44 |
45 | ### Typescript
46 |
47 | ```ts
48 | useThemeMode: () => ThemeContextState;
49 | ```
50 |
51 | ### ThemeContextState
52 |
53 | | 参数 | 类型 | 默认值 | 说明 |
54 | | -------------- | ------------------------------------- | ------- | -------------- |
55 | | browserPrefers | [`BrowserPrefers`](#themeappearance) | `light` | 浏览器外观 |
56 | | themeMode | [`ThemeMode`](#thememode) | `light` | 主题模式 |
57 | | appearance | [`ThemeAppearance`](#themeappearance) | `light` | 显示外观 |
58 | | isDarkMode | `boolean` | `false` | 是否为暗色模式 |
59 |
60 | ### BrowserPrefers
61 |
62 | 浏览器外观,仅为 `dark` 或者 `light`。
63 |
64 | ### ThemeAppearance
65 |
66 | 外观模式,为 `dark` 或者 `light` 或自定义的字符串。
67 |
68 | ### ThemeMode
69 |
70 | 主题模式,可以是 `dark`、`light` 或者 `auto`。
71 |
--------------------------------------------------------------------------------
/docs/api/use-theme.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useTheme
3 | order: 1
4 | group: Hooks
5 | ---
6 |
7 | ## Introduction
8 |
9 | When used in conjunction with container components (`ThemeProvider`), it can obtain the theme information under the container. If `ThemeProvider` is not added, the default value is obtained.
10 |
11 | When `useTheme` is used under the `ThemeProvider` component, the theme value in the ThemeProvider can be obtained.
12 |
13 |
14 |
15 | ## Usage
16 |
17 | ```ts
18 | import { useTheme } from 'antd-style';
19 |
20 | function Theme() {
21 | const theme = useTheme();
22 |
23 | useEffect(() => {
24 | console.log(theme);
25 | }, [theme]);
26 |
27 | return null;
28 | }
29 | ```
30 |
31 | ## Typescript
32 |
33 | ```ts
34 | useTheme = () => Theme;
35 | ```
36 |
37 | ### Return Value
38 |
39 | | Parameter | Description | Type | Default |
40 | | ---------- | ----------- | ---------------------- | ------- |
41 | | themeMode | Theme mode | `dark / light / auto` | `light` |
42 | | appearance | Display | `dark / light` | `light` |
43 | | isDarkMode | Dark mode | `boolean` | `false` |
44 |
--------------------------------------------------------------------------------
/docs/api/use-theme.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useTheme
3 | order: 1
4 | group: Hooks
5 | ---
6 |
7 | ## 简介
8 |
9 | 配合容器组件进行使用(`ThemeProvider`),可以获取到容器下的主题信息。如果不添加 `ThemeProvider`,默认获取到的值为默认值。
10 |
11 | 当 `useTheme` 放在 `ThemeProvider` 组件下使用,可以获得 ThemeProvider 中的主题值。
12 |
13 |
14 |
15 | ## 用法
16 |
17 | ```ts
18 | import { useTheme } from 'antd-style';
19 |
20 | function Theme() {
21 | const theme = useTheme();
22 |
23 | useEffect(() => {
24 | console.log(theme);
25 | }, [theme]);
26 |
27 | return null;
28 | }
29 | ```
30 |
31 | ## Typescript
32 |
33 | ```ts
34 | useTheme = () => Theme;
35 | ```
36 |
37 | ### 返回值
38 |
39 | | 参数 | 说明 | 类型 | 默认值 |
40 | | ---------- | -------------- | ---------------------- | ------- |
41 | | themeMode | 主题模式 | `dark / light / auto` | `light` |
42 | | appearance | 显示外观 | `dark / light` | `light` |
43 | | isDarkMode | 是否为暗色模式 | `boolean` | `false` |
44 |
--------------------------------------------------------------------------------
/docs/best-practice/antd-based-components.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 Styling Best Practices for Customizing Components Based on antd v5
3 | group:
4 | title: UI Library Development
5 | order: 3
6 | ---
7 |
8 | # How to Write Styles Elegantly for a Component Library Based on antd v5?
9 |
--------------------------------------------------------------------------------
/docs/best-practice/antd-based-components.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 基于 antd v5 二次封装组件库
3 | group:
4 | title: 组件库研发
5 | order: 3
6 | ---
7 |
8 | # 基于 antd v5 二开的组件库,应该如何优雅书写样式?
9 |
--------------------------------------------------------------------------------
/docs/best-practice/antd-override.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 自定义 antd 组件样式
3 | group: 主题定制
4 | ---
5 |
6 | # 如何更加优雅地覆写 antd 组件样式?
7 |
8 | ## 基于 ConfigProvider 自定义
9 |
10 | antd 在 V5 提供了全新的 theme 属性用于自定义,因此如果需要自定义组件样式,建议优先采用 CP 上的 theme 字段。
11 |
12 | 示例 demo 如下:
13 |
14 |
15 |
16 | :::info
17 | 更多基于 ConfigProvider 的主题定制能力,详见 [聊聊 Ant Design V5 的主题(上):CSSinJS 动态主题的花活](https://www.yuque.com/antfe/featured/durxuu94nvgvgmzq#vFlnd)。
18 | :::
19 |
20 | antd-style 的 ThemeProvider 是基于 ConfigProvider 的业务层封装,提供业务友好的定制能力,查看:[自定义主题](/zh-CN/guide/custom-theme)
21 |
22 | ## 基本覆写
23 |
24 | `createStyles` 方法存在一个 `prefixCls` 参数,使用该参数可以传入组件的前缀,这样一来,任何的样式覆写都可以随着 prefixCls 的变化而自动变化。
25 |
26 |
27 |
28 | ## 抬升权重覆写
29 |
30 | 在某些组件中,直接添加类名可能因为权重不够高,导致无法覆盖样式,此时可以通过 `&` 符号来抬升相应的权重。
31 |
32 |
33 |
34 | ## 多 classNames 场景覆写
35 |
36 | classNames 是 antd V5 的一个重头戏: [[RFC] Semantic DOM Structure for all Components](https://github.com/ant-design/ant-design/discussions/40221)。
37 | 在过去,我们要做样式定义,需要找很多 dom 节点进行大量的样式覆写,而 antd 版本升级的过程中,有时候会对 dom 结构进行调整。这样一来,我们覆写的样式就会出现问题。
38 |
39 | 而 classNames 将为我们提供一个稳定的 dom 结构 API ,我们可以通过 classNames 传入的类名,将会文档指向对应的 dom 节点,进而大大降低 DOM 变化带来的 Breaking Change 风险,同时也让我们不必再 hack 式地找样式类名。
40 |
41 |
42 |
43 | ## 相关讨论
44 |
45 | - [样式权重问题](https://github.com/ant-design/antd-style/issues/24)
46 |
--------------------------------------------------------------------------------
/docs/best-practice/clay.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Clay Style UI
3 | group: Style Cases
4 | ---
5 |
6 | # Clay Style
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/best-practice/clay.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 黏土风 UI
3 | group: 样式案例
4 | ---
5 |
6 | # 黏土风格
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/best-practice/custom-token-types.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Extending Custom Token Type Definition
3 | group:
4 | title: Theme Customization
5 | order: 1
6 | ---
7 |
8 | # How to extend the CustomToken object type definition for antd-style?
9 |
10 | ## Solution
11 |
12 | By extending the type definition of the `CustomToken` interface for `antd-style`, you can add corresponding token type definitions to the `useTheme` hooks.
13 |
14 | At the same time, by adding generics to the `ThemeProvider` object, you can constrain the input definition of `customToken`.
15 |
16 | ```tsx | pure
17 | import { ThemeProvider, useTheme } from 'antd-style';
18 |
19 | interface NewToken {
20 | customBrandColor: string;
21 | }
22 |
23 | // By extending the type definition of the `CustomToken` interface for `antd-style`, you can add corresponding token type definitions to the `useTheme` hooks
24 | declare module 'antd-style' {
25 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
26 | export interface CustomToken extends NewToken {}
27 | }
28 |
29 | const App = () => {
30 | const token = useTheme();
31 | return {token.customBrandColor}
;
32 | };
33 |
34 | export default () => (
35 | // By adding generics to the `ThemeProvider` object, you can constrain the input definition of the `customToken` interface
36 | customToken={{ customBrandColor: '#c956df' }}>
37 |
38 |
39 | );
40 | ```
41 |
42 | :::info
43 | Since `CustomToken` is likely an empty interface, if the project has configured the `@typescript-eslint/no-empty-interface` rule, the interface definition may be corrected to a type during code formatting, and types cannot be extended, resulting in loss of prompts (related issue: [#16](https://github.com/ant-design/antd-style/issues/16)). Therefore, the solution is to add a rule disabling as shown in the example code above.
44 | :::
45 |
46 | ## Reference Code
47 |
48 | - [dumi-theme-antd-style](https://github.com/arvinxx/dumi-theme-antd-style/blob/master/src/styles/customToken.ts)
49 | - [Ant Design Official Website](https://github.com/ant-design/ant-design/blob/master/.dumi/theme/SiteThemeProvider.tsx)
50 |
51 | ## Related Discussions
52 |
53 | - [🧐\[Question\] How to extend the CustomToken object type definition for antd-style](https://github.com/ant-design/antd-style/issues/16)
54 |
--------------------------------------------------------------------------------
/docs/best-practice/custom-token-types.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 扩展自定义 Token 类型定义
3 | group:
4 | title: 主题定制
5 | order: 1
6 | ---
7 |
8 | # 如何给 antd-style 扩展 CustomToken 对象类型定义?
9 |
10 | ## 解决思路
11 |
12 | 通过给 `antd-style` 扩展 `CustomToken` 接口的类型定义,可以为 `useTheme` hooks 中增加相应的 token 类型定义。
13 |
14 | 同时,给 `ThemeProvider` 对象添加泛型,可以约束 `customToken` 的入参定义。
15 |
16 | ```tsx | pure
17 | import { ThemeProvider, useTheme } from 'antd-style';
18 |
19 | interface NewToken {
20 | customBrandColor: string;
21 | }
22 |
23 | // 通过给 antd-style 扩展 CustomToken 对象类型定义,可以为 useTheme 中增加相应的 token 对象
24 | declare module 'antd-style' {
25 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
26 | export interface CustomToken extends NewToken {}
27 | }
28 |
29 | const App = () => {
30 | const token = useTheme();
31 | return {token.customBrandColor}
;
32 | };
33 |
34 | export default () => (
35 | // 给 ThemeProvider 对象添加泛型后可以约束 customToken 接口的入参定义
36 | customToken={{ customBrandColor: '#c956df' }}>
37 |
38 |
39 | );
40 | ```
41 |
42 | :::info
43 | 由于 CustomToken 大概率是一个空 interface,如果在项目中有配置 ` @typescript-eslint/no-empty-interface` 的规则,就在代码格式化时导致接口定义被订正改为 type,而 type 是无法扩展的,会导致提示丢失(相关 issue: [#16](https://github.com/ant-design/antd-style/issues/16))。因此解决方案为如上述示例代码一样,添加禁用规则。
44 | :::
45 |
46 | ## 参考代码
47 |
48 | - [dumi-theme-antd-style](https://github.com/arvinxx/dumi-theme-antd-style/blob/master/src/styles/customToken.ts)
49 | - [Ant Design 官网](https://github.com/ant-design/ant-design/blob/master/.dumi/theme/SiteThemeProvider.tsx)
50 |
51 | ## 相关讨论
52 |
53 | - [🧐[问题] 请问一下如何给 antd-style 扩展 CustomToken 对象类型定义](https://github.com/ant-design/antd-style/issues/16)
54 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/ConfigProviderOverride.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: true
3 | */
4 |
5 | import { Button, Checkbox, ConfigProvider, Popover, theme } from 'antd';
6 | import { Flexbox } from 'react-layout-kit';
7 |
8 | export default () => {
9 | const { token } = theme.useToken();
10 |
11 | return (
12 |
24 |
25 |
29 | antd V5 的 Popup ,结合 结合 组件级 Token,可以非常简单地实现自定义样式
30 |
31 |
32 | 不再显示
33 |
36 |
37 |
38 | }
39 | color={'blue'}
40 | arrow={{ pointAtCenter: true }}
41 | trigger="hover"
42 | >
43 | antd v5 的组件级自定义样式,轻松又便捷
44 |
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/DefaultOverride.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * defaultShowCode: true
3 | */
4 | import { Button, Space } from 'antd';
5 | import { ThemeProvider, createStyles } from 'antd-style';
6 |
7 | const useStyles = createStyles(({ token, css, prefixCls }) => ({
8 | override: css`
9 | &.${prefixCls}-btn {
10 | background-color: ${token.colorWarning};
11 | }
12 | `,
13 | }));
14 |
15 | const Demo = ({ text }: { text?: string }) => {
16 | const { styles } = useStyles();
17 |
18 | return ;
19 | };
20 |
21 | export default () => {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/InputclassNames.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * defaultShowCode: true
3 | */
4 | import { Input } from 'antd';
5 | import { createStyles } from 'antd-style';
6 |
7 | const useStyles = createStyles(({ token, css, prefixCls }) => ({
8 | bg: css`
9 | background: ${token.colorBgLayout};
10 | padding: 24px;
11 | `,
12 | input: css`
13 | background: transparent;
14 | `,
15 | wrapper: css`
16 | background: transparent;
17 | border: 2px solid ${token.colorBorder};
18 | `,
19 | suffix: css`
20 | color: ${token.colorTextQuaternary};
21 | `,
22 | }));
23 |
24 | export default () => {
25 | const { styles } = useStyles();
26 |
27 | return (
28 |
29 |
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/NestElements.tsx:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | const useStyles = createStyles(({ css, cx }) => {
4 | // 使用 cx 包裹 css
5 | const child = cx(css`
6 | background: red;
7 | width: 100px;
8 | height: 100px;
9 | `);
10 |
11 | return {
12 | parent: css`
13 | cursor: pointer;
14 |
15 | &:hover {
16 | .${child} {
17 | background: blue;
18 | }
19 | }
20 | `,
21 | child,
22 | };
23 | });
24 |
25 | const Demo = () => {
26 | const { styles } = useStyles();
27 |
28 | return (
29 |
30 |
31 | hover to change color
32 |
33 | );
34 | };
35 |
36 | export default Demo;
37 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/OverrideWeight.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * defaultShowCode: true
3 | */
4 | import { App, Layout } from 'antd';
5 | import { createStyles } from 'antd-style';
6 |
7 | const useStyles = createStyles(({ token, css, prefixCls }) => ({
8 | default: css`
9 | .${prefixCls}-layout-header {
10 | background-color: ${token.colorPrimary};
11 | }
12 | `,
13 | moreWeight: css`
14 | // ↓
15 | &.${prefixCls}-layout-header {
16 | background-color: ${token.colorPrimary};
17 | }
18 | `,
19 | }));
20 |
21 | export default () => {
22 | const { styles } = useStyles();
23 |
24 | return (
25 |
26 |
27 | 无法覆盖
28 | 可正常覆盖
29 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/StaticMethod/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 240
3 | */
4 | import { Button, Space } from 'antd';
5 | import Layout from './layout';
6 | import { showMessage, showModal, showNotification } from './request';
7 |
8 | const App = () => (
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/StaticMethod/layout.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 240
3 | */
4 | import { ThemeProvider } from 'antd-style';
5 | import { MessageInstance } from 'antd/es/message/interface';
6 | import { ModalStaticFunctions } from 'antd/es/modal/confirm';
7 | import { NotificationInstance } from 'antd/es/notification/interface';
8 | import { PropsWithChildren } from 'react';
9 | import { Center } from 'react-layout-kit';
10 |
11 | export let message: MessageInstance,
12 | modal: Omit,
13 | notification: NotificationInstance;
14 |
15 | export default ({ children }: PropsWithChildren) => {
16 | return (
17 |
18 | {
23 | message = instances.message;
24 | modal = instances.modal;
25 | notification = instances.notification;
26 | }}
27 | >
28 | {children}
29 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/StaticMethod/request.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 240
3 | */
4 | import { message, modal, notification } from './layout';
5 |
6 | export const showMessage = () => {
7 | message.success('Success!');
8 | };
9 |
10 | export const showNotification = () => {
11 | notification.info({
12 | message: `Notification`,
13 | description: 'Hello, Ant Design Style',
14 | });
15 | };
16 |
17 | export const showModal = () => {
18 | modal.warning({
19 | title: 'This is a warning message',
20 | content: 'some messages...some messages...',
21 | centered: true,
22 | maskClosable: true,
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/docs/best-practice/demos/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.tsx", "**/*.ts"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "declaration": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "jsx": "react-jsx",
9 | "lib": ["DOM", "ESNext"],
10 | "baseUrl": ".",
11 | "paths": {
12 | "antd-style": ["../../../es"]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/docs/best-practice/fix-switch-theme-fouc.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 暗色模式下首屏会 “闪” 一下
3 | group:
4 | title: 主题切换
5 | order: 2
6 | ---
7 |
8 | ## 暗色模式下首屏会 “闪” 一下
9 |
10 | 在使用 antd-style 时,首屏一般是亮色的。这就会导致暗色模式下,用户的界面先显示亮色主题,再切换到暗色主题。用户感受上就是会 “闪” 一下,体验很差。这一类问题有一个专业术语叫做 FOUC 。
11 |
12 | 如何解决?
13 |
14 | 本文预设你已经了解了 antd-style 在 SSG、SSR 下的解决方案。
15 |
16 | 原理分析:
17 |
18 | 1. SSG 产出亮色 HTML;
19 | 2. JS 水合后 cssinjs 运行,重新生成暗色类名与样式;
20 |
21 | FOUC 的根本原因是,服务端不知道客户端此时的主题状态。
22 |
23 | 因此解决思路有两种:
24 |
25 | 1. SSR:将用户客户端的主题状态让服务端可以感知,例如使用 query url、cookie 等机制;
26 | 2. 颜色变量抽取为 CSS 变量;
27 |
28 | ### SSR 解决方案
29 |
30 | 以 next.js App Router 为例:
31 |
32 | ```tsx | pure
33 | const RootLayout = ({ children }: PropsWithChildren) => {
34 | // 从 cookie 中获取 主题变化情况
35 | const cookieStore = cookies();
36 | const appearance = cookieStore.get('theme');
37 |
38 | return (
39 |
40 |
41 |
42 | {children}
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default RootLayout;
51 | ```
52 |
53 | ```tsx | pure
54 | export interface AppThemeProps {
55 | children?: ReactNode;
56 | defaultAppearance?: ThemeAppearance;
57 | }
58 |
59 | const Layout = memo(({ children, defaultAppearance }) => {
60 | console.log('server:appearance', defaultAppearance);
61 |
62 | return (
63 | {
66 | document.cookie = `theme=${appearance};`;
67 | }}
68 | themeMode={themeMode}
69 | >
70 | {children}
71 |
72 | );
73 | });
74 |
75 | export default Layout;
76 | ```
77 |
--------------------------------------------------------------------------------
/docs/best-practice/index.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Best Practices and Case Studies
3 | nav:
4 | title: Best Practices
5 | order: 3
6 | ---
7 |
8 | # Best Practices and Case Studies
9 |
10 | As more and more users adopt antd-style, it has been observed that users still encounter some edge cases in practical applications.
11 |
12 | Therefore, this section has been adjusted to focus on "Best Practices" and "Case Studies", detailing the best practices for using antd-style based on common scenarios encountered by users.
13 |
14 | In addition to best practices, this section also includes some practical case studies, with the hope of providing assistance to you.
15 |
--------------------------------------------------------------------------------
/docs/best-practice/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 最佳实践与案例集
3 | nav:
4 | title: 最佳实践
5 | order: 3
6 | ---
7 |
8 | # 最佳实践与案例集
9 |
10 | 随着 antd-style 被更多用户使用,发现大家在实际应用中还是会遇到一些边角问题。
11 |
12 | 因此这一趴调整为「最佳实践」与「案例集」,按照大家常遇到的场景,来详细阐述 antd-style 使用的最佳实践。
13 |
14 | 当然,除了最佳实践之外,这部分也包含了一些实践案例,希望能够帮助到你。
15 |
--------------------------------------------------------------------------------
/docs/best-practice/mac-select.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Custom Component —— macOS Selector
3 | group:
4 | title: Style Cases
5 | order: 10
6 | ---
7 |
8 | # MacOS Selector
9 |
10 | This is a macOS-style interactive selector that combines [floating-ui](https://floating-ui.com/) with the `antd-style`. If you are tired of the default selector in antd but still want to maintain the visual style of antd, you can use antd-style to create your own interactive components.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/best-practice/mac-select.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 自定义组件: MacOS 选择器
3 | group:
4 | title: 样式案例
5 | order: 10
6 | ---
7 |
8 | # MacOS 选择器
9 |
10 | 这是一个结合 [floating-ui](https://floating-ui.com/) 与 `antd-style` 实现的 macOS 交互风格的选择器。如果用腻了 antd 默认的选择器,但又希望有 antd 的视觉风格,可以使用 antd-style 来实现自己的交互组件。
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/best-practice/mirgration-less-global-style.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: CSS Modules Global Style Override Migration
3 | group:
4 | title: Writing Style
5 | order: 0
6 | ---
7 |
8 | # CSS Modules Global Style Override Migration
9 |
10 | How to handle the usage of `:global` in CSS Modules syntax during migration?
11 |
12 | In CSS Modules, there are scenarios where the `:global` is used to override component styles. How should this part of the code be handled during migration?
13 |
14 | ## Solution
15 |
16 | Preferably, use [codemod](/guide/migrate-less-codemod) for one-click migration. This Codemod will automatically convert the `:global` in CSS Modules syntax to the syntax in antd-style.
17 |
18 | If manual adjustment is needed, simply remove the `:global` syntax.
19 |
20 | Before migration:
21 |
22 | ```less
23 | .container {
24 | :global(.ant-btn-link) {
25 | padding: 0;
26 | font-size: 12px;
27 | }
28 | }
29 | ```
30 |
31 | After migration:
32 |
33 | ```ts
34 | const useStyles = createStyles(({ css }) => ({
35 | container: css`
36 | .ant-btn-link {
37 | padding: 0;
38 | font-size: 12px;
39 | }
40 | `,
41 | }));
42 | ```
43 |
44 | ## Principle Analysis
45 |
46 | Elements in CSS modules are by default given a hash. The `:global` syntax is used to avoid adding a hash to the style name. However, antd-style uses `emotion/css` as the underlying style library, where combined styles do not automatically add a hash. Therefore, simply removing `:global` is sufficient.
47 |
48 | ## Related Discussions
49 |
50 | - [🧐\[Issue\] How to handle the usage of :global in less syntax during migration](https://github.com/ant-design/antd-style/issues/72)
51 |
--------------------------------------------------------------------------------
/docs/best-practice/mirgration-less-global-style.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: CSS Modules 全局样式覆写迁移
3 | group:
4 | title: 样式书写
5 | order: 0
6 | ---
7 |
8 | # CSS Modules 全局样式覆写迁移
9 |
10 | 迁移过程中 CSS Modules 语法中使用到的 `:global` 怎么处理?
11 |
12 | 在 CSS Modules 中有部分场景需要通过 :global 去覆盖组件样式,迁移过程中这部分代码如何处理?
13 |
14 | ## 解决方案
15 |
16 | 优先使用 [codemod](/zh-CN/guide/migrate-less-codemod) 一键迁移,该 Codemod 会自动将 Css Modules 语法中的 :global 转换为 antd-style 中的语法。
17 |
18 | 如需手动调整,那么直接移除 :global 语法既可。
19 |
20 | 迁移前:
21 |
22 | ```less
23 | .container {
24 | :global(.ant-btn-link) {
25 | padding: 0;
26 | font-size: 12px;
27 | }
28 | }
29 | ```
30 |
31 | 迁移后:
32 |
33 | ```ts
34 | const useStyles = createStyles(({ css }) => ({
35 | container: css`
36 | .ant-btn-link {
37 | padding: 0;
38 | font-size: 12px;
39 | }
40 | `,
41 | }));
42 | ```
43 |
44 | ## 原理解析
45 |
46 | css module 中的元素默认会添加 hash,`:global` 语法是为了避免给样式名添加 hash。而 antd-style 使用了 `emotion/css`
47 | 作为底层样式库,其中联合的样式并不会自动添加 hash,因此直接去除 :global 即可。
48 |
49 | ## 相关讨论
50 |
51 | - [🧐[问题] 迁移过程中 less 语法中使用到的 :global 怎么处理](https://github.com/ant-design/antd-style/issues/72)
52 |
--------------------------------------------------------------------------------
/docs/best-practice/nest-element-style.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Writing Linked Styles for Parent-Child Components
3 | group: Writing Style
4 | ---
5 |
6 | # How to Write Linked Styles
7 |
8 | Sometimes we need to modify the style of a child component when hovering over the container component. In this case, we can use `cx` to generate a className.
9 |
10 | ## Demo
11 |
12 | Core code:
13 |
14 | ```ts
15 | const useStyles = createStyles(({ css, cx }) => {
16 | // 1. Use cx to wrap css and get the className (acss-xxx)
17 | const child = cx(css`
18 | background: red;
19 | width: 100px;
20 | height: 100px;
21 | `);
22 |
23 | return {
24 | parent: css`
25 | cursor: pointer;
26 |
27 | &:hover {
28 | // 2. Implement cascading
29 | .${child} {
30 | background: blue;
31 | }
32 | }
33 | `,
34 | // 3. Export the child className
35 | child,
36 | };
37 | });
38 | ```
39 |
40 |
41 |
42 | ## Principle Analysis
43 |
44 | The idea is simple because the `css` method always produces a [serialized style object](/api/create-styles#css). Wrapping the `css` object with `cx` will convert the object into a className (`acss-xxxx`).
45 |
46 | ## Related Discussions
47 |
48 | - [\[Issue\] How to nest styles](https://github.com/ant-design/antd-style/issues/54)
49 | - [\[BUG\] Inconsistency between the classNames generated by internal cx and the exported classNames after enabling the babel-plugin-antd-style plugin](https://github.com/ant-design/antd-style/issues/83)
50 |
--------------------------------------------------------------------------------
/docs/best-practice/nest-element-style.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 父子联动的样式书写
3 | group: 样式书写
4 | ---
5 |
6 | # 如何书写联动样式
7 |
8 | 有时候我们需要实现在 hover 容器组件的时候,修改 child 的样式。这种情况下,我们可以使用 cx 来生成 className。
9 |
10 | ## Demo
11 |
12 | 核心代码:
13 |
14 | ```ts
15 | const useStyles = createStyles(({ css, cx }) => {
16 | // 1. 使用 cx 包裹 css,得到 (acss-xxx) 类名
17 | const child = cx(css`
18 | background: red;
19 | width: 100px;
20 | height: 100px;
21 | `);
22 |
23 | return {
24 | parent: css`
25 | cursor: pointer;
26 |
27 | &:hover {
28 | // 2. 实现级联
29 | .${child} {
30 | background: blue;
31 | }
32 | }
33 | `,
34 | // 3. 导出 child className
35 | child,
36 | };
37 | });
38 | ```
39 |
40 |
41 |
42 | ## 原理解析
43 |
44 | 思路上很简单,因为 `css` 方法产出的始终是[序列化样式对象](/zh-CN/api/create-styles#css)。用 `cx` 包裹 `css` 对象,就会将该对象转成类名 (`acss-xxxx`)。
45 |
46 | ## 相关讨论
47 |
48 | - [[问题] 样式怎么嵌套呢](https://github.com/ant-design/antd-style/issues/54)
49 | - [[BUG] 开启 babel-plugin-antd-style 插件后内部 cx 生成的类名和导出的不一致](https://github.com/ant-design/antd-style/issues/83)
50 |
--------------------------------------------------------------------------------
/docs/best-practice/static-message.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Failure of antd Static Methods to Respond to Themes
3 | group: Theme Customization
4 | ---
5 |
6 | # How to Resolve the Issue of antd Static Methods such as Modal and message Not Responding to Themes?
7 |
8 | In the past, in version 4, these static methods could be used in non-component environments, such as using `message.error` in axios to display error messages.
9 |
10 | Now, in version 5, it is necessary to use `const { message } = App.useApp();`.
11 |
12 | Does this mean that we can no longer use them as before?
13 |
14 | ## Solution
15 |
16 | Of course not. Refer to the demo below. By defining the corresponding instance variables in a separate file, you can still use them in non-React environments and they will still respond to themes.
17 |
18 |
19 |
20 | ## Principle Analysis
21 |
22 | antd-style provides a [`getStaticInstance`](api/theme-provider#consume-static-instance-method) interface in the `ThemeProvider`, from which users can obtain the integrated instance.
23 |
24 | The implementation principle of this method is also simple. Taking message as an example:
25 |
26 | ```tsx | pure
27 | import { message } from 'antd';
28 |
29 | const Provider = ({ getStaticInstance, children, theme }) => {
30 | // 1. Use useMessage to obtain the instance
31 | const [messageInstance, messageContextHolder] = message.useMessage(staticInstanceConfig?.message);
32 |
33 | useEffect(() => {
34 | // 3. Use getStaticInstance to expose the instance
35 | getStaticInstance?.({
36 | message: messageInstance,
37 | });
38 | }, []);
39 |
40 | return (
41 |
42 | {/* 2. Insert the context of message */}
43 | {messageContextHolder}
44 | {children}
45 |
46 | );
47 | };
48 | ```
49 |
--------------------------------------------------------------------------------
/docs/best-practice/static-message.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: antd 静态方法的主题失效
3 | group: 主题定制
4 | ---
5 |
6 | # Modal 、message 等 antd 的静态方法不响应主题,如何解决?
7 |
8 | 原先在 v4 中可以在非组件环境下使用这些静态方法,比如放在 axios 里面用 `message.error` 做一些报错的提示。
9 |
10 | 现在 V5 版本里,需要用 `const { message } = App.useApp();`
11 |
12 | 是不是意味着无法再像以前那样用了?
13 |
14 | ## 解决方案
15 |
16 | 当然不是,参考下方 Demo,通过在独立文件中定义相应的实例变量,即可在非 React 环境下使用,并且仍然响应主题。
17 |
18 |
19 |
20 | ## 原理解析
21 |
22 | antd-style 在 `ThemeProvider` 中提供了一个 [`getStaticInstance`](/zh-CN/api/theme-provider#消费静态实例方法) 接口,用户可以从中获取集成后的实例。
23 |
24 | 该方法的实现原理也很简单,以 message 为例:
25 |
26 | ```tsx | pure
27 | import { message } from 'antd';
28 |
29 | const Provider = ({ getStaticInstance, children, theme }) => {
30 | // 1. 使用 useMessage 获取实例
31 | const [messageInstance, messageContextHolder] = message.useMessage(staticInstanceConfig?.message);
32 |
33 | useEffect(() => {
34 | // 3. 将实例用 getStaticInstance 抛出
35 | getStaticInstance?.({
36 | message: messageInstance,
37 | });
38 | }, []);
39 |
40 | return (
41 |
42 | {/* 2. 插入 message 的上下文 */}
43 | {messageContextHolder}
44 | {children}
45 |
46 | );
47 | };
48 | ```
49 |
--------------------------------------------------------------------------------
/docs/best-practice/styled.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Style Components
3 | group: Style Cases
4 | ---
5 |
6 | # Building Stylish Components Using Styled
7 |
8 | ## Stylish Title Components
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/best-practice/styled.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 样式组件
3 | group: 样式案例
4 | ---
5 |
6 | # 使用 Styled 构建风格样式组件
7 |
8 | ## 风格化标题组件
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/changelog.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Release Notes
3 | nav:
4 | title: Release Notes
5 | order: 999
6 | ---
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/changelog.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 更新日志
3 | nav:
4 | title: 更新日志
5 | order: 999
6 | ---
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demos/StyleProvider/customContainer.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 100
3 | */
4 | import { Button } from 'antd';
5 | import { createStyles, css, StyleProvider } from 'antd-style';
6 |
7 | const useStyles = createStyles({
8 | text: css`
9 | color: blue;
10 | `,
11 | });
12 |
13 | const Text = () => {
14 | const { styles } = useStyles();
15 | return 样式将插入在 body 节点
;
16 | };
17 |
18 | export default () => {
19 | return (
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/docs/demos/StyleProvider/insertpoint.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 50
3 | */
4 | import { createStyles, css, StyleProvider } from 'antd-style';
5 |
6 | const useStyles = createStyles({
7 | text: css`
8 | color: hotpink;
9 | `,
10 | });
11 |
12 | const Text = () => {
13 | const { styles } = useStyles();
14 | return 插入的 style 节点在第一个 meta 标签之后
;
15 | };
16 |
17 | export default () => {
18 | const firstMeta = document.getElementsByTagName('meta')[0];
19 |
20 | return (
21 |
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/docs/demos/StyleProvider/speedy.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 50
3 | */
4 | import { createStyles, css, StyleProvider } from 'antd-style';
5 |
6 | const useStyles = createStyles({
7 | text: css`
8 | color: blue;
9 | `,
10 | });
11 |
12 | const Text = () => {
13 | const { styles } = useStyles();
14 | return 开启 speedy 模式后,style 标签中将不存在具体样式
;
15 | };
16 |
17 | export default () => {
18 | return (
19 |
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/AppGlobalStyle.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 180
3 | */
4 | import { css } from '@emotion/css';
5 | import { App, Divider } from 'antd';
6 | import { ThemeProvider } from 'antd-style';
7 | import { Flexbox } from 'react-layout-kit';
8 |
9 | const appScopeStyle = css`
10 | @font-face {
11 | font-family: 'Rocher';
12 | // refs: https://www.matuzo.at/blog/2023/100daysof-day75/
13 | src: url('/fonts/RocherColorGX.woff2');
14 | }
15 |
16 | .custom-button {
17 | font-family: 'Rocher';
18 | padding: 24px;
19 | font-size: 24px;
20 | }
21 | `;
22 |
23 | export default () => {
24 | return (
25 |
26 | Out App Container Custom Button
27 |
28 |
29 |
30 | in App Container Custom Button
31 |
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/SwitchTheme.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 460
3 | */
4 | import { ThemeProvider } from 'antd-style';
5 | import { Flexbox } from 'react-layout-kit';
6 | import App from '../common/demo';
7 |
8 | export default () => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/TSSupport.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | */
4 | import { ThemeProvider, useTheme } from 'antd-style';
5 |
6 | interface NewToken {
7 | customBrandColor: string;
8 | }
9 |
10 | // 通过给 antd-style 扩展 CustomToken 对象类型定义,可以为 useTheme 中增加相应的 token 对象
11 | declare module 'antd-style' {
12 | export interface CustomToken extends NewToken {}
13 | }
14 |
15 | const App = () => {
16 | const token = useTheme();
17 | return {token.customBrandColor}
;
18 | };
19 |
20 | export default () => (
21 | // 给 `ThemeProvider` 对象添加泛型,可以约束 `customToken` 的入参定义。
22 | customToken={{ customBrandColor: '#c956df' }}>
23 |
24 |
25 | );
26 |
27 | /**
28 | * In some instances, if you are using a previous version of React and/or Typescript,
29 | * you may get an error like this:
30 | *
31 | * 'ThemeProvider' cannot be used as a JSX component.
32 | * Its return type 'ReactNode' is not a valid JSX element.
33 | * Type 'undefined' is not assignable to type 'Element | null'.
34 | *
35 | * You can fix this by either upgrading React and/or Typescript, or by overriding the
36 | * ThemeProvider type definition like this:
37 | *
38 | * import { ThemeProvider, ThemeProviderProps } from 'antd-style'
39 | * ...
40 | * const ThemeProviderComponent = ThemeProvider as (
41 | * props: ThemeProviderProps
42 | * ) => JSX.Element
43 | *
44 | * and then use ThemeProviderComponent instead of ThemeProvider.
45 | */
46 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/WithApp.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: ThemeProvider + App
3 | * description: 由于 App 有 DOM 节点,能限制作用域来修改 DOM 原生样式,因此可以重置原生节点样式
4 | * iframe: 80
5 | */
6 | import { App } from 'antd';
7 | import { ThemeProvider } from 'antd-style';
8 |
9 | import Demo from './demo';
10 |
11 | export default () => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/WithProvider.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: ThemeProvider
3 | * description: 由于 ThemeProvider 没有有根 DOM 节点,因此无法修改原生节点样式(除非搭配 GlobalStyle,而搭配 GlobalStyle 则会污染全局样式)
4 | * iframe: 80
5 | */
6 | import { ThemeProvider } from 'antd-style';
7 | import App from './demo';
8 |
9 | export default () => {
10 | return (
11 | <>
12 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/_app.tsx:
--------------------------------------------------------------------------------
1 | import { Space } from 'antd';
2 | import { useTheme } from 'antd-style';
3 | import { FC } from 'react';
4 |
5 | interface AppProps {
6 | title: string;
7 | tokenName?: string;
8 | }
9 |
10 | const App: FC = ({ title, tokenName }) => {
11 | const token = useTheme();
12 |
13 | // @ts-ignore
14 | const tokenColor = tokenName ? token[tokenName] : token.colorPrimary;
15 |
16 | return (
17 |
18 | {title}
19 |
27 |
35 | {tokenColor || 'None'}
36 |
37 |
38 | {tokenName || '-'}
39 |
40 |
41 | );
42 | };
43 |
44 | export default App;
45 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/customToken.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | * defaultShowCode: true
4 | * codePlacement: left
5 | */
6 | import { ThemeProvider, useTheme } from 'antd-style';
7 | import { Flexbox } from 'react-layout-kit';
8 | import App from './_app';
9 |
10 | export default () => {
11 | const theme = useTheme();
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/default.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * defaultShowCode: true
3 | * codePlacement: left
4 | */
5 | import { Divider } from 'antd';
6 | import { ThemeProvider } from 'antd-style';
7 | import { Container } from './style';
8 | import App from './_app';
9 |
10 | export default () => (
11 | } size={24}>
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/demo.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 80
3 | */
4 | const App = () => {
5 | return (
6 |
9 | );
10 | };
11 | export default App;
12 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/nested-prefixCls.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * debug: true
3 | * iframe: true
4 | */
5 | import { Button } from 'antd';
6 | import { ThemeProvider, createStyles } from 'antd-style';
7 |
8 | const useStyles = createStyles(({ css, prefixCls }) => ({
9 | button: css`
10 | .${prefixCls}-btn {
11 | background: pink;
12 | }
13 | `,
14 | }));
15 |
16 | const Demo = () => {
17 | const { styles } = useStyles();
18 | return (
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | const App = () => {
26 | return (
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 | export default App;
35 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/staticMethod.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 240
3 | */
4 | import { Button, Space } from 'antd';
5 | import { ThemeProvider } from 'antd-style';
6 | import { MessageInstance } from 'antd/es/message/interface';
7 | import { ModalStaticFunctions } from 'antd/es/modal/confirm';
8 | import { NotificationInstance } from 'antd/es/notification/interface';
9 | import { Center } from 'react-layout-kit';
10 |
11 | let message: MessageInstance,
12 | modal: Omit,
13 | notification: NotificationInstance;
14 |
15 | const App = () => {
16 | const showMessage = () => {
17 | message.success('Success!');
18 | };
19 |
20 | const showModal = () => {
21 | modal.warning({
22 | title: 'This is a warning message',
23 | content: 'some messages...some messages...',
24 | centered: true,
25 | maskClosable: true,
26 | });
27 | };
28 |
29 | const showNotification = () => {
30 | notification.info({
31 | message: `Notification`,
32 | description: 'Hello, Ant Design Style',
33 | });
34 | };
35 |
36 | return (
37 |
38 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default () => {
48 | return (
49 |
50 | {
55 | message = instances.message;
56 | modal = instances.modal;
57 | notification = instances.notification;
58 | }}
59 | >
60 |
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/docs/demos/ThemeProvider/style.ts:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { Space } from 'antd';
3 |
4 | export const Container = styled(Space)`
5 | padding: 40px;
6 | background: ${(p) => p.theme.colorBgLayout};
7 | `;
8 |
--------------------------------------------------------------------------------
/docs/demos/api/createInstance/withContainer.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 100
3 | */
4 | import { Button } from 'antd';
5 | import { createInstance } from 'antd-style';
6 |
7 | const { css, StyleProvider, createStyles } = createInstance({
8 | key: 'test',
9 | container: document.body,
10 | });
11 |
12 | const useStyles = createStyles({
13 | text: css`
14 | color: blue;
15 | `,
16 | });
17 |
18 | const Text = () => {
19 | const { styles } = useStyles();
20 | return 我是文本
;
21 | };
22 |
23 | export default () => {
24 | return (
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/docs/demos/api/createInstance/withStyleProviderContainer.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 150
3 | */
4 |
5 | import { Button, Divider } from 'antd';
6 | import { createInstance } from 'antd-style';
7 |
8 | const { css, StyleProvider, createStyles } = createInstance({
9 | key: 'test',
10 | });
11 |
12 | const useStyles = createStyles({
13 | text: css`
14 | color: red;
15 | `,
16 | });
17 |
18 | const Text = () => {
19 | const { styles } = useStyles();
20 | return 我是文本
;
21 | };
22 |
23 | export default () => {
24 | return (
25 | <>
26 |
27 |
28 |
29 |
30 | 下方 style 插入在 head
31 |
32 | >
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/docs/demos/api/createStyles/Responsive.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | * title: 响应式断点
4 | * description: 不同断点下,背景色和文本内容不同
5 | */
6 | import { createStyles, useResponsive } from 'antd-style';
7 |
8 | const useStyles = createStyles(({ css, responsive }) => ({
9 | container: css`
10 | height: 100px;
11 | display: flex;
12 | font-size: 24px;
13 | align-items: center;
14 | justify-content: center;
15 |
16 | background-color: lightskyblue;
17 | color: darkblue;
18 |
19 | ${responsive.tablet} {
20 | background: darkseagreen;
21 | color: darkgreen;
22 | }
23 |
24 | ${responsive.desktop} {
25 | background: darksalmon;
26 | color: saddlebrown;
27 | }
28 |
29 | ${responsive.mobile} {
30 | background: pink;
31 | color: deeppink;
32 | }
33 | `,
34 | }));
35 |
36 | const App = () => {
37 | const { styles } = useStyles();
38 |
39 | const { laptop, desktop, mobile } = useResponsive();
40 | return (
41 |
42 | {mobile ? 'mobile' : desktop ? 'desktop' : laptop ? 'laptop' : 'tablet'}
43 |
44 | );
45 | };
46 |
47 | export default App;
48 |
--------------------------------------------------------------------------------
/docs/demos/api/createStyles/default.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * inherit: true
3 | * defaultShowCode: true
4 | */
5 | import { SmileOutlined } from '@ant-design/icons';
6 | import { Button, Space } from 'antd';
7 | import { createStyles } from 'antd-style';
8 |
9 | const useStyles = createStyles(({ token, css, cx }) => {
10 | const commonCard = css`
11 | border-radius: ${token.borderRadiusLG}px;
12 | padding: ${token.paddingLG}px;
13 | `;
14 |
15 | return {
16 | container: css`
17 | background-color: ${token.colorBgLayout};
18 | padding: 24px;
19 | `,
20 |
21 | defaultCard: css`
22 | ${commonCard};
23 | background: ${token.colorBgContainer};
24 | color: ${token.colorText};
25 | `,
26 |
27 | primaryCard: cx(
28 | commonCard,
29 | css`
30 | background: ${token.colorPrimary};
31 | color: ${token.colorTextLightSolid};
32 | `,
33 | ),
34 | };
35 | });
36 |
37 | const App = () => {
38 | const { styles } = useStyles();
39 |
40 | return (
41 |
42 |
43 |
44 | } />
45 | 操作按钮
46 |
47 | 普通卡片
48 | 主要卡片
49 |
50 |
51 | );
52 | };
53 |
54 | export default App;
55 |
--------------------------------------------------------------------------------
/docs/demos/api/createStyles/label.tsx:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | const useStyles = createStyles(
4 | ({ css }) => ({
5 | text: css`
6 | color: blue;
7 | `,
8 | }),
9 | { label: 'with-label' },
10 | );
11 |
12 | export default () => {
13 | const { styles } = useStyles();
14 | return 赋予 with-label 标签
;
15 | };
16 |
--------------------------------------------------------------------------------
/docs/demos/api/createStyles/with-antd-cp.tsx:
--------------------------------------------------------------------------------
1 | import { SmileOutlined } from '@ant-design/icons';
2 | import { Button, ConfigProvider } from 'antd';
3 | import { createStyles } from 'antd-style';
4 |
5 | const useStyles = createStyles(({ css, prefixCls, iconPrefixCls }) => ({
6 | button: css`
7 | &.${prefixCls}-btn {
8 | background: lightsteelblue;
9 | border: none;
10 | color: royalblue;
11 | }
12 |
13 | .${iconPrefixCls} {
14 | color: darkblue;
15 | }
16 | `,
17 | }));
18 |
19 | const App = () => {
20 | const { styles } = useStyles();
21 |
22 | return (
23 | }>
24 | CP Button
25 |
26 | );
27 | };
28 | export default () => (
29 |
30 |
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/docs/demos/api/useResponsive/Demo/DisplayTag.tsx:
--------------------------------------------------------------------------------
1 | import { Tag } from 'antd';
2 | import { FC } from 'react';
3 |
4 | interface DisplayTagProps {
5 | active?: boolean;
6 | color: 'blue' | 'green';
7 | title: string;
8 | value: string;
9 | }
10 |
11 | export const DisplayTag: FC = ({ active, color, title, value, ...props }) => {
12 | return (
13 |
14 | {title}: {value}
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/docs/demos/api/useResponsive/custom.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | */
4 |
5 | import { ThemeProvider } from 'antd-style';
6 | import Demo from './Demo';
7 | import { Container } from './style';
8 |
9 | export default () => {
10 | return (
11 |
12 |
21 |
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/docs/demos/api/useResponsive/default.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | */
4 |
5 | import Demo from './Demo';
6 | import { Container } from './style';
7 |
8 | export default () => {
9 | return (
10 |
11 |
12 | {/**/}
13 | {/**/}
14 | {/* */}
15 | {/**/}
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/docs/demos/api/useResponsive/style.ts:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 |
3 | export const Container = styled.div`
4 | background: ${({ theme }) => theme.colorBgLayout};
5 | padding: 24px;
6 | `;
7 | export const Label = styled.div`
8 | color: ${(p) => p.theme.colorTextPlaceholder};
9 | `;
10 |
--------------------------------------------------------------------------------
/docs/demos/api/useTheme.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 500
3 | */
4 | import { App, Divider, Space } from 'antd';
5 | import { ThemeProvider, useTheme } from 'antd-style';
6 |
7 | const Demo = () => {
8 | const token = useTheme();
9 |
10 | return (
11 |
12 | colorPrimary
13 |
21 |
29 | {token.colorPrimary}
30 |
31 | colorPrimary
32 |
33 | );
34 | };
35 |
36 | export default () => {
37 | const token = useTheme();
38 | return (
39 |
40 |
41 |
42 |
43 | ThemeProvider & 暗色模式
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
ThemeProvider & token 值
52 |
53 |
54 |
55 |
56 |
未包裹,使用默认值
57 |
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/docs/demos/cases/HeroButton/settings.ts:
--------------------------------------------------------------------------------
1 | export const transition = {
2 | type: 'spring',
3 | duration: 0.7,
4 | bounce: 0.2,
5 | };
6 |
--------------------------------------------------------------------------------
/docs/demos/cases/HeroButton/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ css, token }) => {
4 | const blush = css`
5 | position: absolute;
6 | bottom: -15px;
7 | width: 100px;
8 | height: 30px;
9 | filter: blur(20px);
10 | `;
11 |
12 | return {
13 | container: css`
14 | position: relative;
15 | `,
16 | button: css`
17 | border: none;
18 | //background: #1677FF;
19 | //background: #f2056f;
20 | //background: ${token.purple};
21 | &:hover {
22 | background: linear-gradient(
23 | 90deg,
24 | ${token.blue} 0%,
25 | ${token.geekblue} 30%,
26 | ${token.cyan} 70%
27 | );
28 | }
29 |
30 | canvas {
31 | position: absolute;
32 | width: 100%;
33 | height: 100%;
34 | }
35 | `,
36 | pink: css`
37 | ${blush};
38 | right: 20px;
39 | z-index: 10;
40 | background: ${token.cyan};
41 | `,
42 | blue: css`
43 | ${blush};
44 | right: 20px;
45 | z-index: 10;
46 | background: ${token.purple};
47 | `,
48 |
49 | canvas: css`
50 | z-index: 20;
51 | position: absolute;
52 | top: -100px;
53 | bottom: -100px;
54 | left: -100px;
55 | right: -100px;
56 | width: calc(100% + 200px);
57 | pointer-events: none;
58 | `,
59 | };
60 | });
61 |
--------------------------------------------------------------------------------
/docs/demos/cases/MacSelect/ScrollArrow/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles, css } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ token, cx }, prefixCls: string) => ({
4 | container: cx(
5 | prefixCls,
6 | css`
7 | color: ${token.colorTextPlaceholder};
8 | border-radius: 0;
9 | cursor: default;
10 | font-size: 12px;
11 | line-height: 1;
12 | height: 25px;
13 | user-select: none;
14 | position: absolute;
15 | z-index: 1;
16 | width: 100%;
17 |
18 | &::before {
19 | content: '';
20 | display: block;
21 | width: 100%;
22 | height: 35px;
23 | position: absolute;
24 | left: 0;
25 | z-index: -1;
26 | pointer-events: none;
27 | }
28 |
29 | &[data-dir='up'] {
30 | top: 0;
31 |
32 | &::before {
33 | background: linear-gradient(to bottom, ${token.colorBgElevated} 50%, transparent);
34 | border-radius: 8px 8px 0 0;
35 | top: 1px;
36 | left: 1px;
37 | right: 1px;
38 | width: auto;
39 | }
40 | }
41 |
42 | &[data-dir='down'] {
43 | bottom: 0;
44 |
45 | &::before {
46 | background: linear-gradient(to top, ${token.colorBgElevated} 50%, transparent);
47 | border-radius: 0 0 8px 8px;
48 | top: -10px;
49 | left: 1px;
50 | right: 1px;
51 | width: auto;
52 | }
53 | }
54 | `,
55 | ),
56 | }));
57 |
--------------------------------------------------------------------------------
/docs/demos/cases/MacSelect/Select/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ css, cx, token }, prefixCls: string) => ({
4 | container: cx(
5 | prefixCls,
6 | css`
7 | background: ${token.colorBgElevated};
8 | font-size: ${token.fontSize};
9 | border: 1px solid ${token.colorBorder};
10 | box-shadow: ${token.boxShadowSecondary};
11 | border-radius: 8px;
12 | box-sizing: border-box;
13 | overflow-y: auto;
14 | overscroll-behavior: contain;
15 | scrollbar-width: none;
16 | padding: 5px;
17 | outline: 0;
18 | user-select: none;
19 | width: 160px;
20 |
21 | &::-webkit-scrollbar {
22 | display: none;
23 | }
24 | `,
25 | ),
26 | button: cx(
27 | `${prefixCls}-button`,
28 | css`
29 | all: unset;
30 | font-size: ${token.fontSize}px;
31 | padding: 12px 10px;
32 | line-height: 1;
33 | background: ${token.colorBgContainer};
34 | color: ${token.colorText};
35 | border-radius: ${token.borderRadius}px;
36 | cursor: default;
37 | user-select: none;
38 | border: 1px solid ${token.colorBorder};
39 | -webkit-tap-highlight-color: transparent;
40 |
41 | &:hover {
42 | background: ${token.colorPrimaryBg};
43 | border-color: transparent;
44 | }
45 |
46 | &:focus-visible {
47 | border-color: ${token.colorPrimary};
48 | box-shadow: 0 0 0 2px ${token.colorPrimaryBg};
49 | }
50 | `,
51 | ),
52 | }));
53 |
--------------------------------------------------------------------------------
/docs/demos/cases/MacSelect/SelectItem/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ForwardedRef, forwardRef } from 'react';
2 |
3 | import { useStyles } from './style';
4 |
5 | interface SelectItemProps {
6 | value: any;
7 | label: any;
8 | prefixCls?: string;
9 | isSelected?: boolean;
10 | isActive?: boolean;
11 | ref?: ForwardedRef;
12 | disabled?: boolean;
13 | }
14 |
15 | const SelectItem: FC = forwardRef(
16 | ({ value, label, prefixCls, isSelected, isActive, disabled, ...props }, ref) => {
17 | const { styles, cx } = useStyles({ prefixCls, selected: isSelected });
18 |
19 | return (
20 |
35 | );
36 | },
37 | );
38 |
39 | export default SelectItem;
40 |
--------------------------------------------------------------------------------
/docs/demos/cases/MacSelect/SelectItem/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ css, cx, token }, prefixCls) => ({
4 | item: cx(
5 | `${prefixCls}-item`,
6 |
7 | css`
8 | display: block;
9 | all: unset;
10 | width: 100%;
11 | padding: 12px 10px;
12 | border-radius: 5px;
13 | box-sizing: inherit;
14 | user-select: none;
15 | line-height: 1;
16 | scroll-margin: 50px;
17 |
18 | font-weight: normal;
19 | color: ${token.colorText};
20 | background: transparent;
21 | &:hover {
22 | background: ${token.colorFillTertiary};
23 | }
24 | `,
25 | ),
26 | selected: cx(
27 | `${prefixCls}-item-selected`,
28 | css`
29 | background: ${token.colorPrimaryBg};
30 | font-weight: bold;
31 | &:hover {
32 | background: ${token.colorPrimaryBgHover};
33 | }
34 | `,
35 | ),
36 | active: cx(
37 | `${prefixCls}-item-active`,
38 | css`
39 | background: ${token.colorFillTertiary};
40 | `,
41 | ),
42 | }));
43 |
--------------------------------------------------------------------------------
/docs/demos/cases/MacSelect/data.ts:
--------------------------------------------------------------------------------
1 | import { SelectProps } from 'antd';
2 |
3 | export const fruits: SelectProps['options'] = [
4 | {
5 | label: '🍒 樱桃',
6 | value: 'Cherry',
7 | },
8 | { label: '🍓 草莓', value: 'Strawberry' },
9 | { label: '🍇 葡萄', value: 'Grape' },
10 | { label: '🍎 苹果', value: 'Apple' },
11 | { label: '🍉 西瓜', value: 'Watermelon' },
12 | { label: '🍑 桃子', value: 'Peach' },
13 | { label: '🍊 橘子', value: 'Orange' },
14 | { label: '🍋 柠檬', value: 'Lemon' },
15 | { label: '🍍 菠萝', value: 'Pineapple' },
16 | { label: '🍌 香蕉', value: 'Banana' },
17 | { label: '🥑 鳄梨', value: 'Avocado' },
18 | { label: '🍏 青苹果', value: 'Green Apple' },
19 | { label: '🍈 甜瓜', value: 'Melon' },
20 | { label: '🍐 梨', value: 'Pear' },
21 | { label: '🥝 猕猴桃', value: 'Kiwifruit' },
22 | { label: '🥭 芒果', value: 'Mango' },
23 | { label: '🥥 椰子', value: 'Coconut' },
24 | { label: '🍅 西红柿', value: 'Tomato' },
25 | { label: '🫐 蓝莓', value: 'Blueberry' },
26 | ];
27 |
--------------------------------------------------------------------------------
/docs/demos/cases/MacSelect/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | * title: MacOS 选择器
4 | * description: 演示使用 antd-style 如何实现一个适配 antd 主题风格的组件
5 | */
6 | import { App, Divider, Select } from 'antd';
7 | import { ThemeMode, ThemeProvider, useTheme } from 'antd-style';
8 | import { Center, Flexbox } from 'react-layout-kit';
9 | import { create } from 'zustand';
10 |
11 | import MacSelect from './Select';
12 |
13 | import { ThemeController } from '../../common/ThemeController';
14 |
15 | import { fruits } from './data';
16 |
17 | const useStore = create(() => 'auto');
18 | const Demo = () => {
19 | const theme = useTheme();
20 | return (
21 |
22 |
28 | macOS:
29 |
30 |
31 | antd:
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default () => {
39 | const themeMode = useStore();
40 | return (
41 |
43 | appearance === 'dark'
44 | ? {
45 | token: {
46 | colorText: 'rgb(166,166,166)',
47 | },
48 | }
49 | : undefined
50 | }
51 | themeMode={themeMode}
52 | >
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/docs/demos/cases/Typography/default.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 500
3 | */
4 | import styled from '@emotion/styled';
5 | import { App } from 'antd';
6 | import { ThemeProvider } from 'antd-style';
7 |
8 | import { Flexbox } from 'react-layout-kit';
9 |
10 | import { GroupCollapse, GroupTitle, SubTitle, Title } from '../../common/Typography';
11 |
12 | const Wrapper = styled(App)`
13 | color: ${(p) => p.theme.colorTextLabel};
14 | `;
15 |
16 | export default () => {
17 | return (
18 |
19 |
20 |
21 | Title
22 |
23 | SubTitle
24 |
25 | GroupTitle
26 |
27 | GroupCollapse
28 | 可折叠内容
29 |
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/docs/demos/cases/clay/default.tsx:
--------------------------------------------------------------------------------
1 | import { createStyles, ThemeProvider } from 'antd-style';
2 | import { Center } from 'react-layout-kit';
3 | import { getClayStylish, getClayToken } from './theme';
4 |
5 | const useStyles = createStyles(({ stylish, cx, css, token }) => {
6 | return {
7 | btn: cx(
8 | stylish.clay,
9 | css`
10 | padding: 24px;
11 | border-radius: 24px;
12 | width: 64px;
13 | color: ${token.colorPrimary};
14 | background: ${token.colorPrimaryBgHover};
15 | `,
16 | ),
17 | };
18 | });
19 |
20 | const App = () => {
21 | const { styles } = useStyles();
22 | return (
23 |
24 |
按钮
25 |
26 | );
27 | };
28 |
29 | export default () => {
30 | return (
31 |
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/docs/demos/cases/clay/theme.tsx:
--------------------------------------------------------------------------------
1 | import { css, GetCustomStylish, GetCustomToken } from 'antd-style';
2 |
3 | interface ClayToken {
4 | clayBg: string;
5 | clayBorderRadius: number;
6 | clayShadowOutset: string;
7 | clayShadowInsetPrimary: string;
8 | clayShadowInsetSecondary: string;
9 | }
10 |
11 | interface ClayStylish {
12 | clay: string;
13 | }
14 |
15 | declare module 'antd-style' {
16 | interface CustomToken extends ClayToken {}
17 | interface CustomStylish extends ClayStylish {}
18 | }
19 |
20 | export const getClayToken: GetCustomToken = (theme) => {
21 | return {
22 | clayBg: 'rgba(0, 0, 0, 0.05)',
23 | clayBorderRadius: 32,
24 | clayShadowOutset: '8px 8px 16px 0 rgba(0, 0, 0, 0.25)',
25 | clayShadowInsetPrimary: '-8px -8px 16px 0 rgba(0, 0, 0, 0.25)',
26 | clayShadowInsetSecondary: '8px 8px 16px 0 rgba(255, 255, 255, 0.2)',
27 | };
28 | };
29 |
30 | export const getClayStylish: GetCustomStylish = ({ token }) => ({
31 | clay: css`
32 | background: ${token.clayBg};
33 | border-radius: ${token.clayBorderRadius}px;
34 | box-shadow: ${token.clayShadowOutset}, inset ${token.clayShadowInsetPrimary};
35 | `,
36 | });
37 |
--------------------------------------------------------------------------------
/docs/demos/common/ThemeController.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Divider, Segmented, Typography } from 'antd';
2 | import { ThemeMode, useTheme, useThemeMode } from 'antd-style';
3 | import { FC, PropsWithChildren } from 'react';
4 | import { Flexbox } from 'react-layout-kit';
5 |
6 | const { Text } = Typography;
7 |
8 | const options = [
9 | { label: '自动', value: 'auto' },
10 | { label: '亮色', value: 'light' },
11 | { label: '暗色', value: 'dark' },
12 | ];
13 | interface ThemeControllerProps {
14 | themeMode?: ThemeMode;
15 | onThemeModeChange?: (themeMode: ThemeMode) => void;
16 | }
17 |
18 | export const ThemeController: FC> = ({
19 | children,
20 | themeMode,
21 | onThemeModeChange,
22 | }) => {
23 | const { appearance } = useThemeMode();
24 | const theme = useTheme();
25 | return (
26 |
27 |
28 |
29 | 主题模式:
30 | {themeMode}
31 |
32 | 外观模式:
33 | {appearance}
34 |
35 |
36 |
37 | 主题模式:
38 | onThemeModeChange?.(v as ThemeMode)}
41 | options={options}
42 | />
43 |
44 |
45 |
46 |
47 | {children}
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/docs/demos/common/Typography/ActionPanel.tsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { Space } from 'antd';
3 |
4 | export const ActionPanel = styled(Space)`
5 | .anticon {
6 | color: ${(props) => props.theme.colorIcon};
7 | font-size: 14px;
8 | cursor: pointer;
9 | transition: color 0.2s ease-in-out;
10 | &:hover {
11 | color: ${(props) => props.theme.colorPrimary};
12 | }
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/docs/demos/common/Typography/Avatar.tsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { Avatar as A } from 'antd';
3 | import { useTheme } from 'antd-style';
4 | import { FC } from 'react';
5 |
6 | const AvatarText = styled.span`
7 | color: rgba(255, 255, 255, 0.6);
8 | font-weight: 500;
9 | `;
10 |
11 | interface AvatarProps {
12 | logo?: string;
13 | color?: string;
14 | name?: string;
15 | size?: number;
16 | }
17 |
18 | const Avatar: FC = ({ logo, color, name, size = 36 }) => {
19 | const theme = useTheme();
20 | return (
21 |
30 | {name && {name.slice(0, 1).toUpperCase()}}
31 |
32 | );
33 | };
34 |
35 | export default Avatar;
36 |
--------------------------------------------------------------------------------
/docs/demos/common/Typography/GroupCollapse.tsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { Collapse } from 'antd';
3 | import { FC, PropsWithChildren } from 'react';
4 |
5 | const CustomCollapse = styled(Collapse)`
6 | .typography-collapse-header {
7 | width: 100%;
8 | height: 30px;
9 | margin-bottom: 12px;
10 | padding: 0 !important;
11 | color: ${(props) => props.theme.colorTextTertiary} !important;
12 | font-size: 12px;
13 | line-height: 30px;
14 | border-bottom: 1px solid ${(props) => props.theme.colorSplit};
15 | border-radius: 0 !important;
16 | }
17 | .typography-collapse-content {
18 | overflow: unset !important;
19 | }
20 | .typography-collapse-content-box {
21 | padding: 0 !important;
22 | }
23 | .anticon-right {
24 | top: 6px !important;
25 | right: 0 !important;
26 | left: unset !important;
27 | color: ${(props) => props.theme.colorTextQuaternary} !important;
28 | font-size: 14px;
29 | &:hover {
30 | color: ${(props) => props.theme.colorPrimary} !important;
31 | }
32 | }
33 | `;
34 | interface GroupCollapseProps {
35 | name: string;
36 | id?: string;
37 | }
38 | export const GroupCollapse: FC> = ({
39 | name,
40 | id,
41 | children,
42 | }) => (
43 |
44 |
45 | {children}
46 |
47 |
48 | );
49 |
--------------------------------------------------------------------------------
/docs/demos/common/Typography/GroupTitle.tsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { FC } from 'react';
3 |
4 | const T = styled.div`
5 | font-size: 12px;
6 | color: ${(props) => props.theme.colorTextTertiary};
7 | height: 30px;
8 | line-height: 30px;
9 | border-bottom: 1px solid ${(props) => props.theme.colorSplit};
10 | margin-bottom: 12px;
11 | width: 100%;
12 | `;
13 |
14 | export interface GroupTitleProps {
15 | name: string;
16 | id?: string;
17 | }
18 |
19 | export const GroupTitle: FC = ({ name, id }) => {name};
20 |
--------------------------------------------------------------------------------
/docs/demos/common/Typography/SubTitle.tsx:
--------------------------------------------------------------------------------
1 | import { ReadOutlined } from '@ant-design/icons';
2 | import styled from '@emotion/styled';
3 | import { Row } from 'antd';
4 | import { startCase } from 'lodash';
5 | import { FC, ReactNode } from 'react';
6 |
7 | import { ActionPanel } from './ActionPanel';
8 | import { TextCol } from './Title';
9 |
10 | const T = styled.div`
11 | color: ${(props) => props.theme.colorText};
12 | font-weight: 600;
13 | width: 100%;
14 | `;
15 |
16 | const SubTitleRow = styled(Row)<{ size: number; hover?: boolean }>`
17 | font-size: ${({ size }) => size + 'px'};
18 | margin-bottom: ${({ size }) => size + 'px'};
19 | ${({ hover }) => {
20 | if (!hover) return;
21 | return `
22 | ${ActionPanel} {
23 | display: none;
24 | }
25 | &:hover {
26 | ${ActionPanel} {
27 | display: flex;
28 | }
29 | }
30 | `;
31 | }}
32 | `;
33 |
34 | interface DsSubTitleProps {
35 | name: string;
36 | chineseName?: string;
37 | id?: string;
38 | url?: string;
39 | addon?: ReactNode;
40 | baseUrl?: string;
41 | size?: number;
42 | actionHover?: boolean;
43 | onLinkClick?: () => void;
44 | }
45 |
46 | export const SubTitle: FC = ({
47 | name,
48 | chineseName,
49 | id,
50 | url,
51 | addon,
52 | size = 14,
53 | actionHover,
54 | onLinkClick,
55 | }) => (
56 |
57 |
58 | {[startCase(name), chineseName].filter(Boolean).join(' ')}
59 |
60 |
61 | {addon}
62 | {url && }
63 |
64 |
65 | );
66 |
--------------------------------------------------------------------------------
/docs/demos/common/Typography/Title.tsx:
--------------------------------------------------------------------------------
1 | import { HomeOutlined } from '@ant-design/icons';
2 | import styled from '@emotion/styled';
3 | import { Col, Row } from 'antd';
4 |
5 | import { ActionPanel } from './ActionPanel';
6 | import Avatar from './Avatar';
7 |
8 | const TitleRow = styled(Row)`
9 | margin-bottom: 12px;
10 | `;
11 |
12 | const T = styled.div`
13 | font-size: 16px;
14 | color: ${(props) => props.theme.colorText};
15 | font-weight: 600;
16 | padding-top: 2px;
17 | width: 100%;
18 | `;
19 |
20 | export const TextCol = styled(Col)`
21 | > div {
22 | overflow: hidden;
23 | white-space: nowrap;
24 | text-overflow: ellipsis;
25 | }
26 | `;
27 |
28 | export interface DsTitleProps {
29 | id?: number;
30 | logo?: string;
31 | color?: string;
32 | name: string;
33 | url?: string;
34 | }
35 |
36 | export const Title: React.FC = ({ id, logo, color, name, url }) => (
37 |
38 |
39 |
40 |
41 |
42 | {name}
43 |
44 | {url && }
45 |
46 | );
47 |
--------------------------------------------------------------------------------
/docs/demos/common/Typography/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ActionPanel';
2 | export * from './GroupCollapse';
3 | export * from './GroupTitle';
4 | export * from './SubTitle';
5 | export * from './Title';
6 |
--------------------------------------------------------------------------------
/docs/demos/common/demo.tsx:
--------------------------------------------------------------------------------
1 | import { App, Button, Card, Divider, message, Modal, Typography } from 'antd';
2 | import { useTheme, useThemeMode } from 'antd-style';
3 | import { FC, ReactNode } from 'react';
4 | import { Flexbox } from 'react-layout-kit';
5 |
6 | const StaticModal = Modal._InternalPanelDoNotUseOrYouWillBeFired;
7 | const StaticMessage = message._InternalPanelDoNotUseOrYouWillBeFired;
8 | const { Text } = Typography;
9 |
10 | interface AppProps {
11 | extra?: ReactNode;
12 | }
13 | const Demo: FC = ({ extra }) => {
14 | const theme = useTheme();
15 | const { appearance, themeMode, browserPrefers } = useThemeMode();
16 | return (
17 |
18 |
25 |
26 |
27 | 主题模式:
28 | {themeMode}
29 |
30 | 外观模式:
31 | {appearance}
32 |
33 | 浏览器外观:
34 | {browserPrefers}
35 |
36 | {extra}
37 |
38 |
39 |
40 | 组件示例
41 |
42 |
43 |
44 |
45 |
48 |
49 |
50 | 卡片效果
51 |
52 |
53 | 这是一个静态化呈现的成功弹窗
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 | };
62 | export default Demo;
63 |
--------------------------------------------------------------------------------
/docs/demos/createStyles/AntdToken.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * inherit: true
3 | * title: 结合 antd token
4 | * description: 当切换站点亮暗色模式时,Demo 均能适应站点主题实现自动切换
5 | */
6 | import { SmileOutlined } from '@ant-design/icons';
7 | import { Button, Space } from 'antd';
8 | import { createStyles } from 'antd-style';
9 |
10 | const useStyles = createStyles(({ token, css }) => {
11 | const commonCard = css`
12 | border-radius: ${token.borderRadiusLG}px;
13 | padding: ${token.paddingLG}px;
14 | `;
15 |
16 | return {
17 | container: css`
18 | background-color: ${token.colorBgLayout};
19 | padding: 24px;
20 | `,
21 |
22 | primaryCard: css`
23 | ${commonCard};
24 | background: ${token.colorPrimary};
25 | color: ${token.colorTextLightSolid};
26 | `,
27 |
28 | defaultCard: css`
29 | ${commonCard};
30 | background: ${token.colorBgContainer};
31 | color: ${token.colorText};
32 | `,
33 | };
34 | });
35 |
36 | const App = () => {
37 | const { styles } = useStyles();
38 |
39 | return (
40 |
41 |
42 |
43 | } />
44 | 操作按钮
45 |
46 | 普通卡片
47 | 主要卡片
48 |
49 |
50 | );
51 | };
52 |
53 | export default App;
54 |
--------------------------------------------------------------------------------
/docs/demos/createStyles/Command/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 510
3 | * title: 复杂样式场景下建议独立拆分样式文件
4 | * description: "Demo 示例参考 https://designmaestro.io/"
5 | * defaultShowCode: true
6 | */
7 | import { SearchOutlined } from '@ant-design/icons';
8 |
9 | import { useState } from 'react';
10 | import useStyles from './style';
11 |
12 | const items = [
13 | { label: 'New Figma Project', shortcut: 'fn' },
14 | { label: 'Color Picker', shortcut: '⌘ X', hover: true },
15 | { label: 'Pick Brand Asset', shortcut: '⌘ B' },
16 | { label: 'Pick Brand Color', shortcut: '⌘ A' },
17 | { label: 'Optimize Selected Images', shortcut: '⌘ O' },
18 | { label: 'Remove background from image', shortcut: '⌘ D' },
19 | { label: 'Translate with Deepl', shortcut: '⌘ T' },
20 | { label: 'Quick Notion note', shortcut: '⌘ N' },
21 | { label: 'Search in Google', shortcut: '⌘ G' },
22 | ];
23 |
24 | const Command = () => {
25 | const { styles, cx } = useStyles();
26 | const [hover, setHover] = useState('');
27 |
28 | return (
29 |
30 |
31 |
32 |
Trigger Macro by Name
33 |
34 |
35 |
36 |
37 | {items.map(({ label, shortcut }) => {
38 | return (
39 |
{
43 | setHover(label);
44 | }}
45 | >
46 |
{label}
47 |
{shortcut}
48 |
49 | );
50 | })}
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default Command;
61 |
--------------------------------------------------------------------------------
/docs/demos/createStyles/Command/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles, css } from 'antd-style';
2 |
3 | export default createStyles({
4 | layout: {
5 | background: '#0e0f11',
6 | height: '100%',
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | padding: 40,
11 | color: 'white',
12 | },
13 |
14 | container: css`
15 | padding: 1px;
16 | width: 600px;
17 | border-radius: 1rem;
18 | background-color: #262626;
19 | background-image: linear-gradient(135deg, #ff4593, #ffe713 32%, #17d7ff 66%, #077bff);
20 | position: relative;
21 | box-shadow: rgba(255, 69, 146, 0.2) -40px -40px 200px, rgba(255, 231, 18, 0.2) -4px 0px 200px,
22 | rgba(23, 216, 255, 0.2) 0px 20px 200px, rgba(8, 123, 255, 0.2) 0px 20px 200px;
23 | `,
24 |
25 | searchBox: css`
26 | display: flex;
27 | padding: 1rem;
28 | align-items: center;
29 | border-bottom: 1px solid #444;
30 | border-top-left-radius: 1rem;
31 | border-top-right-radius: 1rem;
32 | background-color: #262626;
33 | `,
34 | placeholder: css`
35 | padding-left: 8px;
36 | flex: 1;
37 | color: hsla(0, 0%, 100%, 0.4);
38 | `,
39 | mask: css`
40 | position: absolute;
41 | left: -21%;
42 | top: auto;
43 | right: -21%;
44 | bottom: -9%;
45 | z-index: 22;
46 | display: block;
47 | height: 16rem;
48 | pointer-events: none;
49 | background-image: linear-gradient(0deg, #0f0f0f 20%, rgba(15, 15, 15, 0));
50 | `,
51 |
52 | menuContainer: css`
53 | padding: 8px 0;
54 | border-bottom-left-radius: 8px;
55 | border-bottom-right-radius: 8px;
56 | background-color: #262626;
57 | `,
58 | menuItem: css`
59 | display: flex;
60 | justify-content: space-between;
61 | align-items: center;
62 | height: 40px;
63 | padding: 0 14px;
64 | color: hsla(0, 0%, 100%, 0.75);
65 | cursor: pointer;
66 | `,
67 | menuItemHover: css`
68 | background-color: #363636;
69 | `,
70 | });
71 |
--------------------------------------------------------------------------------
/docs/demos/createStyles/Keyframes.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * inherit: true
3 | * defaultShowCode: true
4 | */
5 | import { createStyles, keyframes } from 'antd-style';
6 | import { Flexbox } from 'react-layout-kit';
7 |
8 | const useStyles = createStyles(({ token, css }) => {
9 | const bounce = keyframes`
10 | from, 20%, 53%, 80%, to {
11 | transform: translate3d(0,0,0);
12 | }
13 |
14 | 40%, 43% {
15 | transform: translate3d(0, -30px, 0);
16 | }
17 |
18 | 70% {
19 | transform: translate3d(0, -15px, 0);
20 | }
21 |
22 | 90% {
23 | transform: translate3d(0,-4px,0);
24 | }
25 | `;
26 |
27 | return {
28 | one: css`
29 | animation: ${bounce} 1s ease infinite;
30 | `,
31 |
32 | another: css`
33 | @keyframes another {
34 | from,
35 | 20%,
36 | 53%,
37 | 80%,
38 | to {
39 | transform: translate3d(0, 0, 0);
40 | }
41 |
42 | 40%,
43 | 43% {
44 | transform: translate3d(0, -30px, 0);
45 | }
46 |
47 | 70% {
48 | transform: translate3d(0, -15px, 0);
49 | }
50 |
51 | 90% {
52 | transform: translate3d(0, -4px, 0);
53 | }
54 | }
55 |
56 | animation: another 1s ease infinite;
57 | `,
58 | };
59 | });
60 |
61 | const App = () => {
62 | const { styles } = useStyles();
63 |
64 | return (
65 |
66 | keyframes 写法一
67 | keyframes 写法二
68 |
69 | );
70 | };
71 |
72 | export default App;
73 |
--------------------------------------------------------------------------------
/docs/demos/createStyles/SimpleObject.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * defaultShowCode: true
3 | * codePlacement: top
4 | */
5 | import { createStyles, css } from 'antd-style';
6 |
7 | const useStyles = createStyles({
8 | container: {
9 | padding: 24,
10 | background: 'lightslategrey',
11 | },
12 | header: css`
13 | font-size: 16px;
14 | font-weight: 500;
15 | line-height: 24px;
16 | color: white;
17 | `,
18 | text: {
19 | color: 'lightblue',
20 | },
21 | });
22 |
23 | export default () => {
24 | const { styles } = useStyles();
25 |
26 | return (
27 |
28 |
用法一
29 |
普通对象,无需动态性
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/docs/demos/createStyles/default.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: createStyles 标准用法
3 | * description: 包含了两种css书写方式,且集成了 token 的使用
4 | * defaultShowCode: true
5 | */
6 | import { createStyles } from 'antd-style';
7 |
8 | const useStyles = createStyles(({ token, css }) => ({
9 | // 支持 css object 的写法
10 | container: {
11 | backgroundColor: token.colorBgLayout,
12 | borderRadius: token.borderRadiusLG,
13 | maxWidth: 400,
14 | width: '100%',
15 | height: 180,
16 | display: 'flex',
17 | alignItems: 'center',
18 | justifyContent: 'center',
19 | flexDirection: 'column',
20 | marginLeft: 'auto',
21 | marginRight: 'auto',
22 | },
23 | // 也支持通过 css 字符串模板获得和 普通 css 一致的书写体验
24 | card: css`
25 | color: ${token.colorTextTertiary};
26 | box-shadow: ${token.boxShadow};
27 | &:hover {
28 | color: ${token.colorTextSecondary};
29 | box-shadow: ${token.boxShadowSecondary};
30 | }
31 |
32 | padding: ${token.padding}px;
33 | border-radius: ${token.borderRadius}px;
34 | background: ${token.colorBgContainer};
35 | transition: all 100ms ${token.motionEaseInBack};
36 |
37 | margin-bottom: 8px;
38 | cursor: pointer;
39 | `,
40 | }));
41 |
42 | export default () => {
43 | // styles 对象在 useStyles 方法中默认会被缓存,所以不用担心 re-render 问题
44 | const { styles, cx, theme } = useStyles();
45 |
46 | return (
47 | // 使用 cx 可以组织 className
48 |
49 |
createStyles Demo
50 | {/* theme 对象包含了所有的 token 与主题等信息 */}
51 |
当前主题模式:{theme.appearance}
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/docs/demos/createStylish/commonStylish.ts:
--------------------------------------------------------------------------------
1 | // 创建通用的 stylish 函数
2 | import { createStylish } from 'antd-style';
3 |
4 | export const useStylish = createStylish(({ token, css }) => {
5 | const containerBgHover = css`
6 | cursor: pointer;
7 | transition: 150ms background-color ease-in-out;
8 | &:hover {
9 | background: ${token.colorFillQuaternary};
10 | }
11 | `;
12 |
13 | const defaultButtonBase = css`
14 | color: ${token.colorTextSecondary};
15 | background: ${token.colorFillQuaternary};
16 | border-color: transparent;
17 | `;
18 |
19 | return {
20 | defaultButton: css`
21 | ${defaultButtonBase};
22 |
23 | &:hover {
24 | color: ${token.colorText};
25 | background: ${token.colorFillSecondary};
26 | border-color: transparent;
27 | }
28 | &:focus {
29 | ${defaultButtonBase};
30 | border-color: ${token.colorPrimary};
31 | }
32 | `,
33 |
34 | containerBgHover: css`
35 | cursor: pointer;
36 | transition: 150ms background-color ease-in-out;
37 |
38 | &:hover {
39 | background: ${token.colorFillQuaternary};
40 | }
41 | `,
42 |
43 | containerBgL2: css`
44 | ${containerBgHover};
45 | border-radius: 4px;
46 | background: ${token.colorFillQuaternary};
47 |
48 | &:hover {
49 | background: ${token.colorFillTertiary};
50 | }
51 | `,
52 | };
53 | });
54 |
--------------------------------------------------------------------------------
/docs/demos/createStylish/default.tsx:
--------------------------------------------------------------------------------
1 | import { createStyles, css } from 'antd-style';
2 | import { useStylish } from './commonStylish';
3 |
4 | const useStyles = createStyles({
5 | // 支持 css object 的写法
6 | container: {
7 | backgroundColor: '#f5f5f5',
8 | maxWidth: 400,
9 | width: '100%',
10 | height: 180,
11 | display: 'flex',
12 | alignItems: 'center',
13 | justifyContent: 'center',
14 | },
15 | // 也支持通过 css 字符串模板获得和 普通 css 一致的书写体验
16 | btn: css`
17 | padding: 24px;
18 | `,
19 | });
20 |
21 | export default () => {
22 | const { styles, cx } = useStyles();
23 | const stylish = useStylish();
24 |
25 | return (
26 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/docs/demos/globalStyles/AntdToken.tsx:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle, ThemeProvider } from 'antd-style';
2 |
3 | const Global = createGlobalStyle`
4 | .ant-custom-button {
5 | color: ${(p) => p.theme.colorPrimary};
6 | background: ${(p) => p.theme.colorPrimaryBg};
7 | height: ${(p) => p.theme.controlHeight}px;
8 | border-radius: ${(p) => p.theme.borderRadius}px;
9 | padding: 0 ${(p) => p.theme.paddingContentHorizontal}px;
10 |
11 | :hover {
12 | background: ${(p) => p.theme.colorPrimaryBgHover};
13 | color: ${(p) => p.theme.colorPrimaryTextActive};
14 | }
15 |
16 | :active {
17 | background: ${(p) => p.theme.colorPrimaryBorder};
18 | color: ${(p) => p.theme.colorPrimaryText};
19 | }
20 |
21 | border: none;
22 | cursor: pointer;
23 | }
24 | `;
25 |
26 | export default () => {
27 | return (
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/docs/demos/globalStyles/default.tsx:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'antd-style';
2 |
3 | const Global = createGlobalStyle`
4 | .some-class {
5 | color: hotpink;
6 | }
7 | `;
8 |
9 | export default () => {
10 | return (
11 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/docs/demos/guide/component-usage/Button/Default.tsx:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 | import { FC, ReactNode } from 'react';
3 |
4 | const useStyles = createStyles(({ css, cx, token }) => {
5 | const prefix = `my-btn`;
6 | return {
7 | container: cx(
8 | prefix,
9 | css`
10 | &.${prefix} {
11 | padding: 6px 16px;
12 | border-radius: 6px;
13 | border: unset;
14 | background: unset;
15 | cursor: pointer;
16 | }
17 | `,
18 | ),
19 | primary: cx(
20 | `${prefix}-primary`,
21 | css`
22 | &.${prefix}-primary {
23 | color: ${token.colorTextLightSolid};
24 | background: ${token.colorPrimary};
25 | :hover {
26 | background: ${token.colorPrimaryHover};
27 | }
28 | }
29 | `,
30 | ),
31 | filled: cx(
32 | `${prefix}-filled`,
33 | css`
34 | &.${prefix}-filled {
35 | color: ${token.colorPrimary};
36 | background: ${token.colorPrimaryBg};
37 | :hover {
38 | background: ${token.colorPrimaryBgHover};
39 | }
40 | }
41 | `,
42 | ),
43 | default: cx(
44 | `${prefix}-default`,
45 | css`
46 | &.${prefix}-default {
47 | color: ${token.colorTextSecondary};
48 | background: ${token.colorBgContainer};
49 | border: 1px solid ${token.colorBorder};
50 | }
51 | `,
52 | ),
53 | text: cx(
54 | `${prefix}-text`,
55 | css`
56 | &.${prefix}-text {
57 | color: ${token.colorTextSecondary};
58 | :hover {
59 | color: ${token.colorText};
60 | background: ${token.colorFillTertiary};
61 | }
62 | }
63 | `,
64 | ),
65 | };
66 | });
67 |
68 | interface ButtonProps {
69 | children: ReactNode;
70 | className?: string;
71 | type?: 'primary' | 'filled' | 'default' | 'text';
72 | }
73 |
74 | const Button: FC = ({ children, type, className }) => {
75 | const { styles, cx } = useStyles();
76 |
77 | return (
78 |
79 | );
80 | };
81 |
82 | export default Button;
83 |
--------------------------------------------------------------------------------
/docs/demos/guide/component-usage/CustomInstance.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: true
3 | * title: 创建独立的样式实例
4 | * description: createStyles 中将会注入默认的自定义 token,并可以实现主题切换
5 | */
6 | import { Card } from 'antd';
7 | import { createStyles, useTheme } from 'antd-style';
8 | import { Flexbox } from 'react-layout-kit';
9 |
10 | import Button from './CustomTheme/Button';
11 | import { ProDemoProvider } from './CustomTheme/styles';
12 |
13 | const useStyles = createStyles(({ css, prefixCls }) => css``);
14 |
15 | export default () => {
16 | const token = useTheme();
17 | const { styles } = useStyles();
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/docs/demos/guide/component-usage/CustomTheme/Button.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react';
2 | import { createStyles } from './styles';
3 |
4 | const useStyles = createStyles(({ css, cx, prefixCls, token }) => {
5 | const prefix = `${prefixCls}-btn`;
6 | return {
7 | container: cx(
8 | prefix,
9 | css`
10 | &.${prefix} {
11 | padding: 6px 16px;
12 | border-radius: 6px;
13 | border: unset;
14 | background: unset;
15 | cursor: pointer;
16 | }
17 | `,
18 | ),
19 | primary: cx(
20 | `${prefix}-primary`,
21 | css`
22 | &.${prefix}-primary {
23 | color: ${token.colorTextLightSolid};
24 | background: ${token.primaryColor};
25 | :hover {
26 | background: ${token.colorPrimaryHover};
27 | }
28 | }
29 | `,
30 | ),
31 | filled: cx(
32 | `${prefix}-filled`,
33 | css`
34 | &.${prefix}-filled {
35 | color: ${token.colorPrimary};
36 | background: ${token.colorPrimaryBg};
37 | :hover {
38 | background: ${token.colorPrimaryBgHover};
39 | }
40 | }
41 | `,
42 | ),
43 | default: cx(
44 | `${prefix}-default`,
45 | css`
46 | &.${prefix}-default {
47 | color: ${token.colorTextSecondary};
48 | background: ${token.colorBgContainer};
49 | border: 1px solid ${token.colorBorder};
50 | }
51 | `,
52 | ),
53 | text: cx(
54 | `${prefix}-text`,
55 | css`
56 | &.${prefix}-text {
57 | color: ${token.colorTextSecondary};
58 | :hover {
59 | color: ${token.colorText};
60 | background: ${token.colorFillTertiary};
61 | }
62 | }
63 | `,
64 | ),
65 | };
66 | });
67 |
68 | interface ButtonProps {
69 | children: ReactNode;
70 | className?: string;
71 | type?: 'primary' | 'filled' | 'default' | 'text';
72 | }
73 |
74 | const Button: FC = ({ children, type, className }) => {
75 | const { styles, cx } = useStyles();
76 |
77 | return (
78 |
79 | );
80 | };
81 |
82 | export default Button;
83 |
--------------------------------------------------------------------------------
/docs/demos/guide/component-usage/CustomTheme/styles.ts:
--------------------------------------------------------------------------------
1 | import { createInstance } from 'antd-style';
2 |
3 | interface ForDemoToken {
4 | primaryColor: string;
5 | demoBgColor: string;
6 | }
7 |
8 | export const { createStyles, ThemeProvider: ProDemoProvider } = createInstance({
9 | key: 'css',
10 | prefixCls: 'for-demo',
11 | customToken: {
12 | primaryColor: '#ce1472',
13 | demoBgColor: '#f1f2f5',
14 | },
15 | hashPriority: 'low',
16 | });
17 |
--------------------------------------------------------------------------------
/docs/demos/guide/component-usage/demo.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: true
3 | * title: 组件覆写对比
4 | * description: 使用 :where 选择器后,cssinjs 组件覆写组件样式将和传统组件覆写模式一致
5 | */
6 | import { Card } from 'antd';
7 | import { createStyles, useTheme } from 'antd-style';
8 | import { Flexbox } from 'react-layout-kit';
9 |
10 | import Button from './Button/Default';
11 | import WithWhereButton from './Button/WithWhere';
12 |
13 | const useStyles = createStyles(
14 | ({ css }) => css`
15 | .my-btn {
16 | background: darkgreen;
17 | color: white;
18 | }
19 |
20 | .my-btn-primary {
21 | background: springgreen;
22 | color: green;
23 | }
24 | `,
25 | );
26 |
27 | export default () => {
28 | const token = useTheme();
29 | const { styles } = useStyles();
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 强调按钮
43 | 填充按钮
44 | 默认按钮
45 | 文本按钮
46 |
47 |
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/docs/demos/guide/custom-theme/CustomAppearance.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 450
3 | */
4 | import { Segmented, theme } from 'antd';
5 | import { ThemeAppearance, ThemeProvider } from 'antd-style';
6 | import { getAntdTheme } from 'dumi-theme-antd-style/dist/styles/antdTheme';
7 | import { useState } from 'react';
8 |
9 | import App from '../../common/demo';
10 |
11 | type CustomAppearance = ThemeAppearance | 'grey';
12 |
13 | export default () => {
14 | const [appearance, setAppearance] = useState('light');
15 |
16 | return (
17 | {
20 | switch (appearance) {
21 | case 'light':
22 | return {
23 | token: {
24 | colorPrimary: 'purple',
25 | },
26 | };
27 | case 'dark':
28 | return {
29 | token: {
30 | colorPrimary: 'cyan',
31 | },
32 | algorithm: theme.darkAlgorithm,
33 | };
34 |
35 | case 'grey':
36 | return {
37 | algorithm: getAntdTheme('dark')?.algorithm,
38 | };
39 | }
40 | }}
41 | >
42 | setAppearance(e as CustomAppearance)}
52 | />
53 | }
54 | />
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/docs/demos/guide/styled/EmotionStyledProps.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: '@emotion/styled'
3 | * description: 'antd-style 内置了 @emotion/react 的 ThemeContext,所以默认可以响应自定义 Token'
4 | * iframe: 100
5 | */
6 | import { ThemeContext } from '@emotion/react';
7 | import styled from '@emotion/styled';
8 | import { ThemeProvider } from 'antd-style';
9 | import { Flexbox } from 'react-layout-kit';
10 |
11 | const StyledButton = styled.button`
12 | color: ${(p) => p.theme.text};
13 | background: ${(p) => p.theme.colorPrimary};
14 | border: 4px solid ${(p) => p.theme.border};
15 | padding: 8px;
16 | border-radius: ${(p) => p.theme.borderRadius}px;
17 | `;
18 |
19 | export default () => {
20 | return (
21 |
22 |
30 | 注入 @emotion/react Provider
31 |
32 |
33 |
40 | 只包裹 ThemeProvider
41 |
42 |
43 | 不包裹 ThemeProvider
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/docs/demos/guide/styled/SetupStyled/App.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from 'antd-style';
2 | import { Flexbox } from 'react-layout-kit';
3 | import { styled } from 'styled-components';
4 |
5 | const StyledButton = styled.button`
6 | color: ${(p) => p.theme.text};
7 | background: ${(p) => p.theme.colorPrimary};
8 | border: 4px solid ${(p) => p.theme.border};
9 | padding: 8px;
10 | border-radius: ${(p) => p.theme.borderRadius}px;
11 | `;
12 |
13 | export default () => {
14 | return (
15 |
16 |
21 | ThemeProvider2
22 |
23 |
24 |
31 | ThemeProvider1
32 |
33 |
34 | 不包裹 Provider
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/docs/demos/guide/styled/SetupStyled/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 全局设定 Styled 配置
3 | * description: 在入口文件中设定 styled 的 ThemeContext 配置,可以让所有 ThemeProvider 都能响应自定义 token
4 | * iframe: 100
5 | */
6 | import { setupStyled } from 'antd-style';
7 | import { ThemeContext } from 'styled-components';
8 |
9 | setupStyled({ ThemeContext });
10 |
11 | import App from './App';
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/docs/demos/guide/styled/StyledComponentsProps.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: styled-components
3 | * description: 只有注入了 styled-components 的 ThemeContext ,才能响应自定义 token
4 | * iframe: 100
5 | */
6 | import { ThemeProvider } from 'antd-style';
7 | import { Flexbox } from 'react-layout-kit';
8 | import { styled, ThemeContext } from 'styled-components';
9 |
10 | const StyledButton = styled.button`
11 | color: ${(p) => p.theme.text};
12 | background: ${(p) => p.theme.colorPrimary};
13 | border: 4px solid ${(p) => p.theme.border};
14 | padding: 8px;
15 | border-radius: ${(p) => p.theme.borderRadius}px;
16 | `;
17 |
18 | export default () => {
19 | return (
20 |
21 |
29 | 注入 styled-components 的 Provider
30 |
31 |
32 |
39 | 只包裹 ThemeProvider
40 |
41 |
42 | 不包裹 Provider
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/AntdTheme/CustomDark.tsx:
--------------------------------------------------------------------------------
1 | import { theme } from 'antd';
2 | import { ThemeProvider } from 'antd-style';
3 | import { MappingAlgorithm, ThemeConfig } from 'antd';
4 |
5 | import App from '../../../common/demo';
6 |
7 | /**
8 | * 自定义主题算法
9 | * @param seedToken
10 | * @param mapToken
11 | */
12 | const customDarkAlgorithm: MappingAlgorithm = (seedToken, mapToken) => {
13 | const mergeToken = theme.darkAlgorithm(seedToken, mapToken);
14 |
15 | return {
16 | ...mergeToken,
17 | // Layout 颜色
18 | colorBgLayout: '#20252b',
19 | // 容器颜色
20 | colorBgContainer: '#282c34',
21 | // 悬浮类面板颜色
22 | colorBgElevated: '#32363e',
23 | };
24 | };
25 |
26 | const darkThemeConfig: ThemeConfig = {
27 | token: { borderRadius: 2 },
28 | algorithm: [customDarkAlgorithm],
29 | };
30 |
31 | export default () => (
32 | (appearance === 'dark' ? darkThemeConfig : undefined)}
35 | >
36 |
37 |
38 | );
39 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/AntdTheme/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 900
3 | * title: 传入 Antd Theme 主题
4 | * description: 传入不同的 token 、算法组成的 theme ,可以实现主题风格的自定义
5 | */
6 | import { Divider, theme } from 'antd';
7 | import { ThemeProvider } from 'antd-style';
8 |
9 | import App from '../../../common/demo';
10 | import CustomDark from './CustomDark';
11 |
12 | const themeConfig = {
13 | token: { colorPrimary: '#000000' },
14 | algorithm: theme.compactAlgorithm,
15 | };
16 | export default () => {
17 | return (
18 | <>
19 |
20 |
21 |
22 | 定制主题: ☝️修改主色与紧凑模式 👇 自定义暗色风格
23 |
24 | >
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/AutoSwitch.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 460
3 | * title: 自动响应系统主题
4 | * description: 当切换系统的主题时,该 demo 会响应系统主题的切换,用户无需做任何操作
5 | */
6 | import { ThemeProvider } from 'antd-style';
7 |
8 | import App from '../../common/demo';
9 |
10 | export default () => {
11 | return (
12 |
13 |
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/ControlledSwitch/Controller.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | */
4 | import { Flexbox } from 'react-layout-kit';
5 |
6 | import { Card, Segmented } from 'antd';
7 | import { ThemeMode } from 'antd-style';
8 | import { useStore } from './useStore';
9 |
10 | const options = [
11 | { label: '自动', value: 'auto' },
12 | { label: '亮色', value: 'light' },
13 | { label: '暗色', value: 'dark' },
14 | ];
15 |
16 | export default () => {
17 | const themeMode = useStore();
18 |
19 | return (
20 |
21 |
22 | 主题模式:
23 | useStore.setState(v as ThemeMode)}
26 | options={options}
27 | />
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/ControlledSwitch/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 500
3 | * title: 受控模式下的主题切换
4 | * description: 该 Demo 中演示了受控模式的主题切换能力,使用了 zustand 作为全局状态管理方案。
5 | */
6 | import { ThemeProvider } from 'antd-style';
7 |
8 | import App from '../../../common/demo';
9 | import Controller from './Controller';
10 | import { useStore } from './useStore';
11 |
12 | export default () => {
13 | const themeMode = useStore();
14 |
15 | return (
16 |
17 | } />
18 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/ControlledSwitch/useStore.ts:
--------------------------------------------------------------------------------
1 | import { ThemeMode } from 'antd-style';
2 | import { create } from 'zustand';
3 |
4 | export const useStore = create(() => 'dark');
5 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/GlobalSwitch/Controller.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | */
4 | import { Card, Segmented } from 'antd';
5 | import { ThemeMode, useThemeMode } from 'antd-style';
6 | import { Flexbox } from 'react-layout-kit';
7 |
8 | const options = [
9 | { label: '自动', value: 'auto' },
10 | { label: '亮色', value: 'light' },
11 | { label: '暗色', value: 'dark' },
12 | ];
13 |
14 | export default () => {
15 | const { themeMode, setThemeMode } = useThemeMode();
16 |
17 | return (
18 |
19 |
20 | 主题模式:
21 | setThemeMode(v as ThemeMode)}
24 | options={options}
25 | />
26 |
27 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/GlobalSwitch/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 500
3 | * title: 使用 useThemeMode 切换主题
4 | * description: 该 Demo 中演示了使用 useThemeMode 切换主题的能力
5 | */
6 | import { ThemeProvider } from 'antd-style';
7 |
8 | import App from '../../../common/demo';
9 | import Controller from './Controller';
10 |
11 | export default () => {
12 | return (
13 |
14 | } />
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/docs/demos/guide/switch-theme/default.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 470
3 | */
4 | import { ThemeAppearance, ThemeProvider } from 'antd-style';
5 | import { useState } from 'react';
6 | import { Center, Flexbox } from 'react-layout-kit';
7 |
8 | import { Segmented } from 'antd';
9 | import App from '../../common/demo';
10 |
11 | const options = [
12 | { label: '亮色', value: 'light' },
13 | { label: '暗色', value: 'dark' },
14 | ];
15 |
16 | export default () => {
17 | const [appearance, setTheme] = useState('light');
18 | return (
19 |
20 |
21 | setTheme(v as ThemeAppearance)} options={options} />
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/docs/demos/migration/CSSinJSMode/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 200
3 | */
4 | import { SearchOutlined } from '@ant-design/icons';
5 | import { AutoComplete, Input, InputRef } from 'antd';
6 | import classNames from 'classnames';
7 | import React, { useRef, useState } from 'react';
8 |
9 | import useStyles from './style';
10 |
11 | const HeaderSearch: React.FC = () => {
12 | const { styles } = useStyles();
13 |
14 | const [searchMode, setSearchMode] = useState(false);
15 |
16 | const inputRef = useRef(null);
17 |
18 | const inputClass = classNames(styles.input, searchMode ? styles.show : '');
19 |
20 | return (
21 |
22 |
{
25 | setSearchMode(true);
26 | inputRef.current?.focus();
27 | }}
28 | >
29 |
35 |
36 | {
40 | setSearchMode(false);
41 | }}
42 | />
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | export default HeaderSearch;
50 |
--------------------------------------------------------------------------------
/docs/demos/migration/CSSinJSMode/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export default createStyles(({ token }) => ({
4 | container: {
5 | background: token.colorBgLayout,
6 | height: 200,
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | },
11 | headerSearch: {
12 | display: 'inline-flex',
13 | alignItems: 'center',
14 | },
15 | input: {
16 | width: '0',
17 | minWidth: '0',
18 | overflow: 'hidden',
19 | background: 'transparent',
20 | borderRadius: '0',
21 | transition: 'width 0.3s, margin-left 0.3s',
22 | input: { boxShadow: 'none !important' },
23 | },
24 | show: { width: '210px', marginLeft: '8px' },
25 | }));
26 |
--------------------------------------------------------------------------------
/docs/demos/migration/LessMode/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | background: #f5f5f5;
3 | height: 200px;
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | }
8 |
9 | .headerSearch {
10 | display: inline-flex;
11 | align-items: center;
12 |
13 | .input {
14 | width: 0;
15 | min-width: 0;
16 | overflow: hidden;
17 | background: transparent;
18 | border-radius: 0;
19 | transition: width 0.3s, margin-left 0.3s;
20 |
21 | input {
22 | box-shadow: none !important;
23 | }
24 |
25 | &.show {
26 | width: 210px;
27 | margin-left: 8px;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/docs/demos/migration/LessMode/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 200
3 | */
4 | import { SearchOutlined } from '@ant-design/icons';
5 | import { AutoComplete, Input, InputRef } from 'antd';
6 | import classNames from 'classnames';
7 | import { FC, useRef, useState } from 'react';
8 |
9 | // @ts-ignore
10 | import styles from './index.module.less';
11 |
12 | const HeaderSearch: FC = () => {
13 | const [searchMode, setSearchMode] = useState(false);
14 |
15 | const inputRef = useRef(null);
16 |
17 | const inputClass = classNames(styles.input, searchMode ? styles.show : '');
18 |
19 | return (
20 |
21 |
{
24 | setSearchMode(true);
25 | inputRef.current?.focus();
26 | }}
27 | >
28 |
34 |
35 | {
39 | setSearchMode(false);
40 | }}
41 | />
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default HeaderSearch;
49 |
--------------------------------------------------------------------------------
/docs/demos/migration/ProComponentsStatic/CSSinJS.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | */
4 | import styled from '@emotion/styled';
5 | import { App } from 'antd';
6 | import { ThemeProvider } from 'antd-style';
7 | import { Flexbox } from 'react-layout-kit';
8 |
9 | import Statistic from './CSSinJSComponent';
10 |
11 | const Container = styled(Flexbox)`
12 | background: ${(p) => p.theme.colorBgLayout};
13 | `;
14 | export default () => {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/docs/demos/migration/ProComponentsStatic/less.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * compact: true
3 | */
4 | import { App, ConfigProvider, theme } from 'antd';
5 | import { Flexbox } from 'react-layout-kit';
6 | import Statistic from './LessComponent';
7 |
8 | export default () => {
9 | return (
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/docs/demos/styled/basic.tsx:
--------------------------------------------------------------------------------
1 | import DsAvatar from '../common/Typography/Avatar';
2 |
3 | export default () => (
4 |
5 |
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/docs/demos/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.tsx", "**/*.ts"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "declaration": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "jsx": "react-jsx",
9 | "lib": ["DOM", "ESNext"],
10 | "baseUrl": ".",
11 | "paths": {
12 | "antd-style": ["../../es"]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/docs/demos/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { Theme as AntdStyleTheme } from 'antd-style';
2 |
3 | // 为 emotion 的 styled 注入 antd-style 的主题类型
4 | declare module '@emotion/react' {
5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
6 | export interface Theme extends AntdStyleTheme {}
7 | }
8 |
--------------------------------------------------------------------------------
/docs/guide/babel-plugin.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Babel Plugin
3 | order: 1
4 | group: Advanced Usage
5 | ---
6 |
7 | # Babel Plugin
8 |
9 | We provide a Babel plugin for antd-style to enhance the development experience.
10 |
11 | ```bash
12 | pnpm i -D babel-plugin-antd-style
13 | ```
14 |
15 | ## Configuration
16 |
17 | `.babelrc` configuration:
18 |
19 | ```json
20 | {
21 | "plugins": ["antd-style"]
22 | }
23 | ```
24 |
25 | umi/dumi configuration:
26 |
27 | ```js
28 | export default defineConfig({
29 | extraBabelPlugins: [require.resolve('babel-plugin-antd-style')],
30 | });
31 | ```
32 |
33 | ## Features
34 |
35 | ### Support for className resolution
36 |
37 | After installing the Babel plugin, className will be resolved to the corresponding style file directory, effective only in development.
38 |
39 | 
40 |
--------------------------------------------------------------------------------
/docs/guide/babel-plugin.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Babel 插件
3 | order: 1
4 | group: 进阶使用
5 | ---
6 |
7 | # Babel 插件
8 |
9 | 我们提供了 antd-style 的 Babel 插件,以提升研发体验。
10 |
11 | ```bash
12 | pnpm i -D babel-plugin-antd-style
13 | ```
14 |
15 | ## 配置
16 |
17 | `.babelrc` 配置:
18 |
19 | ```json
20 | {
21 | "plugins": ["antd-style"]
22 | }
23 | ```
24 |
25 | umi/dumi 配置:
26 |
27 | ```js
28 | export default defineConfig({
29 | extraBabelPlugins: [require.resolve('babel-plugin-antd-style')],
30 | });
31 | ```
32 |
33 | ## 功能
34 |
35 | ### 支持 className 定位
36 |
37 | 安装 Babel 插件后,className 将会定位到对应的样式文件目录,仅 dev 生效。
38 |
39 | 
40 |
--------------------------------------------------------------------------------
/docs/guide/cssinjs-compiler-difference.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 Compilation Differences Between CSS-in-JS and Less
3 | order: 10
4 | group:
5 | title: Migrating from Less
6 | order: 4
7 | ---
8 |
9 | # Compilation Differences Between CSS-in-JS and Less
10 |
11 | TODO: This document is still being written. Pull requests are welcome.
12 |
--------------------------------------------------------------------------------
/docs/guide/cssinjs-compiler-difference.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 CSSinJS 与 Less 的编译差异
3 | order: 10
4 | group:
5 | title: 从 Less 迁移
6 | order: 4
7 | ---
8 |
9 | # CSSinJS 与 Less 编译差异
10 |
11 | TODO: 本文档还在编写中,欢迎 PR。
12 |
--------------------------------------------------------------------------------
/docs/guide/demos/benchmark/dynamic-value.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 600
3 | */
4 | export default () => (
5 |
9 | );
10 |
--------------------------------------------------------------------------------
/docs/guide/demos/benchmark/large-content.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * iframe: 600
3 | */
4 | export default () => (
5 |
9 | );
10 |
--------------------------------------------------------------------------------
/docs/guide/demos/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.tsx", "**/*.ts"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "declaration": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "jsx": "react-jsx",
9 | "lib": ["DOM", "ESNext"],
10 | "baseUrl": ".",
11 | "paths": {
12 | "antd-style": ["../../../es"]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/docs/guide/index.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | order: 0
4 | nav:
5 | title: Quick Start
6 | order: 0
7 | group:
8 | title: Basic Knowledge
9 | order: 0
10 | ---
11 |
12 | # Introduction
13 |
14 | `antd-style` is a business-level css-in-js solution built on the Ant Design V5 Token System, and it is based on the secondary encapsulation of emotion.
15 |
16 | ## Motivation | Why this library exists
17 |
18 | Antd v5 has been officially released, bringing unparalleled theme customization capabilities through the CSSinJS technology, which we believe is the future direction. However, at present, both internal application landing and community feedback on how to use the antd v5 token system, how to migrate from less, and how to integrate cssinjs into applications have made it difficult for application developers to start using the CSSinJS technology to write styles.
19 |
20 | As a component library, antd's responsibility and boundary are only to provide high-quality basic components, and it does not restrict how the application layer uses style solutions. Developers can use less/sass, styled-component, and other solutions without any problems. However, in order to make the promotion of the v5 token system smoother, we need to provide a best practice for using the antd token system, helping application developers to integrate the CSSinJS technology solution into applications with lower thresholds, and enjoy the UX and DX upgrades brought by new technologies.
21 |
22 | ## Features | What it can do
23 |
24 | It has the following features:
25 |
26 |
27 |
28 | ## What is the difference between it and @ant-design/cssinjs?
29 |
30 | `@ant-design/cssinjs` is a cssinjs solution for implementing the antd component library. It achieves much better performance compared to styled-component and emotion through a more cumbersome syntax. See: [Component-level CSS-in-JS](https://ant.design/docs/blog/css-in-js-cn). However, for applications and component libraries based on antd, this syntax may be too cumbersome and complex, and lacks the ability to consume the antd token system.
31 |
32 | Therefore, the applicable scenarios for antd-style are business applications and component libraries based on antd secondary encapsulation, providing all the capabilities needed for these two scenarios.
33 |
--------------------------------------------------------------------------------
/docs/guide/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 简介
3 | order: 0
4 | nav:
5 | title: 快速上手
6 | order: 0
7 | group:
8 | title: 基础知识
9 | order: 0
10 | ---
11 |
12 | # 简介
13 |
14 | `antd-style` 是基于 Ant Design V5 Token System 构建的业务级 css-in-js 解决方案,它基于 emotion 二次封装。
15 |
16 | ## 动机 | 为什么会有这个库
17 |
18 | antd v5 已正式发布,通过 CSSinJS 的技术带来了无与伦比的主题自定义能力,这也是我们所认为的未来方向。但目前来看,无论是内部落地应用,还是社区的相关反馈,关于 antd v5 token 系统如何使用、less 怎么迁移、应用如何集成 cssinjs 等问题,都让应用开发者较难开始使用 CSSinJS 的技术来书写样式。
19 |
20 | antd 作为一个组件库,它的职责和边界只在于提供高品质的基础组件,应用层如何使用样式方案, antd 并不限制。开发者可以使用 less/sass、styled-component 等方案都没有任何问题。 但为了将 v5 的 token 系统的推行变得更加顺利,我们需要提供一个使用 antd token 系统的最佳实践,帮助应用开发者更低门槛在应用中集成 CSSinJS 技术方案,享受新技术所带来的 UX 和 DX 升级。
21 |
22 | ## 特性 | 它能干什么
23 |
24 | 它具备以下特性:
25 |
26 |
27 |
28 | ## 它和 @ant-design/cssinjs 的区别是什么?
29 |
30 | `@ant-design/cssinjs` 是实现 antd 组件库的一套 cssinjs 方案,它通过比较繁琐的写法换得了相比 styled-component 和 emotion 都要好很多的性能。详见:[组件级别的 CSS-in-JS](https://ant.design/docs/blog/css-in-js-cn)。但对应用和基于 antd 封装的组件库中,这种写法可能过于繁琐和复杂,且缺少消费 antd token 系统的能力。
31 |
32 | 所以 antd-style 的适用场景是业务应用和基于 antd 二次封装的组件库,它会提供这两个场景所需要的所有能力。
33 |
--------------------------------------------------------------------------------
/docs/guide/migrate-less-codemod.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Less Application Automated Migration
3 | order: 0
4 | group: Migrating from Less
5 | ---
6 |
7 | # Migrate Less Applications with Codemod
8 |
9 | To facilitate the unified upgrade of business applications, we provide a one-click migration codemod from less to antd-style.
10 |
11 | ## Usage
12 |
13 | Simply execute the following command in the project root directory:
14 |
15 | ```bash
16 | npx @chenshuai2144/less2cssinjs less2js -i src
17 | ```
18 |
19 | Where `src` is the project directory.
20 |
21 | 
22 |
23 | ## Transformation Logic
24 |
25 | In this codemod, we will perform the following transformations:
26 |
27 | For less files:
28 |
29 | 1. Create a new `[file].style.ts` file, where `file` is the name of the less file.
30 | 2. Convert the styles in the less file to the css object syntax in antd-style.
31 | 3. Flatten the nested syntax in less to a single level.
32 | 4. Automatically replace less variables with antd-style tokens.
33 |
34 | For ts files:
35 |
36 | 1. Replace import less with `import useStyles from '[file].style'`.
37 | 2. Add `const { style } = useStyles()`.
38 |
39 | ## Notes
40 |
41 | TBD
42 |
--------------------------------------------------------------------------------
/docs/guide/migrate-less-codemod.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Less 应用自动化迁移
3 | order: 0
4 | group: 从 Less 迁移
5 | ---
6 |
7 | # 使用 codemod 一键 迁移 Less 应用
8 |
9 | 为方便业务应用的统一升级,我们提供了 less to antd-style 的一键迁移 codemod。
10 |
11 | ## 使用方法
12 |
13 | 直接在项目根目录,执行以下指令即可:
14 |
15 | ```bash
16 | npx @chenshuai2144/less2cssinjs less2js -i src
17 | ```
18 |
19 | src 为项目所在目录
20 |
21 | 
22 |
23 | ## 转换逻辑
24 |
25 | 在这个 codemod 中,我们会做以下转换:
26 |
27 | 针对 less 文件:
28 |
29 | 1. 创建一个新的 `[file].style.ts` 文件,file 使用 less 文件名;
30 | 2. 将 less 文件中的样式转换为 antd-style 中的 css object 语法;
31 | 3. less 中的嵌套层级的语法将会拍平到一级;
32 | 4. less 中的 less 变量将会被自动替换为 antd-style 中的 token;
33 |
34 | 针对 ts 文件:
35 |
36 | 1. 将 import less 替换为 `import useStyles form '[file].style'`;
37 | 2. 添加 `const { style } = useStyles()`;
38 |
39 | ## 注意事项
40 |
41 | TBD
42 |
--------------------------------------------------------------------------------
/docs/guide/performance-comparsion.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Performance Comparison of CSS-in-JS Libraries
3 | order: 100
4 | group: Advanced Usage
5 | ---
6 |
7 | # Performance Comparison of CSS-in-JS
8 |
9 | ## Basic Rendering Performance Comparison
10 |
11 | ```tsx | inline
12 | import { FullscreenOutlined } from '@ant-design/icons';
13 | import { Button } from 'antd';
14 |
15 | export default () => (
16 |
17 | }>
18 | Click to view in full screen
19 |
20 |
21 | );
22 | ```
23 |
24 |
25 |
26 | ## Dynamic Value Rendering Performance Comparison
27 |
28 | ```tsx | inline
29 | import { FullscreenOutlined } from '@ant-design/icons';
30 | import { Button } from 'antd';
31 |
32 | export default () => (
33 |
34 | }>Click to view in full screen
35 |
36 | );
37 | ```
38 |
39 |
40 |
--------------------------------------------------------------------------------
/docs/guide/performance-comparsion.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: CSSinJS 样式库性能对比
3 | order: 100
4 | group: 进阶使用
5 | ---
6 |
7 | # CSS-in-JS 性能对比
8 |
9 | ## 基础渲染性能对比
10 |
11 | ```tsx | inline
12 | import { FullscreenOutlined } from '@ant-design/icons';
13 | import { Button } from 'antd';
14 |
15 | export default () => (
16 |
17 | }>
18 | 点击全屏查看
19 |
20 |
21 | );
22 | ```
23 |
24 |
25 |
26 | ## 动态值渲染性能对比
27 |
28 | ```tsx | inline
29 | import { FullscreenOutlined } from '@ant-design/icons';
30 | import { Button } from 'antd';
31 |
32 | export default () => (
33 |
34 | }>点击全屏查看
35 |
36 | );
37 | ```
38 |
39 |
40 |
--------------------------------------------------------------------------------
/docs/guide/stylish.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 Stylish Composite Styles
3 | order: 20
4 | group: Advanced Usage
5 | ---
6 |
7 | # Organizing Composite Styles with Stylish
8 |
9 | TBD
10 |
--------------------------------------------------------------------------------
/docs/guide/stylish.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚧 stylish 复合样式
3 | order: 20
4 | group: 进阶使用
5 | ---
6 |
7 | # 使用 stylish 组织复合样式
8 |
9 | TBD
10 |
--------------------------------------------------------------------------------
/public/fonts/RocherColorGX.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ant-design/antd-style/1f713910af666064a565a5aa02885011d69d30e0/public/fonts/RocherColorGX.woff2
--------------------------------------------------------------------------------
/src/context/ThemeModeContext.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { useContext } from 'react';
3 | import { vi } from 'vitest';
4 |
5 | import { ThemeModeContext } from './ThemeModeContext';
6 |
7 | describe('ThemeModeContext', () => {
8 | it('should have default values', () => {
9 | const { result } = renderHook(() => useContext(ThemeModeContext));
10 | expect(result.current.appearance).toEqual('light');
11 | expect(result.current.isDarkMode).toEqual(false);
12 | expect(result.current.themeMode).toEqual('light');
13 | expect(result.current.browserPrefers).toEqual('light');
14 | expect(result.current.setAppearance).toBeDefined();
15 | expect(result.current.setThemeMode).toBeDefined();
16 | });
17 |
18 | it('should return false when window is undefined', () => {
19 | const matchMedia = vi.fn();
20 | Object.defineProperty(window, 'matchMedia', { value: matchMedia });
21 |
22 | const { result } = renderHook(() => useContext(ThemeModeContext));
23 | expect(result.current.browserPrefers).toEqual('light');
24 | expect(matchMedia).not.toHaveBeenCalled();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/src/context/ThemeModeContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | import { ThemeContextState } from '@/types';
4 | import { matchBrowserPrefers } from '@/utils/matchBrowserPrefers';
5 |
6 | export const ThemeModeContext = createContext({
7 | appearance: 'light',
8 | setAppearance: () => {},
9 | isDarkMode: false,
10 | themeMode: 'light',
11 | setThemeMode: () => {},
12 | browserPrefers: matchBrowserPrefers('dark')?.matches ? 'dark' : 'light',
13 | });
14 |
--------------------------------------------------------------------------------
/src/context/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ThemeModeContext';
2 |
--------------------------------------------------------------------------------
/src/core/CacheManager.ts:
--------------------------------------------------------------------------------
1 | import { cache } from '@emotion/css';
2 | import { EmotionCache } from '@emotion/css/create-instance';
3 |
4 | export class CacheManager {
5 | private _cacheList: EmotionCache[] = [cache];
6 |
7 | add(cache: EmotionCache): EmotionCache {
8 | const existCache = this.getCache(cache.key);
9 | if (existCache) {
10 | return existCache;
11 | } else {
12 | this._cacheList.push(cache);
13 | return cache;
14 | }
15 | }
16 |
17 | delete(cache: EmotionCache) {
18 | this._cacheList = this._cacheList.filter((c) => c.key !== cache.key);
19 | }
20 |
21 | hasCache(cache: EmotionCache) {
22 | return this._cacheList.some((c) => c.key === cache.key);
23 | }
24 |
25 | getCache(key: string) {
26 | return this._cacheList.find((c) => c.key === key);
27 | }
28 |
29 | getCacheList() {
30 | return this._cacheList;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/core/createCSS.ts:
--------------------------------------------------------------------------------
1 | import { createHashStyleName, insertStyles } from '@/core/insertStyles';
2 | import { ClassNameGenerator, ClassNameGeneratorOption, ClassNamesUtil } from '@/types';
3 | import { classnames, isReactCssResult, mergeCSS } from '@/utils';
4 | import { EmotionCache } from '@emotion/css/create-instance';
5 | import { serializeStyles } from '@emotion/serialize';
6 |
7 | const createClassNameGenerator =
8 | (cache: EmotionCache, options: ClassNameGeneratorOption): ClassNameGenerator =>
9 | (...args) => {
10 | // @ts-ignore
11 | const serialized = serializeStyles(args, cache.registered, undefined);
12 |
13 | insertStyles(cache, serialized, false, options);
14 |
15 | return createHashStyleName(cache.key, serialized.name);
16 | };
17 |
18 | const createCX =
19 | (cache: EmotionCache, classNameGenerator: ClassNameGenerator): ClassNamesUtil =>
20 | (...classNames) => {
21 | // 由于使用了 reactCss 作为基础样式工具,因此在使用 cx 级联 className 时需要使用特殊处理的 cx
22 | // 要将 reactCss 的产出转为 css 产物
23 | const className = classNames.map((c) => (isReactCssResult(c) ? classNameGenerator(c) : c));
24 |
25 | return mergeCSS(cache.registered, classNameGenerator, classnames(className));
26 | };
27 |
28 | /**
29 | * CSS相关方法生成器 用于序列化的样式转换生成 className
30 | * @param cache
31 | * @param options
32 | */
33 | export const createCSS = (cache: EmotionCache, options: ClassNameGeneratorOption) => {
34 | const css = createClassNameGenerator(cache, {
35 | hashPriority: options.hashPriority || 'high',
36 | label: options.label,
37 | });
38 |
39 | const cx = createCX(cache, css);
40 |
41 | return { css, cx };
42 | };
43 |
--------------------------------------------------------------------------------
/src/core/createEmotion.ts:
--------------------------------------------------------------------------------
1 | export { default as createEmotion, type Emotion } from '@emotion/css/create-instance';
2 |
--------------------------------------------------------------------------------
/src/core/createSerializeStyles.ts:
--------------------------------------------------------------------------------
1 | import { CSSInterpolation, SerializedStyles, serializeStyles } from '@emotion/serialize';
2 |
3 | /**
4 | * @title CSS 序列化函数
5 | * @param template - 模板字符串数组
6 | * @param args - CSS 插值数组
7 | * @returns CSS 序列化后的样式
8 | */
9 | export interface SerializeCSS {
10 | (template: TemplateStringsArray, ...args: Array): SerializedStyles;
11 | (...args: Array): SerializedStyles;
12 | }
13 | /**
14 | * 提供给 createStyles 方法,用于将用户写入的 css 字符串序列化成特定结构的样式对象
15 | * @param args
16 | */
17 | export const serializeCSS: SerializeCSS = (...args) =>
18 | // @ts-ignore
19 | serializeStyles(args);
20 |
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CacheManager';
2 | export * from './createCSS';
3 | export * from './createEmotion';
4 | export { serializeCSS, type SerializeCSS } from './createSerializeStyles';
5 |
6 | export const DEFAULT_CSS_PREFIX_KEY = 'acss';
7 |
--------------------------------------------------------------------------------
/src/core/insertStyles.ts:
--------------------------------------------------------------------------------
1 | // copied from https://github.com/emotion-js/emotion/blob/main/packages/utils/src/index.js
2 | import { ClassNameGeneratorOption } from '@/types';
3 | import type { EmotionCache } from '@emotion/css/create-instance';
4 | import type { SerializedStyles } from '@emotion/serialize';
5 | import { registerStyles } from '@emotion/utils';
6 |
7 | const isBrowser = typeof document !== 'undefined';
8 |
9 | export const createHashStyleName = (cacheKey: string, hash: string) => `${cacheKey}-${hash}`;
10 |
11 | /**
12 | * 向浏览器插入样式表
13 | * @param cache
14 | * @param serialized
15 | * @param isStringTag
16 | * @param options
17 | */
18 | export const insertStyles = (
19 | cache: EmotionCache,
20 | serialized: SerializedStyles,
21 | isStringTag: boolean,
22 | options: ClassNameGeneratorOption,
23 | ) => {
24 | const hashPriority = options.hashPriority || 'high';
25 | registerStyles(cache, serialized, isStringTag);
26 |
27 | const hashClassName = `.${createHashStyleName(cache.key, serialized.name)}`;
28 |
29 | const hashSelector = hashPriority === 'low' ? `:where(${hashClassName})` : hashClassName;
30 |
31 | /* c8 ignore start */
32 | if (cache.inserted[serialized.name] === undefined) {
33 | let stylesForSSR = '';
34 | let current = serialized;
35 | do {
36 | let maybeStyles = cache.insert(
37 | serialized === current ? hashSelector : '',
38 | current,
39 | cache.sheet,
40 | true,
41 | );
42 | if (!isBrowser && maybeStyles !== undefined) {
43 | stylesForSSR += maybeStyles;
44 | }
45 | // @ts-ignore
46 | current = current.next;
47 | } while (current !== undefined);
48 |
49 | if (!isBrowser && stylesForSSR.length !== 0) {
50 | return stylesForSSR;
51 | }
52 | }
53 | };
54 | /* c8 ignore end */
55 |
--------------------------------------------------------------------------------
/src/factories/createEmotionContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | import { Emotion } from '@/core';
4 |
5 | export const createEmotionContext = (emotion: Emotion) => createContext(emotion);
6 |
--------------------------------------------------------------------------------
/src/factories/createGlobalStyle.tsx:
--------------------------------------------------------------------------------
1 | import { Global } from '@emotion/react';
2 | import { serializeStyles } from '@emotion/serialize';
3 | import { memo } from 'react';
4 |
5 | import { CSSStyle, Theme } from '@/types';
6 |
7 | export interface GlobalTheme {
8 | theme: Theme;
9 | }
10 |
11 | /**
12 | * 创建全局样式
13 | */
14 | export const createGlobalStyleFactory =
15 | (useTheme: () => Theme) =>
16 | (...styles: CSSStyle) =>
17 | memo((props) => {
18 | const theme = useTheme();
19 | return ;
20 | });
21 |
--------------------------------------------------------------------------------
/src/factories/createStyish.ts:
--------------------------------------------------------------------------------
1 | import { BaseReturnType, ReturnStyleToUse } from '@/types';
2 | import { StyleOrGetStyleFn } from './createStyles/types';
3 |
4 | // FIXME: 需要考虑如何将 createStylish 和 ThemeProvider 中的 customStylish 方法整合在一起,现在是割裂的两个方法
5 |
6 | /**
7 | * 业务应用中创建复合通用样式的进阶
8 | */
9 | export const createStylishFactory =
10 | (createStyles: any) =>
11 | (
12 | cssStyleOrGetCssStyleFn: StyleOrGetStyleFn,
13 | ): ((props?: Props) => ReturnStyleToUse) => {
14 | const useStyles = createStyles(cssStyleOrGetCssStyleFn);
15 |
16 | return (props?: Props): ReturnStyleToUse => {
17 | const { styles } = useStyles(props);
18 |
19 | return styles;
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/src/factories/createStyledThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | import { StyledConfig, StyledThemeProvider } from '@/types';
2 |
3 | /**
4 | * 创建一个 styled api 的 ThemeProvider
5 | * 如果用户有设定 ThemeProvider,就使用用户的,否则使用 ThemeContext.Provider
6 | * @param styledConfig
7 | */
8 | export const createStyledThemeProvider = (styledConfig: StyledConfig): StyledThemeProvider => {
9 | if (styledConfig.ThemeProvider) return styledConfig.ThemeProvider;
10 |
11 | const { ThemeContext } = styledConfig;
12 | return (props) => (
13 | {props.children}
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/factories/createStyles/response.ts:
--------------------------------------------------------------------------------
1 | import { serializeCSS } from '@/core';
2 | import { useAntdToken } from '@/hooks';
3 | import type {
4 | Breakpoint,
5 | BreakpointMapParams,
6 | CSSObject,
7 | ResponsiveMap,
8 | SerializedStyles,
9 | } from '@/types';
10 | import { isReactCssResult } from '@/utils';
11 | import { convertBreakpointToResponsive } from '@/utils/responsive';
12 | import { useMemo } from 'react';
13 |
14 | export const useMediaQueryMap = (): ResponsiveMap => {
15 | const token = useAntdToken();
16 |
17 | const breakpoints: Record = {
18 | xs: `@media (max-width: ${token.screenXSMax}px)`,
19 | sm: `@media (max-width: ${token.screenSMMax}px)`,
20 | md: `@media (max-width: ${token.screenMDMax}px)`,
21 | lg: `@media (max-width: ${token.screenLGMax}px)`,
22 | xl: `@media (max-width: ${token.screenXLMax}px)`,
23 | xxl: `@media (min-width: ${token.screenXXLMin}px)`,
24 | };
25 |
26 | return useMemo(() => convertBreakpointToResponsive(breakpoints), [token]);
27 | };
28 |
29 | /**
30 | * 将响应式对象转换为字符串
31 | * @param obj
32 | * @param map
33 | */
34 | export const convertResponsiveStyleToString = (
35 | obj: BreakpointMapParams,
36 | map: ResponsiveMap,
37 | ): any => {
38 | return Object.entries(obj)
39 | .map(([key, value]) => {
40 | let str = value as SerializedStyles | CSSObject;
41 |
42 | if (!isReactCssResult(value)) {
43 | str = serializeCSS(value);
44 | }
45 |
46 | // @ts-ignore
47 | return map[key] ? `${map[key]} {${str.styles}}` : '';
48 | })
49 | .join('');
50 | };
51 |
--------------------------------------------------------------------------------
/src/factories/createStyles/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | BaseReturnType,
3 | CommonStyleUtils,
4 | FullStylish,
5 | FullToken,
6 | ReturnStyleToUse,
7 | Theme,
8 | ThemeAppearance,
9 | } from '@/types';
10 |
11 | /**
12 | * 书写样式时使用的第一个参数
13 | */
14 | export interface CreateStylesUtils extends CommonStyleUtils {
15 | /**
16 | * 包含 antd 的 token 和所有自定义 token
17 | */
18 | token: FullToken;
19 | stylish: FullStylish;
20 | /**
21 | * ThemeProvider 下当前的主题模式
22 | */
23 | appearance: ThemeAppearance;
24 | /**
25 | * appearance === 'dark' 的语法糖,可以直接使用 isDarkMode 来降低外观的判断成本
26 | */
27 | isDarkMode: boolean;
28 | /**
29 | * 在 ThemeProvider 上标记的 prefix,可以拿到当前的 组件 prefix
30 | * 便于更加灵活地响应组件 prefix
31 | * @default ant
32 | */
33 | prefixCls: string;
34 | iconPrefixCls: string;
35 | }
36 |
37 | /**
38 | * 最终返回 styles 对象的类型定义
39 | */
40 | export interface ReturnStyles extends Pick {
41 | styles: ReturnStyleToUse;
42 | theme: Omit;
43 | iconPrefixCls: string;
44 | prefixCls: string;
45 | }
46 |
47 | // 获取样式
48 | export type GetStyleFn = (
49 | utils: CreateStylesUtils,
50 | props: Props,
51 | ) => Input;
52 |
53 | /**
54 | * 创建样式的函数或者对象
55 | * 可以传入 StyleObject 或者 ()=> StyleObject 函数
56 | * StyleObject 可以是
57 | */
58 | export type StyleOrGetStyleFn =
59 | | Input
60 | | GetStyleFn;
61 |
--------------------------------------------------------------------------------
/src/factories/createUseTheme.ts:
--------------------------------------------------------------------------------
1 | import { StyleEngine, Theme } from '@/types';
2 | import { Context, useContext, useMemo } from 'react';
3 |
4 | import { DEFAULT_THEME_CONTEXT } from '@/functions/setupStyled';
5 | import { useAntdTheme } from '@/hooks/useAntdTheme';
6 | import { useThemeMode } from '@/hooks/useThemeMode';
7 | import { ConfigProvider } from 'antd';
8 |
9 | interface CreateUseThemeOptions {
10 | StyleEngineContext: Context;
11 | }
12 |
13 | export const createUseTheme = (options: CreateUseThemeOptions) => (): Theme => {
14 | const { StyleEngineContext } = options;
15 | const {
16 | StyledThemeContext,
17 | CustomThemeContext,
18 | prefixCls: outPrefixCls,
19 | } = useContext(StyleEngineContext);
20 |
21 | const antdTheme = useAntdTheme();
22 | const themeState = useThemeMode();
23 |
24 | const defaultCustomTheme = useContext(CustomThemeContext);
25 | const styledTheme = useContext(StyledThemeContext ?? DEFAULT_THEME_CONTEXT) || {};
26 |
27 | const { iconPrefixCls, getPrefixCls } = useContext(ConfigProvider.ConfigContext);
28 |
29 | const antdPrefixCls = getPrefixCls();
30 | // 只有当用户在 createInstance 中传入与 ant 不一样的 prefixCls 时,才会使用用户的 prefixCls
31 | // 否则其他情况下都优先使用 antd 的 prefixCls
32 | const prefixCls = outPrefixCls && outPrefixCls !== 'ant' ? outPrefixCls : antdPrefixCls;
33 |
34 | const initTheme = useMemo(
35 | () => ({
36 | ...antdTheme,
37 | ...themeState,
38 | ...defaultCustomTheme,
39 | prefixCls,
40 | iconPrefixCls,
41 | }),
42 | [antdTheme, themeState, defaultCustomTheme, prefixCls, iconPrefixCls],
43 | );
44 |
45 | // 如果是个空值,说明没有套 Provider,返回 antdTheme 的默认值
46 | if (!styledTheme || Object.keys(styledTheme).length === 0) {
47 | return initTheme;
48 | }
49 |
50 | return { ...styledTheme, prefixCls, iconPrefixCls } as Theme;
51 | };
52 |
--------------------------------------------------------------------------------
/src/functions/index.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_CSS_PREFIX_KEY } from '@/core';
2 | import { createInstance } from './createInstance';
3 |
4 | export { extractStaticStyle } from './extractStaticStyle';
5 | export { setupStyled } from './setupStyled';
6 | export { createInstance };
7 |
8 | const styleInstance = createInstance({ key: DEFAULT_CSS_PREFIX_KEY, speedy: false });
9 |
10 | export const {
11 | // **** 样式生成相关 **** //
12 | createStyles,
13 | createGlobalStyle,
14 | createStylish,
15 | // **** 基础样式方法 **** //
16 | css,
17 | cx,
18 | keyframes,
19 | /**
20 | * @deprecated
21 | */
22 | injectGlobal,
23 | //**** 样式表管理 **** //
24 | styleManager,
25 | // ***** 主题相关 ***** //
26 | ThemeProvider,
27 | StyleProvider,
28 | useTheme,
29 | } = styleInstance;
30 |
--------------------------------------------------------------------------------
/src/functions/setupStyled.test.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'react';
2 |
3 | import { StyledConfig, StyledThemeProvider, Theme } from '@/types';
4 | import { DEFAULT_THEME_CONTEXT, setupStyled } from './setupStyled';
5 |
6 | describe('setupStyled', () => {
7 | it('should throw an error if ThemeContext is not provided in config', () => {
8 | expect(() => setupStyled({} as StyledConfig)).toThrow(
9 | 'ThemeContext is required. Please check your config.',
10 | );
11 | });
12 |
13 | it('should update the default theme provider and context with provided config', () => {
14 | const provider = { hello: 1 } as any;
15 | const ThemeContext = { Provider: provider } as Context;
16 | const customThemeProvider = {} as StyledThemeProvider;
17 | const config = {
18 | ThemeContext,
19 | createThemeProvider: () => customThemeProvider,
20 | } as StyledConfig;
21 |
22 | setupStyled(config);
23 |
24 | expect(DEFAULT_THEME_CONTEXT).toEqual(ThemeContext);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/src/functions/setupStyled.ts:
--------------------------------------------------------------------------------
1 | import { ThemeContext, ThemeProvider } from '@emotion/react';
2 | import { Context } from 'react';
3 |
4 | import { createStyledThemeProvider } from '@/factories/createStyledThemeProvider';
5 | import { StyledConfig, StyledThemeProvider, Theme } from '@/types';
6 |
7 | export let DEFAULT_THEME_PROVIDER = ThemeProvider as StyledThemeProvider;
8 | export let DEFAULT_THEME_CONTEXT = ThemeContext as Context;
9 |
10 | export const setupStyled = (config: StyledConfig) => {
11 | if (!config.ThemeContext) {
12 | throw 'ThemeContext is required. Please check your config.';
13 | }
14 |
15 | DEFAULT_THEME_CONTEXT = config.ThemeContext;
16 | DEFAULT_THEME_PROVIDER = createStyledThemeProvider(config);
17 | };
18 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useAntdStylish';
2 | export * from './useAntdTheme';
3 | export * from './useAntdToken';
4 | export * from './useResponsive';
5 | export * from './useThemeMode';
6 |
--------------------------------------------------------------------------------
/src/hooks/useAntdStylish.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 |
3 | import { serializeCSS } from '@/core';
4 | import { createAntdStylish } from '@/stylish/button';
5 | import { AntdStylish } from '@/types';
6 |
7 | import { convertStylishToString } from '@/utils/convertStylish';
8 | import { useAntdToken } from './useAntdToken';
9 | import { useThemeMode } from './useThemeMode';
10 |
11 | export const useAntdStylish = (): AntdStylish => {
12 | const token = useAntdToken();
13 | const { appearance, isDarkMode } = useThemeMode();
14 |
15 | return useMemo(
16 | () =>
17 | convertStylishToString(
18 | createAntdStylish({ token, css: serializeCSS, appearance, isDarkMode }),
19 | ),
20 | [token, appearance, isDarkMode],
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/src/hooks/useAntdTheme.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 |
3 | import { AntdTheme } from '@/types';
4 | import { useAntdStylish } from './useAntdStylish';
5 | import { useAntdToken } from './useAntdToken';
6 |
7 | export const useAntdTheme = (): AntdTheme => {
8 | const token = useAntdToken();
9 | const stylish = useAntdStylish();
10 |
11 | return useMemo(() => ({ ...token, stylish }), [token, stylish]);
12 | };
13 |
--------------------------------------------------------------------------------
/src/hooks/useAntdToken.ts:
--------------------------------------------------------------------------------
1 | import { theme } from 'antd';
2 |
3 | import { AntdToken } from '@/types';
4 |
5 | export const useAntdToken = (): AntdToken => {
6 | const { token } = theme.useToken();
7 |
8 | return token;
9 | };
10 |
--------------------------------------------------------------------------------
/src/hooks/useResponsive.ts:
--------------------------------------------------------------------------------
1 | import { ResponsiveKey } from '@/types';
2 | import { convertBreakpointToResponsive } from '@/utils/responsive';
3 | import { Grid } from 'antd';
4 | import { useMemo } from 'react';
5 |
6 | export const useResponsive = (): Partial> => {
7 | const breakpoints = Grid.useBreakpoint();
8 |
9 | return useMemo(() => convertBreakpointToResponsive(breakpoints), [breakpoints]);
10 | };
11 |
--------------------------------------------------------------------------------
/src/hooks/useThemeMode.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 |
3 | import { ThemeModeContext } from '@/context';
4 | import { ThemeContextState } from '@/types';
5 |
6 | export const useThemeMode = (): ThemeContextState => useContext(ThemeModeContext);
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export type { CacheManager } from './core/CacheManager';
2 | export * from './factories/createStyles/types';
3 | export * from './factories/createThemeProvider/type';
4 | export * from './functions';
5 | export * from './hooks';
6 | export * from './types';
7 |
--------------------------------------------------------------------------------
/src/stylish/button.ts:
--------------------------------------------------------------------------------
1 | import { GetAntdStylish } from '@/types';
2 |
3 | export const createAntdStylish: GetAntdStylish = ({ css, token }) => {
4 | return {
5 | buttonDefaultHover: css({
6 | backgroundColor: token.colorBgContainer,
7 | border: `1px solid ${token.colorBorder}`,
8 | cursor: 'pointer',
9 | ':hover': {
10 | color: token.colorPrimaryHover,
11 | borderColor: token.colorPrimaryHover,
12 | },
13 |
14 | ':active': {
15 | color: token.colorPrimaryActive,
16 | borderColor: token.colorPrimaryActive,
17 | },
18 | }),
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/src/types/appearance.ts:
--------------------------------------------------------------------------------
1 | export type BrowserPrefers = 'dark' | 'light';
2 |
3 | export type ThemeAppearance = 'dark' | 'light' | string;
4 |
5 | export type ThemeMode = 'auto' | 'dark' | 'light';
6 |
--------------------------------------------------------------------------------
/src/types/css.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from '@/types/theme';
2 | import { ArrayClassNamesArg, Emotion } from '@emotion/css/create-instance';
3 | import { CSSInterpolation, Interpolation, SerializedStyles } from '@emotion/serialize';
4 |
5 | export type CSSStyle = Array>;
6 |
7 | export type { CSSObject, SerializedStyles } from '@emotion/serialize';
8 |
9 | export type ClassNameGenerator = Emotion['css'];
10 |
11 | export interface ClassNameGeneratorOption {
12 | label?: string;
13 | hashPriority?: HashPriority;
14 | }
15 |
16 | /**
17 | * @title CSS 工具函数
18 | * @param template - 模板字符串数组
19 | * @param args - CSS 插值数组
20 | * @returns CSS 序列化后的样式
21 | */
22 | export interface CssUtil {
23 | (template: TemplateStringsArray, ...args: Array): SerializedStyles;
24 | (...args: Array): SerializedStyles;
25 | }
26 |
27 | export type ClassNamesArg =
28 | | undefined
29 | | null
30 | | string
31 | | boolean
32 | | { [className: string]: boolean | null | undefined }
33 | | ArrayClassNamesArg
34 | | SerializedStyles;
35 |
36 | /**
37 | * 可以传入多个 css 对象 或者 className 字符串,最终会合并成一个 className 字符串
38 | * 支持入参:{SerializedStyles} | string
39 | */
40 | export type ClassNamesUtil = (...classNames: ClassNamesArg[]) => string;
41 |
42 | export type HashPriority = 'low' | 'high';
43 |
--------------------------------------------------------------------------------
/src/types/function.ts:
--------------------------------------------------------------------------------
1 | import { ThemeConfig } from 'antd';
2 |
3 | import { ThemeAppearance } from './appearance';
4 | import { ClassNamesUtil, CssUtil, SerializedStyles } from './css';
5 | import { AtomInputType } from './genericUtils';
6 | import { ResponsiveKey } from './response';
7 | import type { AntdStylish, AntdToken, AppearanceState, FullToken, Theme } from './theme';
8 |
9 | export type BreakpointMapParams = Partial>;
10 |
11 | export type UseTheme = () => Theme;
12 | /**
13 | * 响应式断点工具函数
14 | */
15 | export interface ResponsiveUtil extends Record {
16 | /**
17 | * 支持使用函数表达式
18 | * @param breakpoints
19 | */
20 | (breakpoints: BreakpointMapParams): SerializedStyles;
21 | }
22 |
23 | /**
24 | * @title 通用样式工具函数
25 | */
26 | export interface CommonStyleUtils {
27 | /**
28 | * @title CSS 类名工具函数
29 | */
30 | cx: ClassNamesUtil;
31 | /**
32 | * @title CSS 序列化函数
33 | */
34 | css: CssUtil;
35 | /**
36 | * @title 响应式媒体查询工具函数
37 | * @description 可以快速创建响应式媒体查询的工具函数
38 | */
39 | responsive: ResponsiveUtil;
40 | }
41 |
42 | /**
43 | * 获取 antd theme 配置
44 | */
45 | export type GetAntdThemeConfig = (appearance: ThemeAppearance) => ThemeConfig | undefined;
46 |
47 | export interface AntdStylishParams extends AppearanceState {
48 | token: AntdToken;
49 | css: CssUtil;
50 | }
51 |
52 | /**
53 | * 创建 antd stylish 配置
54 | */
55 | export type GetAntdStylish = (theme: AntdStylishParams) => {
56 | [T in keyof AntdStylish]: SerializedStyles;
57 | };
58 |
59 | export interface CustomTokenParams extends AppearanceState {
60 | token: AntdToken;
61 | }
62 |
63 | /**
64 | * 创建 自定义 token
65 | */
66 | export type GetCustomToken = (theme: CustomTokenParams) => T;
67 |
68 | export interface CustomStylishParams extends AppearanceState {
69 | token: FullToken;
70 | stylish: AntdStylish;
71 | css: CssUtil;
72 | }
73 |
74 | /**
75 | * 创建 自定义 stylish
76 | */
77 | export type GetCustomStylish = (theme: CustomStylishParams) => {
78 | [T in keyof S]: SerializedStyles;
79 | };
80 |
--------------------------------------------------------------------------------
/src/types/genericUtils.ts:
--------------------------------------------------------------------------------
1 | import { SerializedStyles } from '@emotion/serialize';
2 | import { CSSObject } from './css';
3 |
4 | /**
5 | * 任何一组样式,最基础的入参有三种 CSS Style 对象
6 | * 第一种: css` color: red; ` -> SerializedStyles
7 | * 第二种:cx('abc-xxx',css` color:blue; `) -> string
8 | * 第三种: { color:"red" } -> CSSObject
9 | */
10 | export type AtomInputType = string | CSSObject | SerializedStyles;
11 |
12 | /**
13 | * getStyle 函数的的基础出参类型,我们需要将为这个类型提供准确定义,进而为开发者用户提供精准的类型提示
14 | * 用户输入的类型有两类
15 | * KvObject: 以键值对形态记录的样式 { a: css``,b: css``, c: { ... }}
16 | * AtomInput: css`` 或 { } 的CSSObject
17 | */
18 | export type BaseReturnType = KVObject | AtomInputType;
19 |
20 | type KVObject = Record;
21 |
22 | /**
23 | * @title StyleObjectOnly
24 | * @description 从 BaseReturnType 中排除 string 和 SerializedStyles 类型,只保留对象类型
25 | * @template T - BaseReturnType 的类型变量
26 | * @returns BaseReturnType 中的对象类型
27 | */
28 | type StyleObjectOnly = T extends string
29 | ? never
30 | : T extends SerializedStyles
31 | ? never
32 | : T;
33 |
34 | /**
35 | * 根据用户输入的样式对象,导出可以给用户使用消费的类型泛型
36 | * 譬如用户输入为 { a: css`color: red;`, b: { color: 'red' }
37 | * 输出的类型泛型为 { a:string; b:string }
38 | */
39 | type DefinitionToResult = {
40 | [P in K]: string;
41 | };
42 |
43 | /**
44 | * 根据用户返回的样式对象,返回一个可以给用户使用的类型定义
45 | * 用户输入为 { a: css`color: red;`, b: { color: 'red' }
46 | * 输出的类型泛型为 { a:string; b:string }
47 | */
48 | export type ReturnStyleToUse = T extends string
49 | ? T
50 | : T extends SerializedStyles
51 | ? string
52 | : DefinitionToResult>;
53 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './appearance';
2 | export * from './css';
3 | export * from './function';
4 | export * from './genericUtils';
5 | export * from './response';
6 | export * from './styleManager';
7 | export * from './styled';
8 | export * from './theme';
9 |
--------------------------------------------------------------------------------
/src/types/response.ts:
--------------------------------------------------------------------------------
1 | import { SerializedStyles } from './css';
2 |
3 | export type Breakpoint =
4 | | 'xxl'
5 | | 'xl'
6 | | 'lg'
7 | | 'md'
8 | | 'sm'
9 | /**
10 | * 最小断点,可以作为移动端的判断断点
11 | */
12 | | 'xs';
13 |
14 | export type DeviceScreen = 'mobile' | 'tablet' | 'laptop' | 'desktop';
15 |
16 | export type ResponsiveKey = Breakpoint | DeviceScreen;
17 |
18 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
19 | export interface ResponsiveMap extends Record {
20 | // 在此处扩展响应式映射表
21 | }
22 |
--------------------------------------------------------------------------------
/src/types/styleManager.ts:
--------------------------------------------------------------------------------
1 | import { Emotion } from '@emotion/css/create-instance';
2 |
3 | export type StyleManager = Emotion;
4 |
--------------------------------------------------------------------------------
/src/types/styled.ts:
--------------------------------------------------------------------------------
1 | import { Context, FC, ReactNode } from 'react';
2 | import { Theme } from './theme';
3 |
4 | export interface StyledConfig {
5 | /**
6 | * styled 对象所对应的 ThemeContext
7 | * @requires
8 | */
9 | ThemeContext: Context;
10 | /**
11 | * 可以注入相应 styled 方法的 ThemeProvider,或其他自己定义的ThemeProvider
12 | */
13 | ThemeProvider?: StyledThemeProvider;
14 | }
15 |
16 | export type StyledThemeProvider = FC<{ theme: Theme; children: ReactNode }>;
17 |
18 | /**
19 | * @title 样式引擎
20 | * @description 样式引擎的参数类型
21 | */
22 | export interface StyleEngine {
23 | /**
24 | * @title 自定义主题上下文
25 | * @description 自定义主题的 React 上下文对象
26 | */
27 | CustomThemeContext: Context;
28 | /**
29 | * @title Antd 主题上下文
30 | * @description Antd 主题的 React 上下文对象
31 | */
32 | StyledThemeContext?: Context;
33 | /**
34 | * @title CSS 类名前缀
35 | * @description 当前组件的 CSS 类名前缀
36 | */
37 | prefixCls?: string;
38 | iconPrefixCls?: string;
39 | }
40 |
--------------------------------------------------------------------------------
/src/types/theme.ts:
--------------------------------------------------------------------------------
1 | import { MappingAlgorithm, ThemeConfig } from 'antd';
2 | import { AliasToken } from 'antd/es/theme/interface';
3 |
4 | import { BrowserPrefers, ThemeAppearance, ThemeMode } from './appearance';
5 |
6 | /**
7 | * @title 主题上下文状态
8 | */
9 | export interface ThemeContextState {
10 | /**
11 | * @title 外观
12 | */
13 | appearance: ThemeAppearance;
14 | setAppearance: (appearance: ThemeAppearance) => void;
15 | /**
16 | * @title 主题模式
17 | * @enum ["light", "dark"]
18 | * @enumNames ["亮色模式", "暗色模式"]
19 | * @default "light"
20 | */
21 | themeMode: ThemeMode;
22 | setThemeMode: (themeMode: ThemeMode) => void;
23 | /**
24 | * @title 是否为暗色模式
25 | */
26 | isDarkMode: boolean;
27 | /**
28 | * @title 浏览器偏好的外观
29 | */
30 | browserPrefers: BrowserPrefers;
31 | }
32 |
33 | export type AppearanceState = Pick;
34 |
35 | export type AntdToken = AliasToken;
36 |
37 | /**
38 | * 一组统一封装好的 antd 标准样式
39 | */
40 | export interface AntdStylish {
41 | buttonDefaultHover: string;
42 | }
43 |
44 | /**
45 | * @title 获取 Antd 主题的函数
46 | * @param appearance - 主题外观
47 | * @returns Antd 主题配置对象或 undefined
48 | */
49 | export interface GetAntdTheme {
50 | (appearance: ThemeAppearance): ThemeConfig | undefined;
51 | }
52 |
53 | export type { MappingAlgorithm };
54 |
55 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
56 | export interface CustomToken {}
57 |
58 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
59 | export interface CustomStylish {}
60 |
61 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
62 | export interface CustomTheme extends CustomStylish, CustomToken {}
63 |
64 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
65 | export interface FullStylish extends AntdStylish, CustomStylish {}
66 |
67 | export interface AntdTheme extends AntdToken {
68 | stylish: AntdStylish;
69 | }
70 |
71 | export interface FullToken extends AntdToken, CustomToken {}
72 |
73 | export interface Theme extends FullToken, ThemeContextState {
74 | stylish: FullStylish;
75 | /**
76 | * antd 组件的 prefixCls
77 | */
78 | prefixCls: string;
79 | iconPrefixCls: string;
80 | }
81 |
--------------------------------------------------------------------------------
/src/utils/convertStylish.ts:
--------------------------------------------------------------------------------
1 | import { SerializedStyles } from '@emotion/serialize';
2 |
3 | /**
4 | * 将 stylish 中的 styles 取出作为 可复用的 string
5 | * @param stylish
6 | */
7 | export const convertStylishToString = >(
8 | stylish: T,
9 | ): { [Key in keyof T]: string } =>
10 | Object.fromEntries(Object.entries(stylish).map(([key, value]) => [key, value.styles])) as any;
11 |
--------------------------------------------------------------------------------
/src/utils/createEmotionServer/index.ts:
--------------------------------------------------------------------------------
1 | // copy from
2 | // https://github.com/emotion-js/emotion/blob/main/packages/server/src/create-instance/extract-critical.js
3 |
4 | import { EmotionCache } from '@emotion/utils';
5 |
6 | export const createExtractCritical = (cache: EmotionCache) => {
7 | if (cache.compat !== true) {
8 | // is this good? should we do this automatically?
9 | // this is only for when using the new apis (not emotion or create-emotion)
10 | cache.compat = true;
11 | }
12 |
13 | return (html: string) => {
14 | // parse out ids from html
15 | // reconstruct css/rules/cache to pass
16 | let RGX = new RegExp(cache.key + '-([a-zA-Z0-9-_]+)', 'gm');
17 | let o: {
18 | html: string;
19 | ids: string[];
20 | css: string;
21 | } = {
22 | html: html,
23 | ids: [],
24 | css: '',
25 | };
26 | let match;
27 | let ids: Record = {};
28 |
29 | while ((match = RGX.exec(html)) !== null) {
30 | if (ids[match[1]] === undefined) {
31 | ids[match[1]] = true;
32 | }
33 | }
34 |
35 | o.ids = Object.keys(cache.inserted).filter((id) => {
36 | if (
37 | (ids[id] !== undefined || cache.registered[cache.key + '-' + id] === undefined) &&
38 | cache.inserted[id] !== true
39 | ) {
40 | o.css += cache.inserted[id];
41 | return true;
42 | }
43 |
44 | return false;
45 | });
46 |
47 | return o;
48 | };
49 | };
50 |
--------------------------------------------------------------------------------
/src/utils/css.test.ts:
--------------------------------------------------------------------------------
1 | import { isReactCssResult } from './index';
2 |
3 | describe('isReactCssResult', () => {
4 | it('测试构建产物', () => {
5 | const result = isReactCssResult({
6 | name: '1mf3e6p',
7 | styles:
8 | '\n grid-area: footer;\n border-top: 1px solid rgba(5, 5, 5, 0.06);\n color: rgba(61, 62, 64, 0.45);\n font-size: 14px;\n line-height: 26px;\n text-align: center;\n padding: 24px 0;\n align-self: stretch;\n @media (max-width: 575px) {\n flex-direction: column;\n }\n ',
9 | });
10 | expect(result).toBeTruthy();
11 | });
12 |
13 | it('测试dev产物', () => {
14 | const result = isReactCssResult({
15 | name: '1mf3e6p',
16 | map: undefined,
17 | next: undefined,
18 | styles:
19 | '\n grid-area: footer;\n border-top: 1px solid rgba(5, 5, 5, 0.06);\n color: rgba(61, 62, 64, 0.45);\n font-size: 14px;\n line-height: 26px;\n text-align: center;\n padding: 24px 0;\n align-self: stretch;\n @media (max-width: 575px) {\n flex-direction: column;\n }\n ',
20 | toString: () => {},
21 | });
22 | expect(result).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/utils/css.ts:
--------------------------------------------------------------------------------
1 | import { getRegisteredStyles, RegisteredCache } from '@emotion/utils';
2 |
3 | /* c8 ignore start */
4 | /**
5 | * 判断是否是 ReactCss 的编译产物
6 | * @param params
7 | */
8 | export const isReactCssResult = (params: any) =>
9 | typeof params === 'object' && 'styles' in params && 'name' in params && 'toString' in params;
10 |
11 | // copied from https://github.com/emotion-js/emotion/blob/main/packages/css/src/create-instance.js#L125
12 | export const classnames = (args: any) => {
13 | let cls = '';
14 | for (let i = 0; i < args.length; i++) {
15 | let arg = args[i];
16 | if (arg === null) continue;
17 |
18 | let toAdd;
19 | switch (typeof arg) {
20 | case 'boolean':
21 | break;
22 | case 'object': {
23 | if (Array.isArray(arg)) {
24 | toAdd = classnames(arg);
25 | } else {
26 | toAdd = '';
27 | for (const k in arg) {
28 | if (arg[k] && k) {
29 | // eslint-disable-next-line @typescript-eslint/no-unused-expressions
30 | toAdd && (toAdd += ' ');
31 | toAdd += k;
32 | }
33 | }
34 | }
35 | break;
36 | }
37 | default: {
38 | toAdd = arg;
39 | }
40 | }
41 | if (toAdd) {
42 | // eslint-disable-next-line @typescript-eslint/no-unused-expressions
43 | cls && (cls += ' ');
44 | cls += toAdd;
45 | }
46 | }
47 | return cls;
48 | };
49 |
50 | // copied from https://github.com/emotion-js/emotion/blob/main/packages/css/src/create-instance.js#LL17C62-L17C62
51 | export const mergeCSS = (registered: RegisteredCache, css: any, className: string) => {
52 | const registeredStyles: string[] = [];
53 | const rawClassName = getRegisteredStyles(registered, registeredStyles, className);
54 |
55 | if (registeredStyles.length < 2) {
56 | return className;
57 | }
58 |
59 | return rawClassName + css(registeredStyles);
60 | };
61 | /* c8 ignore end */
62 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './css';
2 |
--------------------------------------------------------------------------------
/src/utils/matchBrowserPrefers.test.ts:
--------------------------------------------------------------------------------
1 | import { ThemeAppearance } from 'antd-style';
2 | import { vi } from 'vitest';
3 | import { matchBrowserPrefers } from './matchBrowserPrefers';
4 |
5 | describe('matchBrowserPrefers', () => {
6 | it('should return false when window is not defined', () => {
7 | expect(matchBrowserPrefers('light').matches).toBe(false);
8 | });
9 |
10 | it('should return a MediaQueryList object when window is defined', () => {
11 | Object.defineProperty(window, 'matchMedia', {
12 | writable: true,
13 | value: vi.fn().mockImplementation((query) => ({
14 | matches: true,
15 | media: query,
16 | onchange: null,
17 | addListener: vi.fn(),
18 | removeListener: vi.fn(),
19 | addEventListener: vi.fn(),
20 | removeEventListener: vi.fn(),
21 | dispatchEvent: vi.fn(),
22 | })),
23 | });
24 |
25 | expect(matchBrowserPrefers('dark').matches).toBe(true);
26 | expect(window.matchMedia).toHaveBeenCalledWith('(prefers-color-scheme: dark)');
27 | });
28 |
29 | test('should return a dummy MediaQueryList object when window is undefined', () => {
30 | const mode = 'dark';
31 | const originalWindow = global.window;
32 | // 模拟 window 不存在的情况
33 | // @ts-ignore
34 | delete global.window;
35 | const result = matchBrowserPrefers(mode);
36 | expect(result.matches).toBe(false);
37 | // 还原全局变量
38 | global.window = originalWindow;
39 | });
40 | it('should return a MediaQueryList-like object when window object is undefined', () => {
41 | const tempWin = window;
42 | // eslint-disable-next-line no-global-assign,no-native-reassign
43 | window = undefined as any;
44 |
45 | const mode: ThemeAppearance = 'dark';
46 | const result = matchBrowserPrefers(mode);
47 |
48 | expect(result.matches).toBe(false);
49 |
50 | // eslint-disable-next-line no-global-assign,no-native-reassign
51 | window = tempWin;
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/src/utils/matchBrowserPrefers.ts:
--------------------------------------------------------------------------------
1 | import { ThemeAppearance } from '@/types';
2 |
3 | export const matchBrowserPrefers = (mode: ThemeAppearance): MediaQueryList => {
4 | if (typeof window !== 'undefined') {
5 | return matchMedia && matchMedia(`(prefers-color-scheme: ${mode})`);
6 | }
7 | // 针对 ssr 做特处
8 | return { matches: false } as MediaQueryList;
9 | };
10 |
--------------------------------------------------------------------------------
/src/utils/responsive.ts:
--------------------------------------------------------------------------------
1 | import { Breakpoint } from '@/types';
2 |
3 | export const convertBreakpointToResponsive = >>(
4 | breakpoints: T,
5 | ): any => ({
6 | ...breakpoints,
7 | mobile: breakpoints.xs,
8 | tablet: breakpoints.md,
9 | laptop: breakpoints.lg,
10 | desktop: breakpoints.xxl,
11 | });
12 |
--------------------------------------------------------------------------------
/src/utils/safeStartTransition.ts:
--------------------------------------------------------------------------------
1 | import { startTransition, TransitionFunction } from 'react';
2 |
3 | export const safeStartTransition = (func: TransitionFunction) => {
4 | if (typeof startTransition === 'function') {
5 | startTransition(func);
6 | } else {
7 | func();
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/tests/components/StyledProvider.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import { StyleManager, styleManager, StyleProvider } from 'antd-style';
3 |
4 | describe('StyledProvider', () => {
5 | it('设置不同的 prefix', () => {
6 | let emotion = {} as StyleManager;
7 | const App = () => {
8 | return (
9 | (emotion = e)}>
10 | 123
11 |
12 | );
13 | };
14 |
15 | render();
16 |
17 | // antd-style 默认的 前缀为 ant-css
18 | expect(styleManager.sheet.key).toEqual('acss');
19 | expect(styleManager.sheet.isSpeedy).toEqual(false);
20 |
21 | expect(emotion.sheet.key).toEqual('test');
22 | expect(emotion.sheet.isSpeedy).toEqual(false);
23 | });
24 |
25 | it('设置 speedy 属性', () => {
26 | let emotion = {} as StyleManager;
27 | const App = () => {
28 | return (
29 | (emotion = e)}>
30 | 123
31 |
32 | );
33 | };
34 | render();
35 |
36 | expect(emotion.sheet.isSpeedy).toEqual(true);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/tests/components/__snapshots__/ThemeProvider.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`ThemeProvider > 注入自定义主题 > 自定义 Stylish 1`] = `
4 | .emotion-0 {
5 | font-size: 14px;
6 | font-weight: 500;
7 | color: #333;
8 | }
9 |
10 |
17 | `;
18 |
19 | exports[`ThemeProvider > 注入自定义主题 > 自定义 Token 1`] = `
20 |
21 |
24 | #c956df
25 |
26 |
27 | `;
28 |
29 | exports[`ThemeProvider > 配合 App 实现局部作用域 1`] = `
30 | .emotion-0 .container {
31 | color: rgb(255, 0, 0);
32 | }
33 |
34 |
63 | `;
64 |
--------------------------------------------------------------------------------
/tests/functions/__snapshots__/createInstance.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`createInstance > 自定义 prefixCls 时,会采用 instance 的 prefixCls 而不是 CP 的prefixCls 1`] = `
4 | .emotion-0.test-btn {
5 | background: lightsteelblue;
6 | border: none;
7 | color: royalblue;
8 | }
9 |
10 | .emotion-0 .cpicon {
11 | color: darkblue;
12 | }
13 |
14 |
45 | `;
46 |
--------------------------------------------------------------------------------
/tests/functions/__snapshots__/createStyish.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`createStylish > 渲染使用 1`] = `
4 | .emotion-0 {
5 | cursor: pointer;
6 | -webkit-transition: 150ms background-color ease-in-out;
7 | transition: 150ms background-color ease-in-out;
8 | border-radius: 4px;
9 | background: rgba(0, 0, 0, 0.02);
10 | }
11 |
12 | .emotion-0:hover {
13 | background: rgba(0, 0, 0, 0.02);
14 | }
15 |
16 | .emotion-0:hover {
17 | background: rgba(0, 0, 0, 0.04);
18 | }
19 |
20 |
23 | bglayout
24 |
25 | `;
26 |
27 | exports[`createStylish > 默认快照 1`] = `
28 | {
29 | "containerBgHover": "acss-5g3hbo",
30 | "containerBgL2": "acss-1ij3f7o",
31 | "defaultButton": "acss-ujg4eo",
32 | }
33 | `;
34 |
--------------------------------------------------------------------------------
/tests/functions/__snapshots__/styled.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`styled > 使用 antd 主题样式 > 在 ThemeProvider 包裹下,能正常获得样式 1`] = `
4 | .emotion-0 {
5 | width: 30px;
6 | color: #1677ff;
7 | background: #e6f4ff;
8 | }
9 |
10 |
11 |
14 | 自定义样式
15 |
16 |
17 | `;
18 |
19 | exports[`styled > 使用 antd 主题样式 > 带有外部传参的方式 1`] = `
20 | .emotion-0 {
21 | border-radius: 8px;
22 | padding: 24px;
23 | background: #ffffff;
24 | color: rgba(0, 0, 0, 0.88);
25 | }
26 |
27 |
34 | `;
35 |
36 | exports[`styled > 使用 antd 主题样式 > 带有外部传参的方式 2`] = `
37 | .emotion-0 {
38 | border-radius: 8px;
39 | padding: 24px;
40 | background: #1677ff;
41 | color: #fff;
42 | }
43 |
44 |
51 | `;
52 |
53 | exports[`styled > 使用 antd 主题样式 > 没有包裹 ThemeProvider 的情况下,找不到样式 1`] = `
54 | .emotion-0 {
55 | width: 30px;
56 | }
57 |
58 |
59 |
62 | 自定义样式
63 |
64 |
65 | `;
66 |
--------------------------------------------------------------------------------
/tests/functions/createGlobalStyle.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import { createGlobalStyle, ThemeProvider } from 'antd-style';
3 |
4 | describe('createGlobalStyle', () => {
5 | it('全局样式', async () => {
6 | const Global = createGlobalStyle`
7 | .some-class {
8 | color: rgb(255, 192, 203);
9 | }
10 | `;
11 |
12 | const { findByTestId } = render(
13 | ,
17 | );
18 |
19 | const item = await findByTestId('content');
20 |
21 | expect(item.firstChild).toHaveStyle({ color: 'rgb(255, 192, 203)' });
22 | });
23 |
24 | it('包裹 ThemeProvider 后可以获取主题样式', async () => {
25 | const Global = createGlobalStyle`
26 | .some-class {
27 | color: ${(p) => p.theme.colorPrimary};
28 | }
29 | `;
30 |
31 | const { findByTestId } = render(
32 | ,
36 | { wrapper: ThemeProvider },
37 | );
38 |
39 | const item = await findByTestId('content');
40 |
41 | expect(item.firstChild).toHaveStyle({ color: '#1677FF' });
42 | });
43 |
44 | it('不包裹 ThemeProvider 也能获得 token', async () => {
45 | const Global = createGlobalStyle`
46 | .some-class {
47 | color: ${(p) => p.theme.colorPrimary};
48 | }
49 | `;
50 |
51 | const { findByTestId } = render(
52 | ,
56 | );
57 |
58 | const item = await findByTestId('content');
59 |
60 | expect(item.firstChild).toHaveStyle({ color: '#1677FF' });
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/tests/functions/createStyish.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, renderHook } from '@testing-library/react';
2 | import { createStylish } from 'antd-style';
3 |
4 | describe('createStylish', () => {
5 | // 创建通用的 stylish 函数
6 | const useCommonStylish = createStylish(({ token, css }) => {
7 | const containerBgHover = css`
8 | cursor: pointer;
9 | transition: 150ms background-color ease-in-out;
10 | &:hover {
11 | background: ${token.colorFillQuaternary};
12 | }
13 | `;
14 |
15 | const defaultButtonBase = css`
16 | color: ${token.colorTextSecondary};
17 | background: ${token.colorFillQuaternary};
18 | border-color: transparent;
19 | `;
20 |
21 | return {
22 | defaultButton: css`
23 | ${defaultButtonBase};
24 |
25 | &:hover {
26 | color: ${token.colorText};
27 | background: ${token.colorFillSecondary};
28 | border-color: transparent;
29 | }
30 | &:focus {
31 | ${defaultButtonBase};
32 | border-color: ${token.colorPrimary};
33 | }
34 | `,
35 |
36 | containerBgHover: css`
37 | cursor: pointer;
38 | transition: 150ms background-color ease-in-out;
39 |
40 | &:hover {
41 | background: ${token.colorFillQuaternary};
42 | }
43 | `,
44 |
45 | containerBgL2: css`
46 | ${containerBgHover};
47 | border-radius: 4px;
48 | background: ${token.colorFillQuaternary};
49 |
50 | &:hover {
51 | background: ${token.colorFillTertiary};
52 | }
53 | `,
54 | };
55 | });
56 |
57 | it('默认快照', () => {
58 | const { result } = renderHook(useCommonStylish);
59 | expect(result.current).toMatchSnapshot();
60 | });
61 |
62 | it('渲染使用', () => {
63 | const App = () => {
64 | const stylish = useCommonStylish();
65 | return bglayout
;
66 | };
67 |
68 | const { container } = render();
69 |
70 | expect(container.firstChild).toMatchSnapshot();
71 | expect(container.firstChild).toHaveStyle({ cursor: 'pointer' });
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/tests/hooks/__snapshots__/useAntdStylish.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`useAntdStylish > 包裹 Provider 后如果自定义主题变量 1`] = `
4 | {
5 | "buttonDefaultHover": "background-color:#ffffff;border:1px solid #d9d9d9;cursor:pointer;:hover{color:#7183eb;border-color:#7183eb;}:active{color:#323db8;border-color:#323db8;}",
6 | }
7 | `;
8 |
9 | exports[`useAntdStylish > 如果没有 Provider,stylish 对象是 antd Token 的默认值 1`] = `
10 | {
11 | "buttonDefaultHover": "background-color:#ffffff;border:1px solid #d9d9d9;cursor:pointer;:hover{color:#4096ff;border-color:#4096ff;}:active{color:#0958d9;border-color:#0958d9;}",
12 | }
13 | `;
14 |
15 | exports[`useAntdStylish > 嵌套 Provider 后能拿到准确的主题值 1`] = `"background-color:#141414;border:1px solid #424242;cursor:pointer;:hover{color:#3c89e8;border-color:#3c89e8;}:active{color:#1554ad;border-color:#1554ad;}"`;
16 |
--------------------------------------------------------------------------------
/tests/hooks/__snapshots__/useTheme.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`useTheme > 包裹 Provider 后如果自定义主题变量 1`] = `
4 | {
5 | "buttonDefaultHover": "background-color:#ffffff;border:1px solid #d9d9d9;cursor:pointer;:hover{color:#7183eb;border-color:#7183eb;}:active{color:#323db8;border-color:#323db8;}",
6 | }
7 | `;
8 |
9 | exports[`useTheme > 如果没有 Provider,theme 对象时是 antd Token 默认值 1`] = `
10 | {
11 | "buttonDefaultHover": "background-color:#ffffff;border:1px solid #d9d9d9;cursor:pointer;:hover{color:#4096ff;border-color:#4096ff;}:active{color:#0958d9;border-color:#0958d9;}",
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/tests/hooks/useAntdStylish.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { ThemeProvider, useAntdStylish } from 'antd-style';
4 | import { FC, PropsWithChildren } from 'react';
5 |
6 | describe('useAntdStylish', () => {
7 | it('如果没有 Provider,stylish 对象是 antd Token 的默认值', () => {
8 | const { result } = renderHook(useAntdStylish);
9 | expect(result.current).toMatchSnapshot();
10 | });
11 |
12 | it('包裹 Provider 后如果自定义主题变量', () => {
13 | const wrapper = ({ children }: PropsWithChildren) => (
14 | {children}
15 | );
16 | const { result } = renderHook(useAntdStylish, { wrapper });
17 | expect(result.current).toMatchSnapshot();
18 | });
19 |
20 | it('嵌套 Provider 后能拿到准确的主题值', () => {
21 | const Wrapper: FC = ({ children }) => {
22 | return (
23 |
24 |
25 | {children}
26 |
27 |
28 | );
29 | };
30 | const { result } = renderHook(useAntdStylish, { wrapper: Wrapper });
31 | expect(result.current.buttonDefaultHover).toMatchSnapshot();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/tests/hooks/useResponsive.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { useResponsive } from 'antd-style';
3 |
4 | describe('useResponsive', () => {
5 | it('获取响应式结果', () => {
6 | const { result } = renderHook(useResponsive);
7 | expect(result.current).toMatchInlineSnapshot(`
8 | {
9 | "desktop": false,
10 | "laptop": false,
11 | "lg": false,
12 | "md": false,
13 | "mobile": false,
14 | "sm": false,
15 | "tablet": false,
16 | "xl": false,
17 | "xs": false,
18 | "xxl": false,
19 | }
20 | `);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/hooks/useThemeMode.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { ThemeProvider, useThemeMode } from 'antd-style';
3 |
4 | describe('useThemeMode', () => {
5 | it('没有包裹 ThemeProvider 能正常使用', () => {
6 | const { result } = renderHook(useThemeMode);
7 |
8 | expect(result.current.appearance).toEqual('light');
9 | expect(result.current.themeMode).toEqual('light');
10 | });
11 |
12 | it('包裹 ThemeProvider 后正常可以正常调用', () => {
13 | const { result } = renderHook(useThemeMode, { wrapper: ThemeProvider });
14 | expect(result.current.appearance).toEqual('light');
15 | expect(result.current.themeMode).toEqual('light');
16 | });
17 |
18 | it('ThemeProvider 传入值', () => {
19 | const { result } = renderHook(useThemeMode, {
20 | wrapper: ({ children }) => {children},
21 | });
22 | expect(result.current.appearance).toEqual('light');
23 | expect(result.current.themeMode).toEqual('auto');
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/tests/hooks/useToken.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useAntdToken } from 'antd-style';
4 |
5 | describe('useAntdToken', () => {
6 | it('can get colorPrimary', () => {
7 | const { result } = renderHook(useAntdToken);
8 | expect(result.current.colorPrimary.toLowerCase()).toEqual('#1677ff');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/tests/test-setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 | import { vi } from 'vitest';
3 |
4 | // 补充 toHaveStyleRule 的 matcher
5 | import { createSerializer, matchers } from '@emotion/jest';
6 | expect.extend(matchers);
7 |
8 | expect.addSnapshotSerializer(createSerializer());
9 |
10 | // 关闭 antd 的 hash
11 | import { theme } from 'antd';
12 | theme.defaultConfig.hashed = false;
13 |
14 | const origError = console.error;
15 |
16 | console.error = function (...msg) {
17 | const filterText = ['', 'WrapperError'];
18 | if (filterText.some((keyword) => msg.join('').includes(keyword))) return;
19 |
20 | return origError.apply(this, msg);
21 | };
22 |
23 | if (typeof window !== 'undefined') {
24 | Object.defineProperty(window, 'matchMedia', {
25 | writable: true,
26 | value: vi.fn().mockImplementation((query) => ({
27 | matches: false,
28 | media: query,
29 | onchange: null,
30 | addListener: vi.fn(), // deprecated
31 | removeListener: vi.fn(), // deprecated
32 | addEventListener: vi.fn(),
33 | removeEventListener: vi.fn(),
34 | dispatchEvent: vi.fn(),
35 | })),
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.tsx", "*.ts", "*.tsx", "../src"],
3 | "extends": "../tsconfig.json",
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 | "paths": {
7 | "antd-style": ["../es"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig-check.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "noEmit": true
5 | },
6 | "exclude": ["./.dumi/theme", ".dumi/tmp"]
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", ".dumirc.ts", "*.ts"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "declaration": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "resolveJsonModule": true,
9 | "jsx": "react-jsx",
10 | "lib": ["DOM", "ESNext"],
11 | "baseUrl": ".",
12 | "paths": {
13 | "@@/*": [".dumi/tmp/*"],
14 | "@/*": ["src/*"],
15 | "antd-style": ["src"],
16 | "antd-style/*": ["src/*", "*"]
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | setupFiles: './tests/test-setup.ts',
6 | environment: 'jsdom',
7 | globals: true,
8 | alias: {
9 | '@': './src',
10 | 'antd-style': './src',
11 | },
12 | coverage: {
13 | provider: 'v8',
14 | reporter: ['text', 'text-summary', 'json', 'lcov'],
15 | },
16 | },
17 | });
18 |
--------------------------------------------------------------------------------