Compare commits
18 Commits
main
...
feat/vapor
| Author | SHA1 | Date |
|---|---|---|
|
|
a15bb9cfb8 | |
|
|
5cc4a63480 | |
|
|
bacb790bfb | |
|
|
5a768e2ae4 | |
|
|
30afa132a2 | |
|
|
23ebeea4b7 | |
|
|
d7ca354b87 | |
|
|
428fdfc182 | |
|
|
73dd6d7625 | |
|
|
2cf758d770 | |
|
|
136543398d | |
|
|
8f73247bed | |
|
|
36a6ed5e34 | |
|
|
ec4017d86f | |
|
|
8d33f2f843 | |
|
|
312f82a5b6 | |
|
|
c49a9aa6c3 | |
|
|
f5560db05d |
|
|
@ -1,36 +0,0 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const restCssPath = path.join(process.cwd(), 'components', 'style', 'reset.css');
|
||||
const tokenStatisticPath = path.join(process.cwd(), 'components', 'version', 'token.json');
|
||||
const tokenMetaPath = path.join(process.cwd(), 'components', 'version', 'token-meta.json');
|
||||
|
||||
function finalizeCompile() {
|
||||
if (fs.existsSync(path.join(__dirname, './es'))) {
|
||||
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'es', 'style', 'reset.css'));
|
||||
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'es', 'version', 'token.json'));
|
||||
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'es', 'version', 'token-meta.json'));
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(__dirname, './lib'))) {
|
||||
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'lib', 'style', 'reset.css'));
|
||||
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'lib', 'version', 'token.json'));
|
||||
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'lib', 'version', 'token-meta.json'));
|
||||
}
|
||||
}
|
||||
|
||||
function finalizeDist() {
|
||||
if (fs.existsSync(path.join(__dirname, './dist'))) {
|
||||
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'dist', 'reset.css'));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compile: {
|
||||
finalize: finalizeCompile,
|
||||
},
|
||||
dist: {
|
||||
finalize: finalizeDist,
|
||||
},
|
||||
bail: true,
|
||||
};
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
codecov:
|
||||
branch: master
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# 🎨 editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
node_modules/
|
||||
**/*.spec.*
|
||||
**/style/
|
||||
*.html
|
||||
/components/test/*
|
||||
es/
|
||||
lib/
|
||||
_site/
|
||||
dist/
|
||||
site/dist/
|
||||
components/version/version.ts
|
||||
site/src/router/demoRoutes.js
|
||||
locale/
|
||||
112
.eslintrc.js
112
.eslintrc.js
|
|
@ -1,112 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
jasmine: true,
|
||||
jest: true,
|
||||
es6: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'@vue/typescript/recommended',
|
||||
'@vue/prettier',
|
||||
// 'prettier',
|
||||
],
|
||||
// extends: [
|
||||
// 'eslint:recommended',
|
||||
// 'plugin:vue/vue3-recommended',
|
||||
// '@vue/typescript/recommended',
|
||||
// '@vue/prettier',
|
||||
// ],
|
||||
plugins: ['markdown', 'jest', '@typescript-eslint', 'import'],
|
||||
globals: {
|
||||
h: true,
|
||||
defineProps: 'readonly',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.md'],
|
||||
processor: 'markdown/markdown',
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
// extends: ['@vue/typescript/recommended', '@vue/prettier'],
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/ban-types': 0,
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 0,
|
||||
'@typescript-eslint/no-empty-function': 0,
|
||||
'@typescript-eslint/no-non-null-assertion': 0,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true },
|
||||
],
|
||||
'@typescript-eslint/ban-ts-comment': 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.vue'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 2021,
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'@typescript-eslint/no-empty-function': 0,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true, argsIgnorePattern: '^_' },
|
||||
],
|
||||
'import/no-named-as-default': 'off',
|
||||
'import/namespace': [2, { allowComputed: true }],
|
||||
'import/no-named-as-default-member': 'off',
|
||||
'import/no-unresolved': [2, { ignore: ['ant-design-vue'] }],
|
||||
'comma-dangle': [2, 'always-multiline'],
|
||||
'no-var': 'error',
|
||||
'no-console': [2, { allow: ['warn', 'error'] }],
|
||||
'object-shorthand': 2,
|
||||
'no-unused-vars': [2, { ignoreRestSiblings: true, argsIgnorePattern: '^_' }],
|
||||
'no-undef': 2,
|
||||
camelcase: 'off',
|
||||
'no-extra-boolean-cast': 'off',
|
||||
semi: ['error', 'always'],
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/require-explicit-emits': 'off',
|
||||
'vue/require-prop-types': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/no-reserved-keys': 'off',
|
||||
'vue/comment-directive': 'off',
|
||||
'vue/prop-name-casing': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'vue/v-on-event-hyphenation': 'off',
|
||||
'vue/max-attributes-per-line': [
|
||||
2,
|
||||
{
|
||||
singleline: 20,
|
||||
multiline: 1,
|
||||
},
|
||||
],
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
};
|
||||
|
|
@ -66,9 +66,6 @@ package-lock.json
|
|||
pnpm-lock.yaml
|
||||
/coverage
|
||||
|
||||
# 备份文件
|
||||
/components/test/*
|
||||
list.txt
|
||||
|
||||
site/dev.js
|
||||
|
||||
|
|
@ -77,10 +74,39 @@ vetur/
|
|||
|
||||
report.html
|
||||
|
||||
site/src/router/demoRoutes.js
|
||||
|
||||
components/version/version.ts
|
||||
components/version/version.tsx
|
||||
components/version/token.json
|
||||
components/version/token-meta.json
|
||||
~component-api.json
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.template
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
|
||||
# Turbo
|
||||
.turbo
|
||||
|
||||
# Vercel
|
||||
.vercel
|
||||
|
||||
# Build Outputs
|
||||
.next/
|
||||
out/
|
||||
build/
|
||||
dist/
|
||||
storybook-static/
|
||||
|
||||
|
||||
# Debug
|
||||
npm-debug.log*
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
vite.config.*.timestamp*
|
||||
*.tsbuildinfo
|
||||
*.log
|
||||
|
||||
.npmrc
|
||||
.tsup/
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install pretty-quick --staged
|
||||
7
.huskyrc
7
.huskyrc
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged",
|
||||
"pre-publish": "npm run lint",
|
||||
"commit-msg": "commitlint -x @commitlint/config-conventional -e $GIT_PARAMS"
|
||||
}
|
||||
}
|
||||
60
.jest.js
60
.jest.js
|
|
@ -1,60 +0,0 @@
|
|||
const libDir = process.env.LIB_DIR;
|
||||
|
||||
const transformIgnorePatterns = [
|
||||
'/dist/',
|
||||
// Ignore modules without es dir.
|
||||
// Update: @babel/runtime should also be transformed
|
||||
// 'node_modules/(?!.*(@babel|lodash-es))',
|
||||
'node_modules/(?!@ant-design/icons-vue|@ant-design/icons-svg|lodash-es)/',
|
||||
];
|
||||
const testPathIgnorePatterns = ['/node_modules/', 'node'];
|
||||
|
||||
function getTestRegex(libDir) {
|
||||
if (libDir === 'dist') {
|
||||
return 'demo\\.test\\.js$';
|
||||
}
|
||||
return '.*\\.test\\.(j|t)sx?$';
|
||||
}
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
setupFiles: ['./tests/setup.js'],
|
||||
setupFilesAfterEnv: ['./tests/setupAfterEnv.ts'],
|
||||
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'vue', 'md', 'jpg'],
|
||||
modulePathIgnorePatterns: ['/_site/'],
|
||||
testPathIgnorePatterns: testPathIgnorePatterns,
|
||||
transform: {
|
||||
'\\.(vue|md)$': '<rootDir>/node_modules/@vue/vue3-jest',
|
||||
'\\.(js|jsx)$': '<rootDir>/node_modules/babel-jest',
|
||||
'\\.(ts|tsx)$': '<rootDir>/node_modules/ts-jest',
|
||||
'\\.svg$': '<rootDir>/node_modules/jest-transform-stub',
|
||||
},
|
||||
testRegex: getTestRegex(libDir),
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$/': '<rootDir>/$1',
|
||||
'^ant-design-vue$': '<rootDir>/components/index',
|
||||
'^ant-design-vue/es/(.*)$': '<rootDir>/components/$1',
|
||||
},
|
||||
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
|
||||
collectCoverage: process.env.COVERAGE === 'true',
|
||||
collectCoverageFrom: [
|
||||
'components/**/*.{js,jsx,vue}',
|
||||
'!components/*/__tests__/**/type.{js,jsx}',
|
||||
'!components/vc-*/**/*',
|
||||
'!components/*/demo/**/*',
|
||||
'!components/_util/**/*',
|
||||
'!components/align/**/*',
|
||||
'!components/trigger/**/*',
|
||||
'!**/node_modules/**',
|
||||
],
|
||||
testEnvironment: 'jsdom',
|
||||
testEnvironmentOptions: {
|
||||
url: 'http://localhost',
|
||||
customExportConditions: ['node', 'node-addons'],
|
||||
},
|
||||
transformIgnorePatterns,
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
babelConfig: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
**/*.svg
|
||||
lib/
|
||||
es/
|
||||
dist/
|
||||
_site/
|
||||
coverage/
|
||||
CNAME
|
||||
LICENSE
|
||||
yarn.lock
|
||||
netlify.toml
|
||||
yarn-error.log
|
||||
*.sh
|
||||
*.snap
|
||||
.gitignore
|
||||
.npmignore
|
||||
.prettierignore
|
||||
.DS_Store
|
||||
.editorconfig
|
||||
.eslintignore
|
||||
**/*.yml
|
||||
**/assets
|
||||
.gitattributes
|
||||
.stylelintrc
|
||||
.vcmrc
|
||||
.png
|
||||
.npmrc.template
|
||||
.huskyrc
|
||||
.gitmodules
|
||||
*.png
|
||||
v2-doc/
|
||||
|
||||
17
.prettierrc
17
.prettierrc
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 100,
|
||||
"proseWrap": "never",
|
||||
"arrowParens": "avoid",
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ".prettierrc",
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
23
.stylelintrc
23
.stylelintrc
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"extends": ["stylelint-config-standard", "stylelint-config-prettier"],
|
||||
"rules": {
|
||||
"comment-empty-line-before": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"function-comma-newline-after": null,
|
||||
"function-name-case": null,
|
||||
"function-parentheses-newline-inside": null,
|
||||
"function-max-empty-lines": null,
|
||||
"function-whitespace-after": null,
|
||||
"indentation": null,
|
||||
"number-leading-zero": null,
|
||||
"number-no-trailing-zeros": null,
|
||||
"rule-empty-line-before": null,
|
||||
"selector-combinator-space-after": null,
|
||||
"selector-list-comma-newline-after": null,
|
||||
"selector-pseudo-element-colon-notation": null,
|
||||
"unit-no-unknown": null,
|
||||
"value-list-max-empty-lines": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"no-descending-specificity": null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-rational-order",
|
||||
"stylelint-config-prettier"
|
||||
],
|
||||
"customSyntax": "postcss-less",
|
||||
"plugins": ["stylelint-declaration-block-no-ignored-properties"],
|
||||
"rules": {
|
||||
"function-name-case": ["lower"],
|
||||
"function-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreFunctions": [
|
||||
"fade",
|
||||
"fadeout",
|
||||
"tint",
|
||||
"darken",
|
||||
"ceil",
|
||||
"fadein",
|
||||
"floor",
|
||||
"unit",
|
||||
"shade",
|
||||
"lighten",
|
||||
"percentage",
|
||||
"-"
|
||||
]
|
||||
}
|
||||
],
|
||||
"import-notation": null,
|
||||
"no-descending-specificity": null,
|
||||
"no-invalid-position-at-import-rule": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"keyframes-name-pattern": null,
|
||||
"custom-property-pattern": null,
|
||||
"number-max-precision": 8,
|
||||
"alpha-value-notation": "number",
|
||||
"color-function-notation": "legacy",
|
||||
"selector-class-pattern": null,
|
||||
"selector-id-pattern": null,
|
||||
"selector-not-notation": null
|
||||
}
|
||||
}
|
||||
17
.vcmrc
17
.vcmrc
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"helpMessage": "\nPlease fix your commit message (and consider using https://www.npmjs.com/package/commitizen)\n",
|
||||
"types": [
|
||||
"feat",
|
||||
"fix",
|
||||
"docs",
|
||||
"style",
|
||||
"refactor",
|
||||
"perf",
|
||||
"test",
|
||||
"chore",
|
||||
"revert",
|
||||
"ci"
|
||||
],
|
||||
"warnOnFail": false,
|
||||
"autoFix": false
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
@import '@ant-design-vue/tailwind-config';
|
||||
@source '../index.html';
|
||||
@source '../src/**/*.{vue,ts}';
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-base-300) transparent;
|
||||
}
|
||||
*:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.shiki.github-dark,
|
||||
.dark-scrollbar {
|
||||
scrollbar-color: rgba(121, 121, 121, 0.4) transparent;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// @ts-check
|
||||
|
||||
export { default } from '@ant-design-vue/eslint-config/vue'
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Playground</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "playground",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite",
|
||||
"lint": "eslint . --fix",
|
||||
"preview": "vite preview",
|
||||
"tsc": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/vue": "^1.1.5",
|
||||
"@heroicons/vue": "^2.1.5",
|
||||
"@ant-design-vue/ui": "*",
|
||||
"@simonwep/pickr": "^1.9.1",
|
||||
"@trpc/client": "^11.0.0",
|
||||
"@trpc/server": "^11.0.0",
|
||||
"@wdns/vue-code-block": "^2.3.3",
|
||||
"clsx": "^2.1.1",
|
||||
"cookies": "^0.9.1",
|
||||
"uuid": "^10.0.0",
|
||||
"vue": "^3.4.34",
|
||||
"vue-router": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design-vue/eslint-config": "*",
|
||||
"@ant-design-vue/prettier-config": "*",
|
||||
"@ant-design-vue/typescript-config": "*",
|
||||
"@ant-design-vue/vite-config": "*",
|
||||
"@ant-design-vue/tailwind-config": "*",
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"@types/cookies": "^0.9.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"@vitejs/plugin-vue": "^5.1.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^4.1.3",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^5.3.5",
|
||||
"vite-plugin-dts": "^3.9.1",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue-tsc": "^3.0.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// @ts-check
|
||||
|
||||
export { default } from "@ant-design-vue/prettier-config/tailwind";
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<button @click="toggleTheme" class="fixed top-2 right-2">toggle {{ appearance }}</button>
|
||||
<a-theme :appearance="appearance">
|
||||
<RouterView />
|
||||
</a-theme>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const appearance = ref('light')
|
||||
|
||||
const toggleTheme = () => {
|
||||
appearance.value = appearance.value === 'light' ? 'dark' : 'light'
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="flex h-screen" :class="pageClass">
|
||||
<TheNavbar v-if="!hideNavbar" :items="navs"></TheNavbar>
|
||||
<div class="flex-1 justify-center px-4 py-16" :class="contentClass">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { provideLayoutOptions } from '@/composables/layout'
|
||||
import { computed, ref } from 'vue'
|
||||
import TheNavbar from './TheNavbar.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
navs: { name: string; path: string }[]
|
||||
hideNavbar?: boolean
|
||||
hideBreadcrumbs?: boolean
|
||||
}>()
|
||||
|
||||
const pageClass = ref<string>()
|
||||
const contentClass = ref<string>()
|
||||
|
||||
provideLayoutOptions({
|
||||
pageClass,
|
||||
contentClass,
|
||||
hideNavbar: computed(() => props.hideNavbar),
|
||||
hideBreadcrumbs: computed(() => props.hideBreadcrumbs),
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="flex flex-wrap gap-8 px-8">
|
||||
<RouterLink v-for="item in items" :key="item.path" :to="item.path">
|
||||
<div
|
||||
class="shadow-xs relative flex w-72 flex-col rounded-lg bg-primary capitalize text-primary-content transition-all hover:scale-105 hover:shadow-xl"
|
||||
>
|
||||
<div className="flex-col gap-2 flex flex-auto p-4 text-sm items-center text-center">
|
||||
<h2 className="font-semibold flex items-center gap-2 text-xl mb-1">
|
||||
{{ item.name }}
|
||||
</h2>
|
||||
<div className="flex flex-wrap items-start gap-2 justify-end">
|
||||
<ArrowRightIcon class="size-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ArrowRightIcon } from '@heroicons/vue/20/solid'
|
||||
|
||||
defineProps<{
|
||||
items: { name: string; path: string }[]
|
||||
}>()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="max-w-full select-none overflow-x-auto px-4 py-2 text-sm">
|
||||
<ul class="flex min-h-min items-center whitespace-nowrap capitalize">
|
||||
<template v-for="(item, i) in items" :key="item.path">
|
||||
<li class="flex items-center">
|
||||
<template v-if="i > 0">
|
||||
<span
|
||||
class="ml-2 mr-3 block size-1.5 rotate-45 transform border-r-[1px] border-t-[1px] border-base-content/70 bg-transparent"
|
||||
></span>
|
||||
</template>
|
||||
<template v-if="item.path !== route.path">
|
||||
<RouterLink class="flex items-center hover:underline" :to="item.path">
|
||||
{{ item.name }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-base-content/70">{{ item.name }}</span>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
defineProps<{ items: { name: string; path: string }[] }>()
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<div class="bg-base-100/90 text-base-content border-base-content/10 border-r">
|
||||
<div class="flex min-h-16 w-full items-center p-2">
|
||||
<div class="justify-start">
|
||||
<div class="group relative inline-block">
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="bg-base-100 z-[1] mt-3 flex w-52 origin-top scale-95 flex-col flex-wrap rounded-lg p-2 text-sm capitalize"
|
||||
>
|
||||
<li v-for="item in items" :key="item.name">
|
||||
<RouterLink
|
||||
:aria-disabled="item.path === route.path"
|
||||
:to="item.path"
|
||||
@click.stop="$event.currentTarget.blur()"
|
||||
class="hover:bg-base-content/10 flex cursor-pointer flex-col rounded-lg px-3 py-2 transition duration-200"
|
||||
>
|
||||
{{ item.name }}
|
||||
</RouterLink>
|
||||
<ul v-if="item.children">
|
||||
<li v-for="child in item.children" :key="child.name">
|
||||
<RouterLink
|
||||
:to="child.path"
|
||||
@click.stop="$event.currentTarget.blur()"
|
||||
class="hover:bg-base-content/10 flex cursor-pointer flex-col rounded-lg px-3 py-2 text-xs opacity-80 transition duration-200"
|
||||
>
|
||||
{{ child.name }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
defineProps<{
|
||||
items: { name: string; path: string; children?: { name: string; path: string }[] }[]
|
||||
}>()
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { inject, InjectionKey, provide, Ref } from 'vue'
|
||||
|
||||
export interface LayoutOptions {
|
||||
pageClass: Ref<string | undefined>
|
||||
contentClass: Ref<string | undefined>
|
||||
hideNavbar: Ref<boolean>
|
||||
hideBreadcrumbs: Ref<boolean>
|
||||
}
|
||||
|
||||
const LayoutOptionsToken: InjectionKey<LayoutOptions> = Symbol()
|
||||
|
||||
export function provideLayoutOptions(options: LayoutOptions) {
|
||||
provide(LayoutOptionsToken, options)
|
||||
}
|
||||
|
||||
export function injectLayoutOptions() {
|
||||
const options = inject(LayoutOptionsToken)
|
||||
if (!options) {
|
||||
throw new Error('"injectLayoutOptions" must be called inside pages')
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import '~/tailwind.css'
|
||||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import App from './App.vue'
|
||||
import routes from './routes'
|
||||
import antd from '@ant-design-vue/ui'
|
||||
import '@ant-design-vue/ui/tailwind.css'
|
||||
import '@ant-design-vue/ui/style.css'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
createApp(App).use(router).use(antd).mount('#app')
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<div class="flex h-[200vh] flex-col gap-2">
|
||||
<a-affix :offset-top="top" @change="onChange">
|
||||
<a-button type="primary" @click="top += 10">Affix top</a-button>
|
||||
</a-affix>
|
||||
<br />
|
||||
<a-affix :offset-bottom="bottom">
|
||||
<a-button type="primary" @click="bottom += 10">Affix bottom</a-button>
|
||||
</a-affix>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
const top = ref<number>(10)
|
||||
const bottom = ref<number>(10)
|
||||
const onChange = (lastAffix: boolean) => {
|
||||
console.log('onChange', lastAffix)
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a-button variant="solid" size="lg">Solid Button</a-button>
|
||||
<a-button variant="solid" size="lg" disabled>Solid Button</a-button>
|
||||
<a-button variant="solid" size="lg" loading>Solid Button</a-button>
|
||||
<a-button variant="solid" size="lg" danger>Danger Solid Button</a-button>
|
||||
<a-button variant="solid" size="lg" danger disabled>Disabled Danger Solid Button</a-button>
|
||||
<br />
|
||||
<a-button variant="outlined" size="sm">Outlined Button</a-button>
|
||||
<a-button variant="outlined" size="md">Outlined Button</a-button>
|
||||
<a-button variant="outlined" size="lg">Outlined Button</a-button>
|
||||
<a-button variant="outlined" size="lg" disabled>Disabled Outlined Button</a-button>
|
||||
<a-button variant="outlined" size="lg" loading>Loading Outlined Button</a-button>
|
||||
<a-button variant="outlined" size="lg" danger>Danger Outlined Button</a-button>
|
||||
<a-button variant="outlined" size="lg" danger disabled>
|
||||
Disabled Danger Outlined Button
|
||||
</a-button>
|
||||
<br />
|
||||
<a-button variant="text" size="sm">Text Button</a-button>
|
||||
<a-button variant="text" size="md">Text Button</a-button>
|
||||
<a-button variant="text" size="lg">Text Button</a-button>
|
||||
<a-button variant="text" size="lg" disabled>Disabled Text Button</a-button>
|
||||
<a-button variant="text" size="lg" loading>Loading Text Button</a-button>
|
||||
<a-button variant="text" size="lg" danger>Danger Text Button</a-button>
|
||||
<a-button variant="text" size="lg" danger disabled>Disabled Danger Text Button</a-button>
|
||||
<br />
|
||||
<a-button variant="link" size="sm">Link Button</a-button>
|
||||
<a-button variant="link" size="md">Link Button</a-button>
|
||||
<a-button variant="link" size="lg">Link Button</a-button>
|
||||
<a-button variant="link" size="lg" disabled>Disabled Link Button</a-button>
|
||||
<a-button variant="link" size="lg" loading>Loading Link Button</a-button>
|
||||
<a-button variant="link" size="lg" danger>Danger Link Button</a-button>
|
||||
<a-button variant="link" size="lg" danger disabled>Disabled Danger Link Button</a-button>
|
||||
<br />
|
||||
<a-button variant="dashed" size="sm">Dashed Button</a-button>
|
||||
<a-button variant="dashed" size="md">Dashed Button</a-button>
|
||||
<a-button variant="dashed" size="lg">Dashed Button</a-button>
|
||||
<a-button variant="dashed" size="lg" disabled>Disabled Dashed Button</a-button>
|
||||
<a-button variant="dashed" size="lg" loading>Loading Dashed Button</a-button>
|
||||
<a-button variant="dashed" size="lg" danger>Danger Dashed Button</a-button>
|
||||
<a-button variant="dashed" size="lg" danger disabled>Disabled Danger Dashed Button</a-button>
|
||||
<br />
|
||||
<a-button variant="filled" size="sm">Filled Button</a-button>
|
||||
<a-button variant="filled" size="md">Filled Button</a-button>
|
||||
<a-button variant="filled" size="lg">Filled Button</a-button>
|
||||
<a-button variant="filled" size="lg" disabled>Disabled Filled Button</a-button>
|
||||
<a-button variant="filled" size="lg" loading>Loading Filled Button</a-button>
|
||||
<a-button variant="filled" size="lg" danger>Danger Filled Button</a-button>
|
||||
<a-button variant="filled" size="lg" danger disabled>Disabled Danger Filled Button</a-button>
|
||||
|
||||
<a-button color="purple">Purple Button</a-button>
|
||||
<a-button color="blue">Blue Button</a-button>
|
||||
<a-button color="green">Green Button</a-button>
|
||||
<a-button color="red">Red Button</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button class="btn btn-primary">Primary</button>
|
||||
<button class="btn btn-secondary">Default</button>
|
||||
<button class="btn btn-error">Danger</button>
|
||||
<button class="btn btn-link">Link</button>
|
||||
<button class="btn btn-ghost">Ghost</button>
|
||||
<button class="btn btn-link">Link</button>
|
||||
<button class="btn btn-link">Link</button>
|
||||
<button class="btn btn-link">Link</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<a-flex gap="middle" vertical>
|
||||
<label>
|
||||
Select axis:
|
||||
<select v-model="axis">
|
||||
<option v-for="item in axisOptions" :key="item">{{ item }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<a-flex :vertical="axis === 'vertical'">
|
||||
<div
|
||||
v-for="(item, index) in new Array(4)"
|
||||
:key="item"
|
||||
:style="{ ...baseStyle, background: `${index % 2 ? '#1677ff' : '#1677ffbf'}` }"
|
||||
/>
|
||||
</a-flex>
|
||||
<hr/>
|
||||
<label>
|
||||
Select justify:
|
||||
<select v-model="justify">
|
||||
<option v-for="item in justifyOptions" :key="item">{{ item }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Select align:
|
||||
<select v-model="align">
|
||||
<option v-for="item in alignOptions" :key="item">{{ item }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<a-flex :style="{ ...boxStyle }" :justify="justify" :align="align">
|
||||
<a-button variant="solid">Primary</a-button>
|
||||
<a-button variant="solid">Primary</a-button>
|
||||
<a-button variant="solid">Primary</a-button>
|
||||
<a-button variant="solid">Primary</a-button>
|
||||
</a-flex>
|
||||
<hr/>
|
||||
<a-flex gap="middle" vertical>
|
||||
<label>
|
||||
Select gap size:
|
||||
<select v-model="gapSize">
|
||||
<option v-for="item in gapSizeOptions" :key="item">{{ item }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<a-flex :gap="gapSize">
|
||||
<a-button variant="solid">Primary</a-button>
|
||||
<a-button>Default</a-button>
|
||||
<a-button variant="dashed">Dashed</a-button>
|
||||
<a-button variant="link">Link</a-button>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
<hr/>
|
||||
<label>
|
||||
Auto wrap:
|
||||
</label>
|
||||
<a-flex wrap="wrap" gap="small">
|
||||
<a-button v-for="item in new Array(24)" :key="item" variant="solid">Button</a-button>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { ref, reactive } from 'vue';
|
||||
|
||||
const baseStyle: CSSProperties = {
|
||||
width: '25%',
|
||||
height: '54px',
|
||||
};
|
||||
const boxStyle: CSSProperties = {
|
||||
width: '100%',
|
||||
height: '120px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #40a9ff',
|
||||
};
|
||||
|
||||
const axisOptions = reactive(['horizontal', 'vertical']);
|
||||
const axis = ref(axisOptions[0]);
|
||||
|
||||
const justifyOptions = reactive([
|
||||
'flex-start',
|
||||
'center',
|
||||
'flex-end',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
]);
|
||||
const justify = ref(justifyOptions[0]);
|
||||
|
||||
const alignOptions = reactive(['flex-start', 'center', 'flex-end']);
|
||||
const align = ref(alignOptions[0]);
|
||||
|
||||
const gapSizeOptions = reactive(['small', 'middle', 'large']);
|
||||
const gapSize = ref(gapSizeOptions[0]);
|
||||
</script>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import { RouteRecordRaw, RouterView } from 'vue-router'
|
||||
import BasicLayout from './components/BasicLayout.vue'
|
||||
import { Fragment, h } from 'vue'
|
||||
|
||||
// /pages/button/basic.vue
|
||||
const items = import.meta.glob('./pages/*/*.vue', { import: 'default', eager: true })
|
||||
|
||||
const categoryRoutes: Record<string, RouteRecordRaw[]> = {}
|
||||
|
||||
Object.keys(items).forEach(path => {
|
||||
const route = path.replace('./pages/', '').replace('.vue', '')
|
||||
const [category, demo] = route.split('/')
|
||||
|
||||
if (!categoryRoutes[category]) {
|
||||
categoryRoutes[category] = []
|
||||
}
|
||||
|
||||
categoryRoutes[category].push({
|
||||
path: demo,
|
||||
component: items[path],
|
||||
})
|
||||
})
|
||||
|
||||
const routes: RouteRecordRaw[] = Object.entries(categoryRoutes).map(([category, children]) => {
|
||||
const renderComponents = () =>
|
||||
h(
|
||||
'div',
|
||||
children.map(child => h(child.component)),
|
||||
)
|
||||
renderComponents.displayName = 'renderComponents'
|
||||
return {
|
||||
path: `/${category}`,
|
||||
component: RouterView,
|
||||
children: [
|
||||
...children,
|
||||
{
|
||||
path: ':demo*',
|
||||
component: renderComponents,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const navs = Object.keys(categoryRoutes).map(category => ({
|
||||
name: category,
|
||||
path: `/${category}`,
|
||||
children: categoryRoutes[category].map(child => ({
|
||||
name: child.path,
|
||||
path: `/${category}/${child.path}`,
|
||||
})),
|
||||
}))
|
||||
routes.push({
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: h('div', 'demo not found'),
|
||||
})
|
||||
export default [
|
||||
{
|
||||
path: '/',
|
||||
component: BasicLayout,
|
||||
children: routes,
|
||||
props: {
|
||||
navs,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<Record<string, never>, Record<string, never>, any>
|
||||
export default component
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AButton: typeof import('@ant-design-vue/ui').Button
|
||||
AAffix: typeof import('@ant-design-vue/ui').Affix
|
||||
}
|
||||
}
|
||||
export {}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export function globRoutes(
|
||||
baseName: string,
|
||||
globs: Record<string, () => Promise<unknown>>,
|
||||
): RouteRecordRaw {
|
||||
const items = Object.entries(globs).map(([path, component]) => {
|
||||
const match = path.match(/^\.\/pages\/(.+)\/index\.ts$/)
|
||||
if (!match) {
|
||||
throw new Error('invalid glob')
|
||||
}
|
||||
return {
|
||||
name: match[1],
|
||||
component,
|
||||
}
|
||||
})
|
||||
|
||||
const home: RouteRecordRaw = {
|
||||
path: '',
|
||||
components: {
|
||||
default: () => import('@/components/HomePage.vue'),
|
||||
breadcrumbs: () => import('@/components/TheBreadcrumbs.vue'),
|
||||
},
|
||||
props: {
|
||||
default: {
|
||||
items: items.map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
path: `/${baseName}/${item.name}`,
|
||||
}
|
||||
}),
|
||||
},
|
||||
breadcrumbs: {
|
||||
items: [
|
||||
{
|
||||
name: 'home',
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
name: baseName,
|
||||
path: `/${baseName}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
name: baseName,
|
||||
title: baseName,
|
||||
},
|
||||
}
|
||||
|
||||
const pages: RouteRecordRaw[] = items.map(item => {
|
||||
return {
|
||||
path: item.name,
|
||||
components: {
|
||||
default: item.component,
|
||||
breadcrumbs: () => import('@/components/TheBreadcrumbs.vue'),
|
||||
},
|
||||
props: {
|
||||
breadcrumbs: {
|
||||
items: [
|
||||
{
|
||||
name: 'home',
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
name: baseName,
|
||||
path: `/${baseName}`,
|
||||
},
|
||||
{
|
||||
name: item.name,
|
||||
path: `/${baseName}/${item.name}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
name: item.name,
|
||||
title: `${baseName} - ${item.name}`,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
path: `/${baseName}`,
|
||||
component: () => import('@/components/BasicLayout.vue'),
|
||||
props: {
|
||||
navs: [
|
||||
{
|
||||
name: 'home',
|
||||
path: '/',
|
||||
},
|
||||
],
|
||||
hideNavbar: true,
|
||||
hideBreadcrumbs: true,
|
||||
},
|
||||
children: [home, ...pages],
|
||||
} as RouteRecordRaw
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@ant-design-vue/typescript-config/tsconfig.vue.json",
|
||||
"include": ["src/**/*.ts", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"~/*": ["./assets/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "@ant-design-vue/typescript-config/tsconfig.node.json",
|
||||
"include": ["vite.config.*"]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import tailwindcss from '@tailwindcss/vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'node:path'
|
||||
import { defineConfig, Plugin } from 'vite'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), tailwindcss()],
|
||||
server: {
|
||||
watch: {
|
||||
ignored: ['!**/node_modules/@ant-design-vue/**'],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
'~': resolve(__dirname, './assets'),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
test: {
|
||||
presets: [['@babel/preset-env']],
|
||||
plugins: [
|
||||
['@vue/babel-plugin-jsx', { mergeProps: false, enableObjectSlots: false }],
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-transform-object-assign',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-runtime',
|
||||
'transform-require-context',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
5
build.sh
5
build.sh
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
rm -rf dist
|
||||
mkdir dist
|
||||
./node_modules/.bin/webpack --config webpack.site.config.js
|
||||
cp dist/index.html index.html
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
export type KeyType = string | number;
|
||||
type ValueType = [number, any]; // [times, realValue]
|
||||
|
||||
const SPLIT = '%';
|
||||
|
||||
class Entity {
|
||||
instanceId: string;
|
||||
constructor(instanceId: string) {
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
/** @private Internal cache map. Do not access this directly */
|
||||
cache = new Map<string, ValueType>();
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ export function createCache() {
|
|||
Array.from(styles).forEach(style => {
|
||||
(style as any)[CSS_IN_JS_INSTANCE] = (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId;
|
||||
|
||||
// Not force move if no head
|
||||
// Not force move if no head
|
||||
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
|
||||
document.head.insertBefore(style, firstChild);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
import type Cache from './Cache';
|
||||
import { extract as tokenExtractStyle, TOKEN_PREFIX } from './hooks/useCacheToken';
|
||||
import { CSS_VAR_PREFIX, extract as cssVarExtractStyle } from './hooks/useCSSVarRegister';
|
||||
import { extract as styleExtractStyle, STYLE_PREFIX } from './hooks/useStyleRegister';
|
||||
import { toStyleStr } from './util';
|
||||
import { ATTR_CACHE_MAP, serialize as serializeCacheMap } from './util/cacheMapUtil';
|
||||
|
||||
const ExtractStyleFns = {
|
||||
[STYLE_PREFIX]: styleExtractStyle,
|
||||
[TOKEN_PREFIX]: tokenExtractStyle,
|
||||
[CSS_VAR_PREFIX]: cssVarExtractStyle,
|
||||
};
|
||||
|
||||
type ExtractStyleType = keyof typeof ExtractStyleFns;
|
||||
|
||||
function isNotNull<T>(value: T | null): value is T {
|
||||
return value !== null;
|
||||
}
|
||||
|
||||
export default function extractStyle(
|
||||
cache: Cache,
|
||||
options?:
|
||||
| boolean
|
||||
| {
|
||||
plain?: boolean;
|
||||
types?: ExtractStyleType | ExtractStyleType[];
|
||||
},
|
||||
) {
|
||||
const { plain = false, types = ['style', 'token', 'cssVar'] } =
|
||||
typeof options === 'boolean' ? { plain: options } : options || {};
|
||||
|
||||
const matchPrefixRegexp = new RegExp(
|
||||
`^(${(typeof types === 'string' ? [types] : types).join('|')})%`,
|
||||
);
|
||||
|
||||
// prefix with `style` is used for `useStyleRegister` to cache style context
|
||||
const styleKeys = Array.from(cache.cache.keys()).filter(key => matchPrefixRegexp.test(key));
|
||||
|
||||
// Common effect styles like animation
|
||||
const effectStyles: Record<string, boolean> = {};
|
||||
|
||||
// Mapping of cachePath to style hash
|
||||
const cachePathMap: Record<string, string> = {};
|
||||
|
||||
let styleText = '';
|
||||
|
||||
styleKeys
|
||||
.map<[number, string] | null>(key => {
|
||||
const cachePath = key.replace(matchPrefixRegexp, '').replace(/%/g, '|');
|
||||
const [prefix] = key.split('%');
|
||||
const extractFn = ExtractStyleFns[prefix as keyof typeof ExtractStyleFns];
|
||||
const extractedStyle = extractFn(cache.cache.get(key)![1], effectStyles, {
|
||||
plain,
|
||||
});
|
||||
if (!extractedStyle) {
|
||||
return null;
|
||||
}
|
||||
const [order, styleId, styleStr] = extractedStyle;
|
||||
if (key.startsWith('style')) {
|
||||
cachePathMap[cachePath] = styleId;
|
||||
}
|
||||
return [order, styleStr];
|
||||
})
|
||||
.filter(isNotNull)
|
||||
.sort(([o1], [o2]) => o1 - o2)
|
||||
.forEach(([, style]) => {
|
||||
styleText += style;
|
||||
});
|
||||
|
||||
// ==================== Fill Cache Path ====================
|
||||
styleText += toStyleStr(
|
||||
`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
[ATTR_CACHE_MAP]: ATTR_CACHE_MAP,
|
||||
},
|
||||
plain,
|
||||
);
|
||||
|
||||
return styleText;
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
|
||||
import { ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
|
||||
import { isClientSide, toStyleStr } from '../util';
|
||||
import type { TokenWithCSSVar } from '../util/css-variables';
|
||||
import { transformToken } from '../util/css-variables';
|
||||
import type { ExtractStyle } from './useGlobalCache';
|
||||
import useGlobalCache from './useGlobalCache';
|
||||
import { uniqueHash } from './useStyleRegister';
|
||||
import type { ComputedRef } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export const CSS_VAR_PREFIX = 'cssVar';
|
||||
|
||||
type CSSVarCacheValue<V, T extends Record<string, V> = Record<string, V>> = [
|
||||
cssVarToken: TokenWithCSSVar<V, T>,
|
||||
cssVarStr: string,
|
||||
styleId: string,
|
||||
cssVarKey: string,
|
||||
];
|
||||
|
||||
const useCSSVarRegister = <V, T extends Record<string, V>>(
|
||||
config: ComputedRef<{
|
||||
path: string[];
|
||||
key: string;
|
||||
prefix?: string;
|
||||
unitless?: Record<string, boolean>;
|
||||
ignore?: Record<string, boolean>;
|
||||
scope?: string;
|
||||
token: any;
|
||||
}>,
|
||||
fn: () => T,
|
||||
) => {
|
||||
const styleContext = useStyleInject();
|
||||
|
||||
const stylePath = computed(() => {
|
||||
return [
|
||||
...config.value.path,
|
||||
config.value.key,
|
||||
config.value.scope || '',
|
||||
config.value.token?._tokenKey,
|
||||
];
|
||||
});
|
||||
|
||||
const cache = useGlobalCache<CSSVarCacheValue<V, T>>(
|
||||
CSS_VAR_PREFIX,
|
||||
stylePath,
|
||||
() => {
|
||||
const originToken = fn();
|
||||
const [mergedToken, cssVarsStr] = transformToken<V, T>(originToken, config.value.key, {
|
||||
prefix: config.value.prefix,
|
||||
unitless: config.value.unitless,
|
||||
ignore: config.value.ignore,
|
||||
scope: config.value.scope || '',
|
||||
});
|
||||
|
||||
const styleId = uniqueHash(stylePath.value, cssVarsStr);
|
||||
return [mergedToken, cssVarsStr, styleId, config.value.key];
|
||||
},
|
||||
([, , styleId]) => {
|
||||
if (isClientSide) {
|
||||
removeCSS(styleId, { mark: ATTR_MARK });
|
||||
}
|
||||
},
|
||||
([, cssVarsStr, styleId]) => {
|
||||
if (!cssVarsStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const style = updateCSS(cssVarsStr, styleId, {
|
||||
mark: ATTR_MARK,
|
||||
prepend: 'queue',
|
||||
attachTo: styleContext.value.container,
|
||||
priority: -999,
|
||||
});
|
||||
|
||||
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value.cache?.instanceId;
|
||||
|
||||
// Used for `useCacheToken` to remove on batch when token removed
|
||||
style.setAttribute(ATTR_TOKEN, config.value.key);
|
||||
},
|
||||
);
|
||||
|
||||
return cache;
|
||||
};
|
||||
|
||||
export const extract: ExtractStyle<CSSVarCacheValue<any>> = (cache, _effectStyles, options) => {
|
||||
const [, styleStr, styleId, cssVarKey] = cache;
|
||||
const { plain } = options || {};
|
||||
|
||||
if (!styleStr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const order = -999;
|
||||
|
||||
// ====================== Style ======================
|
||||
// Used for rc-util
|
||||
const sharedAttrs = {
|
||||
'data-vc-order': 'prependQueue',
|
||||
'data-vc-priority': `${order}`,
|
||||
};
|
||||
|
||||
const styleText = toStyleStr(styleStr, cssVarKey, styleId, sharedAttrs, plain);
|
||||
|
||||
return [order, styleId, styleText];
|
||||
};
|
||||
|
||||
export default useCSSVarRegister;
|
||||
|
|
@ -1,20 +1,19 @@
|
|||
import hash from '@emotion/hash';
|
||||
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
|
||||
import { updateCSS } from '../../../vc-util/Dom/dynamicCSS';
|
||||
import { ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
|
||||
import type Theme from '../theme/Theme';
|
||||
import { flattenToken, memoResult, token2key, toStyleStr } from '../util';
|
||||
import { transformToken } from '../util/css-variables';
|
||||
import type { ExtractStyle } from './useGlobalCache';
|
||||
import useGlobalCache from './useGlobalCache';
|
||||
import { flattenToken, token2key } from '../util';
|
||||
import type { Ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const EMPTY_OVERRIDE = {};
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
// nuxt generate when NODE_ENV is prerender
|
||||
const isPrerender = process.env.NODE_ENV === 'prerender';
|
||||
|
||||
// Generate different prefix to make user selector break in production env.
|
||||
// This helps developer not to do style override directly on the hash id.
|
||||
const hashPrefix = !isProduction && !isPrerender ? 'css-dev-only-do-not-override' : 'css';
|
||||
const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
|
||||
|
||||
export interface Option<DerivativeToken, DesignToken> {
|
||||
/**
|
||||
|
|
@ -46,6 +45,22 @@ export interface Option<DerivativeToken, DesignToken> {
|
|||
override: object,
|
||||
theme: Theme<any, any>,
|
||||
) => DerivativeToken;
|
||||
|
||||
/**
|
||||
* Transform token to css variables.
|
||||
*/
|
||||
cssVar?: {
|
||||
/** Prefix for css variables */
|
||||
prefix?: string;
|
||||
/** Tokens that should not be appended with unit */
|
||||
unitless?: Record<string, boolean>;
|
||||
/** Tokens that should not be transformed to css variables */
|
||||
ignore?: Record<string, boolean>;
|
||||
/** Tokens that preserves origin value */
|
||||
preserve?: Record<string, boolean>;
|
||||
/** Key for current theme. Useful for customizing and should be unique */
|
||||
key?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const tokenKeys = new Map<string, number>();
|
||||
|
|
@ -94,6 +109,7 @@ export const getComputedToken = <DerivativeToken = object, DesignToken = Derivat
|
|||
format?: (token: DesignToken) => DerivativeToken,
|
||||
) => {
|
||||
const derivativeToken = theme.getDerivativeToken(originToken);
|
||||
|
||||
// Merge with override
|
||||
let mergedDerivativeToken = {
|
||||
...derivativeToken,
|
||||
|
|
@ -108,6 +124,16 @@ export const getComputedToken = <DerivativeToken = object, DesignToken = Derivat
|
|||
return mergedDerivativeToken;
|
||||
};
|
||||
|
||||
export const TOKEN_PREFIX = 'token';
|
||||
|
||||
type TokenCacheValue<DerivativeToken> = [
|
||||
token: DerivativeToken & { _tokenKey: string; _themeKey: string },
|
||||
hashId: string,
|
||||
realToken: DerivativeToken & { _tokenKey: string },
|
||||
cssVarStr: string,
|
||||
cssVarKey: string,
|
||||
];
|
||||
|
||||
/**
|
||||
* Cache theme derivative token as global shared one
|
||||
* @param theme Theme entity
|
||||
|
|
@ -119,21 +145,27 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
|
|||
theme: Ref<Theme<any, any>>,
|
||||
tokens: Ref<Partial<DesignToken>[]>,
|
||||
option: Ref<Option<DerivativeToken, DesignToken>> = ref({}),
|
||||
) {
|
||||
const style = useStyleInject();
|
||||
): Ref<TokenCacheValue<DerivativeToken>> {
|
||||
const styleContext = useStyleInject();
|
||||
|
||||
// Basic - We do basic cache here
|
||||
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
|
||||
const tokenStr = computed(() => flattenToken(mergedToken.value));
|
||||
const overrideTokenStr = computed(() => flattenToken(option.value.override || EMPTY_OVERRIDE));
|
||||
const mergedToken = computed(() =>
|
||||
memoResult(() => Object.assign({}, ...tokens.value), tokens.value),
|
||||
);
|
||||
|
||||
const cachedToken = useGlobalCache<[DerivativeToken & { _tokenKey: string }, string]>(
|
||||
'token',
|
||||
const tokenStr = computed(() => flattenToken(mergedToken.value));
|
||||
const overrideTokenStr = computed(() => flattenToken(option.value.override ?? EMPTY_OVERRIDE));
|
||||
|
||||
const cssVarStr = computed(() => (option.value.cssVar ? flattenToken(option.value.cssVar) : ''));
|
||||
|
||||
const cachedToken = useGlobalCache<TokenCacheValue<DerivativeToken>>(
|
||||
TOKEN_PREFIX,
|
||||
computed(() => [
|
||||
option.value.salt || '',
|
||||
theme.value.id,
|
||||
option.value.salt ?? '',
|
||||
theme.value?.id,
|
||||
tokenStr.value,
|
||||
overrideTokenStr.value,
|
||||
cssVarStr.value,
|
||||
]),
|
||||
() => {
|
||||
const {
|
||||
|
|
@ -141,25 +173,82 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
|
|||
override = EMPTY_OVERRIDE,
|
||||
formatToken,
|
||||
getComputedToken: compute,
|
||||
cssVar,
|
||||
} = option.value;
|
||||
const mergedDerivativeToken = compute
|
||||
let mergedDerivativeToken = compute
|
||||
? compute(mergedToken.value, override, theme.value)
|
||||
: getComputedToken(mergedToken.value, override, theme.value, formatToken);
|
||||
|
||||
// Replace token value with css variables
|
||||
const actualToken = { ...mergedDerivativeToken };
|
||||
let cssVarsStr = '';
|
||||
if (!!cssVar) {
|
||||
[mergedDerivativeToken, cssVarsStr] = transformToken(mergedDerivativeToken, cssVar.key!, {
|
||||
prefix: cssVar.prefix,
|
||||
ignore: cssVar.ignore,
|
||||
unitless: cssVar.unitless,
|
||||
preserve: cssVar.preserve,
|
||||
});
|
||||
}
|
||||
|
||||
// Optimize for `useStyleRegister` performance
|
||||
const tokenKey = token2key(mergedDerivativeToken, salt);
|
||||
mergedDerivativeToken._tokenKey = tokenKey;
|
||||
recordCleanToken(tokenKey);
|
||||
actualToken._tokenKey = token2key(actualToken, salt);
|
||||
|
||||
const themeKey = cssVar?.key ?? tokenKey;
|
||||
mergedDerivativeToken._themeKey = themeKey;
|
||||
recordCleanToken(themeKey);
|
||||
|
||||
const hashId = `${hashPrefix}-${hash(tokenKey)}`;
|
||||
mergedDerivativeToken._hashId = hashId; // Not used
|
||||
return [mergedDerivativeToken, hashId];
|
||||
|
||||
return [mergedDerivativeToken, hashId, actualToken, cssVarsStr, cssVar?.key || ''];
|
||||
},
|
||||
cache => {
|
||||
// Remove token will remove all related style
|
||||
cleanTokenStyle(cache[0]._tokenKey, style.value?.cache.instanceId);
|
||||
cleanTokenStyle(cache[0]._themeKey, styleContext.value?.cache?.instanceId);
|
||||
},
|
||||
([token, , , cssVarsStr]) => {
|
||||
const { cssVar } = option.value;
|
||||
if (cssVar && cssVarsStr) {
|
||||
const style = updateCSS(cssVarsStr, hash(`css-variables-${token._themeKey}`), {
|
||||
mark: ATTR_MARK,
|
||||
prepend: 'queue',
|
||||
attachTo: styleContext.value?.container,
|
||||
priority: -999,
|
||||
});
|
||||
|
||||
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value?.cache?.instanceId;
|
||||
|
||||
// Used for `useCacheToken` to remove on batch when token removed
|
||||
style.setAttribute(ATTR_TOKEN, token._themeKey);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
export const extract: ExtractStyle<TokenCacheValue<any>> = (cache, _effectStyles, options) => {
|
||||
const [, , realToken, styleStr, cssVarKey] = cache;
|
||||
const { plain } = options || {};
|
||||
|
||||
if (!styleStr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styleId = realToken._tokenKey;
|
||||
const order = -999;
|
||||
|
||||
// ====================== Style ======================
|
||||
// Used for rc-util
|
||||
const sharedAttrs = {
|
||||
'data-vc-order': 'prependQueue',
|
||||
'data-vc-priority': `${order}`,
|
||||
};
|
||||
|
||||
const styleText = toStyleStr(styleStr, cssVarKey, styleId, sharedAttrs, plain);
|
||||
|
||||
return [order, styleId, styleText];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
||||
import useLayoutEffect from '../../../_util/hooks/useLayoutEffect';
|
||||
import type { ShallowRef, WatchCallback } from 'vue';
|
||||
import { watch } from 'vue';
|
||||
|
||||
type UseCompatibleInsertionEffect = (
|
||||
renderEffect: WatchCallback,
|
||||
effect: (polyfill?: boolean) => ReturnType<WatchCallback>,
|
||||
deps: ShallowRef,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Polyfill `useInsertionEffect` for React < 18
|
||||
* @param renderEffect will be executed in `useMemo`, and do not have callback
|
||||
* @param effect will be executed in `useLayoutEffect`
|
||||
* @param deps
|
||||
*/
|
||||
const useInsertionEffectPolyfill: UseCompatibleInsertionEffect = (renderEffect, effect, deps) => {
|
||||
watch(deps, renderEffect, { immediate: true });
|
||||
useLayoutEffect(() => effect(true), deps);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compatible `useInsertionEffect`
|
||||
* will use `useInsertionEffect` if React version >= 18,
|
||||
* otherwise use `useInsertionEffectPolyfill`.
|
||||
*/
|
||||
const useCompatibleInsertionEffect: UseCompatibleInsertionEffect = useInsertionEffectPolyfill;
|
||||
|
||||
export default useCompatibleInsertionEffect;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
const useRun = () => {
|
||||
return function (fn: () => void) {
|
||||
fn();
|
||||
};
|
||||
};
|
||||
const useEffectCleanupRegister = useRun;
|
||||
|
||||
export default useEffectCleanupRegister;
|
||||
|
|
@ -1,41 +1,37 @@
|
|||
import { useStyleInject } from '../StyleContext';
|
||||
import type { KeyType } from '../Cache';
|
||||
import useCompatibleInsertionEffect from './useCompatibleInsertionEffect';
|
||||
import useHMR from './useHMR';
|
||||
import type { ShallowRef, Ref } from 'vue';
|
||||
import { onBeforeUnmount, watch, watchEffect, shallowRef } from 'vue';
|
||||
export default function useClientCache<CacheType>(
|
||||
import { onBeforeUnmount, watch, computed } from 'vue';
|
||||
|
||||
export type ExtractStyle<CacheValue> = (
|
||||
cache: CacheValue,
|
||||
effectStyles: Record<string, boolean>,
|
||||
options?: {
|
||||
plain?: boolean;
|
||||
},
|
||||
) => [order: number, styleId: string, style: string] | null;
|
||||
|
||||
export default function useGlobalCache<CacheType>(
|
||||
prefix: string,
|
||||
keyPath: Ref<KeyType[]>,
|
||||
cacheFn: () => CacheType,
|
||||
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
|
||||
// Add additional effect trigger by `useInsertionEffect`
|
||||
onCacheEffect?: (cachedValue: CacheType) => void,
|
||||
): ShallowRef<CacheType> {
|
||||
const styleContext = useStyleInject();
|
||||
const fullPathStr = shallowRef('');
|
||||
const res = shallowRef<CacheType>();
|
||||
watchEffect(() => {
|
||||
fullPathStr.value = [prefix, ...keyPath.value].join('%');
|
||||
});
|
||||
const globalCache = computed(() => styleContext.value?.cache);
|
||||
const deps = computed(() => [prefix, ...keyPath.value].join('%'));
|
||||
|
||||
const HMRUpdate = useHMR();
|
||||
const clearCache = (pathStr: string) => {
|
||||
styleContext.value.cache.update(pathStr, prevCache => {
|
||||
const [times = 0, cache] = prevCache || [];
|
||||
const nextCount = times - 1;
|
||||
if (nextCount === 0) {
|
||||
onCacheRemove?.(cache, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
return [times - 1, cache];
|
||||
});
|
||||
};
|
||||
type UpdaterArgs = [times: number, cache: CacheType];
|
||||
|
||||
watch(
|
||||
fullPathStr,
|
||||
(newStr, oldStr) => {
|
||||
if (oldStr) clearCache(oldStr);
|
||||
// Create cache
|
||||
styleContext.value.cache.update(newStr, prevCache => {
|
||||
const [times = 0, cache] = prevCache || [];
|
||||
const buildCache = (updater?: (data: UpdaterArgs) => UpdaterArgs) => {
|
||||
globalCache.value.update(deps.value, prevCache => {
|
||||
const [times = 0, cache] = prevCache || [undefined, undefined];
|
||||
|
||||
// HMR should always ignore cache since developer may change it
|
||||
let tmpCache = cache;
|
||||
|
|
@ -43,16 +39,77 @@ export default function useClientCache<CacheType>(
|
|||
onCacheRemove?.(tmpCache, HMRUpdate);
|
||||
tmpCache = null;
|
||||
}
|
||||
|
||||
const mergedCache = tmpCache || cacheFn();
|
||||
|
||||
return [times + 1, mergedCache];
|
||||
const data: UpdaterArgs = [times, mergedCache];
|
||||
|
||||
// Call updater if need additional logic
|
||||
return updater ? updater(data) : data;
|
||||
});
|
||||
res.value = styleContext.value.cache.get(fullPathStr.value)![1];
|
||||
};
|
||||
|
||||
watch(
|
||||
deps,
|
||||
() => {
|
||||
buildCache();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
onBeforeUnmount(() => {
|
||||
clearCache(fullPathStr.value);
|
||||
});
|
||||
return res;
|
||||
|
||||
let cacheEntity = globalCache.value.get(deps.value);
|
||||
|
||||
// HMR clean the cache but not trigger `useMemo` again
|
||||
// Let's fallback of this
|
||||
// ref https://github.com/ant-design/cssinjs/issues/127
|
||||
if (process.env.NODE_ENV !== 'production' && !cacheEntity) {
|
||||
buildCache();
|
||||
cacheEntity = globalCache.value.get(deps.value);
|
||||
}
|
||||
|
||||
const cacheContent = computed(
|
||||
() =>
|
||||
(globalCache.value.get(deps.value) && globalCache.value.get(deps.value)![1]) ||
|
||||
cacheEntity![1],
|
||||
);
|
||||
|
||||
// Remove if no need anymore
|
||||
useCompatibleInsertionEffect(
|
||||
() => {
|
||||
onCacheEffect?.(cacheContent.value);
|
||||
},
|
||||
polyfill => {
|
||||
// It's bad to call build again in effect.
|
||||
// But we have to do this since StrictMode will call effect twice
|
||||
// which will clear cache on the first time.
|
||||
buildCache(([times, cache]) => {
|
||||
if (polyfill && times === 0) {
|
||||
onCacheEffect?.(cacheContent.value);
|
||||
}
|
||||
return [times + 1, cache];
|
||||
});
|
||||
|
||||
return () => {
|
||||
globalCache.value.update(deps.value, prevCache => {
|
||||
const [times = 0, cache] = prevCache || [];
|
||||
const nextCount = times - 1;
|
||||
if (nextCount <= 0) {
|
||||
if (polyfill || !globalCache.value.get(deps.value)) {
|
||||
onCacheRemove?.(cache, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return [times - 1, cache];
|
||||
});
|
||||
};
|
||||
},
|
||||
deps,
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
buildCache();
|
||||
});
|
||||
|
||||
return cacheContent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,38 +3,30 @@ import type * as CSS from 'csstype';
|
|||
// @ts-ignore
|
||||
import unitless from '@emotion/unitless';
|
||||
import { compile, serialize, stringify } from 'stylis';
|
||||
import type { Theme, Transformer } from '../..';
|
||||
import type Cache from '../../Cache';
|
||||
import type Keyframes from '../../Keyframes';
|
||||
import type { Linter } from '../../linters';
|
||||
import { contentQuotesLinter, hashedAnimationLinter } from '../../linters';
|
||||
import type { HashPriority } from '../../StyleContext';
|
||||
import type { Theme, Transformer } from '..';
|
||||
import type Keyframes from '../Keyframes';
|
||||
import type { Linter } from '../linters';
|
||||
import { contentQuotesLinter, hashedAnimationLinter } from '../linters';
|
||||
import type { HashPriority } from '../StyleContext';
|
||||
import {
|
||||
useStyleInject,
|
||||
ATTR_CACHE_PATH,
|
||||
ATTR_MARK,
|
||||
ATTR_TOKEN,
|
||||
CSS_IN_JS_INSTANCE,
|
||||
} from '../../StyleContext';
|
||||
import { supportLayer } from '../../util';
|
||||
import useGlobalCache from '../useGlobalCache';
|
||||
import { removeCSS, updateCSS } from '../../../../vc-util/Dom/dynamicCSS';
|
||||
} from '../StyleContext';
|
||||
import { isClientSide, supportLayer, toStyleStr } from '../util';
|
||||
import { CSS_FILE_STYLE, existPath, getStyleAndHash } from '../util/cacheMapUtil';
|
||||
import type { ExtractStyle } from './useGlobalCache';
|
||||
import useGlobalCache from './useGlobalCache';
|
||||
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import type { VueNode } from '../../../type';
|
||||
import canUseDom from '../../../../_util/canUseDom';
|
||||
|
||||
import {
|
||||
ATTR_CACHE_MAP,
|
||||
existPath,
|
||||
getStyleAndHash,
|
||||
serialize as serializeCacheMap,
|
||||
} from './cacheMapUtil';
|
||||
|
||||
const isClientSide = canUseDom();
|
||||
import type { VueNode } from '../../type';
|
||||
|
||||
const SKIP_CHECK = '_skip_check_';
|
||||
const MULTI_VALUE = '_multi_value_';
|
||||
|
||||
export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & {
|
||||
animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes;
|
||||
};
|
||||
|
|
@ -60,6 +52,7 @@ export type CSSInterpolation = InterpolationPrimitive | ArrayCSSInterpolation |
|
|||
|
||||
export type CSSOthersObject = Record<string, CSSInterpolation>;
|
||||
|
||||
// @ts-ignore
|
||||
export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSSOthersObject {}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -114,16 +107,6 @@ export interface ParseInfo {
|
|||
parentSelectors: string[];
|
||||
}
|
||||
|
||||
// Global effect style will mount once and not removed
|
||||
// The effect will not save in SSR cache (e.g. keyframes)
|
||||
const globalEffectStyleKeys = new Set();
|
||||
|
||||
/**
|
||||
* @private Test only. Clear the global effect style keys.
|
||||
*/
|
||||
export const _cf =
|
||||
process.env.NODE_ENV !== 'production' ? () => globalEffectStyleKeys.clear() : undefined;
|
||||
|
||||
// Parse CSSObject to style content
|
||||
export const parseStyle = (
|
||||
interpolation: CSSInterpolation,
|
||||
|
|
@ -258,6 +241,7 @@ export const parseStyle = (
|
|||
|
||||
styleStr += `${styleName}:${formatValue};`;
|
||||
}
|
||||
|
||||
const actualValue = (value as any)?.value ?? value;
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
|
|
@ -295,7 +279,7 @@ export const parseStyle = (
|
|||
// ============================================================================
|
||||
// == Register ==
|
||||
// ============================================================================
|
||||
function uniqueHash(path: (string | number)[], styleStr: string) {
|
||||
export function uniqueHash(path: (string | number)[], styleStr: string) {
|
||||
return hash(`${path.join('%')}${styleStr}`);
|
||||
}
|
||||
|
||||
|
|
@ -303,6 +287,17 @@ function uniqueHash(path: (string | number)[], styleStr: string) {
|
|||
// return null;
|
||||
// }
|
||||
|
||||
export const STYLE_PREFIX = 'style';
|
||||
|
||||
type StyleCacheValue = [
|
||||
styleStr: string,
|
||||
tokenKey: string,
|
||||
styleId: string,
|
||||
effectStyle: Record<string, string>,
|
||||
clientOnly: boolean | undefined,
|
||||
order: number,
|
||||
];
|
||||
|
||||
/**
|
||||
* Register a style to the global style sheet.
|
||||
*/
|
||||
|
|
@ -337,22 +332,14 @@ export default function useStyleRegister(
|
|||
}
|
||||
|
||||
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
|
||||
useGlobalCache<
|
||||
[
|
||||
styleStr: string,
|
||||
tokenKey: string,
|
||||
styleId: string,
|
||||
effectStyle: Record<string, string>,
|
||||
clientOnly: boolean | undefined,
|
||||
order: number,
|
||||
]
|
||||
>(
|
||||
'style',
|
||||
useGlobalCache<StyleCacheValue>(
|
||||
STYLE_PREFIX,
|
||||
fullPath,
|
||||
// Create cache if needed
|
||||
() => {
|
||||
const { path, hashId, layer, nonce, clientOnly, order = 0 } = info.value;
|
||||
const { path, hashId, layer, clientOnly, order = 0 } = info.value;
|
||||
const cachePath = fullPath.value.join('|');
|
||||
|
||||
// Get style from SSR inline style directly
|
||||
if (existPath(cachePath)) {
|
||||
const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath);
|
||||
|
|
@ -360,8 +347,10 @@ export default function useStyleRegister(
|
|||
return [inlineCacheStyleStr, tokenKey.value, styleHash, {}, clientOnly, order];
|
||||
}
|
||||
}
|
||||
|
||||
// Generate style
|
||||
const styleObj = styleFn();
|
||||
const { hashPriority, container, transformers, linters, cache } = styleContext.value;
|
||||
const { hashPriority, transformers, linters } = styleContext.value;
|
||||
|
||||
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
|
||||
hashId,
|
||||
|
|
@ -371,18 +360,32 @@ export default function useStyleRegister(
|
|||
transformers,
|
||||
linters,
|
||||
});
|
||||
|
||||
const styleStr = normalizeStyle(parsedStyle);
|
||||
const styleId = uniqueHash(fullPath.value, styleStr);
|
||||
|
||||
if (isMergedClientSide) {
|
||||
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
|
||||
},
|
||||
|
||||
// Remove cache if no need
|
||||
([, , styleId], fromHMR) => {
|
||||
if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
|
||||
removeCSS(styleId, { mark: ATTR_MARK });
|
||||
}
|
||||
},
|
||||
|
||||
// Effect: Inject style here
|
||||
([styleStr, , styleId, effectStyle]) => {
|
||||
if (isMergedClientSide && styleStr !== CSS_FILE_STYLE) {
|
||||
const mergedCSSConfig: Parameters<typeof updateCSS>[2] = {
|
||||
mark: ATTR_MARK,
|
||||
prepend: 'queue',
|
||||
attachTo: container,
|
||||
priority: order,
|
||||
attachTo: styleContext.value.container,
|
||||
priority: info.value.order,
|
||||
};
|
||||
|
||||
const nonceStr = typeof nonce === 'function' ? nonce() : nonce;
|
||||
const nonceStr =
|
||||
typeof info.value.nonce === 'function' ? info.value.nonce() : info.value.nonce;
|
||||
|
||||
if (nonceStr) {
|
||||
mergedCSSConfig.csp = { nonce: nonceStr };
|
||||
|
|
@ -390,45 +393,33 @@ export default function useStyleRegister(
|
|||
|
||||
const style = updateCSS(styleStr, styleId, mergedCSSConfig);
|
||||
|
||||
(style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId;
|
||||
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value.cache.instanceId;
|
||||
|
||||
// Used for `useCacheToken` to remove on batch when token removed
|
||||
style.setAttribute(ATTR_TOKEN, tokenKey.value);
|
||||
|
||||
// Dev usage to find which cache path made this easily
|
||||
// Debug usage. Dev only
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|'));
|
||||
}
|
||||
|
||||
// Inject client side effect style
|
||||
Object.keys(effectStyle).forEach(effectKey => {
|
||||
if (!globalEffectStyleKeys.has(effectKey)) {
|
||||
globalEffectStyleKeys.add(effectKey);
|
||||
|
||||
// Inject
|
||||
updateCSS(normalizeStyle(effectStyle[effectKey]), `_effect-${effectKey}`, {
|
||||
mark: ATTR_MARK,
|
||||
prepend: 'queue',
|
||||
attachTo: container,
|
||||
updateCSS(
|
||||
normalizeStyle(effectStyle[effectKey]),
|
||||
`_effect-${effectKey}`,
|
||||
mergedCSSConfig,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
|
||||
},
|
||||
// Remove cache if no need
|
||||
([, , styleId], fromHMR) => {
|
||||
if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
|
||||
removeCSS(styleId, { mark: ATTR_MARK });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return (node: VueNode) => {
|
||||
return node;
|
||||
// let styleNode: VueNode;
|
||||
// if (!styleContext.ssrInline || isMergedClientSide || !styleContext.defaultCache) {
|
||||
|
||||
// if (!styleContext.value.ssrInline || isMergedClientSide || !styleContext.value.defaultCache) {
|
||||
// styleNode = <Empty />;
|
||||
// } else {
|
||||
// styleNode = (
|
||||
|
|
@ -451,78 +442,25 @@ export default function useStyleRegister(
|
|||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// == SSR ==
|
||||
// ============================================================================
|
||||
export function extractStyle(cache: Cache, plain = false) {
|
||||
const matchPrefix = `style%`;
|
||||
|
||||
// prefix with `style` is used for `useStyleRegister` to cache style context
|
||||
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith(matchPrefix));
|
||||
|
||||
// Common effect styles like animation
|
||||
const effectStyles: Record<string, boolean> = {};
|
||||
|
||||
// Mapping of cachePath to style hash
|
||||
const cachePathMap: Record<string, string> = {};
|
||||
|
||||
let styleText = '';
|
||||
|
||||
function toStyleStr(
|
||||
style: string,
|
||||
tokenKey?: string,
|
||||
styleId?: string,
|
||||
customizeAttrs: Record<string, string> = {},
|
||||
) {
|
||||
const attrs: Record<string, string | undefined> = {
|
||||
...customizeAttrs,
|
||||
[ATTR_TOKEN]: tokenKey,
|
||||
[ATTR_MARK]: styleId,
|
||||
};
|
||||
|
||||
const attrStr = Object.keys(attrs)
|
||||
.map(attr => {
|
||||
const val = attrs[attr];
|
||||
return val ? `${attr}="${val}"` : null;
|
||||
})
|
||||
.filter(v => v)
|
||||
.join(' ');
|
||||
|
||||
return plain ? style : `<style ${attrStr}>${style}</style>`;
|
||||
}
|
||||
|
||||
// ====================== Fill Style ======================
|
||||
type OrderStyle = [order: number, style: string];
|
||||
|
||||
const orderStyles: OrderStyle[] = styleKeys
|
||||
.map(key => {
|
||||
const cachePath = key.slice(matchPrefix.length).replace(/%/g, '|');
|
||||
|
||||
const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: [
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
Record<string, string>,
|
||||
boolean,
|
||||
number,
|
||||
] = cache.cache.get(key)![1];
|
||||
export const extract: ExtractStyle<StyleCacheValue> = (cache, effectStyles, options) => {
|
||||
const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: StyleCacheValue = cache;
|
||||
const { plain } = options || {};
|
||||
|
||||
// Skip client only style
|
||||
if (clientOnly) {
|
||||
return null! as OrderStyle;
|
||||
return null;
|
||||
}
|
||||
|
||||
let keyStyleText = styleStr;
|
||||
|
||||
// ====================== Style ======================
|
||||
// Used for vc-util
|
||||
// Used for rc-util
|
||||
const sharedAttrs = {
|
||||
'data-vc-order': 'prependQueue',
|
||||
'data-vc-priority': `${order}`,
|
||||
};
|
||||
|
||||
let keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs);
|
||||
|
||||
// Save cache path with hash mapping
|
||||
cachePathMap[cachePath] = styleId;
|
||||
keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs, plain);
|
||||
|
||||
// =============== Create effect style ===============
|
||||
if (effectStyle) {
|
||||
|
|
@ -530,37 +468,17 @@ export function extractStyle(cache: Cache, plain = false) {
|
|||
// Effect style can be reused
|
||||
if (!effectStyles[effectKey]) {
|
||||
effectStyles[effectKey] = true;
|
||||
const effectStyleStr = normalizeStyle(effectStyle[effectKey]);
|
||||
keyStyleText += toStyleStr(
|
||||
normalizeStyle(effectStyle[effectKey]),
|
||||
effectStyleStr,
|
||||
tokenKey,
|
||||
`_effect-${effectKey}`,
|
||||
sharedAttrs,
|
||||
plain,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const ret: OrderStyle = [order, keyStyleText];
|
||||
|
||||
return ret;
|
||||
})
|
||||
.filter(o => o);
|
||||
|
||||
orderStyles
|
||||
.sort((o1, o2) => o1[0] - o2[0])
|
||||
.forEach(([, style]) => {
|
||||
styleText += style;
|
||||
});
|
||||
|
||||
// ==================== Fill Cache Path ====================
|
||||
styleText += toStyleStr(
|
||||
`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
[ATTR_CACHE_MAP]: ATTR_CACHE_MAP,
|
||||
},
|
||||
);
|
||||
|
||||
return styleText;
|
||||
}
|
||||
return [order, styleId, keyStyleText];
|
||||
};
|
||||
|
|
@ -1,51 +1,37 @@
|
|||
import useCacheToken from './hooks/useCacheToken';
|
||||
import extractStyle from './extractStyle';
|
||||
import useCacheToken, { getComputedToken } from './hooks/useCacheToken';
|
||||
import useCSSVarRegister from './hooks/useCSSVarRegister';
|
||||
import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister';
|
||||
import useStyleRegister, { extractStyle } from './hooks/useStyleRegister';
|
||||
import useStyleRegister from './hooks/useStyleRegister';
|
||||
import Keyframes from './Keyframes';
|
||||
import type { Linter } from './linters';
|
||||
import { legacyNotSelectorLinter, logicalPropertiesLinter, parentSelectorLinter } from './linters';
|
||||
import type { StyleContextProps, StyleProviderProps } from './StyleContext';
|
||||
import { createCache, useStyleInject, useStyleProvider, StyleProvider } from './StyleContext';
|
||||
import {
|
||||
legacyNotSelectorLinter,
|
||||
logicalPropertiesLinter,
|
||||
NaNLinter,
|
||||
parentSelectorLinter,
|
||||
} from './linters';
|
||||
import type { StyleProviderProps } from './StyleContext';
|
||||
import { createCache, StyleProvider } from './StyleContext';
|
||||
import type { DerivativeFunc, TokenType } from './theme';
|
||||
import { createTheme, Theme } from './theme';
|
||||
import type { Transformer } from './transformers/interface';
|
||||
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
|
||||
import px2remTransformer from './transformers/px2rem';
|
||||
import { supportLogicProps, supportWhere } from './util';
|
||||
import { supportLogicProps, supportWhere, unit } from './util';
|
||||
import { token2CSSVar } from './util/css-variables';
|
||||
|
||||
const cssinjs = {
|
||||
Theme,
|
||||
createTheme,
|
||||
useStyleRegister,
|
||||
useCacheToken,
|
||||
createCache,
|
||||
useStyleInject,
|
||||
useStyleProvider,
|
||||
Keyframes,
|
||||
extractStyle,
|
||||
|
||||
// Transformer
|
||||
legacyLogicalPropertiesTransformer,
|
||||
px2remTransformer,
|
||||
|
||||
// Linters
|
||||
logicalPropertiesLinter,
|
||||
legacyNotSelectorLinter,
|
||||
parentSelectorLinter,
|
||||
|
||||
// cssinjs
|
||||
StyleProvider,
|
||||
};
|
||||
export {
|
||||
Theme,
|
||||
createTheme,
|
||||
useStyleRegister,
|
||||
useCSSVarRegister,
|
||||
useCacheToken,
|
||||
createCache,
|
||||
useStyleInject,
|
||||
useStyleProvider,
|
||||
StyleProvider,
|
||||
Keyframes,
|
||||
extractStyle,
|
||||
getComputedToken,
|
||||
|
||||
// Transformer
|
||||
legacyLogicalPropertiesTransformer,
|
||||
|
|
@ -55,9 +41,11 @@ export {
|
|||
logicalPropertiesLinter,
|
||||
legacyNotSelectorLinter,
|
||||
parentSelectorLinter,
|
||||
NaNLinter,
|
||||
|
||||
// cssinjs
|
||||
StyleProvider,
|
||||
// util
|
||||
token2CSSVar,
|
||||
unit,
|
||||
};
|
||||
export type {
|
||||
TokenType,
|
||||
|
|
@ -66,12 +54,9 @@ export type {
|
|||
DerivativeFunc,
|
||||
Transformer,
|
||||
Linter,
|
||||
StyleContextProps,
|
||||
StyleProviderProps,
|
||||
};
|
||||
|
||||
export const _experimental = {
|
||||
supportModernCSS: () => supportWhere() && supportLogicProps(),
|
||||
};
|
||||
|
||||
export default cssinjs;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import type { Linter } from './interface';
|
||||
import { lintWarning } from './utils';
|
||||
|
||||
const linter: Linter = (key, value, info) => {
|
||||
if ((typeof value === 'string' && /NaN/g.test(value)) || Number.isNaN(value)) {
|
||||
lintWarning(`Unexpected 'NaN' in property '${key}: ${value}'.`, info);
|
||||
}
|
||||
};
|
||||
|
||||
export default linter;
|
||||
|
|
@ -3,4 +3,5 @@ export { default as hashedAnimationLinter } from './hashedAnimationLinter';
|
|||
export type { Linter } from './interface';
|
||||
export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter';
|
||||
export { default as logicalPropertiesLinter } from './logicalPropertiesLinter';
|
||||
export { default as NaNLinter } from './NaNLinter';
|
||||
export { default as parentSelectorLinter } from './parentSelectorLinter';
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ export function lintWarning(message: string, info: LinterInfo) {
|
|||
|
||||
devWarning(
|
||||
false,
|
||||
`[Ant Design Vue CSS-in-JS] ${path ? `Error in '${path}': ` : ''}${message}${
|
||||
parentSelectors.length ? ` Selector info: ${parentSelectors.join(' -> ')}` : ''
|
||||
`[Ant Design Vue CSS-in-JS] ${path ? `Error in ${path}: ` : ''}${message}${
|
||||
parentSelectors.length ? ` Selector: ${parentSelectors.join(' | ')}` : ''
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,36 @@
|
|||
import type { CSSObject } from '..';
|
||||
import type { Transformer } from './interface';
|
||||
|
||||
function splitValues(value: string | number) {
|
||||
function splitValues(value: string | number): [values: (string | number)[], important: boolean] {
|
||||
if (typeof value === 'number') {
|
||||
return [value];
|
||||
return [[value], false];
|
||||
}
|
||||
|
||||
const splitStyle = String(value).split(/\s+/);
|
||||
const rawStyle = String(value).trim();
|
||||
const importantCells = rawStyle.match(/(.*)(!important)/);
|
||||
|
||||
const splitStyle = (importantCells ? importantCells[1] : rawStyle).trim().split(/\s+/);
|
||||
|
||||
// Combine styles split in brackets, like `calc(1px + 2px)`
|
||||
let temp = '';
|
||||
let brackets = 0;
|
||||
return splitStyle.reduce<string[]>((list, item) => {
|
||||
if (item.includes('(')) {
|
||||
temp += item;
|
||||
brackets += item.split('(').length - 1;
|
||||
} else if (item.includes(')')) {
|
||||
temp += ` ${item}`;
|
||||
brackets -= item.split(')').length - 1;
|
||||
if (brackets === 0) {
|
||||
list.push(temp);
|
||||
temp = '';
|
||||
return [
|
||||
splitStyle.reduce<string[]>((list, item) => {
|
||||
if (item.includes('(') || item.includes(')')) {
|
||||
const left = item.split('(').length - 1;
|
||||
const right = item.split(')').length - 1;
|
||||
brackets += left - right;
|
||||
}
|
||||
if (brackets === 0) {
|
||||
list.push(temp + item);
|
||||
temp = '';
|
||||
} else if (brackets > 0) {
|
||||
temp += ` ${item}`;
|
||||
} else {
|
||||
list.push(item);
|
||||
temp += item;
|
||||
}
|
||||
return list;
|
||||
}, []);
|
||||
}, []),
|
||||
!!importantCells,
|
||||
];
|
||||
}
|
||||
|
||||
type MatchValue = string[] & {
|
||||
|
|
@ -105,8 +107,14 @@ const keyMap: Record<string, MatchValue> = {
|
|||
borderEndEndRadius: ['borderBottomRightRadius'],
|
||||
};
|
||||
|
||||
function skipCheck(value: string | number) {
|
||||
return { _skip_check_: true, value };
|
||||
function wrapImportantAndSkipCheck(value: string | number, important: boolean) {
|
||||
let parsedValue = value;
|
||||
|
||||
if (important) {
|
||||
parsedValue = `${parsedValue} !important`;
|
||||
}
|
||||
|
||||
return { _skip_check_: true, value: parsedValue };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -127,25 +135,28 @@ const transform: Transformer = {
|
|||
const matchValue = keyMap[key];
|
||||
|
||||
if (matchValue && (typeof value === 'number' || typeof value === 'string')) {
|
||||
const values = splitValues(value);
|
||||
const [values, important] = splitValues(value);
|
||||
|
||||
if (matchValue.length && matchValue.notSplit) {
|
||||
// not split means always give same value like border
|
||||
matchValue.forEach(matchKey => {
|
||||
clone[matchKey] = skipCheck(value);
|
||||
clone[matchKey] = wrapImportantAndSkipCheck(value, important);
|
||||
});
|
||||
} else if (matchValue.length === 1) {
|
||||
// Handle like `marginBlockStart` => `marginTop`
|
||||
clone[matchValue[0]] = skipCheck(value);
|
||||
clone[matchValue[0]] = wrapImportantAndSkipCheck(value, important);
|
||||
} else if (matchValue.length === 2) {
|
||||
// Handle like `marginBlock` => `marginTop` & `marginBottom`
|
||||
matchValue.forEach((matchKey, index) => {
|
||||
clone[matchKey] = skipCheck(values[index] ?? values[0]);
|
||||
clone[matchKey] = wrapImportantAndSkipCheck(values[index] ?? values[0], important);
|
||||
});
|
||||
} else if (matchValue.length === 4) {
|
||||
// Handle like `inset` => `top` & `right` & `bottom` & `left`
|
||||
matchValue.forEach((matchKey, index) => {
|
||||
clone[matchKey] = skipCheck(values[index] ?? values[index - 2] ?? values[0]);
|
||||
clone[matchKey] = wrapImportantAndSkipCheck(
|
||||
values[index] ?? values[index - 2] ?? values[0],
|
||||
important,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
clone[key] = value;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* respect https://github.com/cuth/postcss-pxtorem
|
||||
*/
|
||||
// @ts-ignore
|
||||
import unitless from '@emotion/unitless';
|
||||
import type { CSSObject } from '..';
|
||||
import type { Transformer } from './interface';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import canUseDom from '../../../../_util/canUseDom';
|
||||
import { ATTR_MARK } from '../../StyleContext';
|
||||
import canUseDom from '../../canUseDom';
|
||||
import { ATTR_MARK } from '../StyleContext';
|
||||
|
||||
export const ATTR_CACHE_MAP = 'data-ant-cssinjs-cache-path';
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
export const token2CSSVar = (token: string, prefix = '') => {
|
||||
return `--${prefix ? `${prefix}-` : ''}${token}`
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
||||
.replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, '$1-$2')
|
||||
.replace(/([a-z])([A-Z0-9])/g, '$1-$2')
|
||||
.toLowerCase();
|
||||
};
|
||||
|
||||
export const serializeCSSVar = <T extends Record<string, any>>(
|
||||
cssVars: T,
|
||||
hashId: string,
|
||||
options?: {
|
||||
scope?: string;
|
||||
},
|
||||
) => {
|
||||
if (!Object.keys(cssVars).length) {
|
||||
return '';
|
||||
}
|
||||
return `.${hashId}${options?.scope ? `.${options.scope}` : ''}{${Object.entries(cssVars)
|
||||
.map(([key, value]) => `${key}:${value};`)
|
||||
.join('')}}`;
|
||||
};
|
||||
|
||||
export type TokenWithCSSVar<V, T extends Record<string, V> = Record<string, V>> = {
|
||||
[key in keyof T]?: string | V;
|
||||
};
|
||||
|
||||
export const transformToken = <V, T extends Record<string, V> = Record<string, V>>(
|
||||
token: T,
|
||||
themeKey: string,
|
||||
config?: {
|
||||
prefix?: string;
|
||||
ignore?: {
|
||||
[key in keyof T]?: boolean;
|
||||
};
|
||||
unitless?: {
|
||||
[key in keyof T]?: boolean;
|
||||
};
|
||||
preserve?: {
|
||||
[key in keyof T]?: boolean;
|
||||
};
|
||||
scope?: string;
|
||||
},
|
||||
): [TokenWithCSSVar<V, T>, string] => {
|
||||
const cssVars: Record<string, string> = {};
|
||||
const result: TokenWithCSSVar<V, T> = {};
|
||||
Object.entries(token).forEach(([key, value]) => {
|
||||
if (config?.preserve?.[key]) {
|
||||
result[key as keyof T] = value;
|
||||
} else if ((typeof value === 'string' || typeof value === 'number') && !config?.ignore?.[key]) {
|
||||
const cssVar = token2CSSVar(key, config?.prefix);
|
||||
cssVars[cssVar] =
|
||||
typeof value === 'number' && !config?.unitless?.[key] ? `${value}px` : String(value);
|
||||
result[key as keyof T] = `var(${cssVar})`;
|
||||
}
|
||||
});
|
||||
return [result, serializeCSSVar(cssVars, themeKey, { scope: config?.scope })];
|
||||
};
|
||||
|
|
@ -1,12 +1,37 @@
|
|||
import hash from '@emotion/hash';
|
||||
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
|
||||
import canUseDom from '../canUseDom';
|
||||
import canUseDom from '../../canUseDom';
|
||||
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
|
||||
import { ATTR_MARK, ATTR_TOKEN } from '../StyleContext';
|
||||
import { Theme } from '../theme';
|
||||
|
||||
import { Theme } from './theme';
|
||||
// Create a cache for memo concat
|
||||
type NestWeakMap<T> = WeakMap<object, NestWeakMap<T> | T>;
|
||||
const resultCache: NestWeakMap<object> = new WeakMap();
|
||||
const RESULT_VALUE = {};
|
||||
|
||||
export function memoResult<T extends object, R>(callback: () => R, deps: T[]): R {
|
||||
let current: WeakMap<any, any> = resultCache;
|
||||
for (let i = 0; i < deps.length; i += 1) {
|
||||
const dep = deps[i];
|
||||
if (!current.has(dep)) {
|
||||
current.set(dep, new WeakMap());
|
||||
}
|
||||
current = current.get(dep)!;
|
||||
}
|
||||
|
||||
if (!current.has(RESULT_VALUE)) {
|
||||
current.set(RESULT_VALUE, callback());
|
||||
}
|
||||
|
||||
return current.get(RESULT_VALUE);
|
||||
}
|
||||
|
||||
// Create a cache here to avoid always loop generate
|
||||
const flattenTokenCache = new WeakMap<any, string>();
|
||||
|
||||
/**
|
||||
* Flatten token to string, this will auto cache the result when token not change
|
||||
*/
|
||||
export function flattenToken(token: any) {
|
||||
let str = flattenTokenCache.get(token) || '';
|
||||
|
||||
|
|
@ -116,3 +141,39 @@ export function supportLogicProps(): boolean {
|
|||
|
||||
return canLogic!;
|
||||
}
|
||||
|
||||
export const isClientSide = canUseDom();
|
||||
|
||||
export function unit(num: string | number) {
|
||||
if (typeof num === 'number') {
|
||||
return `${num}px`;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export function toStyleStr(
|
||||
style: string,
|
||||
tokenKey?: string,
|
||||
styleId?: string,
|
||||
customizeAttrs: Record<string, string> = {},
|
||||
plain = false,
|
||||
) {
|
||||
if (plain) {
|
||||
return style;
|
||||
}
|
||||
const attrs: Record<string, string | undefined> = {
|
||||
...customizeAttrs,
|
||||
[ATTR_TOKEN]: tokenKey,
|
||||
[ATTR_MARK]: styleId,
|
||||
};
|
||||
|
||||
const attrStr = Object.keys(attrs)
|
||||
.map(attr => {
|
||||
const val = attrs[attr];
|
||||
return val ? `${attr}="${val}"` : null;
|
||||
})
|
||||
.filter(v => v)
|
||||
.join(' ');
|
||||
|
||||
return `<style ${attrStr}>${style}</style>`;
|
||||
}
|
||||
|
|
@ -2,32 +2,31 @@ export function isWindow(obj: any): obj is Window {
|
|||
return obj !== null && obj !== undefined && obj === obj.window;
|
||||
}
|
||||
|
||||
export default function getScroll(
|
||||
target: HTMLElement | Window | Document | null,
|
||||
top: boolean,
|
||||
): number {
|
||||
const getScroll = (target: HTMLElement | Window | Document | null): number => {
|
||||
if (typeof window === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
const method = top ? 'scrollTop' : 'scrollLeft';
|
||||
let result = 0;
|
||||
if (isWindow(target)) {
|
||||
result = target[top ? 'scrollY' : 'scrollX'];
|
||||
result = target.pageYOffset;
|
||||
} else if (target instanceof Document) {
|
||||
result = target.documentElement[method];
|
||||
result = target.documentElement.scrollTop;
|
||||
} else if (target instanceof HTMLElement) {
|
||||
result = target[method];
|
||||
result = target.scrollTop;
|
||||
} else if (target) {
|
||||
// According to the type inference, the `target` is `never` type.
|
||||
// Since we configured the loose mode type checking, and supports mocking the target with such shape below::
|
||||
// `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`,
|
||||
// the program may falls into this branch.
|
||||
// Check the corresponding tests for details. Don't sure what is the real scenario this happens.
|
||||
result = target[method];
|
||||
/* biome-ignore lint/complexity/useLiteralKeys: target is a never type */ /* eslint-disable-next-line dot-notation */
|
||||
result = target['scrollTop'];
|
||||
}
|
||||
|
||||
if (target && !isWindow(target) && typeof result !== 'number') {
|
||||
result = ((target.ownerDocument ?? target) as any).documentElement?.[method];
|
||||
result = (target.ownerDocument ?? (target as Document)).documentElement?.scrollTop;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
export default getScroll;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import type { Ref, ShallowRef } from 'vue';
|
||||
|
||||
import { shallowRef, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
function useLayoutEffect(
|
||||
fn: (mount: boolean) => void | VoidFunction,
|
||||
deps?: Ref<any> | Ref<any>[] | ShallowRef<any> | ShallowRef<any>[],
|
||||
) {
|
||||
const firstMount = shallowRef(true);
|
||||
const cleanupFn = ref(null);
|
||||
let stopWatch = null;
|
||||
|
||||
stopWatch = watch(
|
||||
deps,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
if (cleanupFn.value) {
|
||||
cleanupFn.value();
|
||||
}
|
||||
cleanupFn.value = fn(firstMount.value);
|
||||
});
|
||||
},
|
||||
{ immediate: true, flush: 'post' },
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
firstMount.value = false;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (cleanupFn.value) {
|
||||
cleanupFn.value();
|
||||
}
|
||||
if (stopWatch) {
|
||||
stopWatch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const useLayoutUpdateEffect = (callback, deps) => {
|
||||
useLayoutEffect(firstMount => {
|
||||
if (!firstMount) {
|
||||
return callback();
|
||||
}
|
||||
}, deps);
|
||||
};
|
||||
|
||||
export default useLayoutEffect;
|
||||
|
|
@ -14,7 +14,7 @@ interface ScrollToOptions {
|
|||
export default function scrollTo(y: number, options: ScrollToOptions = {}) {
|
||||
const { getContainer = () => window, callback, duration = 450 } = options;
|
||||
const container = getContainer();
|
||||
const scrollTop = getScroll(container, true);
|
||||
const scrollTop = getScroll(container);
|
||||
const startTime = Date.now();
|
||||
|
||||
const frameFunc = () => {
|
||||
|
|
|
|||
|
|
@ -27,10 +27,11 @@ import useStyle from './style';
|
|||
function getDefaultTarget() {
|
||||
return typeof window !== 'undefined' ? window : null;
|
||||
}
|
||||
enum AffixStatus {
|
||||
None,
|
||||
Prepare,
|
||||
}
|
||||
const AFFIX_STATUS_NONE = 0;
|
||||
const AFFIX_STATUS_PREPARE = 1;
|
||||
|
||||
type AffixStatus = typeof AFFIX_STATUS_NONE | typeof AFFIX_STATUS_PREPARE;
|
||||
|
||||
export interface AffixState {
|
||||
affixStyle?: CSSProperties;
|
||||
placeholderStyle?: CSSProperties;
|
||||
|
|
@ -82,7 +83,7 @@ const Affix = defineComponent({
|
|||
const state = reactive({
|
||||
affixStyle: undefined,
|
||||
placeholderStyle: undefined,
|
||||
status: AffixStatus.None,
|
||||
status: AFFIX_STATUS_NONE,
|
||||
lastAffix: false,
|
||||
prevTarget: null,
|
||||
timeout: null,
|
||||
|
|
@ -98,7 +99,12 @@ const Affix = defineComponent({
|
|||
const measure = () => {
|
||||
const { status, lastAffix } = state;
|
||||
const { target } = props;
|
||||
if (status !== AffixStatus.Prepare || !fixedNode.value || !placeholderNode.value || !target) {
|
||||
if (
|
||||
status !== AFFIX_STATUS_PREPARE ||
|
||||
!fixedNode.value ||
|
||||
!placeholderNode.value ||
|
||||
!target
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +114,7 @@ const Affix = defineComponent({
|
|||
}
|
||||
|
||||
const newState = {
|
||||
status: AffixStatus.None,
|
||||
status: AFFIX_STATUS_NONE,
|
||||
} as AffixState;
|
||||
const placeholderRect = getTargetRect(placeholderNode.value as HTMLElement);
|
||||
|
||||
|
|
@ -172,7 +178,7 @@ const Affix = defineComponent({
|
|||
};
|
||||
const prepareMeasure = () => {
|
||||
Object.assign(state, {
|
||||
status: AffixStatus.Prepare,
|
||||
status: AFFIX_STATUS_PREPARE,
|
||||
affixStyle: undefined,
|
||||
placeholderStyle: undefined,
|
||||
});
|
||||
|
|
@ -253,12 +259,13 @@ const Affix = defineComponent({
|
|||
});
|
||||
|
||||
const { prefixCls } = useConfigInject('affix', props);
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
return () => {
|
||||
const { affixStyle, placeholderStyle, status } = state;
|
||||
const className = classNames({
|
||||
[prefixCls.value]: affixStyle,
|
||||
[hashId.value]: true,
|
||||
[cssVarCls.value]: true,
|
||||
});
|
||||
const restProps = omit(props, [
|
||||
'prefixCls',
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { FullToken, GenerateStyle, genStyleHooks, GetDefaultToken } from '../../theme/internal';
|
||||
|
||||
export interface ComponentToken {
|
||||
/**
|
||||
* @desc 弹出层的 z-index
|
||||
* @descEN z-index of popup
|
||||
*/
|
||||
zIndexPopup: number;
|
||||
}
|
||||
|
||||
interface AffixToken extends FullToken<'Affix'> {
|
||||
zIndexPopup: number;
|
||||
//
|
||||
}
|
||||
|
||||
// ============================== Shared ==============================
|
||||
const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
|
||||
const { componentCls } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
position: 'fixed',
|
||||
|
|
@ -18,10 +24,9 @@ const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
|
|||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook('Affix', token => {
|
||||
const affixToken = mergeToken<AffixToken>(token, {
|
||||
export const prepareComponentToken: GetDefaultToken<'Affix'> = token => ({
|
||||
zIndexPopup: token.zIndexBase + 10,
|
||||
});
|
||||
return [genSharedAffixStyle(affixToken)];
|
||||
});
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genStyleHooks('Affix', genSharedAffixStyle, prepareComponentToken);
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ export function getTargetRect(target: BindElement): DOMRect {
|
|||
: ({ top: 0, bottom: window.innerHeight } as DOMRect);
|
||||
}
|
||||
|
||||
export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offsetTop: number) {
|
||||
if (offsetTop !== undefined && targetRect.top > placeholderRect.top - offsetTop) {
|
||||
export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offsetTop?: number) {
|
||||
if (
|
||||
offsetTop !== undefined &&
|
||||
Math.round(targetRect.top) > Math.round(placeholderRect.top) - offsetTop
|
||||
) {
|
||||
return `${offsetTop + targetRect.top}px`;
|
||||
}
|
||||
return undefined;
|
||||
|
|
@ -19,9 +22,12 @@ export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offse
|
|||
export function getFixedBottom(
|
||||
placeholderRect: DOMRect,
|
||||
targetRect: DOMRect,
|
||||
offsetBottom: number,
|
||||
offsetBottom?: number,
|
||||
) {
|
||||
if (
|
||||
offsetBottom !== undefined &&
|
||||
Math.round(targetRect.bottom) < Math.round(placeholderRect.bottom) + offsetBottom
|
||||
) {
|
||||
if (offsetBottom !== undefined && targetRect.bottom < placeholderRect.bottom + offsetBottom) {
|
||||
const targetBottomOffset = window.innerHeight - targetRect.bottom;
|
||||
return `${offsetBottom + targetBottomOffset}px`;
|
||||
}
|
||||
|
|
@ -29,7 +35,7 @@ export function getFixedBottom(
|
|||
}
|
||||
|
||||
// ======================== Observer ========================
|
||||
const TRIGGER_EVENTS = [
|
||||
const TRIGGER_EVENTS: (keyof WindowEventMap)[] = [
|
||||
'resize',
|
||||
'scroll',
|
||||
'touchstart',
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ const Alert = defineComponent({
|
|||
props: alertProps(),
|
||||
setup(props, { slots, emit, attrs, expose }) {
|
||||
const { prefixCls, direction } = useConfigInject('alert', props);
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
const closing = shallowRef(false);
|
||||
const closed = shallowRef(false);
|
||||
const alertNode = shallowRef();
|
||||
|
|
@ -134,6 +134,7 @@ const Alert = defineComponent({
|
|||
[`${prefixClsValue}-closable`]: closable,
|
||||
[`${prefixClsValue}-rtl`]: direction.value === 'rtl',
|
||||
[hashId.value]: true,
|
||||
[cssVarCls.value]: true,
|
||||
});
|
||||
|
||||
const closeIcon = closable ? (
|
||||
|
|
|
|||
|
|
@ -1,13 +1,29 @@
|
|||
import type { CSSInterpolation, CSSObject } from '../../_util/cssinjs';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { CSSObject, unit } from '../../_util/cssinjs';
|
||||
import { FullToken, GenerateStyle, genStyleHooks, GetDefaultToken } from '../../theme/internal';
|
||||
import { resetComponent } from '../../style';
|
||||
import { CSSProperties } from 'vue';
|
||||
|
||||
export interface ComponentToken {}
|
||||
export interface ComponentToken {
|
||||
// Component token here
|
||||
/**
|
||||
* @desc 默认内间距
|
||||
* @descEN Default padding
|
||||
*/
|
||||
defaultPadding: CSSProperties['padding'];
|
||||
/**
|
||||
* @desc 带有描述的内间距
|
||||
* @descEN Padding with description
|
||||
*/
|
||||
withDescriptionPadding: CSSProperties['padding'];
|
||||
/**
|
||||
* @desc 带有描述时的图标尺寸
|
||||
* @descEN Icon size with description
|
||||
*/
|
||||
withDescriptionIconSize: number;
|
||||
}
|
||||
|
||||
type AlertToken = FullToken<'Alert'> & {
|
||||
alertIconSizeLG: number;
|
||||
alertPaddingHorizontal: number;
|
||||
// Custom token here
|
||||
};
|
||||
|
||||
const genAlertTypeStyle = (
|
||||
|
|
@ -17,8 +33,8 @@ const genAlertTypeStyle = (
|
|||
token: AlertToken,
|
||||
alertCls: string,
|
||||
): CSSObject => ({
|
||||
backgroundColor: bgColor,
|
||||
border: `${token.lineWidth}px ${token.lineType} ${borderColor}`,
|
||||
background: bgColor,
|
||||
border: `${unit(token.lineWidth)} ${token.lineType} ${borderColor}`,
|
||||
[`${alertCls}-icon`]: {
|
||||
color: iconColor,
|
||||
},
|
||||
|
|
@ -35,12 +51,11 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
|
|||
lineHeight,
|
||||
borderRadiusLG: borderRadius,
|
||||
motionEaseInOutCirc,
|
||||
alertIconSizeLG,
|
||||
withDescriptionIconSize,
|
||||
colorText,
|
||||
paddingContentVerticalSM,
|
||||
alertPaddingHorizontal,
|
||||
paddingMD,
|
||||
paddingContentHorizontalLG,
|
||||
colorTextHeading,
|
||||
withDescriptionPadding,
|
||||
defaultPadding,
|
||||
} = token;
|
||||
|
||||
return {
|
||||
|
|
@ -49,7 +64,7 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
|
|||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: `${paddingContentVerticalSM}px ${alertPaddingHorizontal}px`, // Fixed horizontal padding here.
|
||||
padding: defaultPadding,
|
||||
wordWrap: 'break-word',
|
||||
borderRadius,
|
||||
|
||||
|
|
@ -67,14 +82,14 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
|
|||
lineHeight: 0,
|
||||
},
|
||||
|
||||
[`&-description`]: {
|
||||
'&-description': {
|
||||
display: 'none',
|
||||
fontSize,
|
||||
lineHeight,
|
||||
},
|
||||
|
||||
'&-message': {
|
||||
color: colorText,
|
||||
color: colorTextHeading,
|
||||
},
|
||||
|
||||
[`&${componentCls}-motion-leave`]: {
|
||||
|
|
@ -96,24 +111,23 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
|
|||
|
||||
[`${componentCls}-with-description`]: {
|
||||
alignItems: 'flex-start',
|
||||
paddingInline: paddingContentHorizontalLG,
|
||||
paddingBlock: paddingMD,
|
||||
|
||||
padding: withDescriptionPadding,
|
||||
[`${componentCls}-icon`]: {
|
||||
marginInlineEnd: marginSM,
|
||||
fontSize: alertIconSizeLG,
|
||||
fontSize: withDescriptionIconSize,
|
||||
lineHeight: 0,
|
||||
},
|
||||
|
||||
[`${componentCls}-message`]: {
|
||||
display: 'block',
|
||||
marginBottom: marginXS,
|
||||
color: colorText,
|
||||
color: colorTextHeading,
|
||||
fontSize: fontSizeLG,
|
||||
},
|
||||
|
||||
[`${componentCls}-description`]: {
|
||||
display: 'block',
|
||||
color: colorText,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -187,7 +201,7 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
|
|||
|
||||
return {
|
||||
[componentCls]: {
|
||||
[`&-action`]: {
|
||||
'&-action': {
|
||||
marginInlineStart: marginXS,
|
||||
},
|
||||
|
||||
|
|
@ -196,7 +210,7 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
|
|||
padding: 0,
|
||||
overflow: 'hidden',
|
||||
fontSize: fontSizeIcon,
|
||||
lineHeight: `${fontSizeIcon}px`,
|
||||
lineHeight: unit(fontSizeIcon),
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
|
|
@ -222,19 +236,17 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
|
|||
};
|
||||
};
|
||||
|
||||
export const genAlertStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSInterpolation => [
|
||||
genBaseStyle(token),
|
||||
genTypeStyle(token),
|
||||
genActionStyle(token),
|
||||
];
|
||||
export const prepareComponentToken: GetDefaultToken<'Alert'> = token => {
|
||||
const paddingHorizontal = 12; // Fixed value here.
|
||||
return {
|
||||
withDescriptionIconSize: token.fontSizeHeading3,
|
||||
defaultPadding: `${token.paddingContentVerticalSM}px ${paddingHorizontal}px`,
|
||||
withDescriptionPadding: `${token.paddingMD}px ${token.paddingContentHorizontalLG}px`,
|
||||
};
|
||||
};
|
||||
|
||||
export default genComponentStyleHook('Alert', token => {
|
||||
const { fontSizeHeading3 } = token;
|
||||
|
||||
const alertToken = mergeToken<AlertToken>(token, {
|
||||
alertIconSizeLG: fontSizeHeading3,
|
||||
alertPaddingHorizontal: 12, // Fixed value here.
|
||||
});
|
||||
|
||||
return [genAlertStyle(alertToken)];
|
||||
});
|
||||
export default genStyleHooks(
|
||||
'Alert',
|
||||
token => [genBaseStyle(token), genTypeStyle(token), genActionStyle(token)],
|
||||
prepareComponentToken,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import AnchorLink from './AnchorLink';
|
|||
import PropTypes from '../_util/vue-types';
|
||||
import devWarning from '../vc-util/devWarning';
|
||||
import { arrayType } from '../_util/type';
|
||||
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
|
||||
|
||||
export type AnchorDirection = 'vertical' | 'horizontal';
|
||||
|
||||
|
|
@ -39,8 +40,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
|
|||
|
||||
if (rect.width || rect.height) {
|
||||
if (container === window) {
|
||||
container = element.ownerDocument!.documentElement!;
|
||||
return rect.top - container.clientTop;
|
||||
return rect.top - element.ownerDocument!.documentElement!.clientTop;
|
||||
}
|
||||
return rect.top - (container as HTMLElement).getBoundingClientRect().top;
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ export const anchorProps = () => ({
|
|||
targetOffset: Number,
|
||||
items: arrayType<AnchorLinkItemProps[]>(),
|
||||
direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'),
|
||||
replace: Boolean,
|
||||
onChange: Function as PropType<(currentActiveLink: string) => void>,
|
||||
onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>,
|
||||
});
|
||||
|
|
@ -91,7 +92,7 @@ export default defineComponent({
|
|||
setup(props, { emit, attrs, slots, expose }) {
|
||||
const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props);
|
||||
const anchorDirection = computed(() => props.direction ?? 'vertical');
|
||||
|
||||
const rootCls = useCSSVarCls(prefixCls);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
devWarning(
|
||||
props.items && typeof slots.default !== 'function',
|
||||
|
|
@ -133,7 +134,7 @@ export default defineComponent({
|
|||
const target = document.getElementById(sharpLinkMatch[1]);
|
||||
if (target) {
|
||||
const top = getOffsetTop(target, container);
|
||||
if (top < offsetTop + bounds) {
|
||||
if (top <= offsetTop + bounds) {
|
||||
linkSections.push({
|
||||
link,
|
||||
top,
|
||||
|
|
@ -170,7 +171,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const container = getContainer.value();
|
||||
const scrollTop = getScroll(container, true);
|
||||
const scrollTop = getScroll(container);
|
||||
const eleOffsetTop = getOffsetTop(targetElement, container);
|
||||
let y = scrollTop + eleOffsetTop;
|
||||
y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
|
||||
|
|
@ -277,6 +278,7 @@ export default defineComponent({
|
|||
title={title}
|
||||
customTitleProps={option}
|
||||
v-slots={{ customTitle: slots.customTitle }}
|
||||
replace={props.replace}
|
||||
>
|
||||
{anchorDirection.value === 'vertical' ? createNestedLink(children) : null}
|
||||
</AnchorLink>
|
||||
|
|
@ -284,7 +286,7 @@ export default defineComponent({
|
|||
})
|
||||
: null;
|
||||
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
||||
|
||||
return () => {
|
||||
const { offsetTop, affix, showInkInFixed } = props;
|
||||
|
|
@ -296,6 +298,8 @@ export default defineComponent({
|
|||
const wrapperClass = classNames(hashId.value, props.wrapperClass, `${pre}-wrapper`, {
|
||||
[`${pre}-wrapper-horizontal`]: anchorDirection.value === 'horizontal',
|
||||
[`${pre}-rtl`]: direction.value === 'rtl',
|
||||
[rootCls.value]: true,
|
||||
[cssVarCls.value]: true,
|
||||
});
|
||||
|
||||
const anchorClass = classNames(pre, {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const anchorLinkProps = () => ({
|
|||
href: String,
|
||||
title: anyType<VueNode | ((item: any) => VueNode)>(),
|
||||
target: String,
|
||||
replace: Boolean,
|
||||
/* private use */
|
||||
customTitleProps: objectType<AnchorLinkItemProps>(),
|
||||
});
|
||||
|
|
@ -53,6 +54,10 @@ export default defineComponent({
|
|||
const { href } = props;
|
||||
contextHandleClick(e, { title: mergedTitle, href });
|
||||
scrollTo(href);
|
||||
if (props.replace) {
|
||||
e.preventDefault();
|
||||
window.location.replace(href);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
|
|
|
|||
|
|
@ -1,21 +1,55 @@
|
|||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { unit } from '../../_util/cssinjs';
|
||||
import {
|
||||
FullToken,
|
||||
GenerateStyle,
|
||||
genStyleHooks,
|
||||
GetDefaultToken,
|
||||
mergeToken,
|
||||
} from '../../theme/internal';
|
||||
import { resetComponent, textEllipsis } from '../../style';
|
||||
|
||||
export interface ComponentToken {}
|
||||
export interface ComponentToken {
|
||||
/**
|
||||
* @desc 链接纵向内间距
|
||||
* @descEN Vertical padding of link
|
||||
*/
|
||||
linkPaddingBlock: number;
|
||||
/**
|
||||
* @desc 链接横向内间距
|
||||
* @descEN Horizontal padding of link
|
||||
*/
|
||||
linkPaddingInlineStart: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc Anchor 组件的 Token
|
||||
* @descEN Token for Anchor component
|
||||
*/
|
||||
interface AnchorToken extends FullToken<'Anchor'> {
|
||||
/**
|
||||
* @desc 容器块偏移量
|
||||
* @descEN Holder block offset
|
||||
*/
|
||||
holderOffsetBlock: number;
|
||||
anchorPaddingBlock: number;
|
||||
anchorPaddingBlockSecondary: number;
|
||||
anchorPaddingInline: number;
|
||||
anchorBallSize: number;
|
||||
anchorTitleBlock: number;
|
||||
/**
|
||||
* @desc 次级锚点块内间距
|
||||
* @descEN Secondary anchor block padding
|
||||
*/
|
||||
anchorPaddingBlockSecondary: number | string;
|
||||
/**
|
||||
* @desc 锚点球大小
|
||||
* @descEN Anchor ball size
|
||||
*/
|
||||
anchorBallSize: number | string;
|
||||
/**
|
||||
* @desc 锚点标题块
|
||||
* @descEN Anchor title block
|
||||
*/
|
||||
anchorTitleBlock: number | string;
|
||||
}
|
||||
|
||||
// ============================== Shared ==============================
|
||||
const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
|
||||
const genSharedAnchorStyle: GenerateStyle<AnchorToken> = token => {
|
||||
const {
|
||||
componentCls,
|
||||
holderOffsetBlock,
|
||||
|
|
@ -24,26 +58,25 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
|
|||
colorPrimary,
|
||||
lineType,
|
||||
colorSplit,
|
||||
calc,
|
||||
} = token;
|
||||
|
||||
return {
|
||||
[`${componentCls}-wrapper`]: {
|
||||
marginBlockStart: -holderOffsetBlock,
|
||||
marginBlockStart: calc(holderOffsetBlock).mul(-1).equal(),
|
||||
paddingBlockStart: holderOffsetBlock,
|
||||
|
||||
// delete overflow: auto
|
||||
// overflow: 'auto',
|
||||
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
[componentCls]: {
|
||||
...resetComponent(token),
|
||||
position: 'relative',
|
||||
paddingInlineStart: lineWidthBold,
|
||||
|
||||
[`${componentCls}-link`]: {
|
||||
paddingBlock: token.anchorPaddingBlock,
|
||||
paddingInline: `${token.anchorPaddingInline}px 0`,
|
||||
paddingBlock: token.linkPaddingBlock,
|
||||
paddingInline: `${unit(token.linkPaddingInlineStart)} 0`,
|
||||
|
||||
'&-title': {
|
||||
...textEllipsis,
|
||||
|
|
@ -73,28 +106,21 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
|
|||
[componentCls]: {
|
||||
'&::before': {
|
||||
position: 'absolute',
|
||||
left: {
|
||||
_skip_check_: true,
|
||||
value: 0,
|
||||
},
|
||||
insetInlineStart: 0,
|
||||
top: 0,
|
||||
height: '100%',
|
||||
borderInlineStart: `${lineWidthBold}px ${lineType} ${colorSplit}`,
|
||||
borderInlineStart: `${unit(lineWidthBold)} ${lineType} ${colorSplit}`,
|
||||
content: '" "',
|
||||
},
|
||||
|
||||
[`${componentCls}-ink`]: {
|
||||
position: 'absolute',
|
||||
left: {
|
||||
_skip_check_: true,
|
||||
value: 0,
|
||||
},
|
||||
insetInlineStart: 0,
|
||||
display: 'none',
|
||||
transform: 'translateY(-50%)',
|
||||
transition: `top ${motionDurationSlow} ease-in-out`,
|
||||
width: lineWidthBold,
|
||||
backgroundColor: colorPrimary,
|
||||
|
||||
[`&${componentCls}-ink-visible`]: {
|
||||
display: 'inline-block',
|
||||
},
|
||||
|
|
@ -109,7 +135,7 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
|
|||
};
|
||||
};
|
||||
|
||||
const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
|
||||
const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = token => {
|
||||
const { componentCls, motionDurationSlow, lineWidthBold, colorPrimary } = token;
|
||||
|
||||
return {
|
||||
|
|
@ -127,7 +153,7 @@ const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSO
|
|||
value: 0,
|
||||
},
|
||||
bottom: 0,
|
||||
borderBottom: `1px ${token.lineType} ${token.colorSplit}`,
|
||||
borderBottom: `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}`,
|
||||
content: '" "',
|
||||
},
|
||||
|
||||
|
|
@ -157,17 +183,23 @@ const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSO
|
|||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook('Anchor', token => {
|
||||
const { fontSize, fontSizeLG, padding, paddingXXS } = token;
|
||||
export const prepareComponentToken: GetDefaultToken<'Anchor'> = token => ({
|
||||
linkPaddingBlock: token.paddingXXS,
|
||||
linkPaddingInlineStart: token.padding,
|
||||
});
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genStyleHooks(
|
||||
'Anchor',
|
||||
token => {
|
||||
const { fontSize, fontSizeLG, paddingXXS, calc } = token;
|
||||
const anchorToken = mergeToken<AnchorToken>(token, {
|
||||
holderOffsetBlock: paddingXXS,
|
||||
anchorPaddingBlock: paddingXXS,
|
||||
anchorPaddingBlockSecondary: paddingXXS / 2,
|
||||
anchorPaddingInline: padding,
|
||||
anchorTitleBlock: (fontSize / 14) * 3,
|
||||
anchorBallSize: fontSizeLG / 2,
|
||||
anchorPaddingBlockSecondary: calc(paddingXXS).div(2).equal(),
|
||||
anchorTitleBlock: calc(fontSize).div(14).mul(3).equal(),
|
||||
anchorBallSize: calc(fontSizeLG).div(2).equal(),
|
||||
});
|
||||
return [genSharedAnchorStyle(anchorToken), genSharedAnchorHorizontalStyle(anchorToken)];
|
||||
});
|
||||
},
|
||||
prepareComponentToken,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
|
|||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { genPresetColor, resetComponent } from '../../style';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface BadgeToken extends FullToken<'Badge'> {
|
||||
badgeFontHeight: number;
|
||||
badgeZIndex: number | string;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
|
|||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { genFocusStyle, resetComponent } from '../../style';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface BreadcrumbToken extends FullToken<'Breadcrumb'> {
|
||||
breadcrumbBaseColor: string;
|
||||
breadcrumbFontSize: number;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,11 @@ export default defineComponent({
|
|||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
devWarning(!size, 'Button.Group', 'Invalid prop `size`.');
|
||||
devWarning(
|
||||
!size || ['large', 'small', 'middle'].includes(size),
|
||||
'Button.Group',
|
||||
'Invalid prop `size`.',
|
||||
);
|
||||
}
|
||||
return {
|
||||
[`${prefixCls.value}`]: true,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export default defineComponent({
|
|||
// emits: ['click', 'mousedown'],
|
||||
setup(props, { slots, attrs, emit, expose }) {
|
||||
const { prefixCls, autoInsertSpaceInButton, direction, size } = useConfigInject('btn', props);
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
const groupSizeContext = GroupSizeContext.useInject();
|
||||
const disabledContext = useInjectDisabled();
|
||||
const mergedDisabled = computed(() => props.disabled ?? disabledContext.value);
|
||||
|
|
@ -95,6 +95,7 @@ export default defineComponent({
|
|||
compactItemClassnames.value,
|
||||
{
|
||||
[hashId.value]: true,
|
||||
[cssVarCls.value]: true,
|
||||
[`${pre}`]: true,
|
||||
[`${pre}-${shape}`]: shape !== 'default' && shape,
|
||||
[`${pre}-${type}`]: type,
|
||||
|
|
@ -216,7 +217,7 @@ export default defineComponent({
|
|||
);
|
||||
|
||||
if (href !== undefined) {
|
||||
return wrapSSR(
|
||||
return wrapCSSVar(
|
||||
<a {...buttonProps} href={href} target={target} ref={buttonNodeRef}>
|
||||
{iconNode}
|
||||
{kids}
|
||||
|
|
@ -239,7 +240,7 @@ export default defineComponent({
|
|||
);
|
||||
}
|
||||
|
||||
return wrapSSR(buttonNode);
|
||||
return wrapCSSVar(buttonNode);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
// Style as inline component
|
||||
import type { ButtonToken } from './token';
|
||||
import { prepareComponentToken, prepareToken } from './token';
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
import { genSubStyleComponent } from '../../theme/internal';
|
||||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import { unit } from '../../_util/cssinjs';
|
||||
|
||||
const genButtonCompactStyle: GenerateStyle<ButtonToken, CSSObject> = token => {
|
||||
const { componentCls, calc } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
// Special styles for Primary Button
|
||||
[`&-compact-item${componentCls}-primary`]: {
|
||||
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
|
||||
{
|
||||
position: 'relative',
|
||||
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
top: calc(token.lineWidth).mul(-1).equal(),
|
||||
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
|
||||
display: 'inline-block',
|
||||
width: token.lineWidth,
|
||||
height: `calc(100% + ${unit(token.lineWidth)} * 2)`,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
},
|
||||
// Special styles for Primary Button
|
||||
'&-compact-vertical-item': {
|
||||
[`&${componentCls}-primary`]: {
|
||||
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
|
||||
{
|
||||
position: 'relative',
|
||||
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
top: calc(token.lineWidth).mul(-1).equal(),
|
||||
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
|
||||
display: 'inline-block',
|
||||
width: `calc(100% + ${unit(token.lineWidth)} * 2)`,
|
||||
height: token.lineWidth,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genSubStyleComponent(
|
||||
['Button', 'compact'],
|
||||
token => {
|
||||
const buttonToken = prepareToken(token);
|
||||
|
||||
return [
|
||||
// Space Compact
|
||||
genCompactItemStyle(buttonToken),
|
||||
genCompactItemVerticalStyle(buttonToken),
|
||||
genButtonCompactStyle(buttonToken),
|
||||
] as CSSObject[];
|
||||
},
|
||||
prepareComponentToken,
|
||||
);
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import type { ButtonToken } from '.';
|
||||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import type { ButtonToken } from './token';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
|
||||
const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({
|
||||
|
|
@ -22,8 +23,8 @@ const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const genGroupStyle: GenerateStyle<ButtonToken> = token => {
|
||||
const { componentCls, fontSize, lineWidth, colorPrimaryHover, colorErrorHover } = token;
|
||||
const genGroupStyle: GenerateStyle<ButtonToken, CSSObject> = token => {
|
||||
const { componentCls, fontSize, lineWidth, groupBorderColor, colorErrorHover } = token;
|
||||
|
||||
return {
|
||||
[`${componentCls}-group`]: [
|
||||
|
|
@ -41,7 +42,7 @@ const genGroupStyle: GenerateStyle<ButtonToken> = token => {
|
|||
},
|
||||
|
||||
'&:not(:first-child)': {
|
||||
marginInlineStart: -lineWidth,
|
||||
marginInlineStart: token.calc(lineWidth).mul(-1).equal(),
|
||||
|
||||
[`&, & > ${componentCls}`]: {
|
||||
borderStartStartRadius: 0,
|
||||
|
|
@ -71,7 +72,7 @@ const genGroupStyle: GenerateStyle<ButtonToken> = token => {
|
|||
},
|
||||
|
||||
// Border Color
|
||||
genButtonBorderStyle(`${componentCls}-primary`, colorPrimaryHover),
|
||||
genButtonBorderStyle(`${componentCls}-primary`, groupBorderColor),
|
||||
genButtonBorderStyle(`${componentCls}-danger`, colorErrorHover),
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,51 +1,59 @@
|
|||
import type { CSSInterpolation, CSSObject } from '../../_util/cssinjs';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import genGroupStyle from './group';
|
||||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import { unit } from '../../_util/cssinjs';
|
||||
|
||||
import { genFocusStyle } from '../../style';
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||
import genGroupStyle from './group';
|
||||
import type { ButtonToken, ComponentToken } from './token';
|
||||
import { prepareComponentToken, prepareToken } from './token';
|
||||
|
||||
/** Component only token. Which will handle additional calculation of alias token */
|
||||
export interface ComponentToken {}
|
||||
|
||||
export interface ButtonToken extends FullToken<'Button'> {
|
||||
// FIXME: should be removed
|
||||
colorOutlineDefault: string;
|
||||
buttonPaddingHorizontal: number;
|
||||
}
|
||||
export type { ComponentToken };
|
||||
|
||||
// ============================== Shared ==============================
|
||||
const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => {
|
||||
const { componentCls, iconCls } = token;
|
||||
const { componentCls, iconCls, fontWeight } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
outline: 'none',
|
||||
position: 'relative',
|
||||
display: 'inline-block',
|
||||
fontWeight: 400,
|
||||
fontWeight,
|
||||
whiteSpace: 'nowrap',
|
||||
textAlign: 'center',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
border: `${token.lineWidth}px ${token.lineType} transparent`,
|
||||
background: 'transparent',
|
||||
border: `${unit(token.lineWidth)} ${token.lineType} transparent`,
|
||||
cursor: 'pointer',
|
||||
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
|
||||
userSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
lineHeight: token.lineHeight,
|
||||
color: token.colorText,
|
||||
|
||||
'&:disabled > *': {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
|
||||
'> span': {
|
||||
display: 'inline-block',
|
||||
},
|
||||
|
||||
[`${componentCls}-icon`]: {
|
||||
lineHeight: 0,
|
||||
},
|
||||
|
||||
// Leave a space between icon and text.
|
||||
[`> ${iconCls} + span, > span + ${iconCls}`]: {
|
||||
marginInlineStart: token.marginXS,
|
||||
},
|
||||
|
||||
[`&:not(${componentCls}-icon-only) > ${componentCls}-icon`]: {
|
||||
[`&${componentCls}-loading-icon, &:not(:last-child)`]: {
|
||||
marginInlineEnd: token.marginXS,
|
||||
},
|
||||
},
|
||||
|
||||
'> a': {
|
||||
color: 'currentColor',
|
||||
},
|
||||
|
|
@ -54,54 +62,29 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
|||
...genFocusStyle(token),
|
||||
},
|
||||
|
||||
[`&${componentCls}-two-chinese-chars::first-letter`]: {
|
||||
letterSpacing: '0.34em',
|
||||
},
|
||||
|
||||
[`&${componentCls}-two-chinese-chars > *:not(${iconCls})`]: {
|
||||
marginInlineEnd: '-0.34em',
|
||||
letterSpacing: '0.34em',
|
||||
},
|
||||
|
||||
// make `btn-icon-only` not too narrow
|
||||
[`&-icon-only${componentCls}-compact-item`]: {
|
||||
flex: 'none',
|
||||
},
|
||||
// Special styles for Primary Button
|
||||
[`&-compact-item${componentCls}-primary`]: {
|
||||
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
|
||||
{
|
||||
position: 'relative',
|
||||
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
top: -token.lineWidth,
|
||||
insetInlineStart: -token.lineWidth,
|
||||
display: 'inline-block',
|
||||
width: token.lineWidth,
|
||||
height: `calc(100% + ${token.lineWidth * 2}px)`,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
},
|
||||
// Special styles for Primary Button
|
||||
'&-compact-vertical-item': {
|
||||
[`&${componentCls}-primary`]: {
|
||||
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
|
||||
{
|
||||
position: 'relative',
|
||||
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
top: -token.lineWidth,
|
||||
insetInlineStart: -token.lineWidth,
|
||||
display: 'inline-block',
|
||||
width: `calc(100% + ${token.lineWidth * 2}px)`,
|
||||
height: token.lineWidth,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
} as CSSObject;
|
||||
};
|
||||
|
||||
const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({
|
||||
'&:not(:disabled)': {
|
||||
const genHoverActiveButtonStyle = (
|
||||
btnCls: string,
|
||||
hoverStyle: CSSObject,
|
||||
activeStyle: CSSObject,
|
||||
): CSSObject => ({
|
||||
[`&:not(:disabled):not(${btnCls}-disabled)`]: {
|
||||
'&:hover': hoverStyle,
|
||||
'&:active': activeStyle,
|
||||
},
|
||||
|
|
@ -117,21 +100,22 @@ const genCircleButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
|
||||
const genRoundButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
borderRadius: token.controlHeight,
|
||||
paddingInlineStart: token.controlHeight / 2,
|
||||
paddingInlineEnd: token.controlHeight / 2,
|
||||
paddingInlineStart: token.calc(token.controlHeight).div(2).equal(),
|
||||
paddingInlineEnd: token.calc(token.controlHeight).div(2).equal(),
|
||||
});
|
||||
|
||||
// =============================== Type ===============================
|
||||
const genDisabledStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
cursor: 'not-allowed',
|
||||
borderColor: token.colorBorder,
|
||||
borderColor: token.borderColorDisabled,
|
||||
color: token.colorTextDisabled,
|
||||
backgroundColor: token.colorBgContainerDisabled,
|
||||
background: token.colorBgContainerDisabled,
|
||||
boxShadow: 'none',
|
||||
});
|
||||
|
||||
const genGhostButtonStyle = (
|
||||
btnCls: string,
|
||||
background: string,
|
||||
textColor: string | false,
|
||||
borderColor: string | false,
|
||||
textColorDisabled: string | false,
|
||||
|
|
@ -141,17 +125,18 @@ const genGhostButtonStyle = (
|
|||
): CSSObject => ({
|
||||
[`&${btnCls}-background-ghost`]: {
|
||||
color: textColor || undefined,
|
||||
backgroundColor: 'transparent',
|
||||
background,
|
||||
borderColor: borderColor || undefined,
|
||||
boxShadow: 'none',
|
||||
|
||||
...genHoverActiveButtonStyle(
|
||||
btnCls,
|
||||
{
|
||||
backgroundColor: 'transparent',
|
||||
background,
|
||||
...hoverStyle,
|
||||
},
|
||||
{
|
||||
backgroundColor: 'transparent',
|
||||
background,
|
||||
...activeStyle,
|
||||
},
|
||||
),
|
||||
|
|
@ -165,7 +150,7 @@ const genGhostButtonStyle = (
|
|||
});
|
||||
|
||||
const genSolidDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
'&:disabled': {
|
||||
[`&:disabled, &${token.componentCls}-disabled`]: {
|
||||
...genDisabledStyle(token),
|
||||
},
|
||||
});
|
||||
|
|
@ -175,7 +160,7 @@ const genSolidButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
});
|
||||
|
||||
const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
'&:disabled': {
|
||||
[`&:disabled, &${token.componentCls}-disabled`]: {
|
||||
cursor: 'not-allowed',
|
||||
color: token.colorTextDisabled,
|
||||
},
|
||||
|
|
@ -185,12 +170,14 @@ const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token
|
|||
const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
...genSolidButtonStyle(token),
|
||||
|
||||
backgroundColor: token.colorBgContainer,
|
||||
borderColor: token.colorBorder,
|
||||
background: token.defaultBg,
|
||||
borderColor: token.defaultBorderColor,
|
||||
color: token.defaultColor,
|
||||
|
||||
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
|
||||
boxShadow: token.defaultShadow,
|
||||
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
color: token.colorPrimaryHover,
|
||||
borderColor: token.colorPrimaryHover,
|
||||
|
|
@ -203,8 +190,9 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
|
||||
...genGhostButtonStyle(
|
||||
token.componentCls,
|
||||
token.colorBgContainer,
|
||||
token.colorBgContainer,
|
||||
token.ghostBg,
|
||||
token.defaultGhostColor,
|
||||
token.defaultGhostBorderColor,
|
||||
token.colorTextDisabled,
|
||||
token.colorBorder,
|
||||
),
|
||||
|
|
@ -214,6 +202,7 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
borderColor: token.colorError,
|
||||
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
color: token.colorErrorHover,
|
||||
borderColor: token.colorErrorBorderHover,
|
||||
|
|
@ -226,6 +215,7 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
|
||||
...genGhostButtonStyle(
|
||||
token.componentCls,
|
||||
token.ghostBg,
|
||||
token.colorError,
|
||||
token.colorError,
|
||||
token.colorTextDisabled,
|
||||
|
|
@ -239,24 +229,26 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
...genSolidButtonStyle(token),
|
||||
|
||||
color: token.colorTextLightSolid,
|
||||
backgroundColor: token.colorPrimary,
|
||||
color: token.primaryColor,
|
||||
background: token.colorPrimary,
|
||||
|
||||
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
|
||||
boxShadow: token.primaryShadow,
|
||||
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
color: token.colorTextLightSolid,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
background: token.colorPrimaryHover,
|
||||
},
|
||||
{
|
||||
color: token.colorTextLightSolid,
|
||||
backgroundColor: token.colorPrimaryActive,
|
||||
background: token.colorPrimaryActive,
|
||||
},
|
||||
),
|
||||
|
||||
...genGhostButtonStyle(
|
||||
token.componentCls,
|
||||
token.ghostBg,
|
||||
token.colorPrimary,
|
||||
token.colorPrimary,
|
||||
token.colorTextDisabled,
|
||||
|
|
@ -272,20 +264,23 @@ const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
),
|
||||
|
||||
[`&${token.componentCls}-dangerous`]: {
|
||||
backgroundColor: token.colorError,
|
||||
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
|
||||
background: token.colorError,
|
||||
boxShadow: token.dangerShadow,
|
||||
color: token.dangerColor,
|
||||
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
backgroundColor: token.colorErrorHover,
|
||||
background: token.colorErrorHover,
|
||||
},
|
||||
{
|
||||
backgroundColor: token.colorErrorActive,
|
||||
background: token.colorErrorActive,
|
||||
},
|
||||
),
|
||||
|
||||
...genGhostButtonStyle(
|
||||
token.componentCls,
|
||||
token.ghostBg,
|
||||
token.colorError,
|
||||
token.colorError,
|
||||
token.colorTextDisabled,
|
||||
|
|
@ -314,8 +309,10 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
color: token.colorLink,
|
||||
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
color: token.colorLinkHover,
|
||||
background: token.linkHoverBg,
|
||||
},
|
||||
{
|
||||
color: token.colorLinkActive,
|
||||
|
|
@ -328,6 +325,7 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
color: token.colorError,
|
||||
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
color: token.colorErrorHover,
|
||||
},
|
||||
|
|
@ -343,13 +341,14 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
// Type: Text
|
||||
const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
color: token.colorText,
|
||||
backgroundColor: token.colorBgTextHover,
|
||||
background: token.textHoverBg,
|
||||
},
|
||||
{
|
||||
color: token.colorText,
|
||||
backgroundColor: token.colorBgTextActive,
|
||||
background: token.colorBgTextActive,
|
||||
},
|
||||
),
|
||||
|
||||
|
|
@ -360,26 +359,19 @@ const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
|||
|
||||
...genPureDisabledButtonStyle(token),
|
||||
...genHoverActiveButtonStyle(
|
||||
token.componentCls,
|
||||
{
|
||||
color: token.colorErrorHover,
|
||||
backgroundColor: token.colorErrorBg,
|
||||
background: token.colorErrorBg,
|
||||
},
|
||||
{
|
||||
color: token.colorErrorHover,
|
||||
backgroundColor: token.colorErrorBg,
|
||||
background: token.colorErrorBg,
|
||||
},
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
// Href and Disabled
|
||||
const genDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
|
||||
...genDisabledStyle(token),
|
||||
[`&${token.componentCls}:hover`]: {
|
||||
...genDisabledStyle(token),
|
||||
},
|
||||
});
|
||||
|
||||
const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => {
|
||||
const { componentCls } = token;
|
||||
|
||||
|
|
@ -389,26 +381,30 @@ const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => {
|
|||
[`${componentCls}-dashed`]: genDashedButtonStyle(token),
|
||||
[`${componentCls}-link`]: genLinkButtonStyle(token),
|
||||
[`${componentCls}-text`]: genTextButtonStyle(token),
|
||||
[`${componentCls}-disabled`]: genDisabledButtonStyle(token),
|
||||
[`${componentCls}-ghost`]: genGhostButtonStyle(
|
||||
token.componentCls,
|
||||
token.ghostBg,
|
||||
token.colorBgContainer,
|
||||
token.colorBgContainer,
|
||||
token.colorTextDisabled,
|
||||
token.colorBorder,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
// =============================== Size ===============================
|
||||
const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSSInterpolation => {
|
||||
const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = '') => {
|
||||
const {
|
||||
componentCls,
|
||||
iconCls,
|
||||
controlHeight,
|
||||
fontSize,
|
||||
lineHeight,
|
||||
lineWidth,
|
||||
borderRadius,
|
||||
buttonPaddingHorizontal,
|
||||
iconCls,
|
||||
buttonPaddingVertical,
|
||||
} = token;
|
||||
|
||||
const paddingVertical = Math.max(0, (controlHeight - fontSize * lineHeight) / 2 - lineWidth);
|
||||
const paddingHorizontal = buttonPaddingHorizontal - lineWidth;
|
||||
|
||||
const iconOnlyCls = `${componentCls}-icon-only`;
|
||||
|
||||
return [
|
||||
|
|
@ -416,8 +412,9 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
|
|||
{
|
||||
[`${componentCls}${sizePrefixCls}`]: {
|
||||
fontSize,
|
||||
lineHeight,
|
||||
height: controlHeight,
|
||||
padding: `${paddingVertical}px ${paddingHorizontal}px`,
|
||||
padding: `${unit(buttonPaddingVertical!)} ${unit(buttonPaddingHorizontal!)}`,
|
||||
borderRadius,
|
||||
|
||||
[`&${iconOnlyCls}`]: {
|
||||
|
|
@ -427,8 +424,8 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
|
|||
[`&${componentCls}-round`]: {
|
||||
width: 'auto',
|
||||
},
|
||||
'> span': {
|
||||
transform: 'scale(1.143)', // 14px -> 16px
|
||||
[iconCls]: {
|
||||
fontSize: token.buttonIconOnlyFontSize,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -441,10 +438,6 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
|
|||
[`${componentCls}-loading-icon`]: {
|
||||
transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`,
|
||||
},
|
||||
|
||||
[`&:not(${iconOnlyCls}) ${componentCls}-loading-icon > ${iconCls}`]: {
|
||||
marginInlineEnd: token.marginXS,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -458,14 +451,24 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
|
|||
];
|
||||
};
|
||||
|
||||
const genSizeBaseButtonStyle: GenerateStyle<ButtonToken> = token => genSizeButtonStyle(token);
|
||||
const genSizeBaseButtonStyle: GenerateStyle<ButtonToken> = token =>
|
||||
genSizeButtonStyle(
|
||||
mergeToken<ButtonToken>(token, {
|
||||
fontSize: token.contentFontSize,
|
||||
lineHeight: token.contentLineHeight,
|
||||
}),
|
||||
);
|
||||
|
||||
const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => {
|
||||
const smallToken = mergeToken<ButtonToken>(token, {
|
||||
controlHeight: token.controlHeightSM,
|
||||
fontSize: token.contentFontSizeSM,
|
||||
lineHeight: token.contentLineHeightSM,
|
||||
padding: token.paddingXS,
|
||||
buttonPaddingHorizontal: 8, // Fixed padding
|
||||
buttonPaddingHorizontal: token.paddingInlineSM,
|
||||
buttonPaddingVertical: token.paddingBlockSM,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
buttonIconOnlyFontSize: token.onlyIconSizeSM,
|
||||
});
|
||||
|
||||
return genSizeButtonStyle(smallToken, `${token.componentCls}-sm`);
|
||||
|
|
@ -474,8 +477,12 @@ const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => {
|
|||
const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = token => {
|
||||
const largeToken = mergeToken<ButtonToken>(token, {
|
||||
controlHeight: token.controlHeightLG,
|
||||
fontSize: token.fontSizeLG,
|
||||
fontSize: token.contentFontSizeLG,
|
||||
lineHeight: token.contentLineHeightLG,
|
||||
buttonPaddingHorizontal: token.paddingInlineLG,
|
||||
buttonPaddingVertical: token.paddingBlockLG,
|
||||
borderRadius: token.borderRadiusLG,
|
||||
buttonIconOnlyFontSize: token.onlyIconSizeLG,
|
||||
});
|
||||
|
||||
return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`);
|
||||
|
|
@ -493,12 +500,10 @@ const genBlockButtonStyle: GenerateStyle<ButtonToken> = token => {
|
|||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook('Button', token => {
|
||||
const { controlTmpOutline, paddingContentHorizontal } = token;
|
||||
const buttonToken = mergeToken<ButtonToken>(token, {
|
||||
colorOutlineDefault: controlTmpOutline,
|
||||
buttonPaddingHorizontal: paddingContentHorizontal,
|
||||
});
|
||||
export default genStyleHooks(
|
||||
'Button',
|
||||
token => {
|
||||
const buttonToken = prepareToken(token);
|
||||
|
||||
return [
|
||||
// Shared
|
||||
|
|
@ -512,14 +517,20 @@ export default genComponentStyleHook('Button', token => {
|
|||
// Block
|
||||
genBlockButtonStyle(buttonToken),
|
||||
|
||||
// Group (type, ghost, danger, disabled, loading)
|
||||
// Group (type, ghost, danger, loading)
|
||||
genTypeButtonStyle(buttonToken),
|
||||
|
||||
// Button Group
|
||||
genGroupStyle(buttonToken),
|
||||
|
||||
// Space Compact
|
||||
genCompactItemStyle(token, { focus: false }),
|
||||
genCompactItemVerticalStyle(token),
|
||||
];
|
||||
});
|
||||
},
|
||||
prepareComponentToken,
|
||||
{
|
||||
unitless: {
|
||||
fontWeight: true,
|
||||
contentLineHeight: true,
|
||||
contentLineHeightSM: true,
|
||||
contentLineHeightLG: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,234 @@
|
|||
import type { CSSProperties } from 'vue';
|
||||
import type { FullToken, GetDefaultToken } from '../../theme/internal';
|
||||
import { getLineHeight, mergeToken } from '../../theme/internal';
|
||||
import type { GenStyleFn } from '../../theme/util/genComponentStyleHook';
|
||||
|
||||
/** Component only token. Which will handle additional calculation of alias token */
|
||||
export interface ComponentToken {
|
||||
/**
|
||||
* @desc 文字字重
|
||||
* @descEN Font weight of text
|
||||
*/
|
||||
fontWeight: CSSProperties['fontWeight'];
|
||||
/**
|
||||
* @desc 默认按钮阴影
|
||||
* @descEN Shadow of default button
|
||||
*/
|
||||
defaultShadow: string;
|
||||
/**
|
||||
* @desc 主要按钮阴影
|
||||
* @descEN Shadow of primary button
|
||||
*/
|
||||
primaryShadow: string;
|
||||
/**
|
||||
* @desc 危险按钮阴影
|
||||
* @descEN Shadow of danger button
|
||||
*/
|
||||
dangerShadow: string;
|
||||
/**
|
||||
* @desc 主要按钮文本颜色
|
||||
* @descEN Text color of primary button
|
||||
*/
|
||||
primaryColor: string;
|
||||
/**
|
||||
* @desc 默认按钮文本颜色
|
||||
* @descEN Text color of default button
|
||||
*/
|
||||
defaultColor: string;
|
||||
/**
|
||||
* @desc 默认按钮背景色
|
||||
* @descEN Background color of default button
|
||||
*/
|
||||
defaultBg: string;
|
||||
/**
|
||||
* @desc 默认按钮边框颜色
|
||||
* @descEN Border color of default button
|
||||
*/
|
||||
defaultBorderColor: string;
|
||||
/**
|
||||
* @desc 危险按钮文本颜色
|
||||
* @descEN Text color of danger button
|
||||
*/
|
||||
dangerColor: string;
|
||||
/**
|
||||
* @desc 禁用状态边框颜色
|
||||
* @descEN Border color of disabled button
|
||||
*/
|
||||
borderColorDisabled: string;
|
||||
/**
|
||||
* @desc 默认幽灵按钮文本颜色
|
||||
* @descEN Text color of default ghost button
|
||||
*/
|
||||
defaultGhostColor: string;
|
||||
/**
|
||||
* @desc 幽灵按钮背景色
|
||||
* @descEN Background color of ghost button
|
||||
*/
|
||||
ghostBg: string;
|
||||
/**
|
||||
* @desc 默认幽灵按钮边框颜色
|
||||
* @descEN Border color of default ghost button
|
||||
*/
|
||||
defaultGhostBorderColor: string;
|
||||
/**
|
||||
* @desc 按钮横向内间距
|
||||
* @descEN Horizontal padding of button
|
||||
*/
|
||||
paddingInline: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 大号按钮横向内间距
|
||||
* @descEN Horizontal padding of large button
|
||||
*/
|
||||
paddingInlineLG: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 小号按钮横向内间距
|
||||
* @descEN Horizontal padding of small button
|
||||
*/
|
||||
paddingInlineSM: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 按钮横向内间距
|
||||
* @descEN Horizontal padding of button
|
||||
*/
|
||||
paddingBlock: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 大号按钮横向内间距
|
||||
* @descEN Horizontal padding of large button
|
||||
*/
|
||||
paddingBlockLG: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 小号按钮横向内间距
|
||||
* @descEN Horizontal padding of small button
|
||||
*/
|
||||
paddingBlockSM: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 只有图标的按钮图标尺寸
|
||||
* @descEN Icon size of button which only contains icon
|
||||
*/
|
||||
onlyIconSize: number;
|
||||
/**
|
||||
* @desc 大号只有图标的按钮图标尺寸
|
||||
* @descEN Icon size of large button which only contains icon
|
||||
*/
|
||||
onlyIconSizeLG: number;
|
||||
/**
|
||||
* @desc 小号只有图标的按钮图标尺寸
|
||||
* @descEN Icon size of small button which only contains icon
|
||||
*/
|
||||
onlyIconSizeSM: number;
|
||||
/**
|
||||
* @desc 按钮组边框颜色
|
||||
* @descEN Border color of button group
|
||||
*/
|
||||
groupBorderColor: string;
|
||||
/**
|
||||
* @desc 链接按钮悬浮态背景色
|
||||
* @descEN Background color of link button when hover
|
||||
*/
|
||||
linkHoverBg: string;
|
||||
/**
|
||||
* @desc 文本按钮悬浮态背景色
|
||||
* @descEN Background color of text button when hover
|
||||
*/
|
||||
textHoverBg: string;
|
||||
/**
|
||||
* @desc 按钮内容字体大小
|
||||
* @descEN Font size of button content
|
||||
*/
|
||||
contentFontSize: number;
|
||||
/**
|
||||
* @desc 大号按钮内容字体大小
|
||||
* @descEN Font size of large button content
|
||||
*/
|
||||
contentFontSizeLG: number;
|
||||
/**
|
||||
* @desc 小号按钮内容字体大小
|
||||
* @descEN Font size of small button content
|
||||
*/
|
||||
contentFontSizeSM: number;
|
||||
/**
|
||||
* @desc 按钮内容字体行高
|
||||
* @descEN Line height of button content
|
||||
*/
|
||||
contentLineHeight: number;
|
||||
/**
|
||||
* @desc 大号按钮内容字体行高
|
||||
* @descEN Line height of large button content
|
||||
*/
|
||||
contentLineHeightLG: number;
|
||||
/**
|
||||
* @desc 小号按钮内容字体行高
|
||||
* @descEN Line height of small button content
|
||||
*/
|
||||
contentLineHeightSM: number;
|
||||
}
|
||||
|
||||
export interface ButtonToken extends FullToken<'Button'> {
|
||||
buttonPaddingHorizontal: CSSProperties['paddingInline'];
|
||||
buttonPaddingVertical: CSSProperties['paddingBlock'];
|
||||
buttonIconOnlyFontSize: number;
|
||||
}
|
||||
|
||||
export const prepareToken: (token: Parameters<GenStyleFn<'Button'>>[0]) => ButtonToken = token => {
|
||||
const { paddingInline, onlyIconSize, paddingBlock } = token;
|
||||
|
||||
const buttonToken = mergeToken<ButtonToken>(token, {
|
||||
buttonPaddingHorizontal: paddingInline,
|
||||
buttonPaddingVertical: paddingBlock,
|
||||
buttonIconOnlyFontSize: onlyIconSize,
|
||||
});
|
||||
|
||||
return buttonToken;
|
||||
};
|
||||
|
||||
export const prepareComponentToken: GetDefaultToken<'Button'> = token => {
|
||||
const contentFontSize = token.contentFontSize ?? token.fontSize;
|
||||
const contentFontSizeSM = token.contentFontSizeSM ?? token.fontSize;
|
||||
const contentFontSizeLG = token.contentFontSizeLG ?? token.fontSizeLG;
|
||||
const contentLineHeight = token.contentLineHeight ?? getLineHeight(contentFontSize);
|
||||
const contentLineHeightSM = token.contentLineHeightSM ?? getLineHeight(contentFontSizeSM);
|
||||
const contentLineHeightLG = token.contentLineHeightLG ?? getLineHeight(contentFontSizeLG);
|
||||
|
||||
return {
|
||||
fontWeight: 400,
|
||||
defaultShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
|
||||
primaryShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
|
||||
dangerShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
|
||||
primaryColor: token.colorTextLightSolid,
|
||||
dangerColor: token.colorTextLightSolid,
|
||||
borderColorDisabled: token.colorBorder,
|
||||
defaultGhostColor: token.colorBgContainer,
|
||||
ghostBg: 'transparent',
|
||||
defaultGhostBorderColor: token.colorBgContainer,
|
||||
paddingInline: token.paddingContentHorizontal - token.lineWidth,
|
||||
paddingInlineLG: token.paddingContentHorizontal - token.lineWidth,
|
||||
paddingInlineSM: 8 - token.lineWidth,
|
||||
onlyIconSize: token.fontSizeLG,
|
||||
onlyIconSizeSM: token.fontSizeLG - 2,
|
||||
onlyIconSizeLG: token.fontSizeLG + 2,
|
||||
groupBorderColor: token.colorPrimaryHover,
|
||||
linkHoverBg: 'transparent',
|
||||
textHoverBg: token.colorBgTextHover,
|
||||
defaultColor: token.colorText,
|
||||
defaultBg: token.colorBgContainer,
|
||||
defaultBorderColor: token.colorBorder,
|
||||
defaultBorderColorDisabled: token.colorBorder,
|
||||
contentFontSize,
|
||||
contentFontSizeSM,
|
||||
contentFontSizeLG,
|
||||
contentLineHeight,
|
||||
contentLineHeightSM,
|
||||
contentLineHeightLG,
|
||||
paddingBlock: Math.max(
|
||||
(token.controlHeight - contentFontSize * contentLineHeight) / 2 - token.lineWidth,
|
||||
0,
|
||||
),
|
||||
paddingBlockSM: Math.max(
|
||||
(token.controlHeightSM - contentFontSizeSM * contentLineHeightSM) / 2 - token.lineWidth,
|
||||
0,
|
||||
),
|
||||
paddingBlockLG: Math.max(
|
||||
(token.controlHeightLG - contentFontSizeLG * contentLineHeightLG) / 2 - token.lineWidth,
|
||||
0,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
|
@ -57,6 +57,18 @@ export interface ThemeConfig {
|
|||
algorithm?: MappingAlgorithm | MappingAlgorithm[];
|
||||
hashed?: boolean;
|
||||
inherit?: boolean;
|
||||
cssVar?:
|
||||
| {
|
||||
/**
|
||||
* Prefix for css variable, default to `antd`.
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* Unique key for theme, should be set manually < react@18.
|
||||
*/
|
||||
key?: string;
|
||||
}
|
||||
| boolean;
|
||||
}
|
||||
|
||||
export const configProviderProps = () => ({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import { useToken } from '../../theme/internal';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
/**
|
||||
* This hook is only for cssVar to add root className for components.
|
||||
* If root ClassName is needed, this hook could be refactored with `-root`
|
||||
* @param prefixCls
|
||||
*/
|
||||
const useCSSVarCls = (prefixCls: Ref<string>) => {
|
||||
const [, , , , cssVar] = useToken();
|
||||
|
||||
return computed(() => (cssVar.value ? `${prefixCls.value}-css-var` : ''));
|
||||
};
|
||||
|
||||
export default useCSSVarCls;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import type { SizeType } from '../SizeContext';
|
||||
import { useInjectSize } from '../SizeContext';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, shallowRef, watch } from 'vue';
|
||||
|
||||
const useSize = <T>(customSize?: T | ((ctxSize: SizeType) => T)): Ref<T> => {
|
||||
const size = useInjectSize();
|
||||
|
||||
const mergedSize = shallowRef(null);
|
||||
|
||||
watch(
|
||||
computed(() => {
|
||||
return [customSize, size.value];
|
||||
}),
|
||||
() => {
|
||||
if (!customSize) {
|
||||
mergedSize.value = size.value as T;
|
||||
}
|
||||
if (typeof customSize === 'string') {
|
||||
mergedSize.value = customSize ?? (size.value as T);
|
||||
}
|
||||
if (customSize instanceof Function) {
|
||||
mergedSize.value = customSize(size.value) as T;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
return mergedSize;
|
||||
};
|
||||
|
||||
export default useSize;
|
||||
|
|
@ -2,13 +2,26 @@ import type { ThemeConfig } from '../context';
|
|||
import { defaultConfig } from '../../theme/internal';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import devWarning from '../../vc-util/warning';
|
||||
const themeKey = 'antdvtheme';
|
||||
export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<ThemeConfig>) {
|
||||
const themeConfig = computed(() => theme?.value || {});
|
||||
const parentThemeConfig = computed<ThemeConfig>(() =>
|
||||
themeConfig.value.inherit === false || !parentTheme?.value ? defaultConfig : parentTheme.value,
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const cssVarEnabled = themeConfig.value.cssVar || parentThemeConfig.value.cssVar;
|
||||
const validKey = !!(
|
||||
(typeof themeConfig.value.cssVar === 'object' && themeConfig.value.cssVar?.key) ||
|
||||
themeKey
|
||||
);
|
||||
devWarning(
|
||||
!cssVarEnabled || validKey,
|
||||
'[Ant Design Vue ConfigProvider] Missing key in `cssVar` config. Please set `cssVar.key` manually in each ConfigProvider inside `cssVar` enabled ConfigProvider.',
|
||||
);
|
||||
}
|
||||
|
||||
const mergedTheme = computed(() => {
|
||||
if (!theme?.value) {
|
||||
return parentTheme?.value;
|
||||
|
|
@ -26,6 +39,17 @@ export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<The
|
|||
} as any;
|
||||
});
|
||||
|
||||
const cssVarKey = `css-var-${themeKey.replace(/:/g, '')}`;
|
||||
|
||||
const mergedCssVar = (themeConfig.value.cssVar ?? parentThemeConfig.value.cssVar) && {
|
||||
prefix: 'ant', // Default to ant
|
||||
...(typeof parentThemeConfig.value.cssVar === 'object' ? parentThemeConfig.value.cssVar : {}),
|
||||
...(typeof themeConfig.value.cssVar === 'object' ? themeConfig.value.cssVar : {}),
|
||||
key:
|
||||
(typeof themeConfig.value.cssVar === 'object' && themeConfig.value.cssVar?.key) ||
|
||||
cssVarKey,
|
||||
};
|
||||
|
||||
// Base token
|
||||
return {
|
||||
...parentThemeConfig.value,
|
||||
|
|
@ -36,6 +60,7 @@ export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<The
|
|||
...themeConfig.value.token,
|
||||
},
|
||||
components: mergedComponents,
|
||||
cssVar: mergedCssVar,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
let uid = 0;
|
||||
const useThemeKey = () => {
|
||||
return 'themekey' + uid++;
|
||||
};
|
||||
|
||||
export default useThemeKey;
|
||||
|
|
@ -15,7 +15,7 @@ import type { ValidateMessages } from '../form/interface';
|
|||
import useStyle from './style';
|
||||
import useTheme from './hooks/useTheme';
|
||||
import defaultSeedToken from '../theme/themes/seed';
|
||||
import type { ConfigProviderInnerProps, ConfigProviderProps, Theme } from './context';
|
||||
import type { ConfigProviderInnerProps, ConfigProviderProps, Theme, ThemeConfig } from './context';
|
||||
import {
|
||||
useConfigContextProvider,
|
||||
useConfigContextInject,
|
||||
|
|
@ -26,7 +26,7 @@ import {
|
|||
import { useProviderSize } from './SizeContext';
|
||||
import { useProviderDisabled } from './DisabledContext';
|
||||
import { createTheme } from '../_util/cssinjs';
|
||||
import { DesignTokenProvider } from '../theme/internal';
|
||||
import { defaultTheme, DesignTokenProvider } from '../theme/context';
|
||||
|
||||
export type {
|
||||
ConfigProviderProps,
|
||||
|
|
@ -226,19 +226,47 @@ const ConfigProvider = defineComponent({
|
|||
|
||||
// ================================ Dynamic theme ================================
|
||||
const memoTheme = computed(() => {
|
||||
const { algorithm, token, ...rest } = mergedTheme.value || {};
|
||||
const { algorithm, token, components, cssVar, ...rest } = mergedTheme.value || {};
|
||||
const themeObj =
|
||||
algorithm && (!Array.isArray(algorithm) || algorithm.length > 0)
|
||||
? createTheme(algorithm)
|
||||
: undefined;
|
||||
: defaultTheme;
|
||||
|
||||
const parsedComponents: any = {};
|
||||
Object.entries(components || {}).forEach(([componentName, componentToken]) => {
|
||||
const parsedToken: typeof componentToken & { theme?: typeof defaultTheme } = {
|
||||
...componentToken,
|
||||
};
|
||||
if ('algorithm' in parsedToken) {
|
||||
if (parsedToken.algorithm === true) {
|
||||
parsedToken.theme = themeObj;
|
||||
} else if (
|
||||
Array.isArray(parsedToken.algorithm) ||
|
||||
typeof parsedToken.algorithm === 'function'
|
||||
) {
|
||||
parsedToken.theme = createTheme(parsedToken.algorithm as any);
|
||||
}
|
||||
delete parsedToken.algorithm;
|
||||
}
|
||||
parsedComponents[componentName] = parsedToken;
|
||||
});
|
||||
|
||||
const mergedToken = {
|
||||
...defaultSeedToken,
|
||||
...token,
|
||||
};
|
||||
|
||||
return {
|
||||
...rest,
|
||||
theme: themeObj,
|
||||
|
||||
token: {
|
||||
...defaultSeedToken,
|
||||
...token,
|
||||
token: mergedToken,
|
||||
components: parsedComponents,
|
||||
override: {
|
||||
override: mergedToken,
|
||||
...parsedComponents,
|
||||
},
|
||||
cssVar: cssVar as Exclude<ThemeConfig['cssVar'], boolean>,
|
||||
};
|
||||
});
|
||||
const validateMessagesRef = computed(() => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import { useStyleRegister } from '../../_util/cssinjs';
|
||||
import { resetIcon } from '../../style';
|
||||
import { useToken } from '../../theme/internal';
|
||||
|
|
@ -13,7 +14,8 @@ const useStyle = (iconPrefixCls: Ref<string>) => {
|
|||
hashId: '',
|
||||
path: ['ant-design-icons', iconPrefixCls.value],
|
||||
})),
|
||||
() => [
|
||||
() =>
|
||||
[
|
||||
{
|
||||
[`.${iconPrefixCls.value}`]: {
|
||||
...resetIcon(),
|
||||
|
|
@ -22,7 +24,7 @@ const useStyle = (iconPrefixCls: Ref<string>) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
] as CSSObject[],
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
|
|||
import { resetComponent, roundedArrow, textEllipsis } from '../../style';
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
export interface ComponentToken {
|
||||
presetsWidth: number;
|
||||
presetsMaxWidth: number;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
|
|||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { resetComponent, textEllipsis } from '../../style';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface DescriptionsToken extends FullToken<'Descriptions'> {
|
||||
descriptionsTitleMarginBottom: number;
|
||||
descriptionsExtraColor: string;
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const BackTop = defineComponent({
|
|||
|
||||
const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => {
|
||||
const { visibilityHeight } = props;
|
||||
const scrollTop = getScroll(e.target, true);
|
||||
const scrollTop = getScroll(e.target);
|
||||
state.visible = scrollTop >= visibilityHeight;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
|||
import { resetComponent } from '../../style';
|
||||
import genFormValidateMotionStyle from './explain';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
export interface FormToken extends FullToken<'Form'> {
|
||||
formItemCls: string;
|
||||
rootPrefixCls: string;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import type { CSSObject } from '../../_util/cssinjs';
|
|||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface GridRowToken extends FullToken<'Grid'> {}
|
||||
|
||||
interface GridColToken extends FullToken<'Grid'> {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { App } from 'vue';
|
|||
|
||||
import * as components from './components';
|
||||
import { default as version } from './version';
|
||||
import cssinjs from './_util/cssinjs';
|
||||
import * as cssinjs from './_util/cssinjs';
|
||||
export * from './components';
|
||||
export * from './_util/cssinjs';
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import type { GlobalToken } from '../../theme/interface';
|
|||
import { clearFix, resetComponent } from '../../style';
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
export type InputToken<T extends GlobalToken = FullToken<'Input'>> = T & {
|
||||
inputAffixPadding: number;
|
||||
inputPaddingVertical: number;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
|||
import { resetComponent, textEllipsis } from '../../style';
|
||||
import { operationUnit } from '../../style';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface PageHeaderToken extends FullToken<'PageHeader'> {
|
||||
pageHeaderPadding: number;
|
||||
pageHeaderPaddingVertical: number;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
|
|||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { genFocusOutline, genFocusStyle, resetComponent } from '../../style';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface PaginationToken extends InputToken<FullToken<'Pagination'>> {
|
||||
paginationItemSize: number;
|
||||
paginationFontFamily: string;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
|
|||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { resetComponent } from '../../style';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface StatisticToken extends FullToken<'Statistic'> {
|
||||
statisticTitleFontSize: number;
|
||||
statisticContentFontSize: number;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ The most basic usage.
|
|||
<template>
|
||||
<a-switch v-model:checked="checked" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
const checked = ref<boolean>(false);
|
||||
const checked = ref<boolean>(true);
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,6 @@ title:
|
|||
import { reactive } from 'vue';
|
||||
const state = reactive({
|
||||
checked1: true,
|
||||
checked2: false,
|
||||
checked2: true,
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ With text and icon.
|
|||
|
||||
<template>
|
||||
<a-space direction="vertical">
|
||||
<a-switch v-model:checked="state.checked1" checked-children="开" un-checked-children="关" />
|
||||
<a-switch v-model:checked="state.checked1" checked-children="开启" un-checked-children="关闭" />
|
||||
<a-switch v-model:checked="state.checked2" checked-children="1" un-checked-children="0" />
|
||||
<a-switch v-model:checked="state.checked3">
|
||||
<template #checkedChildren><check-outlined /></template>
|
||||
|
|
@ -32,6 +32,6 @@ import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
|||
const state = reactive({
|
||||
checked1: true,
|
||||
checked2: false,
|
||||
checked3: false,
|
||||
checked3: true,
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ const Switch = defineComponent({
|
|||
);
|
||||
|
||||
const { prefixCls, direction, size } = useConfigInject('switch', props);
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
const refSwitchNode = ref();
|
||||
const focus = () => {
|
||||
refSwitchNode.value?.focus();
|
||||
|
|
@ -159,10 +159,11 @@ const Switch = defineComponent({
|
|||
[prefixCls.value]: true,
|
||||
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
||||
[hashId.value]: true,
|
||||
[cssVarCls.value]: true,
|
||||
}));
|
||||
|
||||
return () =>
|
||||
wrapSSR(
|
||||
wrapCSSVar(
|
||||
<Wave>
|
||||
<button
|
||||
{...omit(props, [
|
||||
|
|
|
|||
|
|
@ -1,107 +1,172 @@
|
|||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import { unit } from '../../_util/cssinjs';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { genFocusStyle, resetComponent } from '../../style';
|
||||
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||
|
||||
export interface ComponentToken {
|
||||
/**
|
||||
* @desc 开关高度
|
||||
* @descEN Height of Switch
|
||||
*/
|
||||
trackHeight: number | string;
|
||||
/**
|
||||
* @desc 小号开关高度
|
||||
* @descEN Height of small Switch
|
||||
*/
|
||||
trackHeightSM: number | string;
|
||||
/**
|
||||
* @desc 开关最小宽度
|
||||
* @descEN Minimum width of Switch
|
||||
*/
|
||||
trackMinWidth: number | string;
|
||||
/**
|
||||
* @desc 小号开关最小宽度
|
||||
* @descEN Minimum width of small Switch
|
||||
*/
|
||||
trackMinWidthSM: number | string;
|
||||
/**
|
||||
* @desc 开关内边距
|
||||
* @descEN Padding of Switch
|
||||
*/
|
||||
trackPadding: number;
|
||||
/**
|
||||
* @desc 开关把手背景色
|
||||
* @descEN Background color of Switch handle
|
||||
*/
|
||||
handleBg: string;
|
||||
/**
|
||||
* @desc 开关把手阴影
|
||||
* @descEN Shadow of Switch handle
|
||||
*/
|
||||
handleShadow: string;
|
||||
/**
|
||||
* @desc 开关把手大小
|
||||
* @descEN Size of Switch handle
|
||||
*/
|
||||
handleSize: number;
|
||||
/**
|
||||
* @desc 小号开关把手大小
|
||||
* @descEN Size of small Switch handle
|
||||
*/
|
||||
handleSizeSM: number;
|
||||
/**
|
||||
* @desc 内容区域最小边距
|
||||
* @descEN Minimum margin of content area
|
||||
*/
|
||||
innerMinMargin: number;
|
||||
/**
|
||||
* @desc 内容区域最大边距
|
||||
* @descEN Maximum margin of content area
|
||||
*/
|
||||
innerMaxMargin: number;
|
||||
/**
|
||||
* @desc 小号开关内容区域最小边距
|
||||
* @descEN Minimum margin of content area of small Switch
|
||||
*/
|
||||
innerMinMarginSM: number;
|
||||
/**
|
||||
* @desc 小号开关内容区域最大边距
|
||||
* @descEN Maximum margin of content area of small Switch
|
||||
*/
|
||||
innerMaxMarginSM: number;
|
||||
}
|
||||
|
||||
interface SwitchToken extends FullToken<'Switch'> {
|
||||
switchMinWidth: number;
|
||||
switchHeight: number;
|
||||
switchDuration: string;
|
||||
switchColor: string;
|
||||
switchDisabledOpacity: number;
|
||||
switchInnerMarginMin: number;
|
||||
switchInnerMarginMax: number;
|
||||
switchPadding: number;
|
||||
switchPinSize: number;
|
||||
switchBg: string;
|
||||
switchMinWidthSM: number;
|
||||
switchHeightSM: number;
|
||||
switchInnerMarginMinSM: number;
|
||||
switchInnerMarginMaxSM: number;
|
||||
switchPinSizeSM: number;
|
||||
switchHandleShadow: string;
|
||||
switchLoadingIconSize: number;
|
||||
switchLoadingIconSize: number | string;
|
||||
switchLoadingIconColor: string;
|
||||
switchHandleActiveInset: string;
|
||||
}
|
||||
|
||||
const genSwitchSmallStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
||||
const { componentCls } = token;
|
||||
const {
|
||||
componentCls,
|
||||
trackHeightSM,
|
||||
trackPadding,
|
||||
trackMinWidthSM,
|
||||
innerMinMarginSM,
|
||||
innerMaxMarginSM,
|
||||
handleSizeSM,
|
||||
calc,
|
||||
} = token;
|
||||
const switchInnerCls = `${componentCls}-inner`;
|
||||
|
||||
const trackPaddingCalc = unit(calc(handleSizeSM).add(calc(trackPadding).mul(2)).equal());
|
||||
const innerMaxMarginCalc = unit(calc(innerMaxMarginSM).mul(2).equal());
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
[`&${componentCls}-small`]: {
|
||||
minWidth: token.switchMinWidthSM,
|
||||
height: token.switchHeightSM,
|
||||
lineHeight: `${token.switchHeightSM}px`,
|
||||
minWidth: trackMinWidthSM,
|
||||
height: trackHeightSM,
|
||||
lineHeight: unit(trackHeightSM),
|
||||
|
||||
[`${componentCls}-inner`]: {
|
||||
paddingInlineStart: token.switchInnerMarginMaxSM,
|
||||
paddingInlineEnd: token.switchInnerMarginMinSM,
|
||||
paddingInlineStart: innerMaxMarginSM,
|
||||
paddingInlineEnd: innerMinMarginSM,
|
||||
|
||||
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
|
||||
minHeight: trackHeightSM,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
marginInlineStart: `calc(-100% + ${
|
||||
token.switchPinSizeSM + token.switchPadding * 2
|
||||
}px - ${token.switchInnerMarginMaxSM * 2}px)`,
|
||||
marginInlineEnd: `calc(100% - ${token.switchPinSizeSM + token.switchPadding * 2}px + ${
|
||||
token.switchInnerMarginMaxSM * 2
|
||||
}px)`,
|
||||
marginInlineStart: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
|
||||
marginInlineEnd: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-unchecked`]: {
|
||||
marginTop: -token.switchHeightSM,
|
||||
marginTop: calc(trackHeightSM).mul(-1).equal(),
|
||||
marginInlineStart: 0,
|
||||
marginInlineEnd: 0,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-handle`]: {
|
||||
width: token.switchPinSizeSM,
|
||||
height: token.switchPinSizeSM,
|
||||
width: handleSizeSM,
|
||||
height: handleSizeSM,
|
||||
},
|
||||
|
||||
[`${componentCls}-loading-icon`]: {
|
||||
top: (token.switchPinSizeSM - token.switchLoadingIconSize) / 2,
|
||||
top: calc(calc(handleSizeSM).sub(token.switchLoadingIconSize)).div(2).equal(),
|
||||
fontSize: token.switchLoadingIconSize,
|
||||
},
|
||||
|
||||
[`&${componentCls}-checked`]: {
|
||||
[`${componentCls}-inner`]: {
|
||||
paddingInlineStart: token.switchInnerMarginMinSM,
|
||||
paddingInlineEnd: token.switchInnerMarginMaxSM,
|
||||
paddingInlineStart: innerMinMarginSM,
|
||||
paddingInlineEnd: innerMaxMarginSM,
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
marginInlineStart: 0,
|
||||
marginInlineEnd: 0,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-unchecked`]: {
|
||||
marginInlineStart: `calc(100% - ${
|
||||
token.switchPinSizeSM + token.switchPadding * 2
|
||||
}px + ${token.switchInnerMarginMaxSM * 2}px)`,
|
||||
marginInlineEnd: `calc(-100% + ${
|
||||
token.switchPinSizeSM + token.switchPadding * 2
|
||||
}px - ${token.switchInnerMarginMaxSM * 2}px)`,
|
||||
marginInlineStart: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
|
||||
marginInlineEnd: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-handle`]: {
|
||||
insetInlineStart: `calc(100% - ${token.switchPinSizeSM + token.switchPadding}px)`,
|
||||
insetInlineStart: `calc(100% - ${unit(calc(handleSizeSM).add(trackPadding).equal())})`,
|
||||
},
|
||||
},
|
||||
|
||||
[`&:not(${componentCls}-disabled):active`]: {
|
||||
[`&:not(${componentCls}-checked) ${switchInnerCls}`]: {
|
||||
[`${switchInnerCls}-unchecked`]: {
|
||||
marginInlineStart: token.marginXXS / 2,
|
||||
marginInlineEnd: -token.marginXXS / 2,
|
||||
marginInlineStart: calc(token.marginXXS).div(2).equal(),
|
||||
marginInlineEnd: calc(token.marginXXS).mul(-1).div(2).equal(),
|
||||
},
|
||||
},
|
||||
|
||||
[`&${componentCls}-checked ${switchInnerCls}`]: {
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
marginInlineStart: -token.marginXXS / 2,
|
||||
marginInlineEnd: token.marginXXS / 2,
|
||||
marginInlineStart: calc(token.marginXXS).mul(-1).div(2).equal(),
|
||||
marginInlineEnd: calc(token.marginXXS).div(2).equal(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -111,13 +176,13 @@ const genSwitchSmallStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|||
};
|
||||
|
||||
const genSwitchLoadingStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
||||
const { componentCls } = token;
|
||||
const { componentCls, handleSize, calc } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
[`${componentCls}-loading-icon${token.iconCls}`]: {
|
||||
position: 'relative',
|
||||
top: (token.switchPinSize - token.fontSize) / 2,
|
||||
top: calc(calc(handleSize).sub(token.fontSize)).div(2).equal(),
|
||||
color: token.switchLoadingIconColor,
|
||||
verticalAlign: 'top',
|
||||
},
|
||||
|
|
@ -130,17 +195,17 @@ const genSwitchLoadingStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|||
};
|
||||
|
||||
const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
||||
const { componentCls } = token;
|
||||
const { componentCls, trackPadding, handleBg, handleShadow, handleSize, calc } = token;
|
||||
const switchHandleCls = `${componentCls}-handle`;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
[switchHandleCls]: {
|
||||
position: 'absolute',
|
||||
top: token.switchPadding,
|
||||
insetInlineStart: token.switchPadding,
|
||||
width: token.switchPinSize,
|
||||
height: token.switchPinSize,
|
||||
top: trackPadding,
|
||||
insetInlineStart: trackPadding,
|
||||
width: handleSize,
|
||||
height: handleSize,
|
||||
transition: `all ${token.switchDuration} ease-in-out`,
|
||||
|
||||
'&::before': {
|
||||
|
|
@ -149,16 +214,16 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|||
insetInlineEnd: 0,
|
||||
bottom: 0,
|
||||
insetInlineStart: 0,
|
||||
backgroundColor: token.colorWhite,
|
||||
borderRadius: token.switchPinSize / 2,
|
||||
boxShadow: token.switchHandleShadow,
|
||||
backgroundColor: handleBg,
|
||||
borderRadius: calc(handleSize).div(2).equal(),
|
||||
boxShadow: handleShadow,
|
||||
transition: `all ${token.switchDuration} ease-in-out`,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
|
||||
[`&${componentCls}-checked ${switchHandleCls}`]: {
|
||||
insetInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding}px)`,
|
||||
insetInlineStart: `calc(100% - ${unit(calc(handleSize).add(trackPadding).equal())})`,
|
||||
},
|
||||
|
||||
[`&:not(${componentCls}-disabled):active`]: {
|
||||
|
|
@ -177,9 +242,20 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|||
};
|
||||
|
||||
const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
||||
const { componentCls } = token;
|
||||
const {
|
||||
componentCls,
|
||||
trackHeight,
|
||||
trackPadding,
|
||||
innerMinMargin,
|
||||
innerMaxMargin,
|
||||
handleSize,
|
||||
calc,
|
||||
} = token;
|
||||
const switchInnerCls = `${componentCls}-inner`;
|
||||
|
||||
const trackPaddingCalc = unit(calc(handleSize).add(calc(trackPadding).mul(2)).equal());
|
||||
const innerMaxMarginCalc = unit(calc(innerMaxMargin).mul(2).equal());
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
[switchInnerCls]: {
|
||||
|
|
@ -187,8 +263,8 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|||
overflow: 'hidden',
|
||||
borderRadius: 100,
|
||||
height: '100%',
|
||||
paddingInlineStart: token.switchInnerMarginMax,
|
||||
paddingInlineEnd: token.switchInnerMarginMin,
|
||||
paddingInlineStart: innerMaxMargin,
|
||||
paddingInlineEnd: innerMinMargin,
|
||||
transition: `padding-inline-start ${token.switchDuration} ease-in-out, padding-inline-end ${token.switchDuration} ease-in-out`,
|
||||
|
||||
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
|
||||
|
|
@ -197,54 +273,47 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|||
fontSize: token.fontSizeSM,
|
||||
transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`,
|
||||
pointerEvents: 'none',
|
||||
minHeight: trackHeight,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
marginInlineStart: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${
|
||||
token.switchInnerMarginMax * 2
|
||||
}px)`,
|
||||
marginInlineEnd: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${
|
||||
token.switchInnerMarginMax * 2
|
||||
}px)`,
|
||||
marginInlineStart: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
|
||||
marginInlineEnd: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-unchecked`]: {
|
||||
marginTop: -token.switchHeight,
|
||||
marginTop: calc(trackHeight).mul(-1).equal(),
|
||||
marginInlineStart: 0,
|
||||
marginInlineEnd: 0,
|
||||
},
|
||||
},
|
||||
|
||||
[`&${componentCls}-checked ${switchInnerCls}`]: {
|
||||
paddingInlineStart: token.switchInnerMarginMin,
|
||||
paddingInlineEnd: token.switchInnerMarginMax,
|
||||
paddingInlineStart: innerMinMargin,
|
||||
paddingInlineEnd: innerMaxMargin,
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
marginInlineStart: 0,
|
||||
marginInlineEnd: 0,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-unchecked`]: {
|
||||
marginInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${
|
||||
token.switchInnerMarginMax * 2
|
||||
}px)`,
|
||||
marginInlineEnd: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${
|
||||
token.switchInnerMarginMax * 2
|
||||
}px)`,
|
||||
marginInlineStart: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
|
||||
marginInlineEnd: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
|
||||
},
|
||||
},
|
||||
|
||||
[`&:not(${componentCls}-disabled):active`]: {
|
||||
[`&:not(${componentCls}-checked) ${switchInnerCls}`]: {
|
||||
[`${switchInnerCls}-unchecked`]: {
|
||||
marginInlineStart: token.switchPadding * 2,
|
||||
marginInlineEnd: -token.switchPadding * 2,
|
||||
marginInlineStart: calc(trackPadding).mul(2).equal(),
|
||||
marginInlineEnd: calc(trackPadding).mul(-1).mul(2).equal(),
|
||||
},
|
||||
},
|
||||
|
||||
[`&${componentCls}-checked ${switchInnerCls}`]: {
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
marginInlineStart: -token.switchPadding * 2,
|
||||
marginInlineEnd: token.switchPadding * 2,
|
||||
marginInlineStart: calc(trackPadding).mul(-1).mul(2).equal(),
|
||||
marginInlineEnd: calc(trackPadding).mul(2).equal(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -253,7 +322,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|||
};
|
||||
|
||||
const genSwitchStyle = (token: SwitchToken): CSSObject => {
|
||||
const { componentCls } = token;
|
||||
const { componentCls, trackHeight, trackMinWidth } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
|
|
@ -262,9 +331,9 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => {
|
|||
position: 'relative',
|
||||
display: 'inline-block',
|
||||
boxSizing: 'border-box',
|
||||
minWidth: token.switchMinWidth,
|
||||
height: token.switchHeight,
|
||||
lineHeight: `${token.switchHeight}px`,
|
||||
minWidth: trackMinWidth,
|
||||
height: trackHeight,
|
||||
lineHeight: unit(trackHeight),
|
||||
verticalAlign: 'middle',
|
||||
background: token.colorTextQuaternary,
|
||||
border: '0',
|
||||
|
|
@ -306,31 +375,40 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => {
|
|||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook('Switch', token => {
|
||||
const switchHeight = token.fontSize * token.lineHeight;
|
||||
const switchHeightSM = token.controlHeight / 2;
|
||||
const switchPadding = 2; // This is magic
|
||||
const switchPinSize = switchHeight - switchPadding * 2;
|
||||
const switchPinSizeSM = switchHeightSM - switchPadding * 2;
|
||||
export const prepareComponentToken: GetDefaultToken<'Switch'> = token => {
|
||||
const { fontSize, lineHeight, controlHeight, colorWhite } = token;
|
||||
|
||||
const height = fontSize * lineHeight;
|
||||
const heightSM = controlHeight / 2;
|
||||
const padding = 2; // Fixed value
|
||||
const handleSize = height - padding * 2;
|
||||
const handleSizeSM = heightSM - padding * 2;
|
||||
|
||||
return {
|
||||
trackHeight: height,
|
||||
trackHeightSM: heightSM,
|
||||
trackMinWidth: handleSize * 2 + padding * 4,
|
||||
trackMinWidthSM: handleSizeSM * 2 + padding * 2,
|
||||
trackPadding: padding, // Fixed value
|
||||
handleBg: colorWhite,
|
||||
handleSize,
|
||||
handleSizeSM,
|
||||
handleShadow: `0 2px 4px 0 ${new TinyColor('#00230b').setAlpha(0.2).toRgbString()}`,
|
||||
innerMinMargin: handleSize / 2,
|
||||
innerMaxMargin: handleSize + padding + padding * 2,
|
||||
innerMinMarginSM: handleSizeSM / 2,
|
||||
innerMaxMarginSM: handleSizeSM + padding + padding * 2,
|
||||
};
|
||||
};
|
||||
|
||||
export default genStyleHooks(
|
||||
'Switch',
|
||||
token => {
|
||||
const switchToken = mergeToken<SwitchToken>(token, {
|
||||
switchMinWidth: switchPinSize * 2 + switchPadding * 4,
|
||||
switchHeight,
|
||||
switchDuration: token.motionDurationMid,
|
||||
switchColor: token.colorPrimary,
|
||||
switchDisabledOpacity: token.opacityLoading,
|
||||
switchInnerMarginMin: switchPinSize / 2,
|
||||
switchInnerMarginMax: switchPinSize + switchPadding + switchPadding * 2,
|
||||
switchPadding,
|
||||
switchPinSize,
|
||||
switchBg: token.colorBgContainer,
|
||||
switchMinWidthSM: switchPinSizeSM * 2 + switchPadding * 2,
|
||||
switchHeightSM,
|
||||
switchInnerMarginMinSM: switchPinSizeSM / 2,
|
||||
switchInnerMarginMaxSM: switchPinSizeSM + switchPadding + switchPadding * 2,
|
||||
switchPinSizeSM,
|
||||
switchHandleShadow: `0 2px 4px 0 ${new TinyColor('#00230b').setAlpha(0.2).toRgbString()}`,
|
||||
switchLoadingIconSize: token.fontSizeIcon * 0.75,
|
||||
switchLoadingIconSize: token.calc(token.fontSizeIcon).mul(0.75).equal(),
|
||||
switchLoadingIconColor: `rgba(0, 0, 0, ${token.opacityLoading})`,
|
||||
switchHandleActiveInset: '-30%',
|
||||
});
|
||||
|
|
@ -350,4 +428,6 @@ export default genComponentStyleHook('Switch', token => {
|
|||
// small style
|
||||
genSwitchSmallStyle(switchToken),
|
||||
];
|
||||
});
|
||||
},
|
||||
prepareComponentToken,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import getAlphaColor from '../util/getAlphaColor';
|
||||
|
||||
describe('util', () => {
|
||||
describe('getAlphaColor', () => {
|
||||
it('should not process color with alpha', () => {
|
||||
expect(getAlphaColor('rgba(0, 0, 0, 0.5)', 'rgba(255, 255, 255)')).toBe('rgba(0, 0, 0, 0.5)');
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue