refactor: ccsinjs

pull/6213/head
tangjinzhou 2023-01-24 22:51:59 +08:00
parent d64c6a8d53
commit 10b52e0072
80 changed files with 5850 additions and 33 deletions

View File

@ -0,0 +1,58 @@
/* eslint-disable import/prefer-default-export */
import type { CSSInterpolation, CSSObject } from '../_util/cssinjs';
import type { DerivativeToken, FullToken } from '../theme/internal';
import type { OverrideComponent } from '../theme/util/genComponentStyleHook';
function compactItemVerticalBorder(token: DerivativeToken, parentCls: string): CSSObject {
return {
// border collapse
[`&-item:not(${parentCls}-last-item)`]: {
marginBottom: -token.lineWidth,
},
'&-item': {
'&:hover,&:focus,&:active': {
zIndex: 2,
},
'&[disabled]': {
zIndex: 0,
},
},
};
}
function compactItemBorderVerticalRadius(prefixCls: string, parentCls: string): CSSObject {
return {
[`&-item:not(${parentCls}-first-item):not(${parentCls}-last-item)`]: {
borderRadius: 0,
},
[`&-item${parentCls}-first-item:not(${parentCls}-last-item)`]: {
[`&, &${prefixCls}-sm, &${prefixCls}-lg`]: {
borderEndEndRadius: 0,
borderEndStartRadius: 0,
},
},
[`&-item${parentCls}-last-item:not(${parentCls}-first-item)`]: {
[`&, &${prefixCls}-sm, &${prefixCls}-lg`]: {
borderStartStartRadius: 0,
borderStartEndRadius: 0,
},
},
};
}
export function genCompactItemVerticalStyle<T extends OverrideComponent>(
token: FullToken<T>,
): CSSInterpolation {
const compactCls = `${token.componentCls}-compact-vertical`;
return {
[compactCls]: {
...compactItemVerticalBorder(token, compactCls),
...compactItemBorderVerticalRadius(token.componentCls, compactCls),
},
};
}

View File

@ -0,0 +1,103 @@
/* eslint-disable import/prefer-default-export */
import type { CSSInterpolation, CSSObject } from '../_util/cssinjs';
import type { DerivativeToken, FullToken } from '../theme/internal';
import type { OverrideComponent } from '../theme/util/genComponentStyleHook';
interface CompactItemOptions {
focus?: boolean;
/**
* Some component borders are implemented on child elements
* like `Select`
*/
borderElCls?: string;
/**
* Some components have special `focus` className especially with popovers
* like `Select` and `DatePicker`
*/
focusElCls?: string;
}
// handle border collapse
function compactItemBorder(
token: DerivativeToken,
parentCls: string,
options: CompactItemOptions,
): CSSObject {
const { focusElCls, focus, borderElCls } = options;
const childCombinator = borderElCls ? '> *' : '';
const hoverEffects = ['hover', focus ? 'focus' : null, 'active']
.filter(Boolean)
.map(n => `&:${n} ${childCombinator}`)
.join(',');
return {
[`&-item:not(${parentCls}-last-item)`]: {
marginInlineEnd: -token.lineWidth,
},
'&-item': {
[hoverEffects]: {
zIndex: 2,
},
...(focusElCls
? {
[`&${focusElCls}`]: {
zIndex: 2,
},
}
: {}),
[`&[disabled] ${childCombinator}`]: {
zIndex: 0,
},
},
};
}
// handle border-radius
function compactItemBorderRadius(
prefixCls: string,
parentCls: string,
options: CompactItemOptions,
): CSSObject {
const { borderElCls } = options;
const childCombinator = borderElCls ? `> ${borderElCls}` : '';
return {
[`&-item:not(${parentCls}-first-item):not(${parentCls}-last-item) ${childCombinator}`]: {
borderRadius: 0,
},
[`&-item:not(${parentCls}-last-item)${parentCls}-first-item`]: {
[`& ${childCombinator}, &${prefixCls}-sm ${childCombinator}, &${prefixCls}-lg ${childCombinator}`]:
{
borderStartEndRadius: 0,
borderEndEndRadius: 0,
},
},
[`&-item:not(${parentCls}-first-item)${parentCls}-last-item`]: {
[`& ${childCombinator}, &${prefixCls}-sm ${childCombinator}, &${prefixCls}-lg ${childCombinator}`]:
{
borderStartStartRadius: 0,
borderEndStartRadius: 0,
},
},
};
}
export function genCompactItemStyle<T extends OverrideComponent>(
token: FullToken<T>,
options: CompactItemOptions = { focus: true },
): CSSInterpolation {
const { componentCls } = token;
const compactCls = `${componentCls}-compact`;
return {
[compactCls]: {
...compactItemBorder(token, compactCls, options),
...compactItemBorderRadius(componentCls, compactCls, options),
},
};
}

139
components/_style/index.ts Normal file
View File

