fix: improve CSS cheker & fix lint errors (#53236)

* chore: add csslint

* chore: update

* chore: update

* fix css lint

* fix css lint

* fix css lint

* fix csslint

* fix css lint

* fix css lint

* fix css lint

* fix css lint

* fix css lint appearance

https://developer.mozilla.org/zh-CN/docs/Web/CSS/appearance

* fix csslint conic-gradient

* chore: fix lint tree-select

* chore: update logic

* Revert "fix css lint"

This reverts commit 094f58adbb.

* Revert "fix css lint"

This reverts commit 50da87be3a.

* chore: fix css

* chore: update

* chore: rm redundant

* chore: revert change

* chore: update

* chore: update
This commit is contained in:
𝑾𝒖𝒙𝒉 2025-03-22 11:09:03 +08:00 committed by GitHub
parent 9b6714d554
commit 828f512747
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 108 additions and 26 deletions

View File

@ -194,8 +194,8 @@ const genArrowsStyle: GenerateStyle<CarouselToken> = (token) => {
width: arrowLength,
height: arrowLength,
border: `0 solid currentcolor`,
borderInlineWidth: '2px 0',
borderBlockWidth: '2px 0',
borderInlineStartWidth: 2,
borderBlockStartWidth: 2,
borderRadius: 1,
content: '""',
},

View File

@ -5,9 +5,10 @@ import type { ColorPickerToken } from './index';
/**
* @private Internal usage only
* see: https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient#checkerboard
*/
export const getTransBg = (size: string, colorFill: string): CSSObject => ({
backgroundImage: `conic-gradient(${colorFill} 0 25%, transparent 0 50%, ${colorFill} 0 75%, transparent 0)`,
backgroundImage: `conic-gradient(${colorFill} 25%, transparent 25% 50%, ${colorFill} 50% 75%, transparent 75% 100%)`,
backgroundSize: `${size} ${size}`,
});

View File

@ -321,8 +321,8 @@ export const genPanelStyle = (token: SharedPickerToken): CSSObject => {
width: pickerControlIconSize,
height: pickerControlIconSize,
border: `0 solid currentcolor`,
borderBlockWidth: `${unit(pickerControlIconBorderWidth)} 0`,
borderInlineWidth: `${unit(pickerControlIconBorderWidth)} 0`,
borderBlockStartWidth: pickerControlIconBorderWidth,
borderInlineStartWidth: pickerControlIconBorderWidth,
content: '""',
},
},
@ -337,8 +337,8 @@ export const genPanelStyle = (token: SharedPickerToken): CSSObject => {
width: pickerControlIconSize,
height: pickerControlIconSize,
border: '0 solid currentcolor',
borderBlockWidth: `${unit(pickerControlIconBorderWidth)} 0`,
borderInlineWidth: `${unit(pickerControlIconBorderWidth)} 0`,
borderBlockStartWidth: pickerControlIconBorderWidth,
borderInlineStartWidth: pickerControlIconBorderWidth,
content: '""',
},
},

View File

