diff --git a/components/modal/style/index.tsx b/components/modal/style/index.tsx index 627ab5914c..6828613859 100644 --- a/components/modal/style/index.tsx +++ b/components/modal/style/index.tsx @@ -336,6 +336,55 @@ const genRTLStyle: GenerateStyle = token => { }; }; +const genWireframeStyle: GenerateStyle = token => { + const { componentCls, antCls } = token; + const confirmComponentCls = `${componentCls}-confirm`; + + return { + [componentCls]: { + [`${componentCls}-content`]: { + padding: 0, + }, + + [`${componentCls}-header`]: { + padding: token.modalHeaderPadding, + borderBottom: `${token.modalHeaderBorderWidth}px ${token.modalHeaderBorderStyle} ${token.modalHeaderBorderColorSplit}`, + marginBottom: 0, + }, + + [`${componentCls}-body`]: { + padding: token.modalBodyPadding, + }, + + [`${componentCls}-footer`]: { + padding: `${token.modalFooterPaddingVertical}px ${token.modalFooterPaddingHorizontal}px`, + borderTop: `${token.modalFooterBorderWidth}px ${token.modalFooterBorderStyle} ${token.modalFooterBorderColorSplit}`, + borderRadius: `0 0 ${token.radiusLG}px ${token.radiusLG}px`, + marginTop: 0, + }, + }, + + [confirmComponentCls]: { + [`${antCls}-modal-body`]: { + padding: `${token.padding * 2}px ${token.padding * 2}px ${token.paddingLG}px`, + }, + [`${confirmComponentCls}-body`]: { + [`> ${token.iconCls}`]: { + marginInlineEnd: token.margin, + + // `content` after `icon` should set marginLeft + [`+ ${confirmComponentCls}-title + ${confirmComponentCls}-content`]: { + marginInlineStart: token.modalConfirmIconSize + token.margin, + }, + }, + }, + [`${confirmComponentCls}-btns`]: { + marginTop: token.marginLG, + }, + }, + }; +}; + // ============================== Export ============================== export default genComponentStyleHook('Modal', token => { const headerPaddingVertical = token.padding; @@ -370,6 +419,7 @@ export default genComponentStyleHook('Modal', token => { genModalConfirmStyle(modalToken), genRTLStyle(modalToken), genModalMaskStyle(modalToken), + token.wireframe && genWireframeStyle(modalToken), initZoomMotion(modalToken, 'zoom'), ]; }); diff --git a/components/pagination/style/index.tsx b/components/pagination/style/index.tsx index 4da0898b1d..801291c6f2 100644 --- a/components/pagination/style/index.tsx +++ b/components/pagination/style/index.tsx @@ -524,6 +524,66 @@ const genPaginationStyle: GenerateStyle = token => { }; }; +const genBorderedStyle: GenerateStyle = token => { + const { componentCls } = token; + + return { + [`${componentCls}-disabled`]: { + '&, &:hover': { + [`${componentCls}-item-link`]: { + borderColor: token.colorBorder, + }, + }, + + '&:focus-visible': { + [`${componentCls}-item-link`]: { + borderColor: token.colorBorder, + }, + }, + + [`${componentCls}-item`]: { + background: token.colorBgContainerDisabled, + borderColor: token.colorBorder, + }, + + [`${componentCls}-item-link`]: { + background: token.colorBgContainerDisabled, + borderColor: token.colorBorder, + }, + }, + + [componentCls]: { + [`${componentCls}-prev, ${componentCls}-next`]: { + '&:hover button': { + borderColor: token.colorPrimaryHover, + }, + + [`${componentCls}-item-link`]: { + backgroundColor: token.paginationItemLinkBg, + borderColor: token.colorBorder, + }, + + [`&:hover ${componentCls}-item-link`]: { + borderColor: token.colorPrimary, + }, + }, + + [`${componentCls}-item`]: { + backgroundColor: token.paginationItemBg, + border: `${token.controlLineWidth}px ${token.controlLineType} ${token.colorBorder}`, + + '&:hover': { + borderColor: token.colorPrimary, + }, + + '&-active': { + borderColor: token.colorPrimary, + }, + }, + }, + }; +}; + // ============================== Export ============================== export default genComponentStyleHook('Pagination', token => { const paginationToken = mergeToken( @@ -551,5 +611,8 @@ export default genComponentStyleHook('Pagination', token => { }, initInputToken(token), ); - return [genPaginationStyle(paginationToken)]; + return [ + genPaginationStyle(paginationToken), + token.wireframe && genBorderedStyle(paginationToken), + ]; }); diff --git a/components/popover/style/index.tsx b/components/popover/style/index.tsx index 17840ef5aa..b3f9c1827a 100644 --- a/components/popover/style/index.tsx +++ b/components/popover/style/index.tsx @@ -118,10 +118,47 @@ const genColorStyle: GenerateStyle = token => { }; }; +const genWireframeStyle: GenerateStyle = token => { + const { + componentCls, + lineWidth, + lineType, + colorSplit, + paddingSM, + controlHeight, + fontSize, + lineHeight, + padding, + } = token; + + const titlePaddingBlockDist = controlHeight - Math.round(fontSize * lineHeight); + const popoverTitlePaddingBlockTop = titlePaddingBlockDist / 2; + const popoverTitlePaddingBlockBottom = titlePaddingBlockDist / 2 - lineWidth; + const popoverPaddingHorizontal = padding; + + return { + [componentCls]: { + [`${componentCls}-inner`]: { + padding: 0, + }, + + [`${componentCls}-title`]: { + margin: 0, + padding: `${popoverTitlePaddingBlockTop}px ${popoverPaddingHorizontal}px ${popoverTitlePaddingBlockBottom}px`, + borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`, + }, + + [`${componentCls}-inner-content`]: { + padding: `${paddingSM}px ${popoverPaddingHorizontal}px`, + }, + }, + }; +}; + export default genComponentStyleHook( 'Popover', token => { - const { colorBgElevated, colorText, paddingSM } = token; + const { colorBgElevated, colorText, paddingSM, wireframe } = token; const popoverToken = mergeToken(token, { popoverBg: colorBgElevated, @@ -132,6 +169,7 @@ export default genComponentStyleHook( return [ genBaseStyle(popoverToken), genColorStyle(popoverToken), + wireframe && genWireframeStyle(popoverToken), initZoomMotion(popoverToken, 'zoom-big'), ]; }, diff --git a/components/radio/style/index.tsx b/components/radio/style/index.tsx index 12438186c1..51eb7a46fa 100644 --- a/components/radio/style/index.tsx +++ b/components/radio/style/index.tsx @@ -14,7 +14,7 @@ interface RadioToken extends FullToken<'Radio'> { radioTop: number; radioDotSize: number; radioDotDisabledSize: number; - radioDotColor: string; + radioCheckedColor: string; radioDotDisabledColor: string; radioSolidCheckedColor: string; @@ -67,7 +67,7 @@ const getRadioBasicStyle: GenerateStyle = token => { const { componentCls, radioWrapperMarginRight, - radioDotColor, + radioCheckedColor, radioTop, radioSize, motionDurationSlow, @@ -84,6 +84,7 @@ const getRadioBasicStyle: GenerateStyle = token => { radioDotDisabledColor, controlLineType, radioDotDisabledSize, + wireframe, } = token; const radioInnerPrefixCls = `${componentCls}-inner`; @@ -120,7 +121,7 @@ const getRadioBasicStyle: GenerateStyle = token => { insetInlineStart: 0, width: '100%', height: '100%', - border: `${controlLineWidth}px ${controlLineType} ${radioDotColor}`, + border: `${controlLineWidth}px ${controlLineType} ${radioCheckedColor}`, borderRadius: '50%', visibility: 'hidden', animationName: antRadioEffect, @@ -141,7 +142,7 @@ const getRadioBasicStyle: GenerateStyle = token => { [`${componentCls}-wrapper:hover &, &:hover ${radioInnerPrefixCls}`]: { - borderColor: radioDotColor, + borderColor: radioCheckedColor, }, [`${componentCls}-input:focus-visible + ${radioInnerPrefixCls}`]: { @@ -162,7 +163,7 @@ const getRadioBasicStyle: GenerateStyle = token => { height: radioSize, marginBlockStart: radioSize / -2, marginInlineStart: radioSize / -2, - backgroundColor: radioButtonBg, + backgroundColor: wireframe ? radioCheckedColor : radioButtonBg, borderBlockStart: 0, borderInlineStart: 0, borderRadius: radioSize, @@ -200,8 +201,8 @@ const getRadioBasicStyle: GenerateStyle = token => { // 选中状态 [`${componentCls}-checked`]: { [radioInnerPrefixCls]: { - borderColor: radioDotColor, - backgroundColor: radioDotColor, + borderColor: radioCheckedColor, + backgroundColor: wireframe ? radioButtonBg : radioCheckedColor, '&::after': { transform: `scale(${radioDotSize / radioSize})`, @@ -271,7 +272,7 @@ const getRadioButtonStyle: GenerateStyle = token => { controlRadius, controlRadiusSM, controlRadiusLG, - radioDotColor, + radioCheckedColor, radioButtonCheckedBg, radioButtonHoverColor, radioButtonActiveColor, @@ -387,7 +388,7 @@ const getRadioButtonStyle: GenerateStyle = token => { '&:hover': { position: 'relative', - color: radioDotColor, + color: radioCheckedColor, }, '&:has(:focus-visible)': { @@ -403,16 +404,16 @@ const getRadioButtonStyle: GenerateStyle = token => { '&-checked:not(&-disabled)': { zIndex: 1, - color: radioDotColor, + color: radioCheckedColor, background: radioButtonCheckedBg, - borderColor: radioDotColor, + borderColor: radioCheckedColor, '&::before': { - backgroundColor: radioDotColor, + backgroundColor: radioCheckedColor, }, '&:first-child': { - borderColor: radioDotColor, + borderColor: radioCheckedColor, }, '&:hover': { @@ -436,8 +437,8 @@ const getRadioButtonStyle: GenerateStyle = token => { [`${componentCls}-group-solid &-checked:not(&-disabled)`]: { color: radioSolidCheckedColor, - background: radioDotColor, - borderColor: radioDotColor, + background: radioCheckedColor, + borderColor: radioCheckedColor, '&:hover': { color: radioSolidCheckedColor, @@ -494,6 +495,7 @@ export default genComponentStyleHook('Radio', token => { marginXS, controlOutlineWidth, paddingXXS, + wireframe, } = token; // Radio @@ -502,9 +504,11 @@ export default genComponentStyleHook('Radio', token => { const radioSize = fontSizeLG; const radioTop = (Math.round(fontSize * lineHeight) - radioSize) / 2; - const radioDotSize = radioSize - (paddingXXS + controlLineWidth) * 2; const radioDotDisabledSize = radioSize - paddingXXS * 2; - const radioDotColor = colorPrimary; + const radioDotSize = wireframe + ? radioDotDisabledSize + : radioSize - (paddingXXS + controlLineWidth) * 2; + const radioCheckedColor = colorPrimary; // Radio buttons const radioButtonColor = colorText; @@ -521,7 +525,7 @@ export default genComponentStyleHook('Radio', token => { radioTop, radioDotSize, radioDotDisabledSize, - radioDotColor, + radioCheckedColor, radioDotDisabledColor: colorTextDisabled, radioSolidCheckedColor: colorBgContainer, radioButtonBg: colorBgContainer, diff --git a/components/steps/style/index.tsx b/components/steps/style/index.tsx index b3e378b12b..90ccb8c120 100644 --- a/components/steps/style/index.tsx +++ b/components/steps/style/index.tsx @@ -35,24 +35,28 @@ export interface StepsToken extends FullToken<'Steps'> { processTitleColor: string; processDescriptionColor: string; processIconBgColor: string; + processIconBorderColor: string; processDotColor: string; waitIconColor: string; waitTitleColor: string; waitDescriptionColor: string; waitTailColor: string; waitIconBgColor: string; + waitIconBorderColor: string; waitDotColor: string; finishIconColor: string; finishTitleColor: string; finishDescriptionColor: string; finishTailColor: string; finishIconBgColor: string; + finishIconBorderColor: string; finishDotColor: string; errorIconColor: string; errorTitleColor: string; errorDescriptionColor: string; errorTailColor: string; errorIconBgColor: string; + errorIconBorderColor: string; errorDotColor: string; stepsNavActiveColor: string; stepsProgressSize: number; @@ -72,10 +76,12 @@ const genStepsItemStatusStyle = (status: StepItemStatusEnum, token: StepsToken): const descriptionColorKey: keyof StepsToken = `${status}DescriptionColor`; const tailColorKey: keyof StepsToken = `${status}TailColor`; const iconBgColorKey: keyof StepsToken = `${status}IconBgColor`; + const iconBorderColorKey: keyof StepsToken = `${status}IconBorderColor`; const dotColorKey: keyof StepsToken = `${status}DotColor`; return { [`${prefix}-${status} ${prefix}-icon`]: { backgroundColor: token[iconBgColorKey], + borderColor: token[iconBorderColorKey], [`> ${token.componentCls}-icon`]: { color: token[iconColorKey], [`${token.componentCls}-icon-dot`]: { @@ -142,13 +148,13 @@ const genStepsItemStyle: GenerateStyle = token => { lineHeight: `${token.stepsIconSize}px`, textAlign: 'center', borderRadius: token.stepsIconSize, + border: `${token.controlLineWidth}px ${token.controlLineType} transparent`, transition: `background-color ${motionDurationSlow}, border-color ${motionDurationSlow}`, [`${componentCls}-icon`]: { position: 'relative', top: token.stepsIconTop, color: token.colorPrimary, lineHeight: 1, - verticalAlign: 'text-bottom', }, }, [`${stepsItemCls}-tail`]: { @@ -313,50 +319,72 @@ const genStepsStyle: GenerateStyle = token => { export default genComponentStyleHook( 'Steps', token => { + const { + wireframe, + colorTextDisabled, + fontSizeHeading3, + fontSize, + controlHeight, + controlHeightLG, + colorTextLightSolid, + colorText, + colorPrimary, + colorTextLabel, + colorTextDescription, + colorFillContent, + colorPrimaryBg, + colorError, + colorBgContainer, + } = token; + const stepsIconSize = token.controlHeight; const processTailColor = token.colorSplit; const stepsToken = mergeToken(token, { // Steps variable default.less processTailColor, - stepsNavArrowColor: token.colorTextDisabled, + stepsNavArrowColor: colorTextDisabled, stepsIconSize, stepsIconCustomSize: stepsIconSize, stepsIconCustomTop: 0, - stepsIconCustomFontSize: token.fontSizeHeading3, + stepsIconCustomFontSize: fontSizeHeading3, stepsIconTop: -0.5, // magic for ui experience - stepsIconFontSize: token.fontSize, - stepsTitleLineHeight: token.controlHeight, - stepsSmallIconSize: token.fontSizeHeading3, - stepsDotSize: token.controlHeight / 4, - stepsCurrentDotSize: token.controlHeightLG / 4, + stepsIconFontSize: fontSize, + stepsTitleLineHeight: controlHeight, + stepsSmallIconSize: fontSizeHeading3, + stepsDotSize: controlHeight / 4, + stepsCurrentDotSize: controlHeightLG / 4, stepsNavContentMaxWidth: 'auto', // Steps component less variable - processIconColor: token.colorTextLightSolid, - processTitleColor: token.colorText, - processDescriptionColor: token.colorText, - processIconBgColor: token.colorPrimary, - processDotColor: token.colorPrimary, - waitIconColor: token.colorTextLabel, - waitTitleColor: token.colorTextDescription, - waitDescriptionColor: token.colorTextDescription, + processIconColor: colorTextLightSolid, + processTitleColor: colorText, + processDescriptionColor: colorText, + processIconBgColor: colorPrimary, + processIconBorderColor: colorPrimary, + processDotColor: colorPrimary, + waitIconColor: wireframe ? colorTextDisabled : colorTextLabel, + waitTitleColor: colorTextDescription, + waitDescriptionColor: colorTextDescription, waitTailColor: processTailColor, - waitIconBgColor: token.colorFillContent, - waitDotColor: token.colorTextDisabled, - finishIconColor: token.colorPrimary, - finishTitleColor: token.colorText, - finishDescriptionColor: token.colorTextDescription, - finishTailColor: token.colorPrimary, - finishIconBgColor: token.colorPrimaryBg, - finishDotColor: token.colorPrimary, - errorIconColor: token.colorTextLightSolid, - errorTitleColor: token.colorError, - errorDescriptionColor: token.colorError, + waitIconBgColor: wireframe ? colorBgContainer : colorFillContent, + waitIconBorderColor: wireframe ? colorTextDisabled : 'transparent', + waitDotColor: colorTextDisabled, + finishIconColor: colorPrimary, + finishTitleColor: colorText, + finishDescriptionColor: colorTextDescription, + finishTailColor: colorPrimary, + finishIconBgColor: wireframe ? colorBgContainer : colorPrimaryBg, + finishIconBorderColor: wireframe ? colorPrimary : colorPrimaryBg, + finishDotColor: colorPrimary, + errorIconColor: colorTextLightSolid, + errorTitleColor: colorError, + errorDescriptionColor: colorError, errorTailColor: processTailColor, - errorIconBgColor: token.colorError, - errorDotColor: token.colorError, - stepsNavActiveColor: token.colorPrimary, - stepsProgressSize: token.controlHeightLG, + errorIconBgColor: colorError, + errorIconBorderColor: colorError, + errorDotColor: colorError, + stepsNavActiveColor: colorPrimary, + stepsProgressSize: controlHeightLG, }); return [genStepsStyle(stepsToken)]; diff --git a/components/steps/style/progress.ts b/components/steps/style/progress.ts index 2282d17ffc..63ced53814 100644 --- a/components/steps/style/progress.ts +++ b/components/steps/style/progress.ts @@ -33,8 +33,10 @@ const genStepsProgressStyle: GenerateStyle = token => { [`${antCls}-progress`]: { position: 'absolute', - insetBlockStart: (token.stepsIconSize - token.stepsProgressSize) / 2, - insetInlineStart: (token.stepsIconSize - token.stepsProgressSize) / 2, + insetBlockStart: + (token.stepsIconSize - token.stepsProgressSize - token.lineWidth * 2) / 2, + insetInlineStart: + (token.stepsIconSize - token.stepsProgressSize - token.lineWidth * 2) / 2, }, }, }, diff --git a/components/table/style/index.tsx b/components/table/style/index.tsx index e6642d6446..7d45125c75 100644 --- a/components/table/style/index.tsx +++ b/components/table/style/index.tsx @@ -90,6 +90,7 @@ const genTableStyle: GenerateStyle = token => { tableSelectedRowHoverBg, tableFooterTextColor, tableFooterBg, + wireframe, } = token; const tableBorder = `${controlLineWidth}px ${controlLineType} ${tableBorderColor}`; return { @@ -219,36 +220,38 @@ const genTableStyle: GenerateStyle = token => { }, }, - [`${componentCls}:not(${componentCls}-bordered) ${componentCls}-tbody > tr`]: { - [`&${componentCls}-row:hover, &${componentCls}-row${componentCls}-row-selected`]: { - [`+ tr${componentCls}-row > td`]: { - borderTopColor: 'transparent', - }, - }, + [`${componentCls}:not(${componentCls}-bordered) ${componentCls}-tbody > tr`]: wireframe + ? undefined + : { + [`&${componentCls}-row:hover, &${componentCls}-row${componentCls}-row-selected`]: { + [`+ tr${componentCls}-row > td`]: { + borderTopColor: 'transparent', + }, + }, - [`&${componentCls}-row:last-child:hover > td, + [`&${componentCls}-row:last-child:hover > td, &${componentCls}-row${componentCls}-row-selected:last-child > td`]: { - borderBottomColor: 'transparent', - }, + borderBottomColor: 'transparent', + }, - [` + [` &${componentCls}-row:hover > td, > td${componentCls}-cell-row-hover, &${componentCls}-row${componentCls}-row-selected > td `]: { - borderTopColor: 'transparent', + borderTopColor: 'transparent', - '&:first-child': { - borderStartStartRadius: tableRadius, - borderEndStartRadius: tableRadius, - }, + '&:first-child': { + borderStartStartRadius: tableRadius, + borderEndStartRadius: tableRadius, + }, - '&:last-child': { - borderStartEndRadius: tableRadius, - borderEndEndRadius: tableRadius, + '&:last-child': { + borderStartEndRadius: tableRadius, + borderEndEndRadius: tableRadius, + }, + }, }, - }, - }, // ============================ Footer ============================ [`${componentCls}-footer`]: { diff --git a/components/theme/interface.ts b/components/theme/interface.ts index bd3472e2e6..e2cf14415b 100644 --- a/components/theme/interface.ts +++ b/components/theme/interface.ts @@ -203,6 +203,9 @@ export interface SeedToken extends PresetColorType { // Image /** Define default Image opacity. Useful when in dark-like theme */ opacityImage: number; + + // Wireframe + wireframe: boolean; } export interface NeutralColorMapToken { diff --git a/components/theme/themes/seed.ts b/components/theme/themes/seed.ts index 78d4e97f43..e690a44b94 100644 --- a/components/theme/themes/seed.ts +++ b/components/theme/themes/seed.ts @@ -73,5 +73,8 @@ const seedToken: SeedToken = { // Image opacityImage: 1, + + // Wireframe + wireframe: false, }; export default seedToken; diff --git a/components/timeline/style/index.tsx b/components/timeline/style/index.tsx index b6f88a726f..7b71ff00c8 100644 --- a/components/timeline/style/index.tsx +++ b/components/timeline/style/index.tsx @@ -11,6 +11,7 @@ interface TimelineToken extends FullToken<'Timeline'> { timeLineItemCustomHeadPaddingVertical: number; timeLineItemTailWidth: number; timeLinePaddingInlineEnd: number; + timeLineHeadBorderWidth: number; } const genTimelineStyle: GenerateStyle = token => { @@ -54,7 +55,7 @@ const genTimelineStyle: GenerateStyle = token => { width: token.timeLineItemHeadSize, height: token.timeLineItemHeadSize, backgroundColor: token.colorBgContainer, - border: `${token.lineWidth * 3}px ${token.lineType} transparent`, + border: `${token.timeLineHeadBorderWidth}px ${token.lineType} transparent`, borderRadius: '50%', '&-blue': { @@ -219,6 +220,7 @@ export default genComponentStyleHook('Timeline', token => { timeLineItemCustomHeadPaddingVertical: token.paddingXXS, timeLinePaddingInlineEnd: 2, timeLineItemTailWidth: token.lineWidthBold, + timeLineHeadBorderWidth: token.wireframe ? token.lineWidthBold : token.lineWidth * 3, }); return [genTimelineStyle(timeLineToken)]; diff --git a/scripts/collect-token-statistic.js b/scripts/collect-token-statistic.js index 9957300557..d300abcf32 100644 --- a/scripts/collect-token-statistic.js +++ b/scripts/collect-token-statistic.js @@ -8,6 +8,8 @@ const glob = require('glob'); const path = require('path'); const ProgressBar = require('progress'); const { statistic } = require('../components/theme/util/statistic'); +const { DesignTokenContext } = require('../components/theme'); +const seedToken = require('../components/theme/themes/seed'); console.log(chalk.green(`🔥 Collecting token statistics...`)); @@ -46,6 +48,14 @@ styleFiles.forEach(file => { return EmptyElement; }; ReactDOMServer.renderToString(React.createElement(Component)); + // Render wireframe + ReactDOMServer.renderToString( + React.createElement( + DesignTokenContext.Provider, + { value: { token: { ...seedToken, wireframe: true } } }, + React.createElement(Component), + ), + ); }); (async () => { diff --git a/site/theme/template/Layout/DynamicTheme/index.tsx b/site/theme/template/Layout/DynamicTheme/index.tsx index dea79069d2..46d0991b6d 100644 --- a/site/theme/template/Layout/DynamicTheme/index.tsx +++ b/site/theme/template/Layout/DynamicTheme/index.tsx @@ -1,6 +1,6 @@ import { BugOutlined, EyeOutlined } from '@ant-design/icons'; import { TinyColor } from '@ctrl/tinycolor'; -import { Button, Select, Checkbox, Drawer, Form, Input, InputNumber, Space } from 'antd'; +import { Button, Select, Checkbox, Drawer, Form, Input, InputNumber, Space, Switch } from 'antd'; import * as React from 'react'; import { useState } from 'react'; import { useIntl } from 'react-intl'; @@ -121,14 +121,16 @@ export default function DynamicTheme({ case 'number': node = ; break; + case 'boolean': + node = ; + break; default: node = ; } const rules: any[] = [{ required: key !== 'colorTextBase' && key !== 'colorBgBase' }]; - const originColor = new TinyColor(originValue); - if (originValueType === 'string' && originColor.isValid) { + if (originValueType === 'string' && new TinyColor(originValue as string).isValid) { rules.push({ validator: async (_: any, value: string) => { if (!new TinyColor(value).isValid) {