@ -0,0 +1,139 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject } from '../_util/cssinjs';
import type { DerivativeToken } from '../theme/internal';
export { operationUnit } from './operationUnit';
export { roundedArrow } from './roundedArrow';
export { genPresetColor } from './presetColor';
export const textEllipsis: CSSObject = {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
};
export const resetComponent = (token: DerivativeToken): CSSObject => ({
boxSizing: 'border-box',
margin: 0,
padding: 0,
color: token.colorText,
fontSize: token.fontSize,
// font-variant: @font-variant-base;
lineHeight: token.lineHeight,
listStyle: 'none',
// font-feature-settings: @font-feature-settings-base;
fontFamily: token.fontFamily,
});
export const resetIcon = (): CSSObject => ({
display: 'inline-flex',
alignItems: 'center',
color: 'inherit',
fontStyle: 'normal',
lineHeight: 0,
textAlign: 'center',
textTransform: 'none',
// for SVG icon, see https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
verticalAlign: '-0.125em',
textRendering: 'optimizeLegibility',
'-webkit-font-smoothing': 'antialiased',
'-moz-osx-font-smoothing': 'grayscale',
'> *': {
lineHeight: 1,
},
svg: {
display: 'inline-block',
},
});
export const clearFix = (): CSSObject => ({
// https://github.com/ant-design/ant-design/issues/21301#issuecomment-583955229
'&::before': {
display: 'table',
content: '""',
},
'&::after': {
// https://github.com/ant-design/ant-design/issues/21864
display: 'table',
clear: 'both',
content: '""',
},
});
export const genLinkStyle = (token: DerivativeToken): CSSObject => ({
a: {
color: token.colorLink,
textDecoration: token.linkDecoration,
backgroundColor: 'transparent', // remove the gray background on active links in IE 10.
outline: 'none',
cursor: 'pointer',
transition: `color ${token.motionDurationSlow}`,
'-webkit-text-decoration-skip': 'objects', // remove gaps in links underline in iOS 8+ and Safari 8+.
'&:hover': {
color: token.colorLinkHover,
},
'&:active': {
color: token.colorLinkActive,
},
[`&:active,
&:hover`]: {
textDecoration: token.linkHoverDecoration,
outline: 0,
},
// https://github.com/ant-design/ant-design/issues/22503
'&:focus': {
textDecoration: token.linkFocusDecoration,
outline: 0,
},
'&[disabled]': {
color: token.colorTextDisabled,
cursor: 'not-allowed',
},
},
});
export const genCommonStyle = (token: DerivativeToken, componentPrefixCls: string): CSSObject => {
const { fontFamily, fontSize } = token;
const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`;
return {
[rootPrefixSelector]: {
fontFamily,
fontSize,
boxSizing: 'border-box',
'&::before, &::after': {
boxSizing: 'border-box',
},
[rootPrefixSelector]: {
boxSizing: 'border-box',
'&::before, &::after': {
boxSizing: 'border-box',
},
},
},
};
};
export const genFocusOutline = (token: DerivativeToken): CSSObject => ({
outline: `${token.lineWidthBold}px solid ${token.colorPrimaryBorder}`,
outlineOffset: 1,
transition: 'outline-offset 0s, outline 0s',
});
export const genFocusStyle = (token: DerivativeToken): CSSObject => ({
'&:focus-visible': {
...genFocusOutline(token),
},
});

View File

@ -0,0 +1,24 @@
import type { AliasToken, GenerateStyle } from '../../theme/internal';
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook';
const genCollapseMotion: GenerateStyle<TokenWithCommonCls<AliasToken>> = token => ({
[token.componentCls]: {
// For common/openAnimation
[`${token.antCls}-motion-collapse-legacy`]: {
overflow: 'hidden',
'&-active': {
transition: `height ${token.motionDurationMid} ${token.motionEaseInOut},
opacity ${token.motionDurationMid} ${token.motionEaseInOut} !important`,
},
},
[`${token.antCls}-motion-collapse`]: {
overflow: 'hidden',
transition: `height ${token.motionDurationMid} ${token.motionEaseInOut},
opacity ${token.motionDurationMid} ${token.motionEaseInOut} !important`,
},
},
});
export default genCollapseMotion;

View File

@ -0,0 +1,49 @@
import type { CSSInterpolation } from '../../_util/cssinjs';
import { Keyframes } from '../../_util/cssinjs';
import type { AliasToken } from '../../theme/internal';
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook';
import { initMotion } from './motion';
export const fadeIn = new Keyframes('antFadeIn', {
'0%': {
opacity: 0,
},
'100%': {
opacity: 1,
},
});
export const fadeOut = new Keyframes('antFadeOut', {
'0%': {
opacity: 1,
},
'100%': {
opacity: 0,
},
});
export const initFadeMotion = (
token: TokenWithCommonCls<AliasToken>,
sameLevel = false,
): CSSInterpolation => {
const { antCls } = token;
const motionCls = `${antCls}-fade`;
const sameLevelPrefix = sameLevel ? '&' : '';
return [
initMotion(motionCls, fadeIn, fadeOut, token.motionDurationMid, sameLevel),
{
[`
${sameLevelPrefix}${motionCls}-enter,
${sameLevelPrefix}${motionCls}-appear
`]: {
opacity: 0,
animationTimingFunction: 'linear',
},
[`${sameLevelPrefix}${motionCls}-leave`]: {
animationTimingFunction: 'linear',
},
},
];
};

View File

@ -0,0 +1,77 @@
import { fadeIn, fadeOut, initFadeMotion } from './fade';
import {
initMoveMotion,
moveDownIn,
moveDownOut,
moveLeftIn,
moveLeftOut,
moveRightIn,
moveRightOut,
moveUpIn,
moveUpOut,
} from './move';
import {
initSlideMotion,
slideDownIn,
slideDownOut,
slideLeftIn,
slideLeftOut,
slideRightIn,
slideRightOut,
slideUpIn,
slideUpOut,
} from './slide';
import {
initZoomMotion,
zoomBigIn,
zoomBigOut,
zoomDownIn,
zoomDownOut,
zoomIn,
zoomLeftIn,
zoomLeftOut,
zoomOut,
zoomRightIn,
zoomRightOut,
zoomUpIn,
zoomUpOut,
} from './zoom';
import genCollapseMotion from './collapse';
export {
initSlideMotion,
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
slideLeftIn,
slideLeftOut,
slideRightIn,
slideRightOut,
zoomOut,
zoomIn,
zoomBigIn,
zoomLeftOut,
zoomBigOut,
zoomLeftIn,
zoomRightIn,
zoomUpIn,
zoomRightOut,
zoomUpOut,
zoomDownIn,
zoomDownOut,
initZoomMotion,
fadeIn,
fadeOut,
initFadeMotion,
moveRightOut,
moveRightIn,
moveLeftOut,
moveLeftIn,
moveDownOut,
moveDownIn,
moveUpIn,
moveUpOut,
initMoveMotion,
genCollapseMotion,
};

View File

@ -0,0 +1,52 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject, Keyframes } from '../../_util/cssinjs';
const initMotionCommon = (duration: string): CSSObject => ({
animationDuration: duration,
animationFillMode: 'both',
});
// FIXME: origin less code seems same as initMotionCommon. Maybe we can safe remove
const initMotionCommonLeave = (duration: string): CSSObject => ({
animationDuration: duration,
animationFillMode: 'both',
});
export const initMotion = (
motionCls: string,
inKeyframes: Keyframes,
outKeyframes: Keyframes,
duration: string,
sameLevel = false,
): CSSObject => {
const sameLevelPrefix = sameLevel ? '&' : '';
return {
[`
${sameLevelPrefix}${motionCls}-enter,
${sameLevelPrefix}${motionCls}-appear
`]: {
...initMotionCommon(duration),
animationPlayState: 'paused',
},
[`${sameLevelPrefix}${motionCls}-leave`]: {
...initMotionCommonLeave(duration),
animationPlayState: 'paused',
},
[`
${sameLevelPrefix}${motionCls}-enter${motionCls}-enter-active,
${sameLevelPrefix}${motionCls}-appear${motionCls}-appear-active
`]: {
animationName: inKeyframes,
animationPlayState: 'running',
},
[`${sameLevelPrefix}${motionCls}-leave${motionCls}-leave-active`]: {
animationName: outKeyframes,
animationPlayState: 'running',
pointerEvents: 'none',
},
};
};

View File

@ -0,0 +1,163 @@
import type { CSSInterpolation } from '../../_util/cssinjs';
import { Keyframes } from '../../_util/cssinjs';
import type { AliasToken } from '../../theme/internal';
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook';
import { initMotion } from './motion';
export const moveDownIn = new Keyframes('antMoveDownIn', {
'0%': {
transform: 'translate3d(0, 100%, 0)',
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
export const moveDownOut = new Keyframes('antMoveDownOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: 'translate3d(0, 100%, 0)',
transformOrigin: '0 0',
opacity: 0,
},
});
export const moveLeftIn = new Keyframes('antMoveLeftIn', {
'0%': {
transform: 'translate3d(-100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
export const moveLeftOut = new Keyframes('antMoveLeftOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: 'translate3d(-100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0,
},
});
export const moveRightIn = new Keyframes('antMoveRightIn', {
'0%': {
transform: 'translate3d(100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
export const moveRightOut = new Keyframes('antMoveRightOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: 'translate3d(100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0,
},
});
export const moveUpIn = new Keyframes('antMoveUpIn', {
'0%': {
transform: 'translate3d(0, -100%, 0)',
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
export const moveUpOut = new Keyframes('antMoveUpOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: 'translate3d(0, -100%, 0)',
transformOrigin: '0 0',
opacity: 0,
},
});
type MoveMotionTypes = 'move-up' | 'move-down' | 'move-left' | 'move-right';
const moveMotion: Record<MoveMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
'move-up': {
inKeyframes: moveUpIn,
outKeyframes: moveUpOut,
},
'move-down': {
inKeyframes: moveDownIn,
outKeyframes: moveDownOut,
},
'move-left': {
inKeyframes: moveLeftIn,
outKeyframes: moveLeftOut,
},
'move-right': {
inKeyframes: moveRightIn,
outKeyframes: moveRightOut,
},
};
export const initMoveMotion = (
token: TokenWithCommonCls<AliasToken>,
motionName: MoveMotionTypes,
): CSSInterpolation => {
const { antCls } = token;
const motionCls = `${antCls}-${motionName}`;
const { inKeyframes, outKeyframes } = moveMotion[motionName];
return [
initMotion(motionCls, inKeyframes, outKeyframes, token.motionDurationMid),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
opacity: 0,
animationTimingFunction: token.motionEaseOutCirc,
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.motionEaseInOutCirc,
},
},
];
};

View File

@ -0,0 +1,166 @@
import type { CSSInterpolation } from '../../_util/cssinjs';
import { Keyframes } from '../../_util/cssinjs';
import type { AliasToken } from '../../theme/internal';
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook';
import { initMotion } from './motion';
export const slideUpIn = new Keyframes('antSlideUpIn', {
'0%': {
transform: 'scaleY(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
'100%': {
transform: 'scaleY(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
});
export const slideUpOut = new Keyframes('antSlideUpOut', {
'0%': {
transform: 'scaleY(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
'100%': {
transform: 'scaleY(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
});
export const slideDownIn = new Keyframes('antSlideDownIn', {
'0%': {
transform: 'scaleY(0.8)',
transformOrigin: '100% 100%',
opacity: 0,
},
'100%': {
transform: 'scaleY(1)',
transformOrigin: '100% 100%',
opacity: 1,
},
});
export const slideDownOut = new Keyframes('antSlideDownOut', {
'0%': {
transform: 'scaleY(1)',
transformOrigin: '100% 100%',
opacity: 1,
},
'100%': {
transform: 'scaleY(0.8)',
transformOrigin: '100% 100%',
opacity: 0,
},
});
export const slideLeftIn = new Keyframes('antSlideLeftIn', {
'0%': {
transform: 'scaleX(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
'100%': {
transform: 'scaleX(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
});
export const slideLeftOut = new Keyframes('antSlideLeftOut', {
'0%': {
transform: 'scaleX(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
'100%': {
transform: 'scaleX(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
});
export const slideRightIn = new Keyframes('antSlideRightIn', {
'0%': {
transform: 'scaleX(0.8)',
transformOrigin: '100% 0%',
opacity: 0,
},
'100%': {
transform: 'scaleX(1)',
transformOrigin: '100% 0%',
opacity: 1,
},
});
export const slideRightOut = new Keyframes('antSlideRightOut', {
'0%': {
transform: 'scaleX(1)',
transformOrigin: '100% 0%',
opacity: 1,
},
'100%': {
transform: 'scaleX(0.8)',
transformOrigin: '100% 0%',
opacity: 0,
},
});
type SlideMotionTypes = 'slide-up' | 'slide-down' | 'slide-left' | 'slide-right';
const slideMotion: Record<SlideMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
'slide-up': {
inKeyframes: slideUpIn,
outKeyframes: slideUpOut,
},
'slide-down': {
inKeyframes: slideDownIn,
outKeyframes: slideDownOut,
},
'slide-left': {
inKeyframes: slideLeftIn,
outKeyframes: slideLeftOut,
},
'slide-right': {
inKeyframes: slideRightIn,
outKeyframes: slideRightOut,
},
};
export const initSlideMotion = (
token: TokenWithCommonCls<AliasToken>,
motionName: SlideMotionTypes,
): CSSInterpolation => {
const { antCls } = token;
const motionCls = `${antCls}-${motionName}`;
const { inKeyframes, outKeyframes } = slideMotion[motionName];
return [
initMotion(motionCls, inKeyframes, outKeyframes, token.motionDurationMid),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
transform: 'scale(0)',
transformOrigin: '0% 0%',
opacity: 0,
animationTimingFunction: token.motionEaseOutQuint,
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.motionEaseInQuint,
},
},
];
};

View File

@ -0,0 +1,230 @@
import type { CSSInterpolation } from '../../_util/cssinjs';
import { Keyframes } from '../../_util/cssinjs';
import type { AliasToken } from '../../theme/internal';
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook';
import { initMotion } from './motion';
export const zoomIn = new Keyframes('antZoomIn', {
'0%': {
transform: 'scale(0.2)',
opacity: 0,
},
'100%': {
transform: 'scale(1)',
opacity: 1,
},
});
export const zoomOut = new Keyframes('antZoomOut', {
'0%': {
transform: 'scale(1)',
},
'100%': {
transform: 'scale(0.2)',
opacity: 0,
},
});
export const zoomBigIn = new Keyframes('antZoomBigIn', {
'0%': {
transform: 'scale(0.8)',
opacity: 0,
},
'100%': {
transform: 'scale(1)',
opacity: 1,
},
});
export const zoomBigOut = new Keyframes('antZoomBigOut', {
'0%': {
transform: 'scale(1)',
},
'100%': {
transform: 'scale(0.8)',
opacity: 0,
},
});
export const zoomUpIn = new Keyframes('antZoomUpIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '50% 0%',
opacity: 0,
},
'100%': {
transform: 'scale(1)',
transformOrigin: '50% 0%',
},
});
export const zoomUpOut = new Keyframes('antZoomUpOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '50% 0%',
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '50% 0%',
opacity: 0,
},
});
export const zoomLeftIn = new Keyframes('antZoomLeftIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '0% 50%',
opacity: 0,
},
'100%': {
transform: 'scale(1)',
transformOrigin: '0% 50%',
},
});
export const zoomLeftOut = new Keyframes('antZoomLeftOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '0% 50%',
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '0% 50%',
opacity: 0,
},
});
export const zoomRightIn = new Keyframes('antZoomRightIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '100% 50%',
opacity: 0,
},
'100%': {
transform: 'scale(1)',
transformOrigin: '100% 50%',
},
});
export const zoomRightOut = new Keyframes('antZoomRightOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '100% 50%',
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '100% 50%',
opacity: 0,
},
});
export const zoomDownIn = new Keyframes('antZoomDownIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '50% 100%',
opacity: 0,
},
'100%': {
transform: 'scale(1)',
transformOrigin: '50% 100%',
},
});
export const zoomDownOut = new Keyframes('antZoomDownOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '50% 100%',
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '50% 100%',
opacity: 0,
},
});
type ZoomMotionTypes =
| 'zoom'
| 'zoom-big'
| 'zoom-big-fast'
| 'zoom-left'
| 'zoom-right'
| 'zoom-up'
| 'zoom-down';
const zoomMotion: Record<ZoomMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
zoom: {
inKeyframes: zoomIn,
outKeyframes: zoomOut,
},
'zoom-big': {
inKeyframes: zoomBigIn,
outKeyframes: zoomBigOut,
},
'zoom-big-fast': {
inKeyframes: zoomBigIn,
outKeyframes: zoomBigOut,
},
'zoom-left': {
inKeyframes: zoomLeftIn,
outKeyframes: zoomLeftOut,
},
'zoom-right': {
inKeyframes: zoomRightIn,
outKeyframes: zoomRightOut,
},
'zoom-up': {
inKeyframes: zoomUpIn,
outKeyframes: zoomUpOut,
},
'zoom-down': {
inKeyframes: zoomDownIn,
outKeyframes: zoomDownOut,
},
};
export const initZoomMotion = (
token: TokenWithCommonCls<AliasToken>,
motionName: ZoomMotionTypes,
): CSSInterpolation => {
const { antCls } = token;
const motionCls = `${antCls}-${motionName}`;
const { inKeyframes, outKeyframes } = zoomMotion[motionName];
return [
initMotion(
motionCls,
inKeyframes,
outKeyframes,
motionName === 'zoom-big-fast' ? token.motionDurationFast : token.motionDurationMid,
),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
transform: 'scale(0)',
opacity: 0,
animationTimingFunction: token.motionEaseOutCirc,
'&-prepare': {
transform: 'none',
},
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.motionEaseInOutCirc,
},
},
];
};

View File

@ -0,0 +1,21 @@
import type { CSSObject } from '../_util/cssinjs';
import type { DerivativeToken } from '../theme/internal';
// eslint-disable-next-line import/prefer-default-export
export const operationUnit = (token: DerivativeToken): CSSObject => ({
// FIXME: This use link but is a operation unit. Seems should be a colorPrimary.
// And Typography use this to generate link style which should not do this.
color: token.colorLink,
textDecoration: 'none',
outline: 'none',
cursor: 'pointer',
transition: `color ${token.motionDurationSlow}`,
'&:focus, &:hover': {
color: token.colorLinkHover,
},
'&:active': {
color: token.colorLinkActive,
},
});

View File

@ -0,0 +1,249 @@
import type { CSSInterpolation } from '../_util/cssinjs';
import type { AliasToken } from '../theme/internal';
import type { TokenWithCommonCls } from '../theme/util/genComponentStyleHook';
import { roundedArrow } from './roundedArrow';
function connectArrowCls(classList: string[], showArrowCls = '') {
return classList.map(cls => `${showArrowCls}${cls}`).join(',');
}
export const MAX_VERTICAL_CONTENT_RADIUS = 8;
export function getArrowOffset(options: {
sizePopupArrow: number;
contentRadius: number;
borderRadiusOuter: number;
limitVerticalRadius?: boolean;
}) {
const maxVerticalContentRadius = MAX_VERTICAL_CONTENT_RADIUS;
const { sizePopupArrow, contentRadius, borderRadiusOuter, limitVerticalRadius } = options;
const arrowInnerOffset = sizePopupArrow / 2 - Math.ceil(borderRadiusOuter * (Math.sqrt(2) - 1));
const dropdownArrowOffset = (contentRadius > 12 ? contentRadius + 2 : 12) - arrowInnerOffset;
const dropdownArrowOffsetVertical = limitVerticalRadius
? maxVerticalContentRadius - arrowInnerOffset
: dropdownArrowOffset;
return { dropdownArrowOffset, dropdownArrowOffsetVertical };
}
export default function getArrowStyle<Token extends TokenWithCommonCls<AliasToken>>(
token: Token,
options: {
colorBg: string;
showArrowCls?: string;
contentRadius?: number;
limitVerticalRadius?: boolean;
},
): CSSInterpolation {
const {
componentCls,
sizePopupArrow,
marginXXS,
borderRadiusXS,
borderRadiusOuter,
boxShadowPopoverArrow,
} = token;
const {
colorBg,
showArrowCls,
contentRadius = token.borderRadiusLG,
limitVerticalRadius,
} = options;
const { dropdownArrowOffsetVertical, dropdownArrowOffset } = getArrowOffset({
sizePopupArrow,
contentRadius,
borderRadiusOuter,
limitVerticalRadius,
});
const dropdownArrowDistance = sizePopupArrow / 2 + marginXXS;
return {
[componentCls]: {
// ============================ Basic ============================
[`${componentCls}-arrow`]: [
{
position: 'absolute',
zIndex: 1, // lift it up so the menu wouldn't cask shadow on it
display: 'block',
...roundedArrow(
sizePopupArrow,
borderRadiusXS,
borderRadiusOuter,
colorBg,
boxShadowPopoverArrow,
),
'&:before': {
background: colorBg,
},
},
],
// ========================== Placement ==========================
// Here handle the arrow position and rotate stuff
// >>>>> Top
[[
`&-placement-top ${componentCls}-arrow`,
`&-placement-topLeft ${componentCls}-arrow`,
`&-placement-topRight ${componentCls}-arrow`,
].join(',')]: {
bottom: 0,
transform: 'translateY(100%) rotate(180deg)',
},
[`&-placement-top ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%',
},
transform: 'translateX(-50%) translateY(100%) rotate(180deg)',
},
[`&-placement-topLeft ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`&-placement-topRight ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
// >>>>> Bottom
[[
`&-placement-bottom ${componentCls}-arrow`,
`&-placement-bottomLeft ${componentCls}-arrow`,
`&-placement-bottomRight ${componentCls}-arrow`,
].join(',')]: {
top: 0,
transform: `translateY(-100%)`,
},
[`&-placement-bottom ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%',
},
transform: `translateX(-50%) translateY(-100%)`,
},
[`&-placement-bottomLeft ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`&-placement-bottomRight ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
// >>>>> Left
[[
`&-placement-left ${componentCls}-arrow`,
`&-placement-leftTop ${componentCls}-arrow`,
`&-placement-leftBottom ${componentCls}-arrow`,
].join(',')]: {
right: {
_skip_check_: true,
value: 0,
},
transform: 'translateX(100%) rotate(90deg)',
},
[`&-placement-left ${componentCls}-arrow`]: {
top: {
_skip_check_: true,
value: '50%',
},
transform: 'translateY(-50%) translateX(100%) rotate(90deg)',
},
[`&-placement-leftTop ${componentCls}-arrow`]: {
top: dropdownArrowOffsetVertical,
},
[`&-placement-leftBottom ${componentCls}-arrow`]: {
bottom: dropdownArrowOffsetVertical,
},
// >>>>> Right
[[
`&-placement-right ${componentCls}-arrow`,
`&-placement-rightTop ${componentCls}-arrow`,
`&-placement-rightBottom ${componentCls}-arrow`,
].join(',')]: {
left: {
_skip_check_: true,
value: 0,
},
transform: 'translateX(-100%) rotate(-90deg)',
},
[`&-placement-right ${componentCls}-arrow`]: {
top: {
_skip_check_: true,
value: '50%',
},
transform: 'translateY(-50%) translateX(-100%) rotate(-90deg)',
},
[`&-placement-rightTop ${componentCls}-arrow`]: {
top: dropdownArrowOffsetVertical,
},
[`&-placement-rightBottom ${componentCls}-arrow`]: {
bottom: dropdownArrowOffsetVertical,
},
// =========================== Offset ============================
// Offset the popover to account for the dropdown arrow
// >>>>> Top
[connectArrowCls(
[`&-placement-topLeft`, `&-placement-top`, `&-placement-topRight`],
showArrowCls,
)]: {
paddingBottom: dropdownArrowDistance,
},
// >>>>> Bottom
[connectArrowCls(
[`&-placement-bottomLeft`, `&-placement-bottom`, `&-placement-bottomRight`],
showArrowCls,
)]: {
paddingTop: dropdownArrowDistance,
},
// >>>>> Left
[connectArrowCls(
[`&-placement-leftTop`, `&-placement-left`, `&-placement-leftBottom`],
showArrowCls,
)]: {
paddingRight: {
_skip_check_: true,
value: dropdownArrowDistance,
},
},
// >>>>> Right
[connectArrowCls(
[`&-placement-rightTop`, `&-placement-right`, `&-placement-rightBottom`],
showArrowCls,
)]: {
paddingLeft: {
_skip_check_: true,
value: dropdownArrowDistance,
},
},
},
};
}

View File

@ -0,0 +1,35 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject } from '../_util/cssinjs';
import type { AliasToken, PresetColorKey } from '../theme/internal';
import { PresetColors } from '../theme/internal';
import type { TokenWithCommonCls } from '../theme/util/genComponentStyleHook';
interface CalcColor {
/** token[`${colorKey}-1`] */
lightColor: string;
/** token[`${colorKey}-3`] */
lightBorderColor: string;
/** token[`${colorKey}-6`] */
darkColor: string;
/** token[`${colorKey}-7`] */
textColor: string;
}
type GenCSS = (colorKey: PresetColorKey, calcColor: CalcColor) => CSSObject;
export function genPresetColor<Token extends TokenWithCommonCls<AliasToken>>(
token: Token,
genCss: GenCSS,
): CSSObject {
return PresetColors.reduce((prev: CSSObject, colorKey: PresetColorKey) => {
const lightColor = token[`${colorKey}-1`];
const lightBorderColor = token[`${colorKey}-3`];
const darkColor = token[`${colorKey}-6`];
const textColor = token[`${colorKey}-7`];
return {
...prev,
...genCss(colorKey, { lightColor, lightBorderColor, darkColor, textColor }),
};
}, {} as CSSObject);
}

253
components/_style/reset.css Normal file
View File

@ -0,0 +1,253 @@
/* stylelint-disable */
html,
body {
width: 100%;
height: 100%;
}
input::-ms-clear,
input::-ms-reveal {
display: none;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
@-ms-viewport {
width: device-width;
}
body {
margin: 0;
}
[tabindex='-1']:focus {
outline: none;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: 0.5em;
font-weight: 500;
}
p {
margin-top: 0;
margin-bottom: 1em;
}
abbr[title],
abbr[data-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline;
text-decoration: underline dotted;
border-bottom: 0;
cursor: help;
}
address {
margin-bottom: 1em;
font-style: normal;
line-height: inherit;
}
input[type='text'],
input[type='password'],
input[type='number'],
textarea {
-webkit-appearance: none;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1em;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 500;
}
dd {
margin-bottom: 0.5em;
margin-left: 0;
}
blockquote {
margin: 0 0 1em;
}
dfn {
font-style: italic;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
pre,
code,
kbd,
samp {
font-size: 1em;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
}
pre {
margin-top: 0;
margin-bottom: 1em;
overflow: auto;
}
figure {
margin: 0 0 1em;
}
img {
vertical-align: middle;
border-style: none;
}
a,
area,
button,
[role='button'],
input:not([type='range']),
label,
select,
summary,
textarea {
touch-action: manipulation;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75em;
padding-bottom: 0.3em;
text-align: left;
caption-side: bottom;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
color: inherit;
font-size: inherit;
font-family: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html [type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type='radio'],
input[type='checkbox'] {
box-sizing: border-box;
padding: 0;
}
input[type='date'],
input[type='time'],
input[type='datetime-local'],
input[type='month'] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
margin-bottom: 0.5em;
padding: 0;
color: inherit;
font-size: 1.5em;
line-height: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
[type='search'] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type='search']::-webkit-search-cancel-button,
[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
mark {
padding: 0.2em;
background-color: #feffe6;
}

View File

@ -0,0 +1,62 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject } from '../_util/cssinjs';
export const roundedArrow = (
width: number,
innerRadius: number,
outerRadius: number,
bgColor: string,
boxShadow: string,
): CSSObject => {
const unitWidth = width / 2;
const ax = unitWidth - outerRadius * (Math.sqrt(2) - 1);
const ay = unitWidth;
const bx = unitWidth + outerRadius * (1 - 1 / Math.sqrt(2));
const by = unitWidth - outerRadius * (1 - 1 / Math.sqrt(2));
const cx = 2 * unitWidth - innerRadius * (1 / Math.sqrt(2));
const cy = innerRadius * (1 / Math.sqrt(2));
const dx = 4 * unitWidth - cx;
const dy = cy;
const ex = 4 * unitWidth - bx;
const ey = by;
const fx = 4 * unitWidth - ax;
const fy = ay;
return {
borderRadius: { _skip_check_: true, value: `0 0 ${innerRadius}px` },
pointerEvents: 'none',
width: width * 2,
height: width * 2,
overflow: 'hidden',
'&::after': {
content: '""',
position: 'absolute',
width: width / Math.sqrt(2),
height: width / Math.sqrt(2),
bottom: 0,
insetInline: 0,
margin: 'auto',
borderRadius: {
_skip_check_: true,
value: `0 0 ${innerRadius}px 0`,
},
transform: 'translateY(50%) rotate(-135deg)',
boxShadow,
zIndex: 0,
background: 'transparent',
},
'&::before': {
position: 'absolute',
bottom: 0,
insetInlineStart: 0,
width: width * 2,
height: width / 2,
background: bgColor,
clipPath: `path('M ${ax} ${ay} A ${outerRadius} ${outerRadius} 0 0 0 ${bx} ${by} L ${cx} ${cy} A ${innerRadius} ${innerRadius} 0 0 1 ${dx} ${dy} L ${ex} ${ey} A ${outerRadius} ${outerRadius} 0 0 0 ${fx} ${fy} Z')`,
content: '""',
},
};
};

View File

@ -0,0 +1,25 @@
export type KeyType = string | number;
type ValueType = [number, any]; // [times, realValue]
class Entity {
/** @private Internal cache map. Do not access this directly */
cache = new Map<string, ValueType>();
get(keys: KeyType[]): ValueType | null {
return this.cache.get(keys.join('%')) || null;
}
update(keys: KeyType[], valueFn: (origin: ValueType | null) => ValueType | null) {
const path = keys.join('%');
const prevValue = this.cache.get(path)!;
const nextValue = valueFn(prevValue);
if (nextValue === null) {
this.cache.delete(path);
} else {
this.cache.set(path, nextValue);
}
}
}
export default Entity;

View File

@ -0,0 +1,19 @@
import type { CSSInterpolation } from './hooks/useStyleRegister';
class Keyframe {
private name: string;
style: CSSInterpolation;
constructor(name: string, style: CSSInterpolation) {
this.name = name;
this.style = style;
}
getName(hashId = ''): string {
return hashId ? `${hashId}-${this.name}` : this.name;
}
_keyframe = true;
}
export default Keyframe;

View File

@ -0,0 +1,112 @@
import type { InjectionKey, Ref } from 'vue';
import { unref, computed, inject } from 'vue';
import CacheEntity from './Cache';
import type { Linter } from './linters/interface';
import type { Transformer } from './transformers/interface';
export const ATTR_TOKEN = 'data-token-hash';
export const ATTR_MARK = 'data-css-hash';
export const ATTR_DEV_CACHE_PATH = 'data-dev-cache-path';
// Mark css-in-js instance in style element
export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__';
export const CSS_IN_JS_INSTANCE_ID = Math.random().toString(12).slice(2);
export function createCache() {
if (typeof document !== 'undefined' && document.head && document.body) {
const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || [];
const { firstChild } = document.head;
Array.from(styles).forEach(style => {
(style as any)[CSS_IN_JS_INSTANCE] =
(style as any)[CSS_IN_JS_INSTANCE] || CSS_IN_JS_INSTANCE_ID;
// Not force move if no head
document.head.insertBefore(style, firstChild);
});
// Deduplicate of moved styles
const styleHash: Record<string, boolean> = {};
Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(style => {
const hash = style.getAttribute(ATTR_MARK)!;
if (styleHash[hash]) {
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
style.parentNode?.removeChild(style);
}
} else {
styleHash[hash] = true;
}
});
}
return new CacheEntity();
}
export type HashPriority = 'low' | 'high';
export interface StyleContextProps {
autoClear?: boolean;
/** @private Test only. Not work in production. */
mock?: 'server' | 'client';
/**
* Only set when you need ssr to extract style on you own.
* If not provided, it will auto create <style /> on the end of Provider in server side.
*/
cache: CacheEntity;
/** Tell children that this context is default generated context */
defaultCache: boolean;
/** Use `:where` selector to reduce hashId css selector priority */
hashPriority?: HashPriority;
/** Tell cssinjs where to inject style in */
container?: Element | ShadowRoot;
/** Component wil render inline `<style />` for fallback in SSR. Not recommend. */
ssrInline?: boolean;
/** Transform css before inject in document. Please note that `transformers` do not support dynamic update */
transformers?: Transformer[];
/**
* Linters to lint css before inject in document.
* Styles will be linted after transforming.
* Please note that `linters` do not support dynamic update.
*/
linters?: Linter[];
}
const StyleContextKey: InjectionKey<StyleContextProps> = Symbol('StyleContextKey');
export type StyleProviderProps = Partial<StyleContextProps> | Ref<Partial<StyleContextProps>>;
export const useStyleInject = () => {
return inject(StyleContextKey, {
hashPriority: 'low',
cache: createCache(),
defaultCache: true,
});
};
export const useStyleProvider = (props: StyleContextProps) => {
const parentContext = useStyleInject();
const context = computed<StyleContextProps>(() => {
const mergedContext: StyleContextProps = {
...parentContext,
};
const propsValue = unref(props);
(Object.keys(propsValue) as (keyof StyleContextProps)[]).forEach(key => {
const value = propsValue[key];
if (propsValue[key] !== undefined) {
(mergedContext as any)[key] = value;
}
});
const { cache } = propsValue;
mergedContext.cache = mergedContext.cache || createCache();
mergedContext.defaultCache = !cache && parentContext.defaultCache;
return mergedContext;
});
return context;
};
export default {
useStyleInject,
useStyleProvider,
};

View File

@ -0,0 +1,128 @@
import hash from '@emotion/hash';
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, CSS_IN_JS_INSTANCE_ID } from '../StyleContext';
import type Theme from '../theme/Theme';
import useGlobalCache from './useGlobalCache';
import { flattenToken, token2key } from '../util';
import type { Ref } from 'vue';
import { ref, computed } from 'vue';
const EMPTY_OVERRIDE = {};
// 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 = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
export interface Option<DerivativeToken> {
/**
* Generate token with salt.
* This is used to generate different hashId even same derivative token for different version.
*/
salt?: string;
override?: object;
/**
* Format token as you need. Such as:
*
* - rename token
* - merge token
* - delete token
*
* This should always be the same since it's one time process.
* It's ok to useMemo outside but this has better cache strategy.
*/
formatToken?: (mergedToken: any) => DerivativeToken;
}
const tokenKeys = new Map<string, number>();
function recordCleanToken(tokenKey: string) {
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
}
function removeStyleTags(key: string) {
if (typeof document !== 'undefined') {
const styles = document.querySelectorAll(`style[${ATTR_TOKEN}="${key}"]`);
styles.forEach(style => {
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
style.parentNode?.removeChild(style);
}
});
}
}
// Remove will check current keys first
function cleanTokenStyle(tokenKey: string) {
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
const tokenKeyList = Array.from(tokenKeys.keys());
const cleanableKeyList = tokenKeyList.filter(key => {
const count = tokenKeys.get(key) || 0;
return count <= 0;
});
if (cleanableKeyList.length < tokenKeyList.length) {
cleanableKeyList.forEach(key => {
removeStyleTags(key);
tokenKeys.delete(key);
});
}
}
/**
* Cache theme derivative token as global shared one
* @param theme Theme entity
* @param tokens List of tokens, used for cache. Please do not dynamic generate object directly
* @param option Additional config
* @returns Call Theme.getDerivativeToken(tokenObject) to get token
*/
export default function useCacheToken<DerivativeToken = object, DesignToken = DerivativeToken>(
theme: Ref<Theme<any, any>>,
tokens: Ref<Partial<DesignToken>[]>,
option: Ref<Option<DerivativeToken>> = ref({}),
) {
// 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 cachedToken = useGlobalCache<[DerivativeToken & { _tokenKey: string }, string]>(
'token',
computed(() => [
option.value.salt || '',
theme.value.id,
tokenStr.value,
overrideTokenStr.value,
]),
() => {
const { salt = '', override = EMPTY_OVERRIDE, formatToken } = option.value;
const derivativeToken = theme.value.getDerivativeToken(mergedToken.value);
// Merge with override
let mergedDerivativeToken = {
...derivativeToken,
...override,
};
// Format if needed
if (formatToken) {
mergedDerivativeToken = formatToken(mergedDerivativeToken);
}
// Optimize for `useStyleRegister` performance
const tokenKey = token2key(mergedDerivativeToken, salt);
mergedDerivativeToken._tokenKey = tokenKey;
recordCleanToken(tokenKey);
const hashId = `${hashPrefix}-${hash(tokenKey)}`;
mergedDerivativeToken._hashId = hashId; // Not used
return [mergedDerivativeToken, hashId];
},
cache => {
// Remove token will remove all related style
cleanTokenStyle(cache[0]._tokenKey);
},
);
return cachedToken;
}

View File

@ -0,0 +1,59 @@
import { useStyleInject } from '../StyleContext';
import type { KeyType } from '../Cache';
import useHMR from './useHMR';
import type { ComputedRef, Ref } from 'vue';
import { onBeforeUnmount, computed, watch } from 'vue';
import eagerComputed from '../../eagerComputed';
export default function useClientCache<CacheType>(
prefix: string,
keyPath: Ref<KeyType[]>,
cacheFn: () => CacheType,
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
): ComputedRef<CacheType> {
const styleContext = useStyleInject();
const fullPath = computed(() => [prefix, ...keyPath.value]);
const fullPathStr = eagerComputed(() => fullPath.value.join('_'));
const HMRUpdate = useHMR();
const clearCache = () => {
styleContext.cache.update(fullPath.value, prevCache => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
if (nextCount === 0) {
onCacheRemove?.(cache, false);
return null;
}
return [times - 1, cache];
});
};
// Create cache
watch(
fullPathStr,
() => {
clearCache();
styleContext.cache.update(fullPath.value, prevCache => {
const [times = 0, cache] = prevCache || [];
// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}
const mergedCache = tmpCache || cacheFn();
return [times + 1, mergedCache];
});
},
{ immediate: true },
);
onBeforeUnmount(() => {
clearCache();
});
const val = computed(() => styleContext.cache.get(fullPath.value)![1]);
return val;
}

View File

@ -0,0 +1,45 @@
function useProdHMR() {
return false;
}
let webpackHMR = false;
let viteHMR = false;
function useDevHMR() {
return webpackHMR || viteHMR;
}
export default process.env.NODE_ENV === 'production' ? useProdHMR : useDevHMR;
// Webpack `module.hot.accept` do not support any deps update trigger
// We have to hack handler to force mark as HRM
if (
process.env.NODE_ENV !== 'production' &&
typeof module !== 'undefined' &&
module &&
// @ts-ignore
module.hot
) {
const win = window as any;
if (typeof win.webpackHotUpdate === 'function') {
const originWebpackHotUpdate = win.webpackHotUpdate;
win.webpackHotUpdate = (...args: any[]) => {
webpackHMR = true;
setTimeout(() => {
webpackHMR = false;
}, 0);
return originWebpackHotUpdate(...args);
};
// @ts-ignore
} else if (import.meta.hot) {
// @ts-ignore
import.meta.hot.accept(() => {
viteHMR = true;
setTimeout(() => {
viteHMR = false;
}, 0);
});
}
}

View File

@ -0,0 +1,416 @@
import hash from '@emotion/hash';
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 {
useStyleInject,
ATTR_DEV_CACHE_PATH,
ATTR_MARK,
ATTR_TOKEN,
CSS_IN_JS_INSTANCE,
CSS_IN_JS_INSTANCE_ID,
} from '../StyleContext';
import { supportLayer } from '../util';
import useGlobalCache from './useGlobalCache';
import canUseDom from '../../canUseDom';
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import { computed } from 'vue';
import type { VueNode } from '../../type';
const isClientSide = canUseDom();
const SKIP_CHECK = '_skip_check_';
export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & {
animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes;
};
export type CSSPropertiesWithMultiValues = {
[K in keyof CSSProperties]:
| CSSProperties[K]
| Extract<CSSProperties[K], string>[]
| {
[SKIP_CHECK]: boolean;
value: CSSProperties[K] | Extract<CSSProperties[K], string>[];
};
};
export type CSSPseudos = { [K in CSS.Pseudos]?: CSSObject };
type ArrayCSSInterpolation = CSSInterpolation[];
export type InterpolationPrimitive = null | undefined | boolean | number | string | CSSObject;
export type CSSInterpolation = InterpolationPrimitive | ArrayCSSInterpolation | Keyframes;
export type CSSOthersObject = Record<string, CSSInterpolation>;
export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSSOthersObject {}
// ============================================================================
// == Parser ==
// ============================================================================
// Preprocessor style content to browser support one
export function normalizeStyle(styleStr: string) {
const serialized = serialize(compile(styleStr), stringify);
return serialized.replace(/\{%%%\:[^;];}/g, ';');
}
function isCompoundCSSProperty(value: CSSObject[string]) {
return typeof value === 'object' && value && SKIP_CHECK in value;
}
// hash
function injectSelectorHash(key: string, hashId: string, hashPriority?: HashPriority) {
if (!hashId) {
return key;
}
const hashClassName = `.${hashId}`;
const hashSelector = hashPriority === 'low' ? `:where(${hashClassName})` : hashClassName;
// hashId
const keys = key.split(',').map(k => {
const fullPath = k.trim().split(/\s+/);
// Selector HTML Element
let firstPath = fullPath[0] || '';
const htmlElement = firstPath.match(/^\w+/)?.[0] || '';
firstPath = `${htmlElement}${hashSelector}${firstPath.slice(htmlElement.length)}`;
return [firstPath, ...fullPath.slice(1)].join(' ');
});
return keys.join(',');
}
export interface ParseConfig {
hashId?: string;
hashPriority?: HashPriority;
layer?: string;
path?: string;
transformers?: Transformer[];
linters?: Linter[];
}
export interface ParseInfo {
root?: boolean;
injectHash?: boolean;
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,
config: ParseConfig = {},
{ root, injectHash, parentSelectors }: ParseInfo = {
root: true,
parentSelectors: [],
},
): [
parsedStr: string,
// Style content which should be unique on all of the style (e.g. Keyframes).
// Firefox will flick with same animation name when exist multiple same keyframes.
effectStyle: Record<string, string>,
] => {
const { hashId, layer, path, hashPriority, transformers = [], linters = [] } = config;
let styleStr = '';
let effectStyle: Record<string, string> = {};
function parseKeyframes(keyframes: Keyframes) {
const animationName = keyframes.getName(hashId);
if (!effectStyle[animationName]) {
const [parsedStr] = parseStyle(keyframes.style, config, {
root: false,
parentSelectors,
});
effectStyle[animationName] = `@keyframes ${keyframes.getName(hashId)}${parsedStr}`;
}
}
function flattenList(list: ArrayCSSInterpolation, fullList: CSSObject[] = []) {
list.forEach(item => {
if (Array.isArray(item)) {
flattenList(item, fullList);
} else if (item) {
fullList.push(item as CSSObject);
}
});
return fullList;
}
const flattenStyleList = flattenList(
Array.isArray(interpolation) ? interpolation : [interpolation],
);
flattenStyleList.forEach(originStyle => {
// Only root level can use raw string
const style: CSSObject = typeof originStyle === 'string' && !root ? {} : originStyle;
if (typeof style === 'string') {
styleStr += `${style}\n`;
} else if ((style as any)._keyframe) {
// Keyframe
parseKeyframes(style as unknown as Keyframes);
} else {
const mergedStyle = transformers.reduce((prev, trans) => trans?.visit?.(prev) || prev, style);
// Normal CSSObject
Object.keys(mergedStyle).forEach(key => {
const value = mergedStyle[key];
if (
typeof value === 'object' &&
value &&
(key !== 'animationName' || !(value as Keyframes)._keyframe) &&
!isCompoundCSSProperty(value)
) {
let subInjectHash = false;
//
let mergedKey = key.trim();
// Whether treat child as root. In most case it is false.
let nextRoot = false;
//
if ((root || injectHash) && hashId) {
if (mergedKey.startsWith('@')) {
// hashId
subInjectHash = true;
} else {
// hashId
mergedKey = injectSelectorHash(key, hashId, hashPriority);
}
} else if (root && !hashId && (mergedKey === '&' || mergedKey === '')) {
// In case of `{ '&': { a: { color: 'red' } } }` or `{ '': { a: { color: 'red' } } }` without hashId,
// we will get `&{a:{color:red;}}` or `{a:{color:red;}}` string for stylis to compile.
// But it does not conform to stylis syntax,
// and finally we will get `{color:red;}` as css, which is wrong.
// So we need to remove key in root, and treat child `{ a: { color: 'red' } }` as root.
mergedKey = '';
nextRoot = true;
}
const [parsedStr, childEffectStyle] = parseStyle(value as any, config, {
root: nextRoot,
injectHash: subInjectHash,
parentSelectors: [...parentSelectors, mergedKey],
});
effectStyle = {
...effectStyle,
...childEffectStyle,
};
styleStr += `${mergedKey}${parsedStr}`;
} else {
const actualValue = (value as any)?.value ?? value;
if (
process.env.NODE_ENV !== 'production' &&
(typeof value !== 'object' || !(value as any)?.[SKIP_CHECK])
) {
[contentQuotesLinter, hashedAnimationLinter, ...linters].forEach(linter =>
linter(key, actualValue, { path, hashId, parentSelectors }),
);
}
//
const styleName = key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
// Auto suffix with px
let formatValue = actualValue;
if (!unitless[key] && typeof formatValue === 'number' && formatValue !== 0) {
formatValue = `${formatValue}px`;
}
// handle animationName & Keyframe value
if (key === 'animationName' && (value as Keyframes)?._keyframe) {
parseKeyframes(value as Keyframes);
formatValue = (value as Keyframes).getName(hashId);
}
styleStr += `${styleName}:${formatValue};`;
}
});
}
});
if (!root) {
styleStr = `{${styleStr}}`;
} else if (layer && supportLayer()) {
const layerCells = layer.split(',');
const layerName = layerCells[layerCells.length - 1].trim();
styleStr = `@layer ${layerName} {${styleStr}}`;
// Order of layer if needed
if (layerCells.length > 1) {
// zombieJ: stylis do not support layer order, so we need to handle it manually.
styleStr = `@layer ${layer}{%%%:%}${styleStr}`;
}
}
return [styleStr, effectStyle];
};
// ============================================================================
// == Register ==
// ============================================================================
function uniqueHash(path: (string | number)[], styleStr: string) {
return hash(`${path.join('%')}${styleStr}`);
}
function Empty() {
return null;
}
/**
* Register a style to the global style sheet.
*/
export default function useStyleRegister(
info: {
theme: Theme<any, any>;
token: any;
path: string[];
hashId?: string;
layer?: string;
},
styleFn: () => CSSInterpolation,
) {
const styleContext = useStyleInject();
const tokenKey = computed(() => info.token._tokenKey as string);
const fullPath = computed(() => [tokenKey.value, ...info.path]);
// Check if need insert style
let isMergedClientSide = isClientSide;
if (process.env.NODE_ENV !== 'production' && styleContext.mock !== undefined) {
isMergedClientSide = styleContext.mock === 'client';
}
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
const cacheStyle = useGlobalCache(
'style',
fullPath,
// Create cache if needed
() => {
const styleObj = styleFn();
const { hashPriority, container, transformers, linters } = styleContext;
const { path, hashId, layer } = info;
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
hashId,
hashPriority,
layer,
path: path.join('-'),
transformers,
linters,
});
const styleStr = normalizeStyle(parsedStyle);
const styleId = uniqueHash(fullPath.value, styleStr);
if (isMergedClientSide) {
const style = updateCSS(styleStr, styleId, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
});
(style as any)[CSS_IN_JS_INSTANCE] = CSS_IN_JS_INSTANCE_ID;
// 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
if (process.env.NODE_ENV !== 'production') {
style.setAttribute(ATTR_DEV_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,
});
}
});
}
return [styleStr, tokenKey.value, styleId];
},
// Remove cache if no need
([, , styleId], fromHMR) => {
if ((fromHMR || styleContext.autoClear) && isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
);
return (node: VueNode) => {
let styleNode: VueNode;
if (!styleContext.ssrInline || isMergedClientSide || !styleContext.defaultCache) {
styleNode = <Empty />;
} else {
styleNode = (
<style
{...{
[ATTR_TOKEN]: cacheStyle[1],
[ATTR_MARK]: cacheStyle[2],
}}
innerHTML={cacheStyle[0]}
/>
);
}
return (
<>
{styleNode}
{node}
</>
);
};
}
// ============================================================================
// == SSR ==
// ============================================================================
export function extractStyle(cache: Cache) {
// prefix with `style` is used for `useStyleRegister` to cache style context
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith('style%'));
// const tokenStyles: Record<string, string[]> = {};
let styleText = '';
styleKeys.forEach(key => {
const [styleStr, tokenKey, styleId]: [string, string, string] = cache.cache.get(key)![1];
styleText += `<style ${ATTR_TOKEN}="${tokenKey}" ${ATTR_MARK}="${styleId}">${styleStr}</style>`;
});
return styleText;
}

View File

@ -0,0 +1,31 @@
import useCacheToken from './hooks/useCacheToken';
import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister';
import useStyleRegister, { extractStyle } from './hooks/useStyleRegister';
import Keyframes from './Keyframes';
import type { Linter } from './linters';
import { legacyNotSelectorLinter, logicalPropertiesLinter } from './linters';
import { createCache, useStyleInject, useStyleProvider } from './StyleContext';
import type { DerivativeFunc, TokenType } from './theme';
import { createTheme, Theme } from './theme';
import type { Transformer } from './transformers/interface';
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
export {
Theme,
createTheme,
useStyleRegister,
useCacheToken,
createCache,
useStyleInject,
useStyleProvider,
Keyframes,
extractStyle,
// Transformer
legacyLogicalPropertiesTransformer,
// Linters
logicalPropertiesLinter,
legacyNotSelectorLinter,
};
export type { TokenType, CSSObject, CSSInterpolation, DerivativeFunc, Transformer, Linter };

View File

@ -0,0 +1,25 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
if (key === 'content') {
// From emotion: https://github.com/emotion-js/emotion/blob/main/packages/serialize/src/index.js#L63
const contentValuePattern =
/(attr|counters?|url|(((repeating-)?(linear|radial))|conic)-gradient)\(|(no-)?(open|close)-quote/;
const contentValues = ['normal', 'none', 'initial', 'inherit', 'unset'];
if (
typeof value !== 'string' ||
(contentValues.indexOf(value) === -1 &&
!contentValuePattern.test(value) &&
(value.charAt(0) !== value.charAt(value.length - 1) ||
(value.charAt(0) !== '"' && value.charAt(0) !== "'")))
) {
lintWarning(
`You seem to be using a value for 'content' without quotes, try replacing it with \`content: '"${value}"'\`.`,
info,
);
}
}
};
export default linter;