@ -31,7 +31,7 @@ const floatButtonGroupMotion = (token: FloatButtonToken) => {
});
const moveRightIn = new Keyframes('antFloatButtonMoveRightIn', {
'0%': {
transform: `translate3d(${calc(floatButtonSize).mul(-1).equal()}, 0, 0)`,
transform: `translate3d(${unit(calc(floatButtonSize).mul(-1).equal())}, 0, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
@ -48,14 +48,14 @@ const floatButtonGroupMotion = (token: FloatButtonToken) => {
opacity: 1,
},
'100%': {
transform: `translate3d(${calc(floatButtonSize).mul(-1).equal()}, 0, 0)`,
transform: `translate3d(${unit(calc(floatButtonSize).mul(-1).equal())}, 0, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
});
const moveBottomIn = new Keyframes('antFloatButtonMoveBottomIn', {
'0%': {
transform: `translate3d(0, ${calc(floatButtonSize).mul(-1).equal()}, 0)`,
transform: `translate3d(0, ${unit(calc(floatButtonSize).mul(-1).equal())}, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
@ -72,7 +72,7 @@ const floatButtonGroupMotion = (token: FloatButtonToken) => {
opacity: 1,
},
'100%': {
transform: `translate3d(0, ${calc(floatButtonSize).mul(-1).equal()}, 0)`,
transform: `translate3d(0, ${unit(calc(floatButtonSize).mul(-1).equal())}, 0)`,
transformOrigin: '0 0',
opacity: 0,
},

View File

@ -283,7 +283,7 @@ const genFormItemStyle: GenerateStyle<FormToken> = (token) => {
marginInlineStart: token.marginXXS,
color: token.colorTextDescription,
[`&.${formItemCls}-required-mark-hidden`]: {
[`&${formItemCls}-required-mark-hidden`]: {
display: 'none',
},
},

View File

@ -1,6 +1,11 @@
import { unit } from '@ant-design/cssinjs';
import { genBasicInputStyle, genInputGroupStyle, genPlaceholderStyle, initInputToken } from '../../input/style';
import {
genBasicInputStyle,
genInputGroupStyle,
genPlaceholderStyle,
initInputToken,
} from '../../input/style';
import {
genBorderlessStyle,
genFilledGroupStyle,
@ -235,7 +240,6 @@ const genInputNumberStyles: GenerateStyle<InputNumberToken> = (token: InputNumbe
'&[type="number"]::-webkit-inner-spin-button, &[type="number"]::-webkit-outer-spin-button':
{
margin: 0,
webkitAppearance: 'none',
appearance: 'none',
},
},

View File

@ -400,7 +400,7 @@ export const genInputStyle: GenerateStyle<InputToken> = (token: InputToken) => {
'&[type="search"]::-webkit-search-cancel-button, &[type="search"]::-webkit-search-decoration':
{
'-webkit-appearance': 'none',
appearance: 'none',
},
},
};

View File

@ -223,7 +223,7 @@ const genBaseFilledStyle = (
borderColor: 'transparent',
'input&, & input, textarea&, & textarea': {
color: options?.inputColor,
color: options?.inputColor ?? 'unset',
},
'&:hover': {

View File

@ -1,6 +1,11 @@
import { unit } from '@ant-design/cssinjs';
import { genBasicInputStyle, genPlaceholderStyle, initComponentToken, initInputToken } from '../../input/style';
import {
genBasicInputStyle,
genPlaceholderStyle,
initComponentToken,
initInputToken,
} from '../../input/style';
import type { SharedComponentToken, SharedInputToken } from '../../input/style/token';
import {
genBorderlessStyle,
@ -163,7 +168,7 @@ const genMentionsStyle: GenerateStyle<MentionsToken> = (token) => {
[`> textarea, ${componentCls}-measure`]: {
color: colorText,
boxSizing: 'border-box',
minHeight: token.calc(controlHeight).sub(2),
minHeight: token.calc(controlHeight).sub(2).equal(),
margin: 0,
padding: `${unit(paddingBlock)} ${unit(paddingInline)}`,
overflow: 'inherit',

View File

@ -213,7 +213,6 @@ export const genNoticeStyle = (token: NotificationToken): CSSObject => {
position: 'absolute',
display: 'block',
appearance: 'none',
WebkitAppearance: 'none',
inlineSize: `calc(100% - ${unit(borderRadiusLG)} * 2)`,
left: {
_skip_check_: true,

View File

@ -62,7 +62,7 @@ const getSearchInputWithoutBorderStyle: GenerateStyle<SelectToken, CSSObject> =
'&::-webkit-search-cancel-button': {
display: 'none',
'-webkit-appearance': 'none',
appearance: 'none',
},
},
};

View File

@ -419,7 +419,7 @@ export const prepareComponentToken: GetDefaultToken<'Steps'> = (token) => ({
dotSize: token.controlHeight / 4,
dotCurrentSize: token.controlHeightLG / 4,
navArrowColor: token.colorTextDisabled,
navContentMaxWidth: 'auto',
navContentMaxWidth: 'unset',
descriptionMaxWidth: 140,
waitIconColor: token.wireframe ? token.colorTextDisabled : token.colorTextLabel,
waitIconBgColor: token.wireframe ? token.colorBgContainer : token.colorFillContent,

View File

@ -48,7 +48,7 @@ const genStickyStyle: GenerateStyle<TableToken, CSSObject> = (token) => {
height: tableScrollThumbSize,
backgroundColor: tableScrollThumbBg,
borderRadius: stickyScrollBarBorderRadius,
transition: `all ${token.motionDurationSlow}, transform none`,
transition: `all ${token.motionDurationSlow}, transform 0s`,
position: 'absolute',
bottom: 0,

View File

@ -39,6 +39,7 @@ const genBaseStyle: GenerateStyle<TreeSelectToken> = (token) => {
mergeToken<AliasToken & TreeSharedToken & CSSUtil>(token, {
colorBgContainer: colorBgElevated,
}),
false, // 不需要 directory tree 的样式
),
{
[treeCls]: {

View File

@ -432,6 +432,11 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
export const genTreeStyle = (
prefixCls: string,
token: AliasToken & TreeSharedToken & CSSUtil,
/**
*
* @default true
*/
enableDirectory = true,
): CSSInterpolation => {
const treeCls = `.${prefixCls}`;
const treeNodeCls = `${treeCls}-treenode`;
@ -448,8 +453,8 @@ export const genTreeStyle = (
// Basic
genBaseStyle(prefixCls, treeToken),
// Directory
genDirectoryStyle(treeToken),
];
enableDirectory && genDirectoryStyle(treeToken),
].filter(Boolean);
};
export const initComponentToken = (token: AliasToken): TreeSharedToken => {

View File

@ -192,6 +192,7 @@
"@types/adm-zip": "^0.5.6",
"@types/ali-oss": "^6.16.11",
"@types/cli-progress": "^3.11.6",
"@types/css-tree": "^2.3.10",
"@types/fs-extra": "^11.0.4",
"@types/gtag.js": "^0.0.20",
"@types/http-server": "^0.12.4",
@ -232,6 +233,8 @@
"cli-progress": "^3.12.0",
"cross-env": "^7.0.3",
"cross-fetch": "^4.0.0",
"css-tree": "^3.1.0",
"csstree-validator": "^4.0.1",
"cypress-image-diff-html-report": "2.2.0",
"dekko": "^0.2.1",
"dotenv": "^16.4.5",

View File

@ -1,5 +1,8 @@
import path from 'path';
import React from 'react';
import {
createCache,
extractStyle,
legacyNotSelectorLinter,
logicalPropertiesLinter,
NaNLinter,
@ -7,11 +10,19 @@ import {
StyleProvider,
} from '@ant-design/cssinjs';
import chalk from 'chalk';
import { parse } from 'css-tree';
import type { SyntaxParseError } from 'css-tree';
import { validate } from 'csstree-validator';
import fs from 'fs-extra';
import isCI from 'is-ci';
import ReactDOMServer from 'react-dom/server';
import { ConfigProvider } from '../components';
import { generateCssinjs } from './generate-cssinjs';
const tmpDir = path.join(`${__filename}.tmp`);
fs.emptyDirSync(tmpDir);
console.log(chalk.green(`🔥 Checking CSS-in-JS...`));
let errorCount = 0;
@ -25,6 +36,20 @@ console.error = (msg: any) => {
}
};
// https://github.com/csstree/validator/blob/7df8ca/lib/validate.js#L187
function cssValidate(css: string, filename: string) {
const errors: SyntaxParseError[] = [];
const ast = parse(css, {
filename,
positions: true,
onParseError(error) {
errors.push(error);
},
});
return errors.concat(validate(ast));
}
async function checkCSSVar() {
await generateCssinjs({
key: 'check',
@ -39,6 +64,38 @@ async function checkCSSVar() {
},
});
}
async function checkCSSContent() {
const errors = new Map();
await generateCssinjs({
key: 'css-validate',
render(Component: any, filePath: string) {
const cache = createCache();
ReactDOMServer.renderToString(
<StyleProvider cache={cache}>
<Component />
</StyleProvider>,
);
const css = extractStyle(cache, { types: 'style', plain: true });
let showPath = filePath;
if (!isCI) {
const [, name] = filePath.split(path.sep);
const writeLocalPath = path.join(tmpDir, `${name}.css`);
showPath = path.relative(process.cwd(), writeLocalPath);
fs.writeFileSync(writeLocalPath, `/* ${filePath} */\n${css}`);
}
errors.set(filePath, cssValidate(css, showPath));
},
});
for (const [filePath, error] of errors) {
if (error.length > 0) {
errorCount += error.length;
console.log(chalk.red(`${filePath} has ${error.length} errors:`));
console.log(error);
}
}
}
(async () => {
await generateCssinjs({
@ -55,6 +112,7 @@ async function checkCSSVar() {
});
await checkCSSVar();
await checkCSSContent();
if (errorCount > 0) {
console.log(chalk.red(`❌ CSS-in-JS check failed with ${errorCount} errors.`));

View File

@ -7,7 +7,7 @@ type StyleFn = (prefix?: string) => void;
interface GenCssinjsOptions {
key: string;
render: (component: React.FC) => void;
render: (Component: React.FC, filepath: string) => void;
beforeRender?: (componentName: string) => void;
}
@ -35,6 +35,10 @@ export const generateCssinjs = ({ key, beforeRender, render }: GenCssinjsOptions
useRowStyle(prefixCls);
useColStyle(prefixCls);
};
} else if (file.includes('tree-select')) {
const originalUseStyle = (await import(absPath)).default;
useStyle = (prefixCls, treePrefixCls = `${prefixCls}-tree`) =>
originalUseStyle(prefixCls, treePrefixCls);
} else {
useStyle = (await import(absPath)).default;
}
@ -43,6 +47,6 @@ export const generateCssinjs = ({ key, beforeRender, render }: GenCssinjsOptions
return React.createElement('div');
};
beforeRender?.(componentName);
render?.(Demo);
render?.(Demo, path.relative(process.cwd(), file));
}),
);

View File

@ -29,3 +29,5 @@ declare module '@npmcli/run-script' {
declare module '@microflash/rehype-figure';
declare module 'dekko';
declare module 'csstree-validator';