ant-design/components/select/style/index.tsx
MadCcc c4b8c9df93
refactor: genStyleHooks (#46038)
* refactor: genStyleHooks

* chore: update sciprt

* refactor: affix

* refactor: select

* chore: update

* refactor: update

* refactor: update

* refactor: done

* chore: code clean

* chore: code clean

* chore: fix lint

* chore: decrease size limit

* chore: code clean

* chore: code clean

* chore: remove export
2023-11-29 17:23:45 +08:00

512 lines
14 KiB
TypeScript

import type { CSSObject } from '@ant-design/cssinjs';
import { unit } from '@ant-design/cssinjs';
import type { CSSProperties } from 'react';
import { resetComponent, resetIcon, textEllipsis } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
import genDropdownStyle from './dropdown';
import genMultipleStyle from './multiple';
import genSingleStyle from './single';
export interface ComponentToken {
/**
* @desc 下拉菜单 z-index
* @descEN z-index of dropdown
*/
zIndexPopup: number;
/**
* @desc 选项选中时文本颜色
* @descEN Text color when option is selected
*/
optionSelectedColor: string;
/**
* @desc 选项选中时文本字重
* @descEN Font weight when option is selected
*/
optionSelectedFontWeight: CSSProperties['fontWeight'];
/**
* @desc 选项选中时背景色
* @descEN Background color when option is selected
*/
optionSelectedBg: string;
/**
* @desc 选项激活态时背景色
* @descEN Background color when option is active
*/
optionActiveBg: string;
/**
* @desc 选项内间距
* @descEN Padding of option
*/
optionPadding: CSSProperties['padding'];
/**
* @desc 选项字体大小
* @descEN Font size of option
*/
optionFontSize: number;
/**
* @desc 选项行高
* @descEN Line height of option
*/
optionLineHeight: CSSProperties['lineHeight'];
/**
* @desc 选项高度
* @descEN Height of option
*/
optionHeight: number;
/**
* @desc 选框背景色
* @descEN Background color of selector
*/
selectorBg: string;
/**
* @desc 清空按钮背景色
* @descEN Background color of clear button
*/
clearBg: string;
/**
* @desc 单选大号回填项高度
* @descEN Height of single selected item with large size
*/
singleItemHeightLG: number;
/**
* @desc 多选标签背景色
* @descEN Background color of multiple tag
*/
multipleItemBg: string;
/**
* @desc 多选标签边框色
* @descEN Border color of multiple tag
*/
multipleItemBorderColor: string;
/**
* @desc 多选标签高度
* @descEN Height of multiple tag
*/
multipleItemHeight: number;
/**
* @desc 大号多选标签高度
* @descEN Height of multiple tag with large size
*/
multipleItemHeightLG: number;
/**
* @desc 多选框禁用背景
* @descEN Background color of multiple selector when disabled
*/
multipleSelectorBgDisabled: string;
/**
* @desc 多选标签禁用文本颜色
* @descEN Text color of multiple tag when disabled
*/
multipleItemColorDisabled: string;
/**
* @desc 多选标签禁用边框色
* @descEN Border color of multiple tag when disabled
*/
multipleItemBorderColorDisabled: string;
/**
* @internal
*/
showArrowPaddingInlineEnd: number;
}
export interface SelectToken extends FullToken<'Select'> {
rootPrefixCls: string;
inputPaddingHorizontalBase: number | string;
multipleSelectItemHeight: number;
selectHeight: number;
}
// ============================= Selector =============================
const genSelectorStyle: GenerateStyle<SelectToken, CSSObject> = (token) => {
const { componentCls, selectorBg } = token;
return {
position: 'relative',
backgroundColor: selectorBg,
border: `${unit(token.lineWidth)} ${token.lineType} ${token.colorBorder}`,
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
input: {
cursor: 'pointer',
},
[`${componentCls}-show-search&`]: {
cursor: 'text',
input: {
cursor: 'auto',
color: 'inherit',
height: '100%',
},
},
[`${componentCls}-disabled&`]: {
color: token.colorTextDisabled,
background: token.colorBgContainerDisabled,
cursor: 'not-allowed',
[`${componentCls}-multiple&`]: {
background: token.multipleSelectorBgDisabled,
},
input: {
cursor: 'not-allowed',
},
},
};
};
// ============================== Status ==============================
const genStatusStyle = (
rootSelectCls: string,
token: SelectToken & {
borderHoverColor: string;
borderActiveColor: string;
outlineColor: string;
},
overwriteDefaultBorder: boolean = false,
): CSSObject => {
const {
componentCls,
borderHoverColor,
antCls,
borderActiveColor,
outlineColor,
controlOutlineWidth,
} = token;
const overwriteStyle: CSSObject = overwriteDefaultBorder
? {
[`${componentCls}-selector`]: {
borderColor: borderActiveColor,
},
}
: {};
return {
[rootSelectCls]: {
[`&:not(${componentCls}-disabled):not(${componentCls}-customize-input):not(${antCls}-pagination-size-changer)`]:
{
...overwriteStyle,
[`&:hover ${componentCls}-selector`]: {
borderColor: borderHoverColor,
},
[`${componentCls}-focused& ${componentCls}-selector`]: {
borderColor: borderActiveColor,
boxShadow: `0 0 0 ${unit(controlOutlineWidth)} ${outlineColor}`,
outline: 0,
},
},
},
};
};
// ============================== Styles ==============================
// /* Reset search input style */
const getSearchInputWithoutBorderStyle: GenerateStyle<SelectToken, CSSObject> = (token) => {
const { componentCls } = token;
return {
[`${componentCls}-selection-search-input`]: {
margin: 0,
padding: 0,
background: 'transparent',
border: 'none',
outline: 'none',
appearance: 'none',
fontFamily: 'inherit',
'&::-webkit-search-cancel-button': {
display: 'none',
'-webkit-appearance': 'none',
},
},
};
};
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<SelectToken> = (token) => {
const { antCls, componentCls, inputPaddingHorizontalBase, iconCls } = token;
return {
[componentCls]: {
...resetComponent(token),
position: 'relative',
display: 'inline-block',
cursor: 'pointer',
[`&:not(${componentCls}-customize-input) ${componentCls}-selector`]: {
...genSelectorStyle(token),
...getSearchInputWithoutBorderStyle(token),
},
// [`&:not(&-disabled):hover ${selectCls}-selector`]: {
// ...genHoverStyle(token),
// },
// ======================== Selection ========================
[`${componentCls}-selection-item`]: {
flex: 1,
fontWeight: 'normal',
position: 'relative',
userSelect: 'none',
...textEllipsis,
// https://github.com/ant-design/ant-design/issues/40421
[`> ${antCls}-typography`]: {
display: 'inline',
},
},
// ======================= Placeholder =======================
[`${componentCls}-selection-placeholder`]: {
...textEllipsis,
flex: 1,
color: token.colorTextPlaceholder,
pointerEvents: 'none',
},
// ========================== Arrow ==========================
[`${componentCls}-arrow`]: {
...resetIcon(),
position: 'absolute',
top: '50%',
insetInlineStart: 'auto',
insetInlineEnd: inputPaddingHorizontalBase,
height: token.fontSizeIcon,
marginTop: token.calc(token.fontSizeIcon).mul(-1).div(2).equal(),
color: token.colorTextQuaternary,
fontSize: token.fontSizeIcon,
lineHeight: 1,
textAlign: 'center',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
[iconCls]: {
verticalAlign: 'top',
transition: `transform ${token.motionDurationSlow}`,
'> svg': {
verticalAlign: 'top',
},
[`&:not(${componentCls}-suffix)`]: {
pointerEvents: 'auto',
},
},
[`${componentCls}-disabled &`]: {
cursor: 'not-allowed',
},
'> *:not(:last-child)': {
marginInlineEnd: 8, // FIXME: magic
},
},
// ========================== Clear ==========================
[`${componentCls}-clear`]: {
position: 'absolute',
top: '50%',
insetInlineStart: 'auto',
insetInlineEnd: inputPaddingHorizontalBase,
zIndex: 1,
display: 'inline-block',
width: token.fontSizeIcon,
height: token.fontSizeIcon,
marginTop: token.calc(token.fontSizeIcon).mul(-1).div(2).equal(),
color: token.colorTextQuaternary,
fontSize: token.fontSizeIcon,
fontStyle: 'normal',
lineHeight: 1,
textAlign: 'center',
textTransform: 'none',
background: token.clearBg,
cursor: 'pointer',
opacity: 0,
transition: `color ${token.motionDurationMid} ease, opacity ${token.motionDurationSlow} ease`,
textRendering: 'auto',
'&:before': {
display: 'block',
},
'&:hover': {
color: token.colorTextTertiary,
},
},
'&:hover': {
[`${componentCls}-clear`]: {
opacity: 1,
},
},
},
// ========================= Feedback ==========================
[`${componentCls}-has-feedback`]: {
[`${componentCls}-clear`]: {
insetInlineEnd: token
.calc(inputPaddingHorizontalBase)
.add(token.fontSize)
.add(token.paddingXS)
.equal(),
},
},
};
};
// ============================== Styles ==============================
const genSelectStyle: GenerateStyle<SelectToken> = (token) => {
const { componentCls } = token;
return [
{
[componentCls]: {
// ==================== BorderLess ====================
[`&-borderless ${componentCls}-selector`]: {
backgroundColor: `transparent !important`,
borderColor: `transparent !important`,
boxShadow: `none !important`,
},
// ==================== In Form ====================
[`&${componentCls}-in-form-item`]: {
width: '100%',
},
},
},
// =====================================================
// == LTR ==
// =====================================================
// Base
genBaseStyle(token),
// Single
genSingleStyle(token),
// Multiple
genMultipleStyle(token),
// Dropdown
genDropdownStyle(token),
// =====================================================
// == RTL ==
// =====================================================
{
[`${componentCls}-rtl`]: {
direction: 'rtl',
},
},
// =====================================================
// == Status ==
// =====================================================
genStatusStyle(
componentCls,
mergeToken<any>(token, {
borderHoverColor: token.colorPrimaryHover,
borderActiveColor: token.colorPrimary,
outlineColor: token.controlOutline,
}),
),
genStatusStyle(
`${componentCls}-status-error`,
mergeToken<any>(token, {
borderHoverColor: token.colorErrorHover,
borderActiveColor: token.colorError,
outlineColor: token.colorErrorOutline,
}),
true,
),
genStatusStyle(
`${componentCls}-status-warning`,
mergeToken<any>(token, {
borderHoverColor: token.colorWarningHover,
borderActiveColor: token.colorWarning,
outlineColor: token.colorWarningOutline,
}),
true,
),
// =====================================================
// == Space Compact ==
// =====================================================
genCompactItemStyle(token, {
borderElCls: `${componentCls}-selector`,
focusElCls: `${componentCls}-focused`,
}),
];
};
// ============================== Export ==============================
export const prepareComponentToken: GetDefaultToken<'Select'> = (token) => {
const {
fontSize,
lineHeight,
controlHeight,
controlPaddingHorizontal,
zIndexPopupBase,
colorText,
fontWeightStrong,
controlItemBgActive,
controlItemBgHover,
colorBgContainer,
colorFillSecondary,
controlHeightLG,
controlHeightSM,
colorBgContainerDisabled,
colorTextDisabled,
} = token;
return {
zIndexPopup: zIndexPopupBase + 50,
optionSelectedColor: colorText,
optionSelectedFontWeight: fontWeightStrong,
optionSelectedBg: controlItemBgActive,
optionActiveBg: controlItemBgHover,
optionPadding: `${(controlHeight - fontSize * lineHeight) / 2}px ${controlPaddingHorizontal}px`,
optionFontSize: fontSize,
optionLineHeight: lineHeight,
optionHeight: controlHeight,
selectorBg: colorBgContainer,
clearBg: colorBgContainer,
singleItemHeightLG: controlHeightLG,
multipleItemBg: colorFillSecondary,
multipleItemBorderColor: 'transparent',
multipleItemHeight: controlHeightSM,
multipleItemHeightLG: controlHeight,
multipleSelectorBgDisabled: colorBgContainerDisabled,
multipleItemColorDisabled: colorTextDisabled,
multipleItemBorderColorDisabled: 'transparent',
showArrowPaddingInlineEnd: Math.ceil(token.fontSize * 1.25),
};
};
export default genStyleHooks(
'Select',
(token, { rootPrefixCls }) => {
const selectToken: SelectToken = mergeToken<SelectToken>(token, {
rootPrefixCls,
inputPaddingHorizontalBase: token.calc(token.paddingSM).sub(1).equal(),
multipleSelectItemHeight: token.multipleItemHeight,
selectHeight: token.controlHeight,
});
return [genSelectStyle(selectToken)];
},
prepareComponentToken,
{
unitless: {
optionLineHeight: true,
optionSelectedFontWeight: true,
},
},
);