View File

@ -0,0 +1,15 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
if (key === 'animation') {
if (info.hashId && value !== 'none') {
lintWarning(
`You seem to be using hashed animation '${value}', in which case 'animationName' with Keyframe as value is recommended.`,
info,
);
}
}
};
export default linter;

View File

@ -0,0 +1,5 @@
export { default as contentQuotesLinter } from './contentQuotesLinter';
export { default as hashedAnimationLinter } from './hashedAnimationLinter';
export type { Linter } from './interface';
export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter';
export { default as logicalPropertiesLinter } from './logicalPropertiesLinter';

View File

@ -0,0 +1,9 @@
export interface LinterInfo {
path?: string;
hashId?: string;
parentSelectors: string[];
}
export interface Linter {
(key: string, value: string | number, info: LinterInfo): void;
}

View File

@ -0,0 +1,33 @@
import type { Linter, LinterInfo } from './interface';
import { lintWarning } from './utils';
function isConcatSelector(selector: string) {
const notContent = selector.match(/:not\(([^)]*)\)/)?.[1] || '';
// split selector. e.g.
// `h1#a.b` => ['h1', #a', '.b']
const splitCells = notContent.split(/(\[[^[]*])|(?=[.#])/).filter(str => str);
return splitCells.length > 1;
}
function parsePath(info: LinterInfo) {
return info.parentSelectors.reduce((prev, cur) => {
if (!prev) {
return cur;
}
return cur.includes('&') ? cur.replace(/&/g, prev) : `${prev} ${cur}`;
}, '');
}
const linter: Linter = (_key, _value, info) => {
const parentSelectorPath = parsePath(info);
const notList = parentSelectorPath.match(/:not\([^)]*\)/g) || [];
if (notList.length > 0 && notList.some(isConcatSelector)) {
lintWarning(`Concat ':not' selector not support in legacy browsers.`, info);
}
};
export default linter;

View File

@ -0,0 +1,88 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
switch (key) {
case 'marginLeft':
case 'marginRight':
case 'paddingLeft':
case 'paddingRight':
case 'left':
case 'right':
case 'borderLeft':
case 'borderLeftWidth':
case 'borderLeftStyle':
case 'borderLeftColor':
case 'borderRight':
case 'borderRightWidth':
case 'borderRightStyle':
case 'borderRightColor':
case 'borderTopLeftRadius':
case 'borderTopRightRadius':
case 'borderBottomLeftRadius':
case 'borderBottomRightRadius':
lintWarning(
`You seem to be using non-logical property '${key}' which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
return;
case 'margin':
case 'padding':
case 'borderWidth':
case 'borderStyle':
// case 'borderColor':
if (typeof value === 'string') {
const valueArr = value.split(' ').map(item => item.trim());
if (valueArr.length === 4 && valueArr[1] !== valueArr[3]) {
lintWarning(
`You seem to be using '${key}' property with different left ${key} and right ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
}
}
return;
case 'clear':
case 'textAlign':
if (value === 'left' || value === 'right') {
lintWarning(
`You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
}
return;
case 'borderRadius':
if (typeof value === 'string') {
const radiusGroups = value.split('/').map(item => item.trim());
const invalid = radiusGroups.reduce((result, group) => {
if (result) {
return result;
}
const radiusArr = group.split(' ').map(item => item.trim());
// borderRadius: '2px 4px'
if (radiusArr.length >= 2 && radiusArr[0] !== radiusArr[1]) {
return true;
}
// borderRadius: '4px 4px 2px'
if (radiusArr.length === 3 && radiusArr[1] !== radiusArr[2]) {
return true;
}
// borderRadius: '4px 4px 2px 4px'
if (radiusArr.length === 4 && radiusArr[2] !== radiusArr[3]) {
return true;
}
return result;
}, false);
if (invalid) {
lintWarning(
`You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
}
}
return;
default:
}
};
export default linter;

