ant-design/components/radio/style/index.tsx

486 lines
14 KiB
TypeScript

// deps-lint-skip-all
import { CSSInterpolation, Keyframes } from '@ant-design/cssinjs';
import { TinyColor } from '@ctrl/tinycolor';
import {
DerivativeToken,
useStyleRegister,
useToken,
resetComponent,
UseComponentStyleResult,
} from '../../_util/theme';
import { GlobalToken } from '../../_util/theme/interface';
// ============================== Tokens ==============================
interface RadioToken extends DerivativeToken {
radioFocusShadow: string;
radioButtonFocusShadow: string;
radioSize: number;
radioTop: string;
radioDotSize: number;
radioDotColor: string;
radioDotDisabledColor: string;
radioSolidCheckedColor: string;
radioButtonBg: string;
radioButtonCheckedBg: string;
radioButtonColor: string;
radioButtonHoverColor: string;
radioButtonActiveColor: string;
radioButtonPaddingHorizontal: number;
radioDisabledButtonCheckedBg: string;
radioDisabledButtonCheckedColor: string;
radioWrapperMarginRight: number;
}
function getRadioToken(token: GlobalToken) {
// Radio
const radioFocusShadow = `0 0 0 3px ${token.colorPrimaryOutline}`;
const radioButtonFocusShadow = radioFocusShadow;
const radioSize = token.fontSizeLG;
// FIXME: hard code
const radioTop = '0.2em';
// FIXME: hard code
const radioDotSize = radioSize - 8;
const radioDotColor = token.colorPrimary;
// FIXME: hard code
const radioDotDisabledColor = new TinyColor('#000').setAlpha(0.2).toRgbString();
const radioSolidCheckedColor = token.colorBgComponent;
// Radio buttons
const radioButtonBg = token.colorBgComponent;
const radioButtonCheckedBg = token.colorBgComponent;
const radioButtonColor = token.colorText;
const radioButtonHoverColor = token.colorPrimaryHover;
const radioButtonActiveColor = token.colorPrimaryActive;
// FIXME: hard code
const radioButtonPaddingHorizontal = token.padding - 1;
// FIXME: hard code
const radioDisabledButtonCheckedBg = new TinyColor('#000').tint(90).toRgbString();
const radioDisabledButtonCheckedColor = token.colorTextDisabled;
const radioWrapperMarginRight = token.marginXS;
return {
...token,
radioFocusShadow,
radioButtonFocusShadow,
radioSize,
radioTop,
radioDotSize,
radioDotColor,
radioDotDisabledColor,
radioSolidCheckedColor,
radioButtonBg,
radioButtonCheckedBg,
radioButtonColor,
radioButtonHoverColor,
radioButtonActiveColor,
radioButtonPaddingHorizontal,
radioDisabledButtonCheckedBg,
radioDisabledButtonCheckedColor,
radioWrapperMarginRight,
};
}
// ============================== Styles ==============================
const antRadioEffect = new Keyframes('antRadioEffect', {
'0%': { transform: 'scale(1)', opacity: 0.5 },
'100%': { transform: 'scale(1.6)', opacity: 0 },
});
// styles from RadioGroup only
function getGroupRadioStyle(
prefixCls: string,
antPrefix: string,
token: DerivativeToken,
): CSSInterpolation {
const groupPrefixCls = `${prefixCls}-group`;
return {
[`.${groupPrefixCls}`]: {
...resetComponent(token),
display: 'inline-block',
fontSize: 0,
// RTL
'&&-rtl': {
direction: 'rtl',
},
[`.${antPrefix}-badge .${antPrefix}-badge-count`]: {
zIndex: 1,
},
[`> .${antPrefix}-badge:not(:first-child) > .${prefixCls}-button-wrapper`]: {
borderInlineStart: 'none',
},
},
};
}
// Styles from radio-wrapper
function getRadioBasicStyle(prefixCls: string, token: RadioToken): CSSInterpolation {
const radioInnerPrefixCls = `${prefixCls}-inner`;
return {
[`.${prefixCls}-wrapper`]: {
...resetComponent(token),
position: 'relative',
display: 'inline-flex',
alignItems: 'baseline',
marginInlineStart: 0,
marginInlineEnd: token.radioWrapperMarginRight,
cursor: 'pointer',
// RTL
'&&-rtl': {
direction: 'rtl',
},
'&-disabled': {
cursor: 'not-allowed',
},
'&::after': {
display: 'inline-block',
width: 0,
overflow: 'hidden',
content: '"\\a0"',
},
// hashId 在 wrapper 上,只能铺平
[`.${prefixCls}-checked::after`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
width: '100%',
height: '100%',
border: `1px solid ${token.radioDotColor}`,
borderRadius: '50%',
visibility: 'hidden',
animationName: antRadioEffect,
animationDuration: '0.36s',
animationTimingFunction: 'ease-in-out',
animationFillMode: 'both',
content: '""',
},
[`.${prefixCls}`]: {
...resetComponent(token),
position: 'relative',
insetBlockStart: token.radioTop,
display: 'inline-block',
outline: 'none',
cursor: 'pointer',
},
[`.${prefixCls}-wrapper:hover &,
&:hover .${radioInnerPrefixCls},
&-input:focus + .${radioInnerPrefixCls}`]: {
borderColor: token.radioDotColor,
},
[`.${prefixCls}-input:focus + .${radioInnerPrefixCls}`]: {
boxShadow: token.radioFocusShadow,
},
[`.${prefixCls}:hover::after, .${prefixCls}-wrapper:hover &::after`]: {
visibility: 'visible',
},
[`.${prefixCls}-inner`]: {
'&::after': {
position: 'absolute',
insetBlockStart: '50%',
insetInlineStart: '50%',
display: 'block',
width: token.radioSize,
height: token.radioSize,
marginBlockStart: token.radioSize / -2,
marginInlineStart: token.radioSize / -2,
backgroundColor: token.radioDotColor,
borderBlockStart: 0,
borderInlineStart: 0,
borderRadius: token.radioSize,
transform: 'scale(0)',
opacity: 0,
transition: `all ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
content: '""',
},
position: 'relative',
insetBlockStart: 0,
insetInlineStart: 0,
display: 'block',
width: token.radioSize,
height: token.radioSize,
backgroundColor: token.radioButtonBg,
borderColor: token.colorBorder,
borderStyle: 'solid',
borderWidth: token.controlLineWidth,
borderRadius: '50%',
transition: `all ${token.motionDurationSlow}`,
},
[`.${prefixCls}-input`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineEnd: 0,
insetBlockEnd: 0,
insetInlineStart: 0,
zIndex: 1,
cursor: 'pointer',
opacity: 0,
},
// 选中状态
[`.${prefixCls}-checked`]: {
[`.${radioInnerPrefixCls}`]: {
borderColor: token.radioDotColor,
'&::after': {
transform: `scale(${token.radioDotSize / token.radioSize})`,
opacity: 1,
transition: `all ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
},
},
},
[`.${prefixCls}-disabled`]: {
cursor: 'not-allowed',
[`.${radioInnerPrefixCls}`]: {
backgroundColor: token.colorBgComponentDisabled,
borderColor: `${token.colorBorder} !important`,
cursor: 'not-allowed',
'&::after': {
backgroundColor: token.radioDotDisabledColor,
},
},
'&-input': {
cursor: 'not-allowed',
},
[`.${prefixCls}-disabled + span`]: {
color: token.colorTextDisabled,
cursor: 'not-allowed',
},
},
[`span.${prefixCls} + *`]: {
paddingInlineStart: token.paddingXS,
paddingInlineEnd: token.paddingXS,
},
antRadioEffect,
},
};
}
// Styles from radio-button
function getRadioButtonStyle(prefixCls: string, token: RadioToken): CSSInterpolation {
return {
[`.${prefixCls}-button-wrapper`]: {
position: 'relative',
display: 'inline-block',
height: token.controlHeight,
margin: 0,
paddingInline: token.radioButtonPaddingHorizontal,
paddingBlock: 0,
color: token.radioButtonColor,
fontSize: token.fontSize,
lineHeight: `${token.controlHeight - token.controlLineWidth * 2}px`,
background: token.radioButtonBg,
border: `${token.controlLineWidth}px ${token.controlLineType} ${token.colorBorder}`,
// strange align fix for chrome but works
// https://gw.alipayobjects.com/zos/rmsportal/VFTfKXJuogBAXcvfAUWJ.gif
borderBlockStartWidth: token.controlLineWidth + 0.02,
borderInlineStartWidth: 0,
borderInlineEndWidth: token.controlLineWidth,
cursor: 'pointer',
transition: `color ${token.motionDurationSlow}, background ${token.motionDurationSlow}, border-color ${token.motionDurationSlow}, box-shadow ${token.motionDurationSlow}`,
a: {
color: token.radioButtonColor,
},
[`> .${prefixCls}-button`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
zIndex: -1,
width: '100%',
height: '100%',
},
[`.${prefixCls}-group-large &`]: {
height: token.controlHeightLG,
fontSize: token.fontSizeLG,
lineHeight: `${token.controlHeightLG - token.controlLineWidth * 2}px`,
},
[`.${prefixCls}-group-small &`]: {
height: token.controlHeightSM,
paddingInline: token.paddingXS - token.controlLineWidth,
paddingBlock: 0,
lineHeight: `${token.controlHeightSM - token.controlLineWidth * 2}px`,
},
'&:not(:first-child)': {
'&::before': {
position: 'absolute',
insetBlockStart: -token.controlLineWidth,
insetInlineStart: -token.controlLineWidth,
display: 'block',
boxSizing: 'content-box',
width: 1,
height: '100%',
paddingBlock: token.controlLineWidth,
paddingInline: 0,
backgroundColor: token.colorBorder,
transition: `background-color ${token.motionDurationSlow}`,
content: '""',
},
},
'&:first-child': {
borderInlineStart: `${token.controlLineWidth}px ${token.controlLineType} ${token.colorBorder}`,
borderStartStartRadius: token.controlRadius,
borderEndStartRadius: token.controlRadius,
},
'&:last-child': {
borderStartEndRadius: token.controlRadius,
borderEndEndRadius: token.controlRadius,
},
'&:first-child:last-child': {
borderRadius: token.controlRadius,
},
'&:hover': {
position: 'relative',
color: token.radioDotColor,
},
'&:focus-within': {
boxShadow: token.radioButtonFocusShadow,
},
[`.${prefixCls}-inner, input[type='checkbox'], input[type='radio']`]: {
width: 0,
height: 0,
opacity: 0,
pointerEvents: 'none',
},
'&-checked:not(&-disabled)': {
zIndex: 1,
color: token.radioDotColor,
background: token.radioButtonCheckedBg,
borderColor: token.radioDotColor,
'&::before': {
backgroundColor: token.radioDotColor,
},
'&:first-child': {
borderColor: token.radioDotColor,
},
'&:hover': {
color: token.radioButtonHoverColor,
borderColor: token.radioButtonHoverColor,
'&::before': {
backgroundColor: token.radioButtonHoverColor,
},
},
'&:active': {
color: token.radioButtonActiveColor,
borderColor: token.radioButtonActiveColor,
'&::before': {
backgroundColor: token.radioButtonActiveColor,
},
},
'&:focus-within': {
boxShadow: token.radioButtonFocusShadow,
},
},
[`.${prefixCls}-group-solid &-checked:not(&-disabled)`]: {
color: token.radioSolidCheckedColor,
background: token.radioDotColor,
borderColor: token.radioDotColor,
'&:hover': {
color: token.radioSolidCheckedColor,
background: token.radioButtonHoverColor,
borderColor: token.radioButtonHoverColor,
},
'&:active': {
color: token.radioSolidCheckedColor,
background: token.radioButtonActiveColor,
borderColor: token.radioButtonActiveColor,
},
'&:focus-within': {
boxShadow: token.radioButtonFocusShadow,
},
},
'&-disabled': {
color: token.colorTextDisabled,
backgroundColor: token.colorBgComponentDisabled,
borderColor: token.colorBorder,
cursor: 'not-allowed',
'&:first-child, &:hover': {
color: token.colorTextDisabled,
backgroundColor: token.colorBgComponentDisabled,
borderColor: token.colorBorder,
},
},
'&-disabled&-checked': {
color: token.radioDisabledButtonCheckedColor,
backgroundColor: token.radioDisabledButtonCheckedBg,
borderColor: token.colorBorder,
boxShadow: 'none',
},
},
};
}
// ============================== Export ==============================
export function getStyle(
prefixCls: string,
antPrefix: string,
token: RadioToken,
): CSSInterpolation {
return [
getGroupRadioStyle(prefixCls, antPrefix, token),
getRadioBasicStyle(prefixCls, token),
getRadioButtonStyle(prefixCls, token),
];
}
export default function useStyle(prefixCls: string, antPrefix: string): UseComponentStyleResult {
const [theme, token, hashId] = useToken();
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => {
const radioToken = getRadioToken(token);
return getStyle(prefixCls, antPrefix, radioToken);
}),
hashId,
];
}