You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
11 KiB
354 lines
11 KiB
import type { CSSObject } from '../../_util/cssinjs';
|
|
import { TinyColor } from '@ctrl/tinycolor';
|
|
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
|
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
|
import { genFocusStyle, resetComponent } from '../../style';
|
|
|
|
interface SwitchToken extends FullToken<'Switch'> {
|
|
switchMinWidth: number;
|
|
switchHeight: number;
|
|
switchDuration: string;
|
|
switchColor: string;
|
|
switchDisabledOpacity: number;
|
|
switchInnerMarginMin: number;
|
|
switchInnerMarginMax: number;
|
|
switchPadding: number;
|
|
switchPinSize: number;
|
|
switchBg: string;
|
|
switchMinWidthSM: number;
|
|
switchHeightSM: number;
|
|
switchInnerMarginMinSM: number;
|
|
switchInnerMarginMaxSM: number;
|
|
switchPinSizeSM: number;
|
|
switchHandleShadow: string;
|
|
switchLoadingIconSize: number;
|
|
switchLoadingIconColor: string;
|
|
switchHandleActiveInset: string;
|
|
}
|
|
|
|
const genSwitchSmallStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|
const { componentCls } = token;
|
|
const switchInnerCls = `${componentCls}-inner`;
|
|
|
|
return {
|
|
[componentCls]: {
|
|
[`&${componentCls}-small`]: {
|
|
minWidth: token.switchMinWidthSM,
|
|
height: token.switchHeightSM,
|
|
lineHeight: `${token.switchHeightSM}px`,
|
|
|
|
[`${componentCls}-inner`]: {
|
|
paddingInlineStart: token.switchInnerMarginMaxSM,
|
|
paddingInlineEnd: token.switchInnerMarginMinSM,
|
|
[`${switchInnerCls}-checked`]: {
|
|
marginInlineStart: `calc(-100% + ${
|
|
token.switchPinSizeSM + token.switchPadding * 2
|
|
}px - ${token.switchInnerMarginMaxSM * 2}px)`,
|
|
marginInlineEnd: `calc(100% - ${token.switchPinSizeSM + token.switchPadding * 2}px + ${
|
|
token.switchInnerMarginMaxSM * 2
|
|
}px)`,
|
|
},
|
|
|
|
[`${switchInnerCls}-unchecked`]: {
|
|
marginTop: -token.switchHeightSM,
|
|
marginInlineStart: 0,
|
|
marginInlineEnd: 0,
|
|
},
|
|
},
|
|
|
|
[`${componentCls}-handle`]: {
|
|
width: token.switchPinSizeSM,
|
|
height: token.switchPinSizeSM,
|
|
},
|
|
|
|
[`${componentCls}-loading-icon`]: {
|
|
top: (token.switchPinSizeSM - token.switchLoadingIconSize) / 2,
|
|
fontSize: token.switchLoadingIconSize,
|
|
},
|
|
|
|
[`&${componentCls}-checked`]: {
|
|
[`${componentCls}-inner`]: {
|
|
paddingInlineStart: token.switchInnerMarginMinSM,
|
|
paddingInlineEnd: token.switchInnerMarginMaxSM,
|
|
[`${switchInnerCls}-checked`]: {
|
|
marginInlineStart: 0,
|
|
marginInlineEnd: 0,
|
|
},
|
|
|
|
[`${switchInnerCls}-unchecked`]: {
|
|
marginInlineStart: `calc(100% - ${
|
|
token.switchPinSizeSM + token.switchPadding * 2
|
|
}px + ${token.switchInnerMarginMaxSM * 2}px)`,
|
|
marginInlineEnd: `calc(-100% + ${
|
|
token.switchPinSizeSM + token.switchPadding * 2
|
|
}px - ${token.switchInnerMarginMaxSM * 2}px)`,
|
|
},
|
|
},
|
|
|
|
[`${componentCls}-handle`]: {
|
|
insetInlineStart: `calc(100% - ${token.switchPinSizeSM + token.switchPadding}px)`,
|
|
},
|
|
},
|
|
|
|
[`&:not(${componentCls}-disabled):active`]: {
|
|
[`&:not(${componentCls}-checked) ${switchInnerCls}`]: {
|
|
[`${switchInnerCls}-unchecked`]: {
|
|
marginInlineStart: token.marginXXS / 2,
|
|
marginInlineEnd: -token.marginXXS / 2,
|
|
},
|
|
},
|
|
|
|
[`&${componentCls}-checked ${switchInnerCls}`]: {
|
|
[`${switchInnerCls}-checked`]: {
|
|
marginInlineStart: -token.marginXXS / 2,
|
|
marginInlineEnd: token.marginXXS / 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
const genSwitchLoadingStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|
const { componentCls } = token;
|
|
|
|
return {
|
|
[componentCls]: {
|
|
[`${componentCls}-loading-icon${token.iconCls}`]: {
|
|
position: 'relative',
|
|
top: (token.switchPinSize - token.fontSize) / 2,
|
|
color: token.switchLoadingIconColor,
|
|
verticalAlign: 'top',
|
|
},
|
|
|
|
[`&${componentCls}-checked ${componentCls}-loading-icon`]: {
|
|
color: token.switchColor,
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|
const { componentCls } = token;
|
|
const switchHandleCls = `${componentCls}-handle`;
|
|
|
|
return {
|
|
[componentCls]: {
|
|
[switchHandleCls]: {
|
|
position: 'absolute',
|
|
top: token.switchPadding,
|
|
insetInlineStart: token.switchPadding,
|
|
width: token.switchPinSize,
|
|
height: token.switchPinSize,
|
|
transition: `all ${token.switchDuration} ease-in-out`,
|
|
|
|
'&::before': {
|
|
position: 'absolute',
|
|
top: 0,
|
|
insetInlineEnd: 0,
|
|
bottom: 0,
|
|
insetInlineStart: 0,
|
|
backgroundColor: token.colorWhite,
|
|
borderRadius: token.switchPinSize / 2,
|
|
boxShadow: token.switchHandleShadow,
|
|
transition: `all ${token.switchDuration} ease-in-out`,
|
|
content: '""',
|
|
},
|
|
},
|
|
|
|
[`&${componentCls}-checked ${switchHandleCls}`]: {
|
|
insetInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding}px)`,
|
|
},
|
|
|
|
[`&:not(${componentCls}-disabled):active`]: {
|
|
[`${switchHandleCls}::before`]: {
|
|
insetInlineEnd: token.switchHandleActiveInset,
|
|
insetInlineStart: 0,
|
|
},
|
|
|
|
[`&${componentCls}-checked ${switchHandleCls}::before`]: {
|
|
insetInlineEnd: 0,
|
|
insetInlineStart: token.switchHandleActiveInset,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
|
|
const { componentCls } = token;
|
|
const switchInnerCls = `${componentCls}-inner`;
|
|
|
|
return {
|
|
[componentCls]: {
|
|
[switchInnerCls]: {
|
|
display: 'block',
|
|
overflow: 'hidden',
|
|
borderRadius: 100,
|
|
height: '100%',
|
|
paddingInlineStart: token.switchInnerMarginMax,
|
|
paddingInlineEnd: token.switchInnerMarginMin,
|
|
transition: `padding-inline-start ${token.switchDuration} ease-in-out, padding-inline-end ${token.switchDuration} ease-in-out`,
|
|
|
|
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
|
|
display: 'block',
|
|
color: token.colorTextLightSolid,
|
|
fontSize: token.fontSizeSM,
|
|
transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`,
|
|
pointerEvents: 'none',
|
|
},
|
|
|
|
[`${switchInnerCls}-checked`]: {
|
|
marginInlineStart: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${
|
|
token.switchInnerMarginMax * 2
|
|
}px)`,
|
|
marginInlineEnd: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${
|
|
token.switchInnerMarginMax * 2
|
|
}px)`,
|
|
},
|
|
|
|
[`${switchInnerCls}-unchecked`]: {
|
|
marginTop: -token.switchHeight,
|
|
marginInlineStart: 0,
|
|
marginInlineEnd: 0,
|
|
},
|
|
},
|
|
|
|
[`&${componentCls}-checked ${switchInnerCls}`]: {
|
|
paddingInlineStart: token.switchInnerMarginMin,
|
|
paddingInlineEnd: token.switchInnerMarginMax,
|
|
[`${switchInnerCls}-checked`]: {
|
|
marginInlineStart: 0,
|
|
marginInlineEnd: 0,
|
|
},
|
|
|
|
[`${switchInnerCls}-unchecked`]: {
|
|
marginInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${
|
|
token.switchInnerMarginMax * 2
|
|
}px)`,
|
|
marginInlineEnd: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${
|
|
token.switchInnerMarginMax * 2
|
|
}px)`,
|
|
},
|
|
},
|
|
|
|
[`&:not(${componentCls}-disabled):active`]: {
|
|
[`&:not(${componentCls}-checked) ${switchInnerCls}`]: {
|
|
[`${switchInnerCls}-unchecked`]: {
|
|
marginInlineStart: token.switchPadding * 2,
|
|
marginInlineEnd: -token.switchPadding * 2,
|
|
},
|
|
},
|
|
|
|
[`&${componentCls}-checked ${switchInnerCls}`]: {
|
|
[`${switchInnerCls}-checked`]: {
|
|
marginInlineStart: -token.switchPadding * 2,
|
|
marginInlineEnd: token.switchPadding * 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
const genSwitchStyle = (token: SwitchToken): CSSObject => {
|
|
const { componentCls } = token;
|
|
|
|
return {
|
|
[componentCls]: {
|
|
...resetComponent(token),
|
|
|
|
position: 'relative',
|
|
display: 'inline-block',
|
|
boxSizing: 'border-box',
|
|
minWidth: token.switchMinWidth,
|
|
height: token.switchHeight,
|
|
lineHeight: `${token.switchHeight}px`,
|
|
verticalAlign: 'middle',
|
|
background: token.colorTextQuaternary,
|
|
border: '0',
|
|
borderRadius: 100,
|
|
cursor: 'pointer',
|
|
transition: `all ${token.motionDurationMid}`,
|
|
userSelect: 'none',
|
|
|
|
[`&:hover:not(${componentCls}-disabled)`]: {
|
|
background: token.colorTextTertiary,
|
|
},
|
|
|
|
...genFocusStyle(token),
|
|
|
|
[`&${componentCls}-checked`]: {
|
|
background: token.switchColor,
|
|
|
|
[`&:hover:not(${componentCls}-disabled)`]: {
|
|
background: token.colorPrimaryHover,
|
|
},
|
|
},
|
|
|
|
[`&${componentCls}-loading, &${componentCls}-disabled`]: {
|
|
cursor: 'not-allowed',
|
|
opacity: token.switchDisabledOpacity,
|
|
|
|
'*': {
|
|
boxShadow: 'none',
|
|
cursor: 'not-allowed',
|
|
},
|
|
},
|
|
|
|
// rtl style
|
|
[`&${componentCls}-rtl`]: {
|
|
direction: 'rtl',
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
// ============================== Export ==============================
|
|
export default genComponentStyleHook('Switch', token => {
|
|
const switchHeight = token.fontSize * token.lineHeight;
|
|
const switchHeightSM = token.controlHeight / 2;
|
|
const switchPadding = 2; // This is magic
|
|
const switchPinSize = switchHeight - switchPadding * 2;
|
|
const switchPinSizeSM = switchHeightSM - switchPadding * 2;
|
|
|
|
const switchToken = mergeToken<SwitchToken>(token, {
|
|
switchMinWidth: switchPinSize * 2 + switchPadding * 4,
|
|
switchHeight,
|
|
switchDuration: token.motionDurationMid,
|
|
switchColor: token.colorPrimary,
|
|
switchDisabledOpacity: token.opacityLoading,
|
|
switchInnerMarginMin: switchPinSize / 2,
|
|
switchInnerMarginMax: switchPinSize + switchPadding + switchPadding * 2,
|
|
switchPadding,
|
|
switchPinSize,
|
|
switchBg: token.colorBgContainer,
|
|
switchMinWidthSM: switchPinSizeSM * 2 + switchPadding * 2,
|
|
switchHeightSM,
|
|
switchInnerMarginMinSM: switchPinSizeSM / 2,
|
|
switchInnerMarginMaxSM: switchPinSizeSM + switchPadding + switchPadding * 2,
|
|
switchPinSizeSM,
|
|
switchHandleShadow: `0 2px 4px 0 ${new TinyColor('#00230b').setAlpha(0.2).toRgbString()}`,
|
|
switchLoadingIconSize: token.fontSizeIcon * 0.75,
|
|
switchLoadingIconColor: `rgba(0, 0, 0, ${token.opacityLoading})`,
|
|
switchHandleActiveInset: '-30%',
|
|
});
|
|
|
|
return [
|
|
genSwitchStyle(switchToken),
|
|
|
|
// inner style
|
|
genSwitchInnerStyle(switchToken),
|
|
|
|
// handle style
|
|
genSwitchHandleStyle(switchToken),
|
|
|
|
// loading style
|
|
genSwitchLoadingStyle(switchToken),
|
|
|
|
// small style
|
|
genSwitchSmallStyle(switchToken),
|
|
];
|
|
});
|