View File

@ -0,0 +1,13 @@
import devWarning from '../../../vc-util/warning';
import type { LinterInfo } from './interface';
export function lintWarning(message: string, info: LinterInfo) {
const { path, parentSelectors } = info;
devWarning(
false,
`[Ant Design Vue CSS-in-JS] ${path ? `Error in '${path}': ` : ''}${message}${
parentSelectors.length ? ` Selector info: ${parentSelectors.join(' -> ')}` : ''
}`,
);
}

View File

@ -0,0 +1,38 @@
import warning from '../../warning';
import type { DerivativeFunc, TokenType } from './interface';
let uuid = 0;
/**
* Theme with algorithms to derive tokens from design tokens.
* Use `createTheme` first which will help to manage the theme instance cache.
*/
export default class Theme<DesignToken extends TokenType, DerivativeToken extends TokenType> {
private derivatives: DerivativeFunc<DesignToken, DerivativeToken>[];
public readonly id: number;
constructor(
derivatives:
| DerivativeFunc<DesignToken, DerivativeToken>
| DerivativeFunc<DesignToken, DerivativeToken>[],
) {
this.derivatives = Array.isArray(derivatives) ? derivatives : [derivatives];
this.id = uuid;
if (derivatives.length === 0) {
warning(
derivatives.length > 0,
'[Ant Design Vue CSS-in-JS] Theme should have at least one derivative function.',
);
}
uuid += 1;
}
getDerivativeToken(token: DesignToken): DerivativeToken {
return this.derivatives.reduce<DerivativeToken>(
(result, derivative) => derivative(token, result),
undefined as any,
);
}
}

View File

@ -0,0 +1,135 @@
import type Theme from './Theme';
import type { DerivativeFunc } from './interface';
// ================================== Cache ==================================
type ThemeCacheMap = Map<
DerivativeFunc<any, any>,
{
map?: ThemeCacheMap;
value?: [Theme<any, any>, number];
}
>;
type DerivativeOptions = DerivativeFunc<any, any>[];
export function sameDerivativeOption(left: DerivativeOptions, right: DerivativeOptions) {
if (left.length !== right.length) {
return false;
}
for (let i = 0; i < left.length; i++) {
if (left[i] !== right[i]) {
return false;
}
}
return true;
}
export default class ThemeCache {
public static MAX_CACHE_SIZE = 20;
public static MAX_CACHE_OFFSET = 5;
private readonly cache: ThemeCacheMap;
private keys: DerivativeOptions[];
private cacheCallTimes: number;
constructor() {
this.cache = new Map();
this.keys = [];
this.cacheCallTimes = 0;
}
public size(): number {
return this.keys.length;
}
private internalGet(
derivativeOption: DerivativeOptions,
updateCallTimes = false,
): [Theme<any, any>, number] | undefined {
let cache: ReturnType<ThemeCacheMap['get']> = { map: this.cache };
derivativeOption.forEach(derivative => {
if (!cache) {
cache = undefined;
} else {
cache = cache?.map?.get(derivative);
}
});
if (cache?.value && updateCallTimes) {
cache.value[1] = this.cacheCallTimes++;
}
return cache?.value;
}
public get(derivativeOption: DerivativeOptions): Theme<any, any> | undefined {
return this.internalGet(derivativeOption, true)?.[0];
}
public has(derivativeOption: DerivativeOptions): boolean {
return !!this.internalGet(derivativeOption);
}
public set(derivativeOption: DerivativeOptions, value: Theme<any, any>): void {
// New cache
if (!this.has(derivativeOption)) {
if (this.size() + 1 > ThemeCache.MAX_CACHE_SIZE + ThemeCache.MAX_CACHE_OFFSET) {
const [targetKey] = this.keys.reduce<[DerivativeOptions, number]>(
(result, key) => {
const [, callTimes] = result;
if (this.internalGet(key)![1] < callTimes) {
return [key, this.internalGet(key)![1]];
}
return result;
},
[this.keys[0], this.cacheCallTimes],
);
this.delete(targetKey);
}
this.keys.push(derivativeOption);
}
let cache = this.cache;
derivativeOption.forEach((derivative, index) => {
if (index === derivativeOption.length - 1) {
cache.set(derivative, { value: [value, this.cacheCallTimes++] });
} else {
const cacheValue = cache.get(derivative);
if (!cacheValue) {
cache.set(derivative, { map: new Map() });
} else if (!cacheValue.map) {
cacheValue.map = new Map();
}
cache = cache.get(derivative)!.map!;
}
});
}
private deleteByPath(
currentCache: ThemeCacheMap,
derivatives: DerivativeFunc<any, any>[],
): Theme<any, any> | undefined {
const cache = currentCache.get(derivatives[0])!;
if (derivatives.length === 1) {
if (!cache.map) {
currentCache.delete(derivatives[0]);
} else {
currentCache.set(derivatives[0], { map: cache.map });
}
return cache.value?.[0];
}
const result = this.deleteByPath(cache.map!, derivatives.slice(1));
if ((!cache.map || cache.map.size === 0) && !cache.value) {
currentCache.delete(derivatives[0]);
}
return result;
}
public delete(derivativeOption: DerivativeOptions): Theme<any, any> | undefined {
// If cache exists
if (this.has(derivativeOption)) {
this.keys = this.keys.filter(item => !sameDerivativeOption(item, derivativeOption));
return this.deleteByPath(this.cache, derivativeOption);
}
return undefined;
}
}

View File

@ -0,0 +1,26 @@
import ThemeCache from './ThemeCache';
import Theme from './Theme';
import type { DerivativeFunc, TokenType } from './interface';
const cacheThemes = new ThemeCache();
/**
* Same as new Theme, but will always return same one if `derivative` not changed.
*/
export default function createTheme<
DesignToken extends TokenType,
DerivativeToken extends TokenType,
>(
derivatives:
| DerivativeFunc<DesignToken, DerivativeToken>[]
| DerivativeFunc<DesignToken, DerivativeToken>,
) {
const derivativeArr = Array.isArray(derivatives) ? derivatives : [derivatives];
// Create new theme if not exist
if (!cacheThemes.has(derivativeArr)) {
cacheThemes.set(derivativeArr, new Theme(derivativeArr));
}
// Get theme from cache and return
return cacheThemes.get(derivativeArr)!;
}

View File

@ -0,0 +1,4 @@
export { default as createTheme } from './createTheme';
export { default as Theme } from './Theme';
export { default as ThemeCache } from './ThemeCache';
export type { TokenType, DerivativeFunc } from './interface';

View File

@ -0,0 +1,5 @@
export type TokenType = object;
export type DerivativeFunc<DesignToken extends TokenType, DerivativeToken extends TokenType> = (
designToken: DesignToken,
derivativeToken?: DerivativeToken,
) => DerivativeToken;

View File

@ -0,0 +1,5 @@
import type { CSSObject } from '..';
export interface Transformer {
visit?: (cssObj: CSSObject) => CSSObject;
}

View File

@ -0,0 +1,162 @@
import type { CSSObject } from '..';
import type { Transformer } from './interface';
function splitValues(value: string | number) {
if (typeof value === 'number') {
return [value];
}
const splitStyle = String(value).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 = '';
}
} else if (brackets > 0) {
temp += item;
} else {
list.push(item);
}
return list;
}, []);
}
type MatchValue = string[] & {
notSplit?: boolean;
};
function noSplit(list: MatchValue): MatchValue {
list.notSplit = true;
return list;
}
const keyMap: Record<string, MatchValue> = {
// Inset
inset: ['top', 'right', 'bottom', 'left'],
insetBlock: ['top', 'bottom'],
insetBlockStart: ['top'],
insetBlockEnd: ['bottom'],
insetInline: ['left', 'right'],
insetInlineStart: ['left'],
insetInlineEnd: ['right'],
// Margin
marginBlock: ['marginTop', 'marginBottom'],
marginBlockStart: ['marginTop'],
marginBlockEnd: ['marginBottom'],
marginInline: ['marginLeft', 'marginRight'],
marginInlineStart: ['marginLeft'],
marginInlineEnd: ['marginRight'],
// Padding
paddingBlock: ['paddingTop', 'paddingBottom'],
paddingBlockStart: ['paddingTop'],
paddingBlockEnd: ['paddingBottom'],
paddingInline: ['paddingLeft', 'paddingRight'],
paddingInlineStart: ['paddingLeft'],
paddingInlineEnd: ['paddingRight'],
// Border
borderBlock: noSplit(['borderTop', 'borderBottom']),
borderBlockStart: noSplit(['borderTop']),
borderBlockEnd: noSplit(['borderBottom']),
borderInline: noSplit(['borderLeft', 'borderRight']),
borderInlineStart: noSplit(['borderLeft']),
borderInlineEnd: noSplit(['borderRight']),
// Border width
borderBlockWidth: ['borderTopWidth', 'borderBottomWidth'],
borderBlockStartWidth: ['borderTopWidth'],
borderBlockEndWidth: ['borderBottomWidth'],
borderInlineWidth: ['borderLeftWidth', 'borderRightWidth'],
borderInlineStartWidth: ['borderLeftWidth'],
borderInlineEndWidth: ['borderRightWidth'],
// Border style
borderBlockStyle: ['borderTopStyle', 'borderBottomStyle'],
borderBlockStartStyle: ['borderTopStyle'],
borderBlockEndStyle: ['borderBottomStyle'],
borderInlineStyle: ['borderLeftStyle', 'borderRightStyle'],
borderInlineStartStyle: ['borderLeftStyle'],
borderInlineEndStyle: ['borderRightStyle'],
// Border color
borderBlockColor: ['borderTopColor', 'borderBottomColor'],
borderBlockStartColor: ['borderTopColor'],
borderBlockEndColor: ['borderBottomColor'],
borderInlineColor: ['borderLeftColor', 'borderRightColor'],
borderInlineStartColor: ['borderLeftColor'],
borderInlineEndColor: ['borderRightColor'],
// Border radius
borderStartStartRadius: ['borderTopLeftRadius'],
borderStartEndRadius: ['borderTopRightRadius'],
borderEndStartRadius: ['borderBottomLeftRadius'],
borderEndEndRadius: ['borderBottomRightRadius'],
};
function skipCheck(value: string | number) {
return { _skip_check_: true, value };
}
/**
* Convert css logical properties to legacy properties.
* Such as: `margin-block-start` to `margin-top`.
* Transform list:
* - inset
* - margin
* - padding
* - border
*/
const transform: Transformer = {
visit: cssObj => {
const clone: CSSObject = {};
Object.keys(cssObj).forEach(key => {
const value = cssObj[key];
const matchValue = keyMap[key];
if (matchValue && (typeof value === 'number' || typeof value === 'string')) {
const values = splitValues(value);
if (matchValue.length && matchValue.notSplit) {
// not split means always give same value like border
matchValue.forEach(matchKey => {
clone[matchKey] = skipCheck(value);
});
} else if (matchValue.length === 1) {
// Handle like `marginBlockStart` => `marginTop`
clone[matchValue[0]] = skipCheck(value);
} else if (matchValue.length === 2) {
// Handle like `marginBlock` => `marginTop` & `marginBottom`
matchValue.forEach((matchKey, index) => {
clone[matchKey] = skipCheck(values[index] ?? values[0]);
});
} 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]);
});
} else {
clone[key] = value;
}
} else {
clone[key] = value;
}
});
return clone;
},
};
export default transform;

View File

@ -0,0 +1,68 @@
import hash from '@emotion/hash';
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
import canUseDom from '../canUseDom';
export function flattenToken(token: any) {
let str = '';
Object.keys(token).forEach(key => {
const value = token[key];
str += key;
if (value && typeof value === 'object') {
str += flattenToken(value);
} else {
str += value;
}
});
return str;
}
/**
* Convert derivative token to key string
*/
export function token2key(token: any, salt: string): string {
return hash(`${salt}_${flattenToken(token)}`);
}
const layerKey = `layer-${Date.now()}-${Math.random()}`.replace(/\./g, '');
const layerWidth = '903px';
function supportSelector(styleStr: string, handleElement?: (ele: HTMLElement) => void): boolean {
if (canUseDom()) {
updateCSS(styleStr, layerKey);
const ele = document.createElement('div');
ele.style.position = 'fixed';
ele.style.left = '0';
ele.style.top = '0';
handleElement?.(ele);
document.body.appendChild(ele);
if (process.env.NODE_ENV !== 'production') {
ele.innerHTML = 'Test';
ele.style.zIndex = '9999999';
}
const support = getComputedStyle(ele).width === layerWidth;
ele.parentNode?.removeChild(ele);
removeCSS(layerKey);
return support;
}
return false;
}
let canLayer: boolean | undefined = undefined;
export function supportLayer(): boolean {
if (canLayer === undefined) {
canLayer = supportSelector(
`@layer ${layerKey} { .${layerKey} { width: ${layerWidth}!important; } }`,
ele => {
ele.className = layerKey;
},
);
}
return canLayer!;
}

View File

@ -31,6 +31,7 @@ export default (
getPrefixCls: ConfigProviderProps['getPrefixCls'];
autocomplete: ComputedRef<string>;
csp: ComputedRef<CSPConfig>;
iconPrefixCls: ComputedRef<string>;
} => {
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
'configProvider',
@ -38,6 +39,7 @@ export default (
);
const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls));
const direction = computed(() => props.direction ?? configProvider.direction);
const iconPrefixCls = computed(() => props.iconPrefixCls ?? configProvider.iconPrefixCls);
const rootPrefixCls = computed(() => configProvider.getPrefixCls());
const autoInsertSpaceInButton = computed(() => configProvider.autoInsertSpaceInButton);
const renderEmpty = computed(() => configProvider.renderEmpty);
@ -80,5 +82,6 @@ export default (
getPrefixCls: configProvider.getPrefixCls,
autocomplete,
csp,
iconPrefixCls,
};
};

View File

@ -0,0 +1,240 @@
import type { CSSInterpolation, CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../_style';
export interface ComponentToken {}
type AlertToken = FullToken<'Alert'> & {
alertIconSizeLG: number;
alertPaddingHorizontal: number;
};
const genAlertTypeStyle = (
bgColor: string,
borderColor: string,
iconColor: string,
token: AlertToken,
alertCls: string,
): CSSObject => ({
backgroundColor: bgColor,
border: `${token.lineWidth}px ${token.lineType} ${borderColor}`,
[`${alertCls}-icon`]: {
color: iconColor,
},
});
export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
const {
componentCls,
motionDurationSlow: duration,
marginXS,
marginSM,
fontSize,
fontSizeLG,
lineHeight,
borderRadiusLG: borderRadius,
motionEaseInOutCirc,
alertIconSizeLG,
colorText,
paddingContentVerticalSM,
alertPaddingHorizontal,
paddingMD,
paddingContentHorizontalLG,
} = token;
return {
[componentCls]: {
...resetComponent(token),
position: 'relative',
display: 'flex',
alignItems: 'center',
padding: `${paddingContentVerticalSM}px ${alertPaddingHorizontal}px`, // Fixed horizontal padding here.
wordWrap: 'break-word',
borderRadius,
[`&${componentCls}-rtl`]: {
direction: 'rtl',
},
[`${componentCls}-content`]: {
flex: 1,
minWidth: 0,
},
[`${componentCls}-icon`]: {
marginInlineEnd: marginXS,
lineHeight: 0,
},
[`&-description`]: {
display: 'none',
fontSize,
lineHeight,
},
'&-message': {
color: colorText,
},
[`&${componentCls}-motion-leave`]: {
overflow: 'hidden',
opacity: 1,
transition: `max-height ${duration} ${motionEaseInOutCirc}, opacity ${duration} ${motionEaseInOutCirc},
padding-top ${duration} ${motionEaseInOutCirc}, padding-bottom ${duration} ${motionEaseInOutCirc},
margin-bottom ${duration} ${motionEaseInOutCirc}`,
},
[`&${componentCls}-motion-leave-active`]: {
maxHeight: 0,
marginBottom: '0 !important',
paddingTop: 0,
paddingBottom: 0,
opacity: 0,
},
},
[`${componentCls}-with-description`]: {
alignItems: 'flex-start',
paddingInline: paddingContentHorizontalLG,
paddingBlock: paddingMD,
[`${componentCls}-icon`]: {
marginInlineEnd: marginSM,
fontSize: alertIconSizeLG,
lineHeight: 0,
},
[`${componentCls}-message`]: {
display: 'block',
marginBottom: marginXS,
color: colorText,
fontSize: fontSizeLG,
},
[`${componentCls}-description`]: {
display: 'block',
},
},
[`${componentCls}-banner`]: {
marginBottom: 0,
border: '0 !important',
borderRadius: 0,
},
};
};
export const genTypeStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
const {
componentCls,
colorSuccess,
colorSuccessBorder,
colorSuccessBg,
colorWarning,
colorWarningBorder,
colorWarningBg,
colorError,
colorErrorBorder,
colorErrorBg,
colorInfo,
colorInfoBorder,
colorInfoBg,
} = token;
return {
[componentCls]: {
'&-success': genAlertTypeStyle(
colorSuccessBg,
colorSuccessBorder,
colorSuccess,
token,
componentCls,
),
'&-info': genAlertTypeStyle(colorInfoBg, colorInfoBorder, colorInfo, token, componentCls),
'&-warning': genAlertTypeStyle(
colorWarningBg,
colorWarningBorder,
colorWarning,
token,
componentCls,
),
'&-error': {
...genAlertTypeStyle(colorErrorBg, colorErrorBorder, colorError, token, componentCls),
[`${componentCls}-description > pre`]: {
margin: 0,
padding: 0,
},
},
},
};
};
export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
const {
componentCls,
iconCls,
motionDurationMid,
marginXS,
fontSizeIcon,
colorIcon,
colorIconHover,
} = token;
return {
[componentCls]: {
[`&-action`]: {
marginInlineStart: marginXS,
},
[`${componentCls}-close-icon`]: {
marginInlineStart: marginXS,
padding: 0,
overflow: 'hidden',
fontSize: fontSizeIcon,
lineHeight: `${fontSizeIcon}px`,
backgroundColor: 'transparent',
border: 'none',
outline: 'none',
cursor: 'pointer',
[`${iconCls}-close`]: {
color: colorIcon,
transition: `color ${motionDurationMid}`,
'&:hover': {
color: colorIconHover,
},
},
},
'&-close-text': {
color: colorIcon,
transition: `color ${motionDurationMid}`,
'&:hover': {
color: colorIconHover,
},
},
},
};
};
export const genAlertStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSInterpolation => [
genBaseStyle(token),
genTypeStyle(token),
genActionStyle(token),
];
export default genComponentStyleHook('Alert', token => {
const { fontSizeHeading3 } = token;
const alertToken = mergeToken<AlertToken>(token, {
alertIconSizeLG: fontSizeHeading3,
alertPaddingHorizontal: 12, // Fixed value here.
});
return [genAlertStyle(alertToken)];
});

View File

@ -72,6 +72,7 @@ export interface ConfigConsumerProps {
}
export const configProviderProps = () => ({
iconPrefixCls: String,
getTargetContainer: {
type: Function as PropType<() => HTMLElement>,
},

View File

@ -202,7 +202,7 @@ const ConfigProvider = defineComponent({
);
},
});
export const defaultIconPrefixCls = 'anticon';
export const defaultConfigProvider: UnwrapRef<ConfigProviderProps> = reactive({
getPrefixCls: (suffixCls: string, customizePrefixCls?: string) => {
if (customizePrefixCls) return customizePrefixCls;
@ -210,6 +210,7 @@ export const defaultConfigProvider: UnwrapRef<ConfigProviderProps> = reactive({
},
renderEmpty: defaultRenderEmpty,
direction: 'ltr',
iconPrefixCls: defaultIconPrefixCls,
});
ConfigProvider.config = setGlobalConfig;

View File

@ -0,0 +1,9 @@
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)');
});
});
});

31
components/theme/index.ts Normal file
View File

@ -0,0 +1,31 @@
/* eslint-disable import/prefer-default-export */
import { defaultConfig, useToken as useInternalToken } from './internal';
import type { GlobalToken } from './interface';
import defaultAlgorithm from './themes/default';
import darkAlgorithm from './themes/dark';
import compactAlgorithm from './themes/compact';
// ZombieJ: We export as object to user but array in internal.
// This is used to minimize the bundle size for antd package but safe to refactor as object also.
// Please do not export internal `useToken` directly to avoid something export unexpected.
/** Get current context Design Token. Will be different if you are using nest theme config. */
function useToken() {
const [theme, token, hashId] = useInternalToken();
return { theme, token, hashId };
}
export { type GlobalToken };
export default {
/** @private Test Usage. Do not use in production. */
defaultConfig,
/** Default seedToken */
defaultSeed: defaultConfig.token,
useToken,
defaultAlgorithm,
darkAlgorithm,
compactAlgorithm,
};

View File

@ -0,0 +1,148 @@
import type { CSSProperties } from 'vue';
import type { MapToken } from './maps';
// ======================================================================
// == Alias Token ==
// ======================================================================
// 🔥🔥🔥🔥🔥🔥🔥 DO NOT MODIFY THIS. PLEASE CONTACT DESIGNER. 🔥🔥🔥🔥🔥🔥🔥
export interface AliasToken extends MapToken {
// Background
colorFillContentHover: string;
colorFillAlter: string;
colorFillContent: string;
colorBgContainerDisabled: string;
colorBgTextHover: string;
colorBgTextActive: string;
// Border
colorBorderBg: string;
/**
* @nameZH 线
* @desc 线 colorBorderSecondary
*/
colorSplit: string;
// Text
colorTextPlaceholder: string;
colorTextDisabled: string;
colorTextHeading: string;
colorTextLabel: string;
colorTextDescription: string;
colorTextLightSolid: string;
/** Weak action. Such as `allowClear` or Alert close button */
colorIcon: string;
/** Weak action hover color. Such as `allowClear` or Alert close button */
colorIconHover: string;
colorLink: string;
colorLinkHover: string;
colorLinkActive: string;
colorHighlight: string;
controlOutline: string;
colorWarningOutline: string;
colorErrorOutline: string;
// Font
/** Operation icon in Select, Cascader, etc. icon fontSize. Normal is same as fontSizeSM */
fontSizeIcon: number;
/** For heading like h1, h2, h3 or option selected item */
fontWeightStrong: number;
// Control
controlOutlineWidth: number;
controlItemBgHover: string; // Note. It also is a color
controlItemBgActive: string; // Note. It also is a color
controlItemBgActiveHover: string; // Note. It also is a color
controlInteractiveSize: number;
controlItemBgActiveDisabled: string; // Note. It also is a color
// Padding
paddingXXS: number;
paddingXS: number;
paddingSM: number;
padding: number;
paddingMD: number;
paddingLG: number;
paddingXL: number;
// Padding Content
paddingContentHorizontalLG: number;
paddingContentHorizontal: number;
paddingContentHorizontalSM: number;
paddingContentVerticalLG: number;
paddingContentVertical: number;
paddingContentVerticalSM: number;
// Margin
marginXXS: number;
marginXS: number;
marginSM: number;
margin: number;
marginMD: number;
marginLG: number;
marginXL: number;
marginXXL: number;
// =============== Legacy: should be remove ===============
opacityLoading: number;
boxShadow: string;
boxShadowSecondary: string;
linkDecoration: CSSProperties['textDecoration'];
linkHoverDecoration: CSSProperties['textDecoration'];
linkFocusDecoration: CSSProperties['textDecoration'];
controlPaddingHorizontal: number;
controlPaddingHorizontalSM: number;
// Media queries breakpoints
screenXS: number;
screenXSMin: number;
screenXSMax: number;
screenSM: number;
screenSMMin: number;
screenSMMax: number;
screenMD: number;
screenMDMin: number;
screenMDMax: number;
screenLG: number;
screenLGMin: number;
screenLGMax: number;
screenXL: number;
screenXLMin: number;
screenXLMax: number;
screenXXL: number;
screenXXLMin: number;
/** Used for DefaultButton, Switch which has default outline */
controlTmpOutline: string;
// FIXME: component box-shadow, should be removed
/** @internal */
boxShadowPopoverArrow: string;
/** @internal */
boxShadowCard: string;
/** @internal */
boxShadowDrawerRight: string;
/** @internal */
boxShadowDrawerLeft: string;
/** @internal */
boxShadowDrawerUp: string;
/** @internal */
boxShadowDrawerDown: string;
/** @internal */
boxShadowTabsOverflowLeft: string;
/** @internal */
boxShadowTabsOverflowRight: string;
/** @internal */
boxShadowTabsOverflowTop: string;
/** @internal */
boxShadowTabsOverflowBottom: string;
}

View File

@ -0,0 +1,119 @@
import type { ComponentToken as AlertComponentToken } from '../../alert/style';
// import type { ComponentToken as AnchorComponentToken } from '../../anchor/style';
// import type { ComponentToken as AvatarComponentToken } from '../../avatar/style';
// import type { ComponentToken as BackTopComponentToken } from '../../back-top/style';
// import type { ComponentToken as ButtonComponentToken } from '../../button/style';
// import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style';
// import type { ComponentToken as CalendarComponentToken } from '../../calendar/style';
// import type { ComponentToken as CardComponentToken } from '../../card/style';
// import type { ComponentToken as CarouselComponentToken } from '../../carousel/style';
// import type { ComponentToken as CascaderComponentToken } from '../../cascader/style';
// import type { ComponentToken as CheckboxComponentToken } from '../../checkbox/style';
// import type { ComponentToken as CollapseComponentToken } from '../../collapse/style';
// import type { ComponentToken as DatePickerComponentToken } from '../../date-picker/style';
// import type { ComponentToken as DividerComponentToken } from '../../divider/style';
// import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style';
// import type { ComponentToken as DrawerComponentToken } from '../../drawer/style';
// import type { ComponentToken as EmptyComponentToken } from '../../empty/style';
// import type { ComponentToken as ImageComponentToken } from '../../image/style';
// import type { ComponentToken as InputNumberComponentToken } from '../../input-number/style';
// import type { ComponentToken as LayoutComponentToken } from '../../layout/style';
// import type { ComponentToken as ListComponentToken } from '../../list/style';
// import type { ComponentToken as MentionsComponentToken } from '../../mentions/style';
// import type { ComponentToken as MenuComponentToken } from '../../menu/style';
// import type { ComponentToken as MessageComponentToken } from '../../message/style';
// import type { ComponentToken as ModalComponentToken } from '../../modal/style';
// import type { ComponentToken as NotificationComponentToken } from '../../notification/style';
// import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style';
// import type { ComponentToken as PopoverComponentToken } from '../../popover/style';
// import type { ComponentToken as ProgressComponentToken } from '../../progress/style';
// import type { ComponentToken as RadioComponentToken } from '../../radio/style';
// import type { ComponentToken as RateComponentToken } from '../../rate/style';
// import type { ComponentToken as ResultComponentToken } from '../../result/style';
// import type { ComponentToken as SegmentedComponentToken } from '../../segmented/style';
// import type { ComponentToken as SelectComponentToken } from '../../select/style';
// import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/style';
// import type { ComponentToken as SliderComponentToken } from '../../slider/style';
// import type { ComponentToken as SpaceComponentToken } from '../../space/style';
// import type { ComponentToken as SpinComponentToken } from '../../spin/style';
// import type { ComponentToken as StepsComponentToken } from '../../steps/style';
// import type { ComponentToken as TableComponentToken } from '../../table/style';
// import type { ComponentToken as TabsComponentToken } from '../../tabs/style';
// import type { ComponentToken as TagComponentToken } from '../../tag/style';
// import type { ComponentToken as TimelineComponentToken } from '../../timeline/style';
// import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
// import type { ComponentToken as TransferComponentToken } from '../../transfer/style';
// import type { ComponentToken as TypographyComponentToken } from '../../typography/style';
// import type { ComponentToken as UploadComponentToken } from '../../upload/style';
// import type { ComponentToken as TourComponentToken } from '../../tour/style';
// import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style';
// import type { ComponentToken as AppComponentToken } from '../../app/style';
// import type { ComponentToken as WaveToken } from '../../_util/wave/style';
export interface ComponentTokenMap {
Affix?: {};
Alert?: AlertComponentToken;
}
// Anchor?: AnchorComponentToken;
// Avatar?: AvatarComponentToken;
// BackTop?: BackTopComponentToken;
// Badge?: {};
// Button?: ButtonComponentToken;
// Breadcrumb?: {};
// Card?: CardComponentToken;
// Carousel?: CarouselComponentToken;
// Cascader?: CascaderComponentToken;
// Checkbox?: CheckboxComponentToken;
// Collapse?: CollapseComponentToken;
// DatePicker?: DatePickerComponentToken;
// Descriptions?: {};
// Divider?: DividerComponentToken;
// Drawer?: DrawerComponentToken;
// Dropdown?: DropdownComponentToken;
// Empty?: EmptyComponentToken;
// FloatButton?: FloatButtonComponentToken;
// Form?: {};
// Grid?: {};
// Image?: ImageComponentToken;
// Input?: {};
// InputNumber?: InputNumberComponentToken;
// Layout?: LayoutComponentToken;
// List?: ListComponentToken;
// Mentions?: MentionsComponentToken;
// Notification?: NotificationComponentToken;
// Pagination?: {};
// Popover?: PopoverComponentToken;
// Popconfirm?: PopconfirmComponentToken;
// Rate?: RateComponentToken;
// Radio?: RadioComponentToken;
// Result?: ResultComponentToken;
// Segmented?: SegmentedComponentToken;
// Select?: SelectComponentToken;
// Skeleton?: SkeletonComponentToken;
// Slider?: SliderComponentToken;
// Spin?: SpinComponentToken;
// Statistic?: {};
// Switch?: {};
// Tag?: TagComponentToken;
// Tree?: {};
// TreeSelect?: {};
// Typography?: TypographyComponentToken;
// Timeline?: TimelineComponentToken;
// Transfer?: TransferComponentToken;
// Tabs?: TabsComponentToken;
// Calendar?: CalendarComponentToken;
// Steps?: StepsComponentToken;
// Menu?: MenuComponentToken;
// Modal?: ModalComponentToken;
// Message?: MessageComponentToken;
// Upload?: UploadComponentToken;
// Tooltip?: TooltipComponentToken;
// Table?: TableComponentToken;
// Space?: SpaceComponentToken;
// Progress?: ProgressComponentToken;
// Tour?: TourComponentToken;
// QRCode?: QRCodeComponentToken;
// App?: AppComponentToken;
// /** @private Internal TS definition. Do not use. */
// Wave?: WaveToken;

View File

@ -0,0 +1,25 @@
import type { ComponentTokenMap } from './components';
import type { AliasToken } from './alias';
export type OverrideToken = {
[key in keyof ComponentTokenMap]: Partial<ComponentTokenMap[key]> & Partial<AliasToken>;
};
/** Final token which contains the components level override */
export type GlobalToken = AliasToken & ComponentTokenMap;
export { PresetColors } from './presetColors';
export type { PresetColorType, ColorPalettes, PresetColorKey } from './presetColors';
export type { SeedToken } from './seeds';
export type {
MapToken,
ColorMapToken,
ColorNeutralMapToken,
CommonMapToken,
HeightMapToken,
SizeMapToken,
FontMapToken,
StyleMapToken,
} from './maps';
export type { AliasToken } from './alias';
export type { ComponentTokenMap } from './components';

View File

@ -0,0 +1,437 @@
export interface ColorNeutralMapToken {
/**
* @internal
*/
colorTextBase: string;
/**
* @internal
*/
colorBgBase: string;
// ---------- Text ---------- //
/**
* @nameZH
* @desc W3C使
*/
colorText: string;
/**
* @nameZH
* @desc Label Menu
*/
colorTextSecondary: string;
/**
* @nameZH
* @desc
*/
colorTextTertiary: string;
/**
* @nameZH
* @desc
*/
colorTextQuaternary: string;
// ---------- Border ---------- //
/**
* @nameZH
* @nameEN Default Border Color
* @desc 使, 线线
* @descEN Default border color, used to separate different elements, such as: form separator, card separator, etc.
*/
colorBorder: string;
/**
* @nameZH
* @nameEN Secondary Border Color
* @desc 使 colorSplit 使
* @descEN Slightly lighter than the default border color, this color is the same as `colorSplit`. Solid color is used.
*/
colorBorderSecondary: string;
// ---------- Fill ---------- //
/**
* @nameZH
* @desc Slider hover
*/
colorFill: string;
/**
* @nameZH
* @desc RateSkeleton Hover Table
*/
colorFillSecondary: string;
/**
* @nameZH
* @desc SliderSegmented 使
*/
colorFillTertiary: string;
/**
* @nameZH
* @desc
*/
colorFillQuaternary: string;
// ---------- Surface ---------- //
/**
* @nameZH
* @desc B1 使 token
*/
colorBgLayout: string;
/**
* @nameZH
* @desc `colorBgElevated`
*/
colorBgContainer: string;
/**
* @nameZH
* @desc token `colorBgContainer`
*/
colorBgElevated: string;
/**
* @nameZH
* @desc Tooltip
*/
colorBgSpotlight: string;
}
/**
*
*/
interface ColorPrimaryMapToken {
/**
* @nameZH
* @desc */
colorPrimary: string; // 6
/**
* @nameZH
* @nameEN Light Background Color of Primary Color
* @desc
* @descEN Light background color of primary color, usually used for weak visual level selection state.
*/
colorPrimaryBg: string; // 1
/**
* @nameZH
* @desc
*/
colorPrimaryBgHover: string; // 2
/**
* @nameZH
* @desc Slider
*/
colorPrimaryBorder: string; // 3
/**
* @nameZH
* @desc Slider Button Hover 使
*/
colorPrimaryBorderHover: string; // 4
/**
* @nameZH
* @desc 使
*/
colorPrimaryHover: string; // 5
/**
* @nameZH
* @desc
*/
colorPrimaryActive: string; // 7
/**
* @nameZH
* @desc
*/
colorPrimaryTextHover: string; // 8
/**
* @nameZH
* @desc
*/
colorPrimaryText: string; // 9
/**
* @nameZH
* @desc
*/
colorPrimaryTextActive: string; // 10
}
interface ColorSuccessMapToken {
/**
* @nameZH
* @nameEN Light Background Color of Success Color
* @desc Tag Alert
* @descEN Light background color of success color, used for Tag and Alert success state background color
*/
colorSuccessBg: string; // 1
/**
* @nameZH
* @nameEN Hover State Color of Light Success Background
* @desc antd 使 token
* @descEN Light background color of success color, but antd does not use this token currently
*/
colorSuccessBgHover: string; // 2
/**
* @nameZH
* @desc Tag Alert
*/
colorSuccessBorder: string; // 3
/**
* @nameZH
* @desc
*/
colorSuccessBorderHover: string; // 4
/**
* @nameZH
* @desc
*/
colorSuccessHover: string; // 5
/**
* @nameZH
* @desc ResultProgress 使
*/
colorSuccess: string; // 6
/**
* @nameZH
* @desc
*/
colorSuccessActive: string; // 7
/**
* @nameZH
* @desc
*/
colorSuccessTextHover: string; // 8
/**
* @nameZH
* @desc
*/
colorSuccessText: string; // 9
/**
* @nameZH
* @desc
*/
colorSuccessTextActive: string; // 10
}
interface ColorWarningMapToken {
/**
* @nameZH
*/
colorWarningBg: string; // 1
/**
* @nameZH
* @desc
*/
colorWarningBgHover: string; // 2
/**
* @nameZH
* @desc
*/
colorWarningBorder: string; // 3
/**
* @nameZH
* @desc
*/
colorWarningBorderHover: string; // 4
/**
* @nameZH
* @desc
*/
colorWarningHover: string; // 5
/**
* @nameZH
* @desc Notification Alert Input 使
*/
colorWarning: string; // 6
/**
* @nameZH
* @desc
*/
colorWarningActive: string; // 7
/**
* @nameZH
* @desc
*/
colorWarningTextHover: string; // 8
/**
* @nameZH
* @desc
*/
colorWarningText: string; // 9
/**
* @nameZH
* @desc
*/
colorWarningTextActive: string; // 10
}
interface ColorInfoMapToken {
/**
* @nameZH
* @desc
*/
colorInfoBg: string; // 1
/**
* @nameZH
* @desc
*/
colorInfoBgHover: string; // 2
/**
* @nameZH
*/
colorInfoBorder: string; // 3
/**
* @nameZH
*/
colorInfoBorderHover: string; // 4
/**
* @nameZH
*/
colorInfoHover: string; // 5
/**
* @nameZH
*/
colorInfo: string; // 6
/**
* @nameZH
*/
colorInfoActive: string; // 7
/**
* @nameZH
*/
colorInfoTextHover: string; // 8
/**
* @nameZH
*/
colorInfoText: string; // 9
/**
* @nameZH
*/
colorInfoTextActive: string; // 10
}
interface ColorErrorMapToken {
/**
* @nameZH
*/
colorErrorBg: string; // 1
/**
* @nameZH
*/
colorErrorBgHover: string; // 2
/**
* @nameZH
*/
colorErrorBorder: string; // 3
/**
* @nameZH
*/
colorErrorBorderHover: string; // 4
/**
* @nameZH
*/
colorErrorHover: string; // 5
/**
* @nameZH
*/
colorError: string; // 6
/**
* @nameZH
*/
colorErrorActive: string; // 7
/**
* @nameZH
*/
colorErrorTextHover: string; // 8
/**
* @nameZH
*/
colorErrorText: string; // 9
/**
* @nameZH
*/
colorErrorTextActive: string; // 10
}
export interface ColorMapToken
extends ColorNeutralMapToken,
ColorPrimaryMapToken,
ColorSuccessMapToken,
ColorWarningMapToken,
ColorErrorMapToken,
ColorInfoMapToken {
/**
* @nameZH
* @desc
* @descEN Pure white color don't changed by theme
* @default #FFFFFF
*/
colorWhite: string;
/**
* @nameZH
* @nameEN Background color of the mask
* @desc ModalDrawer 使 token
* @descEN The background color of the mask, used to cover the content below the mask, Modal, Drawer and other components use this token
*/
colorBgMask: string;
/**
* @nameZH
* @desc
* @default #0000
*/
// colorBlack: string;
}

View File

@ -0,0 +1,49 @@
export interface FontMapToken {
// Font Size
fontSizeSM: number;
fontSize: number;
fontSizeLG: number;
fontSizeXL: number;
/**
* @nameZH
* @desc H1 使
* @default 38
*/
fontSizeHeading1: number;
/**
* @nameZH
* @desc h2 使
* @default 30
*/
fontSizeHeading2: number;
/**
* @nameZH
* @desc h3 使
* @default 24
*/
fontSizeHeading3: number;
/**
* @nameZH
* @desc h4 使
* @default 20
*/
fontSizeHeading4: number;
/**
* @nameZH
* @desc h5 使
* @default 16
*/
fontSizeHeading5: number;
// LineHeight
lineHeight: number;
lineHeightLG: number;
lineHeightSM: number;
lineHeightHeading1: number;
lineHeightHeading2: number;
lineHeightHeading3: number;
lineHeightHeading4: number;
lineHeightHeading5: number;
}

View File

@ -0,0 +1,33 @@
import type { ColorPalettes } from '../presetColors';
import type { SeedToken } from '../seeds';
import type { SizeMapToken, HeightMapToken } from './size';
import type { ColorMapToken } from './colors';
import type { StyleMapToken } from './style';
import type { FontMapToken } from './font';
export * from './colors';
export * from './style';
export * from './size';
export * from './font';
export interface CommonMapToken extends StyleMapToken {
// Motion
motionDurationFast: string;
motionDurationMid: string;
motionDurationSlow: string;
}
// ======================================================================
// == Map Token ==
// ======================================================================
// 🔥🔥🔥🔥🔥🔥🔥 DO NOT MODIFY THIS. PLEASE CONTACT DESIGNER. 🔥🔥🔥🔥🔥🔥🔥
export interface MapToken
extends SeedToken,
ColorPalettes,
ColorMapToken,
SizeMapToken,
HeightMapToken,
StyleMapToken,
FontMapToken,
CommonMapToken {}

View File

@ -0,0 +1,68 @@
export interface SizeMapToken {
/**
* @nameZH XXL
* @default 48
*/
sizeXXL: number;
/**
* @nameZH XL
* @default 32
*/
sizeXL: number;
/**
* @nameZH LG
* @default 24
*/
sizeLG: number;
/**
* @nameZH MD
* @default 20
*/
sizeMD: number;
/** Same as size by default, but could be larger in compact mode */
sizeMS: number;
/**
* @nameZH
* @desc
* @default 16
*/
size: number;
/**
* @nameZH SM
* @default 12
*/
sizeSM: number;
/**
* @nameZH XS
* @default 8
*/
sizeXS: number;
/**
* @nameZH XXS
* @default 4
*/
sizeXXS: number;
}
export interface HeightMapToken {
// Control
/** Only Used for control inside component like Multiple Select inner selection item */
/**
* @nameZH
* @nameEN XS component height
*/
controlHeightXS: number;
/**
* @nameZH
* @nameEN SM component height
*/
controlHeightSM: number;
/**
* @nameZH
* @nameEN LG component height
*/
controlHeightLG: number;
}

View File

@ -0,0 +1,38 @@
export interface StyleMapToken {
/**
* @nameZH 线
* @nameEN Line Width
* @desc 线 ButtonInputSelect
* @descEN The default line width of the outline class components, such as Button, Input, Select, etc.
* @default 1
*/
lineWidthBold: number;
/**
* @nameZH XS
* @desc XS Segmented Arrow
* @descEN XS size border radius, used in some small border radius components, such as Segmented, Arrow and other components.
* @default 2
*/
borderRadiusXS: number;
/**
* @nameZH SM
* @nameEN SM Border Radius
* @desc SM ButtonInputSelect small size
* @descEN SM size border radius, used in small size components, such as Button, Input, Select and other input components in small size
* @default 4
*/
borderRadiusSM: number;
/**
* @nameZH LG
* @nameEN LG Border Radius
* @desc LG CardModal
* @descEN LG size border radius, used in some large border radius components, such as Card, Modal and other components.
* @default 8
*/
borderRadiusLG: number;
/**
* @default 4
*/
borderRadiusOuter: number;
}

View File

@ -0,0 +1,25 @@
export const PresetColors = [
'blue',
'purple',
'cyan',
'green',
'magenta',
'pink',
'red',
'orange',
'yellow',
'volcano',
'geekblue',
'lime',
'gold',
] as const;
export type PresetColorKey = typeof PresetColors[number];
export type PresetColorType = Record<PresetColorKey, string>;
type ColorPaletteKeyIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export type ColorPalettes = {
[key in `${keyof PresetColorType}-${ColorPaletteKeyIndex}`]: string;
};

View File

@ -0,0 +1,216 @@
import type { PresetColorType } from './presetColors';
// ======================================================================
// == Seed Token ==
// ======================================================================
// 🔥🔥🔥🔥🔥🔥🔥 DO NOT MODIFY THIS. PLEASE CONTACT DESIGNER. 🔥🔥🔥🔥🔥🔥🔥
export interface SeedToken extends PresetColorType {
// ---------- Color ---------- //
/**
* @nameZH
* @nameEN Brand Color
* @desc
* @descEN Brand color is one of the most direct visual elements to reflect the characteristics and communication of the product. After you have selected the brand color, we will automatically generate a complete color palette and assign it effective design semantics.
*/
colorPrimary: string;
/**
* @nameZH
* @nameEN Success Color
* @desc Token ResultProgress 使
* @descEN Used to represent the token sequence of operation success, such as Result, Progress and other components will use these map tokens.
*/
colorSuccess: string;
/**
* @nameZH
* @nameEN Warning Color
* @desc Token Notification Alert Input 使
* @descEN Used to represent the warning map token, such as Notification, Alert, etc. Alert or Control component(like Input) will use these map tokens.
*/
colorWarning: string;
/**
* @nameZH
* @nameEN Error Color
* @desc Token Result
* @descEN Used to represent the visual elements of the operation failure, such as the error Button, error Result component, etc.
*/
colorError: string;
/**
* @nameZH
* @nameEN Info Color
* @desc Token Alert Tag Progress
* @descEN Used to represent the operation information of the Token sequence, such as Alert, Tag, Progress, and other components use these map tokens.
*/
colorInfo: string;
/**
* @nameZH
* @nameEN Seed Text Color
* @desc v5 **使 Seed Token**
* @descEN Used to derive the base variable of the text color gradient. In v5, we added a layer of text color derivation algorithm to produce gradient variables of text color gradient. But please do not use this Seed Token directly in the code!
*/
colorTextBase: string;
/**
* @nameZH
* @nameEN Seed Background Color
* @desc v5 **使 Seed Token**
* @descEN Used to derive the base variable of the background color gradient. In v5, we added a layer of background color derivation algorithm to produce map token of background color. But PLEASE DO NOT USE this Seed Token directly in the code!
*/
colorBgBase: string;
// ---------- Font ---------- //
/**
* @nameZH
* @nameEN FontFamily
* @desc Ant Design 使
*/
fontFamily: string;
/**
* @nameZH
* @nameEN Default Font Size
* @desc 使广
* @default 14
*/
fontSize: number;
// ---------- Line ---------- //
/**
* @nameZH 线
* @nameEN Base Line Width
* @desc 线
* @descEN Border width of base components
*/
lineWidth: number;
/**
* @nameZH 线
* @nameEN Line Style
* @desc 线线
* @descEN Border style of base components
*/
lineType: string;
// ---------- BorderRadius ---------- //
/**
* @nameZH
* @nameEN Base Border Radius
* @descEN Border radius of base components
* @desc
*/
borderRadius: number;
// ---------- Size ---------- //
/**
* @nameZH
* @nameEN Size Change Unit
* @desc Ant Design 4 便
* @descEN The unit of size change, in Ant Design, our base unit is 4, which is more fine-grained control of the size step
* @default 4
*/
sizeUnit: number;
/**
* @nameZH
* @nameEN Size Base Step
* @desc V5 2
* @descEN The base step of size change, the size step combined with the size change unit, can derive various size steps. By adjusting the step, you can get different layout modes, such as the size step of the compact mode of V5 is 2
* @default 4
*/
sizeStep: number;
/**
* @nameZH
*/
sizePopupArrow: number;
/**
* @nameZH
* @nameEN Base Control Height
* @desc Ant Design
* @descEN The height of the basic controls such as buttons and input boxes in Ant Design
* @default 32
*/
controlHeight: number;
// ---------- zIndex ---------- //
/**
* @nameZH zIndex
* @nameEN Base zIndex
* @desc Z Z BackTop Affix
* @descEN The base Z axis value of all components, which can be used to control the level of some floating components based on the Z axis value, such as BackTop, Affix, etc.
*
* @default 0
*/
zIndexBase: number;
/**
* @nameZH zIndex
* @nameEN popup base zIndex
* @desc Z Z FloatButton AffixModal
* @descEN Base zIndex of component like FloatButton, Affix which can be cover by large popup
* @default 1000
*/
zIndexPopupBase: number;
// ---------- Opacity ---------- //
/**
* @nameZH
* @nameEN Define default Image opacity. Useful when in dark-like theme
*/
opacityImage: number;
// ---------- motion ---------- //
// TODO: 缺一个懂 motion 的人来收敛 Motion 相关的 Token
/**
* @nameZH
* @nameEN Animation Duration Unit
* @desc
* @descEN The unit of animation duration change
* @default 100ms
*/
motionUnit: number;
/**
* @nameZH
*/
motionBase: number;
motionEaseOutCirc: string;
motionEaseInOutCirc: string;
motionEaseInOut: string;
motionEaseOutBack: string;
motionEaseInBack: string;
motionEaseInQuint: string;
motionEaseOutQuint: string;
motionEaseOut: string;
// ---------- Style ---------- //
/**
* @nameZH 线
* @nameEN Wireframe Style
* @desc 线使 V4
* @default false
*/
wireframe: boolean;
}

View File

@ -0,0 +1,97 @@
import type { CSSInterpolation, Theme } from '../_util/cssinjs';
import { createTheme, useCacheToken, useStyleRegister } from '../_util/cssinjs';
import version from '../version';
import type {
AliasToken,
GlobalToken,
MapToken,
OverrideToken,
PresetColorType,
PresetColorKey,
SeedToken,
} from './interface';
import { PresetColors } from './interface';
import defaultDerivative from './themes/default';
import defaultSeedToken from './themes/seed';
import formatToken from './util/alias';
import type { FullToken } from './util/genComponentStyleHook';
import genComponentStyleHook from './util/genComponentStyleHook';
import statisticToken, { merge as mergeToken, statistic } from './util/statistic';
import type { VueNode } from '../_util/type';
import type { ComputedRef, InjectionKey } from 'vue';
import { computed, inject } from 'vue';
const defaultTheme = createTheme(defaultDerivative);
export {
// colors
PresetColors,
// Statistic
statistic,
statisticToken,
mergeToken,
// hooks
useStyleRegister,
genComponentStyleHook,
};
export type {
SeedToken,
AliasToken,
PresetColorType,
PresetColorKey,
// FIXME: Remove this type
AliasToken as DerivativeToken,
FullToken,
};
// ================================ Context =================================
// To ensure snapshot stable. We disable hashed in test env.
export const defaultConfig = {
token: defaultSeedToken,
hashed: true,
};
export interface DesignTokenContext {
token: Partial<AliasToken>;
theme?: Theme<SeedToken, MapToken>;
components?: OverrideToken;
hashed?: string | boolean;
}
//defaultConfig
const DesignTokenContextKey: InjectionKey<DesignTokenContext> = Symbol('DesignTokenContext');
// ================================== Hook ==================================
export function useToken(): [
ComputedRef<Theme<SeedToken, MapToken>>,
ComputedRef<GlobalToken>,
ComputedRef<string>,
] {
const designTokenContext = inject(DesignTokenContextKey, defaultConfig);
const salt = computed(() => `${version}-${designTokenContext.hashed || ''}`);
const mergedTheme = computed(() => designTokenContext.theme || defaultTheme);
const cacheToken = useCacheToken<GlobalToken, SeedToken>(
mergedTheme,
computed(() => [defaultSeedToken, designTokenContext.token]),
computed(() => ({
salt: salt.value,
override: { override: designTokenContext.token, ...designTokenContext.components },
formatToken,
})),
);
return [
mergedTheme,
computed(() => cacheToken.value[0]),
computed(() => (designTokenContext.hashed ? cacheToken.value[1] : '')),
];
}
export type UseComponentStyleResult = [(node: VueNode) => VueNode, string];
export type GenerateStyle<
ComponentToken extends object = AliasToken,
ReturnType = CSSInterpolation,
> = (token: ComponentToken) => ReturnType;

View File

@ -0,0 +1,20 @@
import type { ColorNeutralMapToken } from '../interface';
export interface ColorMap {
1: string;
2: string;
3: string;
4: string;
5: string;
6: string;
7: string;
8: string;
9: string;
10: string;
}
export type GenerateColorMap = (baseColor: string) => ColorMap;
export type GenerateNeutralColorMap = (
bgBaseColor: string,
textBaseColor: string,
) => ColorNeutralMapToken;

View File

@ -0,0 +1,19 @@
import type { SeedToken, SizeMapToken } from '../../interface';
export default function genSizeMapToken(token: SeedToken): SizeMapToken {
const { sizeUnit, sizeStep } = token;
const compactSizeStep = sizeStep - 2;
return {
sizeXXL: sizeUnit * (compactSizeStep + 10),
sizeXL: sizeUnit * (compactSizeStep + 6),
sizeLG: sizeUnit * (compactSizeStep + 2),
sizeMD: sizeUnit * (compactSizeStep + 2),
sizeMS: sizeUnit * (compactSizeStep + 1),
size: sizeUnit * compactSizeStep,
sizeSM: sizeUnit * compactSizeStep,
sizeXS: sizeUnit * (compactSizeStep - 1),
sizeXXS: sizeUnit * (compactSizeStep - 1),
};
}

View File

@ -0,0 +1,27 @@
import genControlHeight from '../shared/genControlHeight';
import type { MapToken, SeedToken } from '../../interface';
import defaultAlgorithm from '../default';
import genCompactSizeMapToken from './genCompactSizeMapToken';
import genFontMapToken from '../shared/genFontMapToken';
import type { DerivativeFunc } from '../../../_util/cssinjs';
const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
const mergedMapToken = mapToken ?? defaultAlgorithm(token);
const fontSize = mergedMapToken.fontSizeSM; // Smaller size font-size as base
const controlHeight = mergedMapToken.controlHeight - 4;
return {
...mergedMapToken,
...genCompactSizeMapToken(mapToken ?? token),
// font
...genFontMapToken(fontSize),
// controlHeight
controlHeight,
...genControlHeight({ ...mergedMapToken, controlHeight }),
};
};
export default derivative;

View File

@ -0,0 +1,9 @@
import { TinyColor } from '@ctrl/tinycolor';
export const getAlphaColor = (baseColor: string, alpha: number) =>
new TinyColor(baseColor).setAlpha(alpha).toRgbString();
export const getSolidColor = (baseColor: string, brightness: number) => {
const instance = new TinyColor(baseColor);
return instance.lighten(brightness).toHexString();
};

View File

@ -0,0 +1,53 @@
import { generate } from '@ant-design/colors';
import type { GenerateColorMap, GenerateNeutralColorMap } from '../ColorMap';
import { getAlphaColor, getSolidColor } from './colorAlgorithm';
export const generateColorPalettes: GenerateColorMap = (baseColor: string) => {
const colors = generate(baseColor, { theme: 'dark' });
return {
1: colors[0],
2: colors[1],
3: colors[2],
4: colors[3],
5: colors[6],
6: colors[5],
7: colors[4],
8: colors[6],
9: colors[5],
10: colors[4],
// 8: colors[9],
// 9: colors[8],
// 10: colors[7],
};
};
export const generateNeutralColorPalettes: GenerateNeutralColorMap = (
bgBaseColor: string,
textBaseColor: string,
) => {
const colorBgBase = bgBaseColor || '#000';
const colorTextBase = textBaseColor || '#fff';
return {
colorBgBase,
colorTextBase,
colorText: getAlphaColor(colorTextBase, 0.85),
colorTextSecondary: getAlphaColor(colorTextBase, 0.65),
colorTextTertiary: getAlphaColor(colorTextBase, 0.45),
colorTextQuaternary: getAlphaColor(colorTextBase, 0.25),
colorFill: getAlphaColor(colorTextBase, 0.18),
colorFillSecondary: getAlphaColor(colorTextBase, 0.12),
colorFillTertiary: getAlphaColor(colorTextBase, 0.08),
colorFillQuaternary: getAlphaColor(colorTextBase, 0.04),
colorBgElevated: getSolidColor(colorBgBase, 12),
colorBgContainer: getSolidColor(colorBgBase, 8),
colorBgLayout: getSolidColor(colorBgBase, 0),
colorBgSpotlight: getSolidColor(colorBgBase, 26),
colorBorder: getSolidColor(colorBgBase, 26),
colorBorderSecondary: getSolidColor(colorBgBase, 19),
};
};

View File

@ -0,0 +1,42 @@
import { generate } from '@ant-design/colors';
import type { DerivativeFunc } from '../../../_util/cssinjs';
import type { ColorPalettes, MapToken, PresetColorType, SeedToken } from '../../interface';
import { defaultPresetColors } from '../seed';
import genColorMapToken from '../shared/genColorMapToken';
import { generateColorPalettes, generateNeutralColorPalettes } from './colors';
import defaultAlgorithm from '../default';
const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
const colorPalettes = Object.keys(defaultPresetColors)
.map((colorKey: keyof PresetColorType) => {
const colors = generate(token[colorKey], { theme: 'dark' });
return new Array(10).fill(1).reduce((prev, _, i) => {
prev[`${colorKey}-${i + 1}`] = colors[i];
return prev;
}, {}) as ColorPalettes;
})
.reduce((prev, cur) => {
prev = {
...prev,
...cur,
};
return prev;
}, {} as ColorPalettes);
const mergedMapToken = mapToken ?? defaultAlgorithm(token);
return {
...mergedMapToken,
// Dark tokens
...colorPalettes,
// Colors
...genColorMapToken(token, {
generateColorPalettes,
generateNeutralColorPalettes,
}),
};
};
export default derivative;

View File

@ -0,0 +1,9 @@
import { TinyColor } from '@ctrl/tinycolor';
export const getAlphaColor = (baseColor: string, alpha: number) =>
new TinyColor(baseColor).setAlpha(alpha).toRgbString();
export const getSolidColor = (baseColor: string, brightness: number) => {
const instance = new TinyColor(baseColor);
return instance.darken(brightness).toHexString();
};

View File

@ -0,0 +1,53 @@
import { generate } from '@ant-design/colors';
import type { GenerateColorMap, GenerateNeutralColorMap } from '../ColorMap';
import { getAlphaColor, getSolidColor } from './colorAlgorithm';
export const generateColorPalettes: GenerateColorMap = (baseColor: string) => {
const colors = generate(baseColor);
return {
1: colors[0],
2: colors[1],
3: colors[2],
4: colors[3],
5: colors[4],
6: colors[5],
7: colors[6],
8: colors[4],
9: colors[5],
10: colors[6],
// 8: colors[7],
// 9: colors[8],
// 10: colors[9],
};
};
export const generateNeutralColorPalettes: GenerateNeutralColorMap = (
bgBaseColor: string,
textBaseColor: string,
) => {
const colorBgBase = bgBaseColor || '#fff';
const colorTextBase = textBaseColor || '#000';
return {
colorBgBase,
colorTextBase,
colorText: getAlphaColor(colorTextBase, 0.88),
colorTextSecondary: getAlphaColor(colorTextBase, 0.65),
colorTextTertiary: getAlphaColor(colorTextBase, 0.45),
colorTextQuaternary: getAlphaColor(colorTextBase, 0.25),
colorFill: getAlphaColor(colorTextBase, 0.15),
colorFillSecondary: getAlphaColor(colorTextBase, 0.06),
colorFillTertiary: getAlphaColor(colorTextBase, 0.04),
colorFillQuaternary: getAlphaColor(colorTextBase, 0.02),
colorBgLayout: getSolidColor(colorBgBase, 4),
colorBgContainer: getSolidColor(colorBgBase, 0),
colorBgElevated: getSolidColor(colorBgBase, 0),
colorBgSpotlight: getAlphaColor(colorTextBase, 0.85),
colorBorder: getSolidColor(colorBgBase, 15),
colorBorderSecondary: getSolidColor(colorBgBase, 6),
};
};

View File

@ -0,0 +1,46 @@
import { generate } from '@ant-design/colors';
import genControlHeight from '../shared/genControlHeight';
import genSizeMapToken from '../shared/genSizeMapToken';
import type { ColorPalettes, MapToken, PresetColorType, SeedToken } from '../../interface';
import { defaultPresetColors } from '../seed';
import genColorMapToken from '../shared/genColorMapToken';
import genCommonMapToken from '../shared/genCommonMapToken';
import { generateColorPalettes, generateNeutralColorPalettes } from './colors';
import genFontMapToken from '../shared/genFontMapToken';
export default function derivative(token: SeedToken): MapToken {
const colorPalettes = Object.keys(defaultPresetColors)
.map((colorKey: keyof PresetColorType) => {
const colors = generate(token[colorKey]);
return new Array(10).fill(1).reduce((prev, _, i) => {
prev[`${colorKey}-${i + 1}`] = colors[i];
return prev;
}, {}) as ColorPalettes;
})
.reduce((prev, cur) => {
prev = {
...prev,
...cur,
};
return prev;
}, {} as ColorPalettes);
return {
...token,
...colorPalettes,
// Colors
...genColorMapToken(token, {
generateColorPalettes,
generateNeutralColorPalettes,
}),
// Font
...genFontMapToken(token.fontSize),
// Size
...genSizeMapToken(token),
// Height
...genControlHeight(token),
// Others
...genCommonMapToken(token),
};
}

View File

@ -0,0 +1,76 @@
import type { PresetColorType, SeedToken } from '../internal';
export const defaultPresetColors: PresetColorType = {
blue: '#1677ff',
purple: '#722ED1',
cyan: '#13C2C2',
green: '#52C41A',
magenta: '#EB2F96',
pink: '#eb2f96',
red: '#F5222D',
orange: '#FA8C16',
yellow: '#FADB14',
volcano: '#FA541C',
geekblue: '#2F54EB',
gold: '#FAAD14',
lime: '#A0D911',
};
const seedToken: SeedToken = {
// preset color palettes
...defaultPresetColors,
// Color
colorPrimary: '#1677ff',
colorSuccess: '#52c41a',
colorWarning: '#faad14',
colorError: '#ff4d4f',
colorInfo: '#1677ff',
colorTextBase: '',
colorBgBase: '',
// Font
fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'`,
fontSize: 14,
// Line
lineWidth: 1,
lineType: 'solid',
// Motion
motionUnit: 0.1,
motionBase: 0,
motionEaseOutCirc: 'cubic-bezier(0.08, 0.82, 0.17, 1)',
motionEaseInOutCirc: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
motionEaseOut: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
motionEaseInOut: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
motionEaseOutBack: 'cubic-bezier(0.12, 0.4, 0.29, 1.46)',
motionEaseInBack: 'cubic-bezier(0.71, -0.46, 0.88, 0.6)',
motionEaseInQuint: 'cubic-bezier(0.755, 0.05, 0.855, 0.06)',
motionEaseOutQuint: 'cubic-bezier(0.23, 1, 0.32, 1)',
// Radius
borderRadius: 6,
// Size
sizeUnit: 4,
sizeStep: 4,
sizePopupArrow: 16,
// Control Base
controlHeight: 32,
// zIndex
zIndexBase: 0,
zIndexPopupBase: 1000,
// Image
opacityImage: 1,
// Wireframe
wireframe: false,
};
export default seedToken;

View File

@ -0,0 +1,92 @@
import { TinyColor } from '@ctrl/tinycolor';
import type { ColorMapToken, SeedToken } from '../../interface';
import type { GenerateColorMap, GenerateNeutralColorMap } from '../ColorMap';
interface PaletteGenerators {
generateColorPalettes: GenerateColorMap;
generateNeutralColorPalettes: GenerateNeutralColorMap;
}
export default function genColorMapToken(
seed: SeedToken,
{ generateColorPalettes, generateNeutralColorPalettes }: PaletteGenerators,
): ColorMapToken {
const {
colorSuccess: colorSuccessBase,
colorWarning: colorWarningBase,
colorError: colorErrorBase,
colorInfo: colorInfoBase,
colorPrimary: colorPrimaryBase,
colorBgBase,
colorTextBase,
} = seed;
const primaryColors = generateColorPalettes(colorPrimaryBase);
const successColors = generateColorPalettes(colorSuccessBase);
const warningColors = generateColorPalettes(colorWarningBase);
const errorColors = generateColorPalettes(colorErrorBase);
const infoColors = generateColorPalettes(colorInfoBase);
const neutralColors = generateNeutralColorPalettes(colorBgBase, colorTextBase);
return {
...neutralColors,
colorPrimaryBg: primaryColors[1],
colorPrimaryBgHover: primaryColors[2],
colorPrimaryBorder: primaryColors[3],
colorPrimaryBorderHover: primaryColors[4],
colorPrimaryHover: primaryColors[5],
colorPrimary: primaryColors[6],
colorPrimaryActive: primaryColors[7],
colorPrimaryTextHover: primaryColors[8],
colorPrimaryText: primaryColors[9],
colorPrimaryTextActive: primaryColors[10],
colorSuccessBg: successColors[1],
colorSuccessBgHover: successColors[2],
colorSuccessBorder: successColors[3],
colorSuccessBorderHover: successColors[4],
colorSuccessHover: successColors[4],
colorSuccess: successColors[6],
colorSuccessActive: successColors[7],
colorSuccessTextHover: successColors[8],
colorSuccessText: successColors[9],
colorSuccessTextActive: successColors[10],
colorErrorBg: errorColors[1],
colorErrorBgHover: errorColors[2],
colorErrorBorder: errorColors[3],
colorErrorBorderHover: errorColors[4],
colorErrorHover: errorColors[5],
colorError: errorColors[6],
colorErrorActive: errorColors[7],
colorErrorTextHover: errorColors[8],
colorErrorText: errorColors[9],
colorErrorTextActive: errorColors[10],
colorWarningBg: warningColors[1],
colorWarningBgHover: warningColors[2],
colorWarningBorder: warningColors[3],
colorWarningBorderHover: warningColors[4],
colorWarningHover: warningColors[4],
colorWarning: warningColors[6],
colorWarningActive: warningColors[7],
colorWarningTextHover: warningColors[8],
colorWarningText: warningColors[9],
colorWarningTextActive: warningColors[10],
colorInfoBg: infoColors[1],
colorInfoBgHover: infoColors[2],
colorInfoBorder: infoColors[3],
colorInfoBorderHover: infoColors[4],
colorInfoHover: infoColors[4],
colorInfo: infoColors[6],
colorInfoActive: infoColors[7],
colorInfoTextHover: infoColors[8],
colorInfoText: infoColors[9],
colorInfoTextActive: infoColors[10],
colorBgMask: new TinyColor('#000').setAlpha(0.45).toRgbString(),
colorWhite: '#fff',
};
}

View File

@ -0,0 +1,19 @@
import type { CommonMapToken, SeedToken } from '../../interface';
import genRadius from './genRadius';
export default function genCommonMapToken(token: SeedToken): CommonMapToken {
const { motionUnit, motionBase, borderRadius, lineWidth } = token;
return {
// motion
motionDurationFast: `${(motionBase + motionUnit).toFixed(1)}s`,
motionDurationMid: `${(motionBase + motionUnit * 2).toFixed(1)}s`,
motionDurationSlow: `${(motionBase + motionUnit * 3).toFixed(1)}s`,
// line
lineWidthBold: lineWidth + 1,
// radius
...genRadius(borderRadius),
};
}

View File

@ -0,0 +1,13 @@
import type { HeightMapToken, SeedToken } from '../../interface';
const genControlHeight = (token: SeedToken): HeightMapToken => {
const { controlHeight } = token;
return {
controlHeightSM: controlHeight * 0.75,
controlHeightXS: controlHeight * 0.5,
controlHeightLG: controlHeight * 1.25,
};
};
export default genControlHeight;

View File

@ -0,0 +1,33 @@
import type { FontMapToken } from '../../interface';
import genFontSizes from './genFontSizes';
const genFontMapToken = (fontSize: number): FontMapToken => {
const fontSizePairs = genFontSizes(fontSize);
const fontSizes = fontSizePairs.map(pair => pair.size);
const lineHeights = fontSizePairs.map(pair => pair.lineHeight);
return {
fontSizeSM: fontSizes[0],
fontSize: fontSizes[1],
fontSizeLG: fontSizes[2],
fontSizeXL: fontSizes[3],
fontSizeHeading1: fontSizes[6],
fontSizeHeading2: fontSizes[5],
fontSizeHeading3: fontSizes[4],
fontSizeHeading4: fontSizes[3],
fontSizeHeading5: fontSizes[2],
lineHeight: lineHeights[1],
lineHeightLG: lineHeights[2],
lineHeightSM: lineHeights[0],
lineHeightHeading1: lineHeights[6],
lineHeightHeading2: lineHeights[5],
lineHeightHeading3: lineHeights[4],
lineHeightHeading4: lineHeights[3],
lineHeightHeading5: lineHeights[2],
};
};
export default genFontMapToken;

View File

@ -0,0 +1,22 @@
// https://zhuanlan.zhihu.com/p/32746810
export default function getFontSizes(base: number) {
const fontSizes = new Array(10).fill(null).map((_, index) => {
const i = index - 1;
const baseSize = base * 2.71828 ** (i / 5);
const intSize = index > 1 ? Math.floor(baseSize) : Math.ceil(baseSize);
// Convert to even
return Math.floor(intSize / 2) * 2;
});
fontSizes[1] = base;
return fontSizes.map(size => {
const height = size + 8;
return {
size,
lineHeight: height / size,
};
});
}

View File

@ -0,0 +1,59 @@
import type { MapToken } from '../../interface';
const genRadius = (
radiusBase: number,
): Pick<
MapToken,
'borderRadiusXS' | 'borderRadiusSM' | 'borderRadiusLG' | 'borderRadius' | 'borderRadiusOuter'
> => {
let radiusLG = radiusBase;
let radiusSM = radiusBase;
let radiusXS = radiusBase;
let radiusOuter = radiusBase;
// radiusLG
if (radiusBase < 6 && radiusBase >= 5) {
radiusLG = radiusBase + 1;
} else if (radiusBase < 16 && radiusBase >= 6) {
radiusLG = radiusBase + 2;
} else if (radiusBase >= 16) {
radiusLG = 16;
}
// radiusSM
if (radiusBase < 7 && radiusBase >= 5) {
radiusSM = 4;
} else if (radiusBase < 8 && radiusBase >= 7) {
radiusSM = 5;
} else if (radiusBase < 14 && radiusBase >= 8) {
radiusSM = 6;
} else if (radiusBase < 16 && radiusBase >= 14) {
radiusSM = 7;
} else if (radiusBase >= 16) {
radiusSM = 8;
}
// radiusXS
if (radiusBase < 6 && radiusBase >= 2) {
radiusXS = 1;
} else if (radiusBase >= 6) {
radiusXS = 2;
}
// radiusOuter
if (radiusBase > 4 && radiusBase < 8) {
radiusOuter = 4;
} else if (radiusBase >= 8) {
radiusOuter = 6;
}
return {
borderRadius: radiusBase > 16 ? 16 : radiusBase,
borderRadiusXS: radiusXS,
borderRadiusSM: radiusSM,
borderRadiusLG: radiusLG,
borderRadiusOuter: radiusOuter,
};
};
export default genRadius;

View File

@ -0,0 +1,17 @@
import type { SeedToken, SizeMapToken } from '../../interface';
export default function genSizeMapToken(token: SeedToken): SizeMapToken {
const { sizeUnit, sizeStep } = token;
return {
sizeXXL: sizeUnit * (sizeStep + 8), // 48
sizeXL: sizeUnit * (sizeStep + 4), // 32
sizeLG: sizeUnit * (sizeStep + 2), // 24
sizeMD: sizeUnit * (sizeStep + 1), // 20
sizeMS: sizeUnit * sizeStep, // 16
size: sizeUnit * sizeStep, // 16
sizeSM: sizeUnit * (sizeStep - 1), // 12
sizeXS: sizeUnit * (sizeStep - 2), // 8
sizeXXS: sizeUnit * (sizeStep - 3), // 4
};
}

View File

@ -0,0 +1,192 @@
import { TinyColor } from '@ctrl/tinycolor';
import type { AliasToken, MapToken, OverrideToken, SeedToken } from '../interface';
import getAlphaColor from './getAlphaColor';
import seedToken from '../themes/seed';
/** Raw merge of `@ant-design/cssinjs` token. Which need additional process */
type RawMergedToken = MapToken & OverrideToken & { override: Partial<AliasToken> };
/**
* Seed (designer) > Derivative (designer) > Alias (developer).
*
* Merge seed & derivative & override token and generate alias token for developer.
*/
export default function formatToken(derivativeToken: RawMergedToken): AliasToken {
const { override, ...restToken } = derivativeToken;
const overrideTokens = { ...override };
Object.keys(seedToken).forEach(token => {
delete overrideTokens[token as keyof SeedToken];
});
const mergedToken = {
...restToken,
...overrideTokens,
};
const screenXS = 480;
const screenSM = 576;
const screenMD = 768;
const screenLG = 992;
const screenXL = 1200;
const screenXXL = 1600;
// Generate alias token
const aliasToken: AliasToken = {
...mergedToken,
colorLink: mergedToken.colorInfoText,
colorLinkHover: mergedToken.colorInfoHover,
colorLinkActive: mergedToken.colorInfoActive,
// ============== Background ============== //
colorFillContent: mergedToken.colorFillSecondary,
colorFillContentHover: mergedToken.colorFill,
colorFillAlter: mergedToken.colorFillQuaternary,
colorBgContainerDisabled: mergedToken.colorFillTertiary,
// ============== Split ============== //
colorBorderBg: mergedToken.colorBgContainer,
colorSplit: getAlphaColor(mergedToken.colorBorderSecondary, mergedToken.colorBgContainer),
// ============== Text ============== //
colorTextPlaceholder: mergedToken.colorTextQuaternary,
colorTextDisabled: mergedToken.colorTextQuaternary,
colorTextHeading: mergedToken.colorText,
colorTextLabel: mergedToken.colorTextSecondary,
colorTextDescription: mergedToken.colorTextTertiary,
colorTextLightSolid: mergedToken.colorWhite,
colorHighlight: mergedToken.colorError,
colorBgTextHover: mergedToken.colorFillSecondary,
colorBgTextActive: mergedToken.colorFill,
colorIcon: mergedToken.colorTextTertiary,
colorIconHover: mergedToken.colorText,
colorErrorOutline: getAlphaColor(mergedToken.colorErrorBg, mergedToken.colorBgContainer),
colorWarningOutline: getAlphaColor(mergedToken.colorWarningBg, mergedToken.colorBgContainer),
// Font
fontSizeIcon: mergedToken.fontSizeSM,
// Control
lineWidth: mergedToken.lineWidth,
controlOutlineWidth: mergedToken.lineWidth * 2,
// Checkbox size and expand icon size
controlInteractiveSize: mergedToken.controlHeight / 2,
controlItemBgHover: mergedToken.colorFillTertiary,
controlItemBgActive: mergedToken.colorPrimaryBg,
controlItemBgActiveHover: mergedToken.colorPrimaryBgHover,
controlItemBgActiveDisabled: mergedToken.colorFill,
controlTmpOutline: mergedToken.colorFillQuaternary,
controlOutline: getAlphaColor(mergedToken.colorPrimaryBg, mergedToken.colorBgContainer),
lineType: mergedToken.lineType,
borderRadius: mergedToken.borderRadius,
borderRadiusXS: mergedToken.borderRadiusXS,
borderRadiusSM: mergedToken.borderRadiusSM,
borderRadiusLG: mergedToken.borderRadiusLG,
fontWeightStrong: 600,
opacityLoading: 0.65,
linkDecoration: 'none',
linkHoverDecoration: 'none',
linkFocusDecoration: 'none',
controlPaddingHorizontal: 12,
controlPaddingHorizontalSM: 8,
paddingXXS: mergedToken.sizeXXS,
paddingXS: mergedToken.sizeXS,
paddingSM: mergedToken.sizeSM,
padding: mergedToken.size,
paddingMD: mergedToken.sizeMD,
paddingLG: mergedToken.sizeLG,
paddingXL: mergedToken.sizeXL,
paddingContentHorizontalLG: mergedToken.sizeLG,
paddingContentVerticalLG: mergedToken.sizeMS,
paddingContentHorizontal: mergedToken.sizeMS,
paddingContentVertical: mergedToken.sizeSM,
paddingContentHorizontalSM: mergedToken.size,
paddingContentVerticalSM: mergedToken.sizeXS,
marginXXS: mergedToken.sizeXXS,
marginXS: mergedToken.sizeXS,
marginSM: mergedToken.sizeSM,
margin: mergedToken.size,
marginMD: mergedToken.sizeMD,
marginLG: mergedToken.sizeLG,
marginXL: mergedToken.sizeXL,
marginXXL: mergedToken.sizeXXL,
boxShadow: `
0 1px 2px 0 rgba(0, 0, 0, 0.03),
0 1px 6px -1px rgba(0, 0, 0, 0.02),
0 2px 4px 0 rgba(0, 0, 0, 0.02)
`,
boxShadowSecondary: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
screenXS,
screenXSMin: screenXS,
screenXSMax: screenSM - 1,
screenSM,
screenSMMin: screenSM,
screenSMMax: screenMD - 1,
screenMD,
screenMDMin: screenMD,
screenMDMax: screenLG - 1,
screenLG,
screenLGMin: screenLG,
screenLGMax: screenXL - 1,
screenXL,
screenXLMin: screenXL,
screenXLMax: screenXXL - 1,
screenXXL,
screenXXLMin: screenXXL,
// FIXME: component box-shadow, should be removed
boxShadowPopoverArrow: '3px 3px 7px rgba(0, 0, 0, 0.1)',
boxShadowCard: `
0 1px 2px -2px ${new TinyColor('rgba(0, 0, 0, 0.16)').toRgbString()},
0 3px 6px 0 ${new TinyColor('rgba(0, 0, 0, 0.12)').toRgbString()},
0 5px 12px 4px ${new TinyColor('rgba(0, 0, 0, 0.09)').toRgbString()}
`,
boxShadowDrawerRight: `
-6px 0 16px 0 rgba(0, 0, 0, 0.08),
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
-9px 0 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerLeft: `
6px 0 16px 0 rgba(0, 0, 0, 0.08),
3px 0 6px -4px rgba(0, 0, 0, 0.12),
9px 0 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerUp: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerDown: `
0 -6px 16px 0 rgba(0, 0, 0, 0.08),
0 -3px 6px -4px rgba(0, 0, 0, 0.12),
0 -9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowTabsOverflowLeft: 'inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowRight: 'inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowTop: 'inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowBottom: 'inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)',
// Override AliasToken
...overrideTokens,
};
return aliasToken;
}

View File

@ -0,0 +1,111 @@
/* eslint-disable no-redeclare */
import { useStyleRegister } from '../../_util/cssinjs';
import type { CSSInterpolation } from '../../_util/cssinjs';
import { genCommonStyle, genLinkStyle } from '../../_style';
import type { UseComponentStyleResult } from '../internal';
import { mergeToken, statisticToken, useToken } from '../internal';
import type { ComponentTokenMap, GlobalToken } from '../interface';
import useConfigInject from 'ant-design-vue/es/_util/hooks/useConfigInject';
export type OverrideTokenWithoutDerivative = ComponentTokenMap;
export type OverrideComponent = keyof OverrideTokenWithoutDerivative;
export type GlobalTokenWithComponent<ComponentName extends OverrideComponent> = GlobalToken &
ComponentTokenMap[ComponentName];
export interface StyleInfo<ComponentName extends OverrideComponent> {
hashId: string;
prefixCls: string;
rootPrefixCls: string;
iconPrefixCls: string;
overrideComponentToken: ComponentTokenMap[ComponentName];
}
export type TokenWithCommonCls<T> = T & {
/** Wrap component class with `.` prefix */
componentCls: string;
/** Origin prefix which do not have `.` prefix */
prefixCls: string;
/** Wrap icon class with `.` prefix */
iconCls: string;
/** Wrap ant prefixCls class with `.` prefix */
antCls: string;
};
export type FullToken<ComponentName extends OverrideComponent> = TokenWithCommonCls<
GlobalTokenWithComponent<ComponentName>
>;
export default function genComponentStyleHook<ComponentName extends OverrideComponent>(
component: ComponentName,
styleFn: (token: FullToken<ComponentName>, info: StyleInfo<ComponentName>) => CSSInterpolation,
getDefaultToken?:
| OverrideTokenWithoutDerivative[ComponentName]
| ((token: GlobalToken) => OverrideTokenWithoutDerivative[ComponentName]),
) {
return (prefixCls: string): UseComponentStyleResult => {
const [theme, token, hashId] = useToken();
const { getPrefixCls, iconPrefixCls } = useConfigInject('', {});
const rootPrefixCls = getPrefixCls();
// Generate style for all a tags in antd component.
useStyleRegister(
{
theme: theme.value,
token: token.value,
hashId: hashId.value,
path: ['Shared', rootPrefixCls],
},
() => [
{
// Link
'&': genLinkStyle(token.value),
},
],
);
return [
useStyleRegister(
{
theme: theme.value,
token: token.value,
hashId: hashId.value,
path: [component, prefixCls, iconPrefixCls.value],
},
() => {
const { token: proxyToken, flush } = statisticToken(token.value);
const defaultComponentToken =
typeof getDefaultToken === 'function'
? (getDefaultToken as any)(proxyToken)
: getDefaultToken;
const mergedComponentToken = { ...defaultComponentToken, ...token.value[component] };
const componentCls = `.${prefixCls}`;
const mergedToken = mergeToken<
TokenWithCommonCls<GlobalTokenWithComponent<OverrideComponent>>
>(
proxyToken,
{
componentCls,
prefixCls,
iconCls: `.${iconPrefixCls}`,
antCls: `.${rootPrefixCls}`,
},
mergedComponentToken,
);
const styleInterpolation = styleFn(mergedToken as unknown as FullToken<ComponentName>, {
hashId: hashId.value,
prefixCls,
rootPrefixCls,
iconPrefixCls: iconPrefixCls.value,
overrideComponentToken: token.value[component],
});
flush(component, mergedComponentToken);
return [genCommonStyle(token.value, prefixCls), styleInterpolation];
},
),
hashId.value,
];
};
}

View File

@ -0,0 +1,29 @@
import { TinyColor } from '@ctrl/tinycolor';
function isStableColor(color: number): boolean {
return color >= 0 && color <= 255;
}
function getAlphaColor(frontColor: string, backgroundColor: string): string {
const { r: fR, g: fG, b: fB, a: originAlpha } = new TinyColor(frontColor).toRgb();
if (originAlpha < 1) {
return frontColor;
}
const { r: bR, g: bG, b: bB } = new TinyColor(backgroundColor).toRgb();
for (let fA = 0.01; fA <= 1; fA += 0.01) {
const r = Math.round((fR - bR * (1 - fA)) / fA);
const g = Math.round((fG - bG * (1 - fA)) / fA);
const b = Math.round((fB - bB * (1 - fA)) / fA);
if (isStableColor(r) && isStableColor(g) && isStableColor(b)) {
return new TinyColor({ r, g, b, a: Math.round(fA * 100) / 100 }).toRgbString();
}
}
// fallback
/* istanbul ignore next */
return new TinyColor({ r: fR, g: fG, b: fB, a: 1 }).toRgbString();
}
export default getAlphaColor;

View File

@ -0,0 +1,75 @@
declare const CSSINJS_STATISTIC: any;
const enableStatistic =
process.env.NODE_ENV !== 'production' || typeof CSSINJS_STATISTIC !== 'undefined';
let recording = true;
/**
* This function will do as `Object.assign` in production. But will use Object.defineProperty:get to
* pass all value access in development. To support statistic field usage with alias token.
*/
export function merge<T extends object>(...objs: Partial<T>[]): T {
/* istanbul ignore next */
if (!enableStatistic) {
return Object.assign({}, ...objs);
}
recording = false;
const ret = {} as T;
objs.forEach(obj => {
const keys = Object.keys(obj);
keys.forEach(key => {
Object.defineProperty(ret, key, {
configurable: true,
enumerable: true,
get: () => (obj as any)[key],
});
});
});
recording = true;
return ret;
}
/** @private Internal Usage. Not use in your production. */
export const statistic: Record<
string,
{ global: string[]; component: Record<string, string | number> }
> = {};
/** @private Internal Usage. Not use in your production. */
// eslint-disable-next-line camelcase
export const _statistic_build_: typeof statistic = {};
/* istanbul ignore next */
function noop() {}
/** Statistic token usage case. Should use `merge` function if you do not want spread record. */
export default function statisticToken<T extends object>(token: T) {
let tokenKeys: Set<string> | undefined;
let proxy = token;
let flush: (componentName: string, componentToken: Record<string, string | number>) => void =
noop;
if (enableStatistic) {
tokenKeys = new Set<string>();
proxy = new Proxy(token, {
get(obj: any, prop: any) {
if (recording) {
tokenKeys!.add(prop);
}
return obj[prop];
},
});
flush = (componentName, componentToken) => {
statistic[componentName] = { global: Array.from(tokenKeys!), component: componentToken };
};
}
return { token: proxy, keys: tokenKeys, flush };
}

View File

@ -1,7 +1,12 @@
export default function contains(root: HTMLElement | null | undefined, n?: HTMLElement) {
export default function contains(root: Node | null | undefined, n?: Node) {
if (!root) {
return false;
}
return root.contains(n);
// Use native if support
if (root.contains) {
return root.contains(n);
}
return false;
}

View File

@ -1,6 +1,21 @@
import canUseDom from '../../_util/canUseDom';
import contains from './contains';
const MARK_KEY = `vc-util-key` as any;
const APPEND_ORDER = 'data-rc-order';
const MARK_KEY = `rc-util-key`;
const containerCache = new Map<ContainerType, Node & ParentNode>();
export type ContainerType = Element | ShadowRoot;
export type Prepend = boolean | 'queue';
export type AppendType = 'prependQueue' | 'append' | 'prepend';
interface Options {
attachTo?: ContainerType;
csp?: { nonce?: string };
prepend?: Prepend;
mark?: string;
}
function getMark({ mark }: Options = {}) {
if (mark) {
@ -9,13 +24,6 @@ function getMark({ mark }: Options = {}) {
return MARK_KEY;
}
interface Options {
attachTo?: Element;
csp?: { nonce?: string };
prepend?: boolean;
mark?: string;
}
function getContainer(option: Options) {
if (option.attachTo) {
return option.attachTo;
@ -25,25 +33,55 @@ function getContainer(option: Options) {
return head || document.body;
}
function getOrder(prepend?: Prepend): AppendType {
if (prepend === 'queue') {
return 'prependQueue';
}
return prepend ? 'prepend' : 'append';
}
/**
* Find style which inject by rc-util
*/
function findStyles(container: ContainerType) {
return Array.from((containerCache.get(container) || container).children).filter(
node => node.tagName === 'STYLE',
) as HTMLStyleElement[];
}
export function injectCSS(css: string, option: Options = {}) {
if (!canUseDom()) {
return null;
}
const { csp, prepend } = option;
const styleNode = document.createElement('style');
if (option.csp?.nonce) {
styleNode.nonce = option.csp?.nonce;
styleNode.setAttribute(APPEND_ORDER, getOrder(prepend));
if (csp?.nonce) {
styleNode.nonce = csp?.nonce;
}
styleNode.innerHTML = css;
const container = getContainer(option);
const { firstChild } = container;
if (option.prepend && container.prepend) {
// Use `prepend` first
container.prepend(styleNode);
} else if (option.prepend && firstChild) {
// Fallback to `insertBefore` like IE not support `prepend`
if (prepend) {
// If is queue `prepend`, it will prepend first style and then append rest style
if (prepend === 'queue') {
const existStyle = findStyles(container).filter(node =>
['prepend', 'prependQueue'].includes(node.getAttribute(APPEND_ORDER)),
);
if (existStyle.length) {
container.insertBefore(styleNode, existStyle[existStyle.length - 1].nextSibling);
return styleNode;
}
}
// Use `insertBefore` as `prepend`
container.insertBefore(styleNode, firstChild);
} else {
container.appendChild(styleNode);
@ -52,32 +90,47 @@ export function injectCSS(css: string, option: Options = {}) {
return styleNode;
}
const containerCache = new Map<Element, Node & ParentNode>();
function findExistNode(key: string, option: Options = {}) {
const container = getContainer(option);
return Array.from(containerCache.get(container).children).find(
node => node.tagName === 'STYLE' && node.getAttribute(getMark(option)) === key,
) as HTMLStyleElement;
return findStyles(container).find(node => node.getAttribute(getMark(option)) === key);
}
export function removeCSS(key: string, option: Options = {}) {
const existNode = findExistNode(key, option);
if (existNode) {
const container = getContainer(option);
container.removeChild(existNode);
}
}
existNode?.parentNode?.removeChild(existNode);
/**
* qiankun will inject `appendChild` to insert into other
*/
function syncRealContainer(container: ContainerType, option: Options) {
const cachedRealContainer = containerCache.get(container);
// Find real container when not cached or cached container removed
if (!cachedRealContainer || !contains(document, cachedRealContainer)) {
const placeholderStyle = injectCSS('', option);
const { parentNode } = placeholderStyle;
containerCache.set(container, parentNode);
container.removeChild(placeholderStyle);
}
}
/**
* manually clear container cache to avoid global cache in unit testes
*/
export function clearContainerCache() {
containerCache.clear();
}
export function updateCSS(css: string, key: string, option: Options = {}) {
const container = getContainer(option);
// Get real parent
if (!containerCache.has(container)) {
const placeholderStyle = injectCSS('', option);
const { parentNode } = placeholderStyle;
containerCache.set(container, parentNode);
parentNode.removeChild(placeholderStyle);
}
// Sync real parent
syncRealContainer(container, option);
const existNode = findExistNode(key, option);

View File

@ -0,0 +1,52 @@
import warning from './warning';
/**
* Deeply compares two object literals.
* @param obj1 object 1
* @param obj2 object 2
* @param shallow shallow compare
* @returns
*/
function isEqual(obj1: any, obj2: any, shallow = false): boolean {
// https://github.com/mapbox/mapbox-gl-js/pull/5979/files#diff-fde7145050c47cc3a306856efd5f9c3016e86e859de9afbd02c879be5067e58f
const refSet = new Set<any>();
function deepEqual(a: any, b: any, level = 1): boolean {
const circular = refSet.has(a);
warning(!circular, 'Warning: There may be circular references');
if (circular) {
return false;
}
if (a === b) {
return true;
}
if (shallow && level > 1) {
return false;
}
refSet.add(a);
const newLevel = level + 1;
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i], newLevel)) {
return false;
}
}
return true;
}
if (a && b && typeof a === 'object' && typeof b === 'object') {
const keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) {
return false;
}
return keys.every(key => deepEqual(a[key], b[key], newLevel));
}
// other
return false;
}
return deepEqual(obj1, obj2);
}
export default isEqual;

View File

@ -245,7 +245,7 @@
"through2": "^3.0.0",
"ts-jest": "^28.0.5",
"ts-loader": "^9.1.0",
"typescript": "~4.5.2",
"typescript": "~4.9.3",
"umi-request": "^1.3.5",
"unified": "9.2.2",
"url-loader": "^3.0.0",
@ -276,10 +276,13 @@
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.1.0",
"@babel/runtime": "^7.10.5",
"@ctrl/tinycolor": "^3.4.0",
"@ctrl/tinycolor": "^3.5.0",
"@emotion/hash": "^0.9.0",
"@emotion/unitless": "^0.8.0",
"@simonwep/pickr": "~1.8.0",
"array-tree-filter": "^2.1.0",
"async-validator": "^4.0.0",
"csstype": "^3.1.1",
"dayjs": "^1.10.5",
"dom-align": "^1.12.1",
"dom-scroll-into-view": "^2.0.0",
@ -288,6 +291,7 @@
"resize-observer-polyfill": "^1.5.1",
"scroll-into-view-if-needed": "^2.2.25",
"shallow-equal": "^1.0.0",
"stylis": "^4.1.3",
"vue-types": "^3.0.0",
"warning": "^4.0.0"
},