refactor: change Select into CSSINJS (#34339)

* chore: init

* chore: init single

* chore: merge next

* chore: select single base

* chore: basic all style of select

* chore: speed of dropdown

* chore: move size in single

* chore: single size

* chore: lg style

* chore: left & right

* chore: rtl

* chore: auto rtl

* chore: rtl

* fix: height cal

* chore: clean up

* chore: status color
This commit is contained in:
二货机器人 2022-03-08 10:29:00 +08:00 committed by GitHub
parent 465c30f8fc
commit 068584b76c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1770 additions and 472 deletions

View File

@ -42,5 +42,6 @@ const getTransitionName = (rootPrefixCls: string, motion: string, transitionName
} }
return `${rootPrefixCls}-${motion}`; return `${rootPrefixCls}-${motion}`;
}; };
export { getTransitionName, getTransitionDirection }; export { getTransitionName, getTransitionDirection };
export default collapseMotion; export default collapseMotion;

View File

@ -15,16 +15,26 @@ const defaultDesignToken: DesignToken = {
borderStyle: 'solid', borderStyle: 'solid',
borderRadius: 2, borderRadius: 2,
borderColor: new TinyColor({ h: 0, s: 0, v: 85 }).toHexString(), borderColor: new TinyColor({ h: 0, s: 0, v: 85 }).toHexString(),
borderColorSplit: new TinyColor({ h: 0, s: 0, v: 94 }).toHexString(),
easeInOut: `cubic-bezier(0.645, 0.045, 0.355, 1)`, easeInOut: `cubic-bezier(0.645, 0.045, 0.355, 1)`,
easeInOutCirc: `cubic-bezier(0.78, 0.14, 0.15, 0.86)`, easeInOutCirc: `cubic-bezier(0.78, 0.14, 0.15, 0.86)`,
easeOutBack: `cubic-bezier(0.12, 0.4, 0.29, 1.46)`, easeOutBack: `cubic-bezier(0.12, 0.4, 0.29, 1.46)`,
easeInQuint: `cubic-bezier(0.755, 0.05, 0.855, 0.06)`,
easeOutQuint: `cubic-bezier(0.23, 1, 0.32, 1)`,
outlineWidth: 2,
outlineBlurSize: 0,
fontSize: 14, fontSize: 14,
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'`,
textColor: new TinyColor('#000').setAlpha(0.85).toRgbString(), textColor: new TinyColor('#000').setAlpha(0.85).toRgbString(),
textColorSecondary: new TinyColor('#000').setAlpha(0.45).toRgbString(), textColorSecondary: new TinyColor('#000').setAlpha(0.45).toRgbString(),
textColorDisabled: new TinyColor('#000').setAlpha(0.25).toRgbString(), textColorDisabled: new TinyColor('#000').setAlpha(0.25).toRgbString(),
textColorInverse: '#fff', textColorInverse: '#fff',
placeholderColor: new TinyColor({ h: 0, s: 0, v: 75 }).setAlpha(0.5).toRgbString(),
headingColor: new TinyColor('#000').setAlpha(0.85).toRgbString(), headingColor: new TinyColor('#000').setAlpha(0.85).toRgbString(),
@ -32,7 +42,7 @@ const defaultDesignToken: DesignToken = {
itemHoverBackground: '#f5f5f5', itemHoverBackground: '#f5f5f5',
height: 32, controlHeight: 32,
padding: 16, padding: 16,
margin: 16, margin: 16,
@ -47,6 +57,8 @@ const defaultDesignToken: DesignToken = {
componentBackgroundDisabled: new TinyColor({ h: 0, s: 0, v: 96 }).toHexString(), componentBackgroundDisabled: new TinyColor({ h: 0, s: 0, v: 96 }).toHexString(),
duration: 0.3, duration: 0.3,
zIndexDropdown: 1050,
}; };
export default defaultDesignToken; export default defaultDesignToken;

View File

@ -1,11 +1,35 @@
import React from 'react'; import React from 'react';
import { generate } from '@ant-design/colors'; import { generate } from '@ant-design/colors';
import { TinyColor } from '@ctrl/tinycolor';
import { CSSObject, Theme, useCacheToken, useStyleRegister } from '@ant-design/cssinjs'; import { CSSObject, Theme, useCacheToken, useStyleRegister } from '@ant-design/cssinjs';
import defaultDesignToken from './default'; import defaultDesignToken from './default';
import version from '../../version'; import version from '../../version';
import { resetComponent } from './util'; import { resetComponent, resetIcon } from './util';
import {
initSlideMotion,
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
slideLeftIn,
slideLeftOut,
slideRightIn,
slideRightOut,
} from './util/slide';
export { resetComponent }; export {
resetComponent,
resetIcon,
initSlideMotion,
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
slideLeftIn,
slideLeftOut,
slideRightIn,
slideRightOut,
};
export interface DesignToken { export interface DesignToken {
primaryColor: string; primaryColor: string;
@ -19,24 +43,32 @@ export interface DesignToken {
borderStyle: string; borderStyle: string;
borderRadius: number; borderRadius: number;
borderColor: string; borderColor: string;
borderColorSplit: string;
easeInOut: string; easeInOut: string;
easeInOutCirc: string; easeInOutCirc: string;
easeOutBack: string; easeOutBack: string;
easeInQuint: string;
easeOutQuint: string;
outlineWidth: number;
outlineBlurSize: number;
fontSize: number; fontSize: number;
fontFamily: string;
textColor: string; textColor: string;
textColorSecondary: string; textColorSecondary: string;
textColorDisabled: string; textColorDisabled: string;
textColorInverse: string; textColorInverse: string;
placeholderColor: string;
headingColor: string;
iconColorHover: string; iconColorHover: string;
headingColor: string;
itemHoverBackground: string; itemHoverBackground: string;
height: number; controlHeight: number;
padding: number; padding: number;
margin: number; margin: number;
@ -48,25 +80,40 @@ export interface DesignToken {
componentBackgroundDisabled: string; componentBackgroundDisabled: string;
duration: number; duration: number;
zIndexDropdown: number;
boxShadow?: string;
} }
/** This is temporary token definition since final token definition is not ready yet. */ /** This is temporary token definition since final token definition is not ready yet. */
export interface DerivativeToken extends Omit<DesignToken, 'duration'> { export interface DerivativeToken extends Omit<DesignToken, 'duration'> {
primaryHoverColor: string; primaryHoverColor: string;
primaryActiveColor: string; primaryActiveColor: string;
primaryOutlineColor: string;
errorHoverColor: string; errorHoverColor: string;
errorActiveColor: string; errorActiveColor: string;
errorOutlineColor: string;
warningHoverColor: string;
warningOutlineColor: string;
itemActiveBackground: string; itemActiveBackground: string;
linkColor: string; linkColor: string;
fontSizeSM: number; fontSizeSM: number;
fontSizeLG: number; fontSizeLG: number;
heightSM: number; /** @private Only Used for control inside component like Multiple Select inner selection item */
heightLG: number; controlHeightXS: number;
controlHeightSM: number;
controlHeightLG: number;
controlPaddingHorizontal: number;
controlPaddingHorizontalSM: number;
paddingSM: number;
paddingXS: number; paddingXS: number;
paddingXXS: number;
marginXS: number; marginXS: number;
duration: string; duration: string;
durationMid: string;
durationFast: string; durationFast: string;
// TMP // TMP
@ -77,30 +124,53 @@ export { useStyleRegister };
// =============================== Derivative =============================== // =============================== Derivative ===============================
function derivative(designToken: DesignToken): DerivativeToken { function derivative(designToken: DesignToken): DerivativeToken {
const primaryColors = generate(designToken.primaryColor); const { primaryColor, errorColor, warningColor } = designToken;
const errorColors = generate(designToken.errorColor);
const primaryColors = generate(primaryColor);
const errorColors = generate(errorColor);
const warningColors = generate(warningColor);
const paddingSM = (designToken.padding / 4) * 3;
const paddingXS = designToken.padding * 0.5;
return { return {
// FIXME: Need design token
boxShadow: `
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05)`,
...designToken, ...designToken,
tmpPrimaryHoverColorWeak: primaryColors[0], tmpPrimaryHoverColorWeak: primaryColors[0],
primaryHoverColor: primaryColors[4], primaryHoverColor: primaryColors[4],
primaryActiveColor: primaryColors[6], primaryActiveColor: primaryColors[6],
primaryOutlineColor: new TinyColor(primaryColor).setAlpha(0.2).toRgbString(),
errorHoverColor: errorColors[4], errorHoverColor: errorColors[4],
errorActiveColor: errorColors[6], errorActiveColor: errorColors[6],
errorOutlineColor: new TinyColor(errorColor).setAlpha(0.2).toRgbString(),
itemActiveBackground: primaryColors[1], warningHoverColor: warningColors[4],
warningOutlineColor: new TinyColor(warningColor).setAlpha(0.2).toRgbString(),
linkColor: designToken.primaryColor, itemActiveBackground: primaryColors[0],
linkColor: primaryColor,
fontSizeSM: designToken.fontSize - 2, fontSizeSM: designToken.fontSize - 2,
fontSizeLG: designToken.fontSize + 2, fontSizeLG: designToken.fontSize + 2,
heightSM: designToken.height * 0.75, controlHeightXS: designToken.controlHeight / 2,
heightLG: designToken.height * 1.25, controlHeightSM: designToken.controlHeight * 0.75,
paddingXS: designToken.padding * 0.5, controlHeightLG: designToken.controlHeight * 1.25,
controlPaddingHorizontal: paddingSM,
controlPaddingHorizontalSM: paddingXS,
paddingSM,
paddingXS,
paddingXXS: designToken.padding * 0.25,
marginXS: designToken.margin * 0.5, marginXS: designToken.margin * 0.5,
duration: `${designToken.duration}s`, duration: `${designToken.duration}s`,
durationMid: `${(designToken.duration / 3) * 2}s`,
durationFast: `${designToken.duration / 3}s`, durationFast: `${designToken.duration / 3}s`,
}; };
} }

View File

@ -1,15 +0,0 @@
/* eslint-disable import/prefer-default-export */
import { CSSObject } from '@ant-design/cssinjs';
import type { DerivativeToken } from '.';
export const resetComponent = (token: DerivativeToken): CSSObject => ({
boxSizing: 'border-box',
margin: 0,
padding: 0,
color: token.textColor,
fontSize: token.fontSize,
// font-variant: @font-variant-base;
lineHeight: token.lineHeight,
listStyle: 'none',
// font-feature-settings: @font-feature-settings-base;
});

View File

@ -0,0 +1,41 @@
/* eslint-disable import/prefer-default-export */
import { CSSObject } from '@ant-design/cssinjs';
import type { DerivativeToken } from '..';
export const resetComponent = (token: DerivativeToken): CSSObject => ({
boxSizing: 'border-box',
margin: 0,
padding: 0,
color: token.textColor,
fontSize: token.fontSize,
// font-variant: @font-variant-base;
lineHeight: token.lineHeight,
listStyle: 'none',
// font-feature-settings: @font-feature-settings-base;
});
export const resetIcon = (): CSSObject => ({
display: 'inline-block',
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',
},
'& &-icon': {
display: 'block',
},
});

View File

@ -0,0 +1,52 @@
/* eslint-disable import/prefer-default-export */
import { CSSObject, Keyframes } from '@ant-design/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 = (
hashId: string,
motionName: string,
inKeyframes: Keyframes,
outKeyframes: Keyframes,
duration: string,
): CSSObject => {
const motionCls = `.${motionName}`;
return {
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
...initMotionCommon(duration),
animationPlayState: 'paused',
},
[`${motionCls}-leave`]: {
...initMotionCommonLeave(duration),
animationPlayState: 'paused',
},
[`
${motionCls}-enter${motionCls}-enter-active,
${motionCls}-appear${motionCls}-appear-active
`]: {
animationName: inKeyframes.getName(hashId),
animationPlayState: 'running',
},
[`${motionCls}-leave${motionCls}-leave-active`]: {
animationName: outKeyframes.getName(hashId),
animationPlayState: 'running',
pointerEvents: 'none',
},
};
};

View File

@ -0,0 +1,145 @@
import { CSSInterpolation, Keyframes } from '@ant-design/cssinjs';
import type { DerivativeToken } from '..';
import { initMotion } from './motion';
export const initSlideMotion = (
hashId: string,
rootPrefixCls: string,
motionName: string,
inKeyframes: Keyframes,
outKeyframes: Keyframes,
token: DerivativeToken,
): CSSInterpolation => {
const rootMotionName = `${rootPrefixCls}-${motionName}`;
const motionCls = `.${rootMotionName}`;
return [
initMotion(hashId, rootMotionName, inKeyframes, outKeyframes, token.durationMid),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
opacity: 0,
animationTimingFunction: token.easeOutQuint,
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.easeInQuint,
},
},
];
};
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,
},
});

View File

@ -15,33 +15,39 @@ import {
// FIXME: missing token // FIXME: missing token
type AlertToken = DerivativeToken & { type AlertToken = DerivativeToken & {
alertMessageColor: string, alertMessageColor: string;
alertCloseColor: string, alertCloseColor: string;
alertCloseHoverColor: string, alertCloseHoverColor: string;
alertInfoBgColor: string, alertInfoBgColor: string;
alertInfoIconColor: string, alertInfoIconColor: string;
alertInfoBorderColor: string, alertInfoBorderColor: string;
alertSuccessBgColor: string, alertSuccessBgColor: string;
alertSuccessIconColor: string, alertSuccessIconColor: string;
alertSuccessBorderColor: string, alertSuccessBorderColor: string;
alertWarningBgColor: string, alertWarningBgColor: string;
alertWarningIconColor: string, alertWarningIconColor: string;
alertWarningBorderColor: string, alertWarningBorderColor: string;
alertErrorBgColor: string, alertErrorBgColor: string;
alertErrorIconColor: string, alertErrorIconColor: string;
alertErrorBorderColor: string, alertErrorBorderColor: string;
alertWithDescriptionIconSize: number, alertWithDescriptionIconSize: number;
alertWithDescriptionPadding: string, alertWithDescriptionPadding: string;
alertWithDescriptionPaddingVertical: number, alertWithDescriptionPaddingVertical: number;
alertWithDescriptionNoIconPaddingVertical: number, alertWithDescriptionNoIconPaddingVertical: number;
} };
const genAlertTypeStyle = (bgColor: string, borderColor: string, iconColor: string, token: AlertToken, alertCls: string): CSSObject => ({ const genAlertTypeStyle = (
bgColor: string,
borderColor: string,
iconColor: string,
token: AlertToken,
alertCls: string,
): CSSObject => ({
backgroundColor: bgColor, backgroundColor: bgColor,
border: `${token.borderWidth}px ${token.borderStyle} ${borderColor}`, border: `${token.borderWidth}px ${token.borderStyle} ${borderColor}`,
[`${alertCls}-icon`]: { [`${alertCls}-icon`]: {
@ -164,11 +170,35 @@ export const genTypeStyle = (alertCls: string, token: AlertToken): CSSObject =>
return { return {
[alertCls]: { [alertCls]: {
'&-success': genAlertTypeStyle(alertSuccessBgColor, alertSuccessBorderColor, alertSuccessIconColor, token, alertCls), '&-success': genAlertTypeStyle(
'&-info': genAlertTypeStyle(alertInfoBgColor, alertInfoBorderColor, alertInfoIconColor, token, alertCls), alertSuccessBgColor,
'&-warning': genAlertTypeStyle(alertWarningBgColor, alertWarningBorderColor, alertWarningIconColor, token, alertCls), alertSuccessBorderColor,
alertSuccessIconColor,
token,
alertCls,
),
'&-info': genAlertTypeStyle(
alertInfoBgColor,
alertInfoBorderColor,
alertInfoIconColor,
token,
alertCls,
),
'&-warning': genAlertTypeStyle(
alertWarningBgColor,
alertWarningBorderColor,
alertWarningIconColor,
token,
alertCls,
),
'&-error': { '&-error': {
...genAlertTypeStyle(alertErrorBgColor, alertErrorBorderColor, alertErrorIconColor, token, alertCls), ...genAlertTypeStyle(
alertErrorBgColor,
alertErrorBorderColor,
alertErrorIconColor,
token,
alertCls,
),
[`${alertCls}-description > pre`]: { [`${alertCls}-description > pre`]: {
margin: 0, margin: 0,
padding: 0, padding: 0,
@ -178,14 +208,12 @@ export const genTypeStyle = (alertCls: string, token: AlertToken): CSSObject =>
}; };
}; };
export const genActionStyle = (alertCls: string, iconPrefixCls: string, token: AlertToken): CSSObject => { export const genActionStyle = (
const { alertCls: string,
duration, iconPrefixCls: string,
marginXS, token: AlertToken,
fontSizeSM, ): CSSObject => {
alertCloseColor, const { duration, marginXS, fontSizeSM, alertCloseColor, alertCloseHoverColor } = token;
alertCloseHoverColor,
} = token;
return { return {
[alertCls]: { [alertCls]: {
@ -225,10 +253,7 @@ export const genActionStyle = (alertCls: string, iconPrefixCls: string, token: A
}; };
export const genRTLStyle = (alertCls: string, token: AlertToken): CSSObject => { export const genRTLStyle = (alertCls: string, token: AlertToken): CSSObject => {
const { const { alertWithDescriptionIconSize, alertWithDescriptionPaddingVertical } = token;
alertWithDescriptionIconSize,
alertWithDescriptionPaddingVertical,
} = token;
return { return {
[alertCls]: { [alertCls]: {
@ -314,7 +339,10 @@ export const genAlertStyle = (
]; ];
}; };
export default function useStyle(prefixCls: string, iconPrefixCls: string): UseComponentStyleResult { export default function useStyle(
prefixCls: string,
iconPrefixCls: string,
): UseComponentStyleResult {
const [theme, token, hashId] = useToken(); const [theme, token, hashId] = useToken();
return [ return [

View File

@ -54,16 +54,16 @@ const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject
// ============================== Shape =============================== // ============================== Shape ===============================
const genCircleButtonStyle = (token: DerivativeToken): CSSObject => ({ const genCircleButtonStyle = (token: DerivativeToken): CSSObject => ({
minWidth: token.height, minWidth: token.controlHeight,
paddingLeft: 0, paddingLeft: 0,
paddingRight: 0, paddingRight: 0,
borderRadius: '50%', borderRadius: '50%',
}); });
const genRoundButtonStyle = (token: DerivativeToken): CSSObject => ({ const genRoundButtonStyle = (token: DerivativeToken): CSSObject => ({
borderRadius: token.height, borderRadius: token.controlHeight,
paddingLeft: token.height / 2, paddingLeft: token.controlHeight / 2,
paddingRight: token.height / 2, paddingRight: token.controlHeight / 2,
width: 'auto', width: 'auto',
}); });
@ -296,7 +296,7 @@ const genSizeButtonStyle = (
): CSSInterpolation => { ): CSSInterpolation => {
const paddingVertical = Math.max( const paddingVertical = Math.max(
0, 0,
(token.height - token.fontSize * token.lineHeight) / 2 - token.borderWidth, (token.controlHeight - token.fontSize * token.lineHeight) / 2 - token.borderWidth,
); );
const paddingHorizontal = token.padding - token.borderWidth; const paddingHorizontal = token.padding - token.borderWidth;
@ -307,11 +307,11 @@ const genSizeButtonStyle = (
withPrefix( withPrefix(
{ {
fontSize: token.fontSize, fontSize: token.fontSize,
height: token.height, height: token.controlHeight,
padding: `${paddingVertical}px ${paddingHorizontal}px`, padding: `${paddingVertical}px ${paddingHorizontal}px`,
[`&${iconOnlyCls}`]: { [`&${iconOnlyCls}`]: {
width: token.height, width: token.controlHeight,
paddingLeft: 0, paddingLeft: 0,
paddingRight: 0, paddingRight: 0,
@ -357,7 +357,7 @@ const genSizeSmallButtonStyle = (
): CSSInterpolation => { ): CSSInterpolation => {
const largeToken: DerivativeToken = { const largeToken: DerivativeToken = {
...token, ...token,
height: token.heightSM, controlHeight: token.controlHeightSM,
padding: token.paddingXS, padding: token.paddingXS,
}; };
@ -371,7 +371,7 @@ const genSizeLargeButtonStyle = (
): CSSInterpolation => { ): CSSInterpolation => {
const largeToken: DerivativeToken = { const largeToken: DerivativeToken = {
...token, ...token,
height: token.heightLG, controlHeight: token.controlHeightLG,
fontSize: token.fontSizeLG, fontSize: token.fontSizeLG,
}; };

View File

@ -14,6 +14,8 @@ import { FormItemStatusContext } from '../form/context';
import { getMergedStatus, getStatusClassNames, InputStatus } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames, InputStatus } from '../_util/statusUtils';
import { getTransitionName, getTransitionDirection, SelectCommonPlacement } from '../_util/motion'; import { getTransitionName, getTransitionDirection, SelectCommonPlacement } from '../_util/motion';
import useStyle from './style';
type RawValue = string | number; type RawValue = string | number;
export { OptionProps, BaseSelectRef as RefSelectProps, BaseOptionType, DefaultOptionType }; export { OptionProps, BaseSelectRef as RefSelectProps, BaseOptionType, DefaultOptionType };
@ -71,6 +73,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
const { const {
getPopupContainer: getContextPopupContainer, getPopupContainer: getContextPopupContainer,
getPrefixCls, getPrefixCls,
iconPrefixCls,
renderEmpty, renderEmpty,
direction, direction,
virtual, virtual,
@ -81,6 +84,8 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
const prefixCls = getPrefixCls('select', customizePrefixCls); const prefixCls = getPrefixCls('select', customizePrefixCls);
const rootPrefixCls = getPrefixCls(); const rootPrefixCls = getPrefixCls();
const [wrapSSR, hashId] = useStyle(rootPrefixCls, prefixCls, iconPrefixCls);
const mode = React.useMemo(() => { const mode = React.useMemo(() => {
const { mode: m } = props as InternalSelectProps<OptionType>; const { mode: m } = props as InternalSelectProps<OptionType>;
@ -125,9 +130,13 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
const selectProps = omit(props as typeof props & { itemIcon: any }, ['suffixIcon', 'itemIcon']); const selectProps = omit(props as typeof props & { itemIcon: any }, ['suffixIcon', 'itemIcon']);
const rcSelectRtlDropDownClassName = classNames(dropdownClassName, { const rcSelectRtlDropDownClassName = classNames(
dropdownClassName,
{
[`${prefixCls}-dropdown-${direction}`]: direction === 'rtl', [`${prefixCls}-dropdown-${direction}`]: direction === 'rtl',
}); },
hashId,
);
const mergedSize = customizeSize || size; const mergedSize = customizeSize || size;
const mergedClassName = classNames( const mergedClassName = classNames(
@ -139,6 +148,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
}, },
getStatusClassNames(prefixCls, mergedStatus, hasFeedback), getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
className, className,
hashId,
); );
// ===================== Placement ===================== // ===================== Placement =====================
@ -151,7 +161,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
: ('bottomLeft' as SelectCommonPlacement); : ('bottomLeft' as SelectCommonPlacement);
}; };
return ( return wrapSSR(
<RcSelect<any, any> <RcSelect<any, any>
ref={ref as any} ref={ref as any}
virtual={virtual} virtual={virtual}
@ -177,7 +187,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
getPopupContainer={getPopupContainer || getContextPopupContainer} getPopupContainer={getPopupContainer || getContextPopupContainer}
dropdownClassName={rcSelectRtlDropDownClassName} dropdownClassName={rcSelectRtlDropDownClassName}
showArrow={hasFeedback || showArrow} showArrow={hasFeedback || showArrow}
/> />,
); );
}; };

View File

@ -0,0 +1,162 @@
import { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import {
resetComponent,
initSlideMotion,
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
} from '../../_util/theme';
import type { SelectToken } from '.';
const genItemStyle = (token: SelectToken): CSSObject => {
const { controlPaddingHorizontal } = token;
return {
position: 'relative',
display: 'block',
minHeight: token.controlHeight,
padding: `${
(token.controlHeight - token.fontSize * token.lineHeight) / 2
}px ${controlPaddingHorizontal}px`,
color: token.textColor,
fontWeight: 'normal',
fontSize: token.fontSize,
lineHeight: token.lineHeight,
};
};
export default function genSingleStyle(token: SelectToken, hashId: string): CSSInterpolation {
const { rootPrefixCls, antCls, selectCls } = token;
const selectItemCls = `${selectCls}-item`;
return [
{
[`${selectCls}-dropdown`]: {
// ========================== Popup ==========================
...resetComponent(token),
position: 'absolute',
top: -9999,
zIndex: token.zIndexDropdown,
boxSizing: 'border-box',
padding: `${token.paddingXXS}px 0`,
overflow: 'hidden',
fontSize: token.fontSize,
// Fix select render lag of long text in chrome
// https://github.com/ant-design/ant-design/issues/11456
// https://github.com/ant-design/ant-design/issues/11843
fontVariant: 'initial',
backgroundColor: token.componentBackground,
borderRadius: token.borderRadius,
outline: 'none',
boxShadow: token.boxShadow,
[`
&${antCls}-slide-up-enter${antCls}-slide-up-enter-active&-placement-bottomLeft,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active&-placement-bottomLeft
`]: {
animationName: slideUpIn.getName(hashId),
},
[`
&${antCls}-slide-up-enter${antCls}-slide-up-enter-active&-placement-topLeft,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active&-placement-topLeft
`]: {
animationName: slideDownIn.getName(hashId),
},
[`&${antCls}-slide-up-leave${antCls}-slide-up-leave-active&-placement-bottomLeft`]: {
animationName: slideUpOut.getName(hashId),
},
[`&${antCls}-slide-up-leave${antCls}-slide-up-leave-active&-placement-topLeft`]: {
animationName: slideDownOut.getName(hashId),
},
'&-hidden': {
display: 'none',
},
'&-empty': {
color: token.textColorDisabled,
},
// ========================= Options =========================
[`${selectItemCls}-empty`]: {
...genItemStyle(token),
color: token.textColorDisabled,
},
[`${selectItemCls}`]: {
...genItemStyle(token),
cursor: 'pointer',
transition: `background ${token.duration} ease`,
// =========== Group ============
'&-group': {
color: token.textColorSecondary,
fontSize: token.fontSizeSM,
cursor: 'default',
},
// =========== Option ===========
'&-option': {
display: 'flex',
'&-content': {
flex: 'auto',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
'&-state': {
flex: 'none',
},
[`&-active:not(${selectItemCls}-option-disabled)`]: {
backgroundColor: token.itemHoverBackground,
},
[`&-selected:not(${selectItemCls}-option-disabled)`]: {
color: token.textColor,
fontWeight: 600, // FIXME: Need design token?
backgroundColor: token.itemActiveBackground,
[`${selectItemCls}-option-state`]: {
color: token.primaryColor,
},
},
'&-disabled': {
[`&${selectItemCls}-option-selected`]: {
backgroundColor: token.componentBackgroundDisabled,
},
color: token.textColorDisabled,
cursor: 'not-allowed',
},
'&-grouped': {
paddingInlineStart: token.controlPaddingHorizontal * 2,
},
},
},
// =========================== RTL ===========================
'&-rtl': {
direction: 'rtl',
},
},
},
// Follow code may reuse in other components
initSlideMotion(hashId, rootPrefixCls, 'slide-up', slideUpIn, slideUpOut, token),
initSlideMotion(hashId, rootPrefixCls, 'slide-down', slideDownIn, slideDownOut, token),
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
];
}

View File

@ -1,322 +1,322 @@
@import '../../style/themes/index'; // @import '../../style/themes/index';
@import '../../style/mixins/index'; // @import '../../style/mixins/index';
@import '../../input/style/mixin'; // @import '../../input/style/mixin';
@import './single'; // @import './single';
@import './multiple'; // @import './multiple';
@import './status'; // @import './status';
@select-prefix-cls: ~'@{ant-prefix}-select'; // @select-prefix-cls: ~'@{ant-prefix}-select';
@select-height-without-border: @input-height-base - 2 * @border-width-base; // @select-height-without-border: @input-height-base - 2 * @border-width-base;
@select-dropdown-edge-child-vertical-padding: @dropdown-edge-child-vertical-padding; // @select-dropdown-edge-child-vertical-padding: @dropdown-edge-child-vertical-padding;
.select-selector() { // .select-selector() {
position: relative; // position: relative;
background-color: @select-background; // background-color: @select-background;
border: @border-width-base @border-style-base @select-border-color; // border: @border-width-base @border-style-base @select-border-color;
border-radius: @border-radius-base; // border-radius: @border-radius-base;
transition: all 0.3s @ease-in-out; // transition: all 0.3s @ease-in-out;
input { // input {
cursor: pointer; // cursor: pointer;
} // }
.@{select-prefix-cls}-show-search& { // .@{select-prefix-cls}-show-search& {
cursor: text; // cursor: text;
input { // input {
cursor: auto; // cursor: auto;
} // }
} // }
.@{select-prefix-cls}-focused:not(.@{select-prefix-cls}-disabled)& { // .@{select-prefix-cls}-focused:not(.@{select-prefix-cls}-disabled)& {
.active(); // .active();
} // }
.@{select-prefix-cls}-disabled& { // .@{select-prefix-cls}-disabled& {
color: @disabled-color; // color: @disabled-color;
background: @input-disabled-bg; // background: @input-disabled-bg;
cursor: not-allowed; // cursor: not-allowed;
.@{select-prefix-cls}-multiple& { // .@{select-prefix-cls}-multiple& {
background: @select-multiple-disabled-background; // background: @select-multiple-disabled-background;
} // }
input { // input {
cursor: not-allowed; // cursor: not-allowed;
} // }
} // }
} // }
/* Reset search input style */ // /* Reset search input style */
.select-search-input-without-border() { // .select-search-input-without-border() {
.@{select-prefix-cls}-selection-search-input { // .@{select-prefix-cls}-selection-search-input {
margin: 0; // margin: 0;
padding: 0; // padding: 0;
background: transparent; // background: transparent;
border: none; // border: none;
outline: none; // outline: none;
appearance: none; // appearance: none;
&::-webkit-search-cancel-button { // &::-webkit-search-cancel-button {
display: none; // display: none;
/* stylelint-disable-next-line property-no-vendor-prefix */ // /* stylelint-disable-next-line property-no-vendor-prefix */
-webkit-appearance: none; // -webkit-appearance: none;
} // }
} // }
} // }
.@{select-prefix-cls} { // .@{select-prefix-cls} {
.reset-component(); // .reset-component();
position: relative; // position: relative;
display: inline-block; // display: inline-block;
cursor: pointer; // cursor: pointer;
&:not(&-customize-input) &-selector { // &:not(&-customize-input) &-selector {
.select-selector(); // .select-selector();
.select-search-input-without-border(); // .select-search-input-without-border();
} // }
&:not(&-disabled):hover &-selector { // &:not(&-disabled):hover &-selector {
.hover(); // .hover();
} // }
// ======================== Selection ======================== // // ======================== Selection ========================
&-selection-item { // &-selection-item {
flex: 1; // flex: 1;
overflow: hidden; // overflow: hidden;
font-weight: normal; // font-weight: normal;
white-space: nowrap; // white-space: nowrap;
text-overflow: ellipsis; // text-overflow: ellipsis;
// IE11 css hack. `*::-ms-backdrop,` is a must have // // IE11 css hack. `*::-ms-backdrop,` is a must have
@media all and (-ms-high-contrast: none) { // @media all and (-ms-high-contrast: none) {
*::-ms-backdrop, // *::-ms-backdrop,
& { // & {
flex: auto; // flex: auto;
} // }
} // }
} // }
// ======================= Placeholder ======================= // // ======================= Placeholder =======================
&-selection-placeholder { // &-selection-placeholder {
flex: 1; // flex: 1;
overflow: hidden; // overflow: hidden;
color: @input-placeholder-color; // color: @input-placeholder-color;
white-space: nowrap; // white-space: nowrap;
text-overflow: ellipsis; // text-overflow: ellipsis;
pointer-events: none; // pointer-events: none;
// IE11 css hack. `*::-ms-backdrop,` is a must have // // IE11 css hack. `*::-ms-backdrop,` is a must have
@media all and (-ms-high-contrast: none) { // @media all and (-ms-high-contrast: none) {
*::-ms-backdrop, // *::-ms-backdrop,
& { // & {
flex: auto; // flex: auto;
} // }
} // }
} // }
// ========================== Arrow ========================== // // ========================== Arrow ==========================
&-arrow { // &-arrow {
.iconfont-mixin(); // .iconfont-mixin();
position: absolute; // position: absolute;
top: 50%; // top: 50%;
right: @control-padding-horizontal - 1px; // right: @control-padding-horizontal - 1px;
display: flex; // display: flex;
align-items: center; // align-items: center;
height: @font-size-sm; // height: @font-size-sm;
margin-top: (-@font-size-sm / 2); // margin-top: (-@font-size-sm / 2);
color: @disabled-color; // color: @disabled-color;
font-size: @font-size-sm; // font-size: @font-size-sm;
line-height: 1; // line-height: 1;
text-align: center; // text-align: center;
pointer-events: none; // pointer-events: none;
.@{iconfont-css-prefix} { // .@{iconfont-css-prefix} {
vertical-align: top; // vertical-align: top;
transition: transform 0.3s; // transition: transform 0.3s;
> svg { // > svg {
vertical-align: top; // vertical-align: top;
} // }
&:not(.@{select-prefix-cls}-suffix) { // &:not(.@{select-prefix-cls}-suffix) {
pointer-events: auto; // pointer-events: auto;
} // }
} // }
.@{select-prefix-cls}-disabled & { // .@{select-prefix-cls}-disabled & {
cursor: not-allowed; // cursor: not-allowed;
} // }
} // }
// ========================== Clear ========================== // // ========================== Clear ==========================
&-clear { // &-clear {
position: absolute; // position: absolute;
top: 50%; // top: 50%;
right: @control-padding-horizontal - 1px; // right: @control-padding-horizontal - 1px;
z-index: 1; // z-index: 1;
display: inline-block; // display: inline-block;
width: @font-size-sm; // width: @font-size-sm;
height: @font-size-sm; // height: @font-size-sm;
margin-top: (-@font-size-sm / 2); // margin-top: (-@font-size-sm / 2);
color: @disabled-color; // color: @disabled-color;
font-size: @font-size-sm; // font-size: @font-size-sm;
font-style: normal; // font-style: normal;
line-height: 1; // line-height: 1;
text-align: center; // text-align: center;
text-transform: none; // text-transform: none;
background: @select-clear-background; // background: @select-clear-background;
cursor: pointer; // cursor: pointer;
opacity: 0; // opacity: 0;
transition: color 0.3s ease, opacity 0.15s ease; // transition: color 0.3s ease, opacity 0.15s ease;
text-rendering: auto; // text-rendering: auto;
&::before { // &::before {
display: block; // display: block;
} // }
&:hover { // &:hover {
color: @text-color-secondary; // color: @text-color-secondary;
} // }
.@{select-prefix-cls}:hover & { // .@{select-prefix-cls}:hover & {
opacity: 1; // opacity: 1;
} // }
} // }
// ========================== Popup ========================== // // ========================== Popup ==========================
&-dropdown { // &-dropdown {
.reset-component(); // .reset-component();
position: absolute; // position: absolute;
top: -9999px; // top: -9999px;
left: -9999px; // left: -9999px;
z-index: @zindex-dropdown; // z-index: @zindex-dropdown;
box-sizing: border-box; // box-sizing: border-box;
padding: @select-dropdown-edge-child-vertical-padding 0; // padding: @select-dropdown-edge-child-vertical-padding 0;
overflow: hidden; // overflow: hidden;
font-size: @font-size-base; // font-size: @font-size-base;
// Fix select render lag of long text in chrome // // Fix select render lag of long text in chrome
// https://github.com/ant-design/ant-design/issues/11456 // // https://github.com/ant-design/ant-design/issues/11456
// https://github.com/ant-design/ant-design/issues/11843 // // https://github.com/ant-design/ant-design/issues/11843
font-variant: initial; // font-variant: initial;
background-color: @select-dropdown-bg; // background-color: @select-dropdown-bg;
border-radius: @border-radius-base; // border-radius: @border-radius-base;
outline: none; // outline: none;
box-shadow: @box-shadow-base; // box-shadow: @box-shadow-base;
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft, // &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft { // &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft {
animation-name: antSlideUpIn; // animation-name: antSlideUpIn;
} // }
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft, // &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft { // &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft {
animation-name: antSlideDownIn; // animation-name: antSlideDownIn;
} // }
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft { // &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft {
animation-name: antSlideUpOut; // animation-name: antSlideUpOut;
} // }
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft { // &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft {
animation-name: antSlideDownOut; // animation-name: antSlideDownOut;
} // }
&-hidden { // &-hidden {
display: none; // display: none;
} // }
&-empty { // &-empty {
color: @disabled-color; // color: @disabled-color;
} // }
} // }
// ========================= Options ========================= // // ========================= Options =========================
.item() { // .item() {
position: relative; // position: relative;
display: block; // display: block;
min-height: @select-dropdown-height; // min-height: @select-dropdown-height;
padding: @select-dropdown-vertical-padding @control-padding-horizontal; // padding: @select-dropdown-vertical-padding @control-padding-horizontal;
color: @text-color; // color: @text-color;
font-weight: normal; // font-weight: normal;
font-size: @select-dropdown-font-size; // font-size: @select-dropdown-font-size;
line-height: @select-dropdown-line-height; // line-height: @select-dropdown-line-height;
} // }
&-item-empty { // &-item-empty {
.item(); // .item();
color: @disabled-color; // color: @disabled-color;
} // }
&-item { // &-item {
.item(); // .item();
cursor: pointer; // cursor: pointer;
transition: background 0.3s ease; // transition: background 0.3s ease;
// =========== Group ============ // // =========== Group ============
&-group { // &-group {
color: @text-color-secondary; // color: @text-color-secondary;
font-size: @font-size-sm; // font-size: @font-size-sm;
cursor: default; // cursor: default;
} // }
// =========== Option =========== // // =========== Option ===========
&-option { // &-option {
display: flex; // display: flex;
&-content { // &-content {
flex: auto; // flex: auto;
overflow: hidden; // overflow: hidden;
white-space: nowrap; // white-space: nowrap;
text-overflow: ellipsis; // text-overflow: ellipsis;
} // }
&-state { // &-state {
flex: none; // flex: none;
} // }
&-active:not(&-disabled) { // &-active:not(&-disabled) {
background-color: @select-item-active-bg; // background-color: @select-item-active-bg;
} // }
&-selected:not(&-disabled) { // &-selected:not(&-disabled) {
color: @select-item-selected-color; // color: @select-item-selected-color;
font-weight: @select-item-selected-font-weight; // font-weight: @select-item-selected-font-weight;
background-color: @select-item-selected-bg; // background-color: @select-item-selected-bg;
.@{select-prefix-cls}-item-option-state { // .@{select-prefix-cls}-item-option-state {
color: @primary-color; // color: @primary-color;
} // }
} // }
&-disabled { // &-disabled {
&.@{select-prefix-cls}-item-option-selected { // &.@{select-prefix-cls}-item-option-selected {
background-color: @select-multiple-disabled-background; // background-color: @select-multiple-disabled-background;
} // }
color: @disabled-color; // color: @disabled-color;
cursor: not-allowed; // cursor: not-allowed;
} // }
&-grouped { // &-grouped {
padding-left: @control-padding-horizontal * 2; // padding-left: @control-padding-horizontal * 2;
} // }
} // }
} // }
// ============================================================ // // ============================================================
// == Size == // // == Size ==
// ============================================================ // // ============================================================
&-lg { // &-lg {
font-size: @font-size-lg; // font-size: @font-size-lg;
} // }
// no border style // // no border style
&-borderless &-selector { // &-borderless &-selector {
background-color: transparent !important; // background-color: transparent !important;
border-color: transparent !important; // border-color: transparent !important;
box-shadow: none !important; // box-shadow: none !important;
} // }
} // }
@import './rtl'; // @import './rtl';

View File

@ -1,7 +1,372 @@
import '../../style/index.less'; // import '../../style/index.less';
import './index.less'; // import './index.less';
// style dependencies // style dependencies
import '../../empty/style'; import '../../empty/style';
// deps-lint-skip: form // deps-lint-skip-all
import { CSSObject, CSSInterpolation } from '@ant-design/cssinjs';
import {
DerivativeToken,
useStyleRegister,
useToken,
resetComponent,
resetIcon,
UseComponentStyleResult,
} from '../../_util/theme';
import genSingleStyle from './single';
import genMultipleStyle from './multiple';
import genDropdownStyle from './dropdown';
export type SelectToken = DerivativeToken & {
rootPrefixCls: string;
antCls: string;
selectCls: string;
iconPrefixCls: string;
inputPaddingHorizontalBase: number;
};
// ============================= Selector =============================
const genSelectorStyle = (token: SelectToken): CSSObject => {
const { selectCls } = token;
return {
position: 'relative',
backgroundColor: token.componentBackground,
border: `${token.borderWidth}px ${token.borderStyle} ${token.borderColor}`,
borderRadius: token.borderRadius,
transition: `all ${token.duration} ${token.easeInOut}`,
input: {
cursor: 'pointer',
},
[`${selectCls}-show-search&`]: {
cursor: 'text',
input: {
cursor: 'auto',
},
},
[`${selectCls}-disabled&`]: {
color: token.textColorDisabled,
background: token.componentBackgroundDisabled,
cursor: 'not-allowed',
[`${selectCls}-multiple&`]: {
background: token.componentBackgroundDisabled,
},
input: {
cursor: 'not-allowed',
},
},
};
};
// ============================== Status ==============================
const genStatusStyle = (
rootSelectCls: string,
token: {
selectCls: string;
borderHoverColor: string;
outlineColor: string;
outlineWidth: number;
outlineBlurSize: number;
borderWidth: number;
},
overwriteDefaultBorder: boolean = false,
): CSSObject => {
const { selectCls, borderHoverColor, outlineColor } = token;
const overwriteStyle: CSSObject = overwriteDefaultBorder
? {
[`${selectCls}-selector`]: {
borderColor: borderHoverColor,
},
}
: {};
return {
[rootSelectCls]: {
[`&:not(${selectCls}-disabled):not(${selectCls}-customize-input)`]: {
...overwriteStyle,
[`${selectCls}-focused& ${selectCls}-selector`]: {
borderColor: borderHoverColor,
// FIXME: missing variable of `@input-outline-offset`
boxShadow: `0 0 ${token.outlineBlurSize}px ${token.outlineWidth}px ${outlineColor}`,
borderRightWidth: `${token.borderWidth}px !important`,
outline: 0,
},
[`&:hover ${selectCls}-selector`]: {
borderColor: borderHoverColor,
borderRightWidth: `${token.borderWidth}px !important`,
},
},
},
};
};
// ============================== Styles ==============================
// /* Reset search input style */
const getSearchInputWithoutBorderStyle = (token: SelectToken): CSSObject => {
const { selectCls } = token;
return {
[`${selectCls}-selection-search-input`]: {
margin: 0,
padding: 0,
background: 'transparent',
border: 'none',
outline: 'none',
appearance: 'none',
'&::-webkit-search-cancel-button': {
display: 'none',
'-webkit-appearance': 'none',
},
},
};
};
// =============================== Base ===============================
const genBaseStyle = (token: SelectToken): CSSObject => {
const { selectCls, iconPrefixCls, inputPaddingHorizontalBase } = token;
return {
[selectCls]: {
...resetComponent(token),
position: 'relative',
display: 'inline-block',
cursor: 'pointer',
[`&:not(&-customize-input) ${selectCls}-selector`]: {
...genSelectorStyle(token),
...getSearchInputWithoutBorderStyle(token),
},
// [`&:not(&-disabled):hover ${selectCls}-selector`]: {
// ...genHoverStyle(token),
// },
// ======================== Selection ========================
[`${selectCls}-selection-item`]: {
flex: 1,
overflow: 'hidden',
fontWeight: 'normal',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
// ======================= Placeholder =======================
[`${selectCls}-selection-placeholder`]: {
flex: 1,
overflow: 'hidden',
color: token.placeholderColor,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
pointerEvents: 'none',
},
// ========================== Arrow ==========================
[`${selectCls}-arrow`]: {
...resetIcon(),
position: 'absolute',
top: '50%',
insetInlineStart: 'auto',
insetInlineEnd: inputPaddingHorizontalBase,
width: token.fontSizeSM,
height: token.fontSizeSM,
marginTop: -token.fontSizeSM / 2,
color: token.textColorDisabled,
fontSize: token.fontSizeSM,
lineHeight: 1,
textAlign: 'center',
pointerEvents: 'none',
[`.${iconPrefixCls}`]: {
verticalAlign: 'top',
transition: `transform ${token.duration}`,
'> svg': {
verticalAlign: 'top',
},
[`&:not(${selectCls}-suffix)`]: {
pointerEvents: 'auto',
},
},
[`${selectCls}-disabled &`]: {
cursor: 'not-allowed',
},
},
// ========================== Clear ==========================
[`${selectCls}-clear`]: {
position: 'absolute',
top: '50%',
insetInlineStart: 'auto',
insetInlineEnd: inputPaddingHorizontalBase,
zIndex: 1,
display: 'inline-block',
width: token.fontSizeSM,
height: token.fontSizeSM,
marginTop: -token.fontSizeSM / 2,
color: token.textColorDisabled,
fontSize: token.fontSizeSM,
fontStyle: 'normal',
lineHeight: 1,
textAlign: 'center',
textTransform: 'none',
background: token.componentBackground,
cursor: 'pointer',
opacity: 0,
transition: `color ${token.duration} ease, opacity ${token.duration} ease`,
textRendering: 'auto',
'&:before': {
display: 'block',
},
'&:hover': {
color: token.textColorSecondary,
},
[`${selectCls}:hover &`]: {
opacity: 1,
},
},
},
// ========================= Feedback ==========================
[`${selectCls}-has-feedback`]: {
[`${selectCls}-clear`]: {
insetInlineEnd: token.padding * 2,
},
// FIXME: what's this? @MadCcc
[`${selectCls}-selection-selected-value`]: {
paddingInlineEnd: 42,
},
[`${selectCls}-feedback-icon`]: {
fontSize: token.fontSize,
textAlign: 'center',
visibility: 'visible',
animation: `zoomIn ${token.duration} ${token.easeOutBack}`,
pointerEvents: 'none',
'&:not(:first-child)': {
marginInlineStart: token.marginXS,
},
},
},
};
};
// ============================== Styles ==============================
export const genSelectStyle = (
rootPrefixCls: string,
prefixCls: string,
iconPrefixCls: string,
token: DerivativeToken,
hashId: string,
): CSSInterpolation => {
const antCls = `.${rootPrefixCls}`;
const selectCls = `.${prefixCls}`;
const inputPaddingHorizontalBase = token.controlPaddingHorizontal - 1;
const selectToken: SelectToken = {
...token,
rootPrefixCls,
antCls,
selectCls,
iconPrefixCls,
inputPaddingHorizontalBase,
};
return [
// ==================== BorderLess ====================
{
[selectCls]: {
[`&-borderless ${selectCls}-selector`]: {
backgroundColor: `transparent !important`,
borderColor: `transparent !important`,
boxShadow: `none !important`,
},
},
},
// =====================================================
// == LTR ==
// =====================================================
// Base
genBaseStyle(selectToken),
// Single
genSingleStyle(selectToken),
// Multiple
genMultipleStyle(selectToken),
// Dropdown
genDropdownStyle(selectToken, hashId),
// =====================================================
// == RTL ==
// =====================================================
{
[`${selectCls}-rtl`]: {
direction: 'rtl',
},
},
// =====================================================
// == Status ==
// =====================================================
genStatusStyle(selectCls, {
...selectToken,
borderHoverColor: token.primaryHoverColor,
outlineColor: token.primaryOutlineColor,
}),
genStatusStyle(
`${selectCls}-status-error`,
{
...selectToken,
borderHoverColor: token.errorHoverColor,
outlineColor: token.errorOutlineColor,
},
true,
),
genStatusStyle(
`${selectCls}-status-warning`,
{
...selectToken,
borderHoverColor: token.warningHoverColor,
outlineColor: token.warningOutlineColor,
},
true,
),
];
};
// ============================== Export ==============================
export default function useStyle(
rootPrefixCls: string,
prefixCls: string,
iconPrefixCls: string,
): UseComponentStyleResult {
const [theme, token, hashId] = useToken();
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [
genSelectStyle(rootPrefixCls, prefixCls, iconPrefixCls, token, hashId),
]),
hashId,
];
}

View File

@ -0,0 +1,232 @@
import { CSSObject, CSSInterpolation } from '@ant-design/cssinjs';
import type { SelectToken } from '.';
import { resetIcon } from '../../_util/theme';
const FIXED_ITEM_MARGIN = 2;
function getSelectItemStyle({ controlHeightSM, controlHeight, borderWidth }: SelectToken) {
const selectItemDist = (controlHeight - controlHeightSM) / 2 - borderWidth;
const selectItemMargin = Math.ceil(selectItemDist / 2);
return [selectItemDist, selectItemMargin];
}
function genSizeStyle(token: SelectToken, suffix?: string): CSSObject {
const { selectCls, iconPrefixCls } = token;
const selectOverflowPrefixCls = `${selectCls}-selection-overflow`;
const selectItemHeight = token.controlHeightSM;
const [selectItemDist] = getSelectItemStyle(token);
const suffixCls = suffix ? `${selectCls}-${suffix}` : '';
return {
[`${selectCls}-multiple${suffixCls}`]: {
fontSize: token.fontSize,
/**
* Do not merge `height` & `line-height` under style with `selection` & `search`, since chrome
* may update to redesign with its align logic.
*/
// =========================== Overflow ===========================
[selectOverflowPrefixCls]: {
position: 'relative',
display: 'flex',
flex: 'auto',
flexWrap: 'wrap',
maxWidth: '100%',
'&-item': {
flex: 'none',
alignSelf: 'center',
maxWidth: '100%',
display: 'inline-flex',
},
},
// ========================= Selector =========================
[`${selectCls}-selector`]: {
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
// Multiple is little different that horizontal is follow the vertical
padding: `${selectItemDist - FIXED_ITEM_MARGIN}px ${FIXED_ITEM_MARGIN * 2}px`,
[`${selectCls}-show-search&`]: {
cursor: 'text',
},
[`${selectCls}-disabled&`]: {
background: token.componentBackgroundDisabled,
cursor: 'not-allowed',
},
'&:after': {
display: 'inline-block',
width: 0,
margin: `${FIXED_ITEM_MARGIN}px 0`,
lineHeight: `${selectItemHeight}px`,
content: '"\\a0"',
},
},
[`
&${selectCls}-show-arrow ${selectCls}-selector,
&${selectCls}-allow-clear ${selectCls}-selector
`]: {
paddingInlineEnd: token.fontSizeSM + token.controlPaddingHorizontal,
},
// ======================== Selections ========================
[`${selectCls}-selection-item`]: {
position: 'relative',
display: 'flex',
flex: 'none',
boxSizing: 'border-box',
maxWidth: '100%',
height: selectItemHeight,
marginTop: FIXED_ITEM_MARGIN,
marginBottom: FIXED_ITEM_MARGIN,
lineHeight: `${selectItemHeight - token.borderWidth * 2}px`,
background: token.background,
border: `${token.borderWidth}px solid ${token.borderColorSplit}`,
borderRadius: token.borderRadius,
cursor: 'default',
transition: `font-size ${token.duration}, line-height ${token.duration}, height ${token.duration}`,
userSelect: 'none',
marginInlineEnd: FIXED_ITEM_MARGIN * 2,
paddingInlineStart: token.paddingXS,
paddingInlineEnd: token.paddingXS / 2,
[`${selectCls}-disabled&`]: {
color: token.textColorDisabled,
borderColor: token.borderColor,
cursor: 'not-allowed',
},
// It's ok not to do this, but 24px makes bottom narrow in view should adjust
'&-content': {
display: 'inline-block',
marginInlineEnd: token.paddingXS / 2,
overflow: 'hidden',
whiteSpace: 'pre', // fix whitespace wrapping. custom tags display all whitespace within.
textOverflow: 'ellipsis',
},
'&-remove': {
...resetIcon(),
display: 'inline-block',
color: token.textColorSecondary,
fontWeight: 'bold',
fontSize: 10,
lineHeight: 'inherit',
cursor: 'pointer',
[`> .${iconPrefixCls}`]: {
verticalAlign: '-0.2em',
},
'&:hover': {
color: token.iconColorHover,
},
},
},
// ========================== Input ==========================
[`${selectOverflowPrefixCls}-item + ${selectOverflowPrefixCls}-item`]: {
[`${selectCls}-selection-search`]: {
marginInlineStart: 0,
},
},
[`${selectCls}-selection-search`]: {
display: 'inline-flex',
position: 'relative',
maxWidth: '100%',
// FIXME: no sure this style
marginInlineStart: token.inputPaddingHorizontalBase - selectItemDist,
[`
&-input,
&-mirror
`]: {
height: selectItemHeight,
fontFamily: token.fontFamily,
lineHeight: `${selectItemHeight}px`,
transition: `all ${token.duration}`,
},
'&-input': {
width: '100%',
minWidth: 4.1, // fix search cursor missing
},
'&-mirror': {
position: 'absolute',
top: 0,
insetInlineStart: 0,
insetInlineEnd: 'auto',
zIndex: 999,
whiteSpace: 'pre', // fix whitespace wrapping caused width calculation bug
visibility: 'hidden',
},
},
// ======================= Placeholder =======================
[`${selectCls}-selection-placeholder `]: {
position: 'absolute',
top: '50%',
insetInlineStart: token.inputPaddingHorizontalBase,
insetInlineEnd: token.inputPaddingHorizontalBase,
transform: 'translateY(-50%)',
transition: `all ${token.duration}`,
},
},
};
}
export default function genMultipleStyle(token: SelectToken): CSSInterpolation {
const { selectCls } = token;
const smallToken: SelectToken = {
...token,
controlHeight: token.controlHeightSM,
controlHeightSM: token.controlHeightXS,
};
const [, smSelectItemMargin] = getSelectItemStyle(token);
return [
genSizeStyle(token),
// ======================== Small ========================
// Shared
genSizeStyle(smallToken, 'sm'),
// Padding
{
[`${selectCls}-multiple${selectCls}-sm`]: {
[`${selectCls}-selection-placeholder`]: {
insetInlineStart: token.controlPaddingHorizontalSM - token.borderWidth,
insetInlineEnd: 'auto',
},
// https://github.com/ant-design/ant-design/issues/29559
[`${selectCls}-selection-search`]: {
marginInlineStart: smSelectItemMargin,
},
},
},
// ======================== Large ========================
// Shared
genSizeStyle(
{
...token,
fontSize: token.fontSizeLG,
controlHeight: token.controlHeightLG,
controlHeightSM: token.controlHeight,
},
'lg',
),
];
}

View File

@ -0,0 +1,187 @@
import { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import type { SelectToken } from '.';
function genSizeStyle(token: SelectToken, suffix?: string): CSSObject {
const { selectCls, inputPaddingHorizontalBase } = token;
const selectHeightWithoutBorder = token.controlHeight - token.borderWidth * 2;
const selectionItemPadding = Math.ceil(token.fontSize * 1.25);
const suffixCls = suffix ? `${selectCls}-${suffix}` : '';
return {
[`${selectCls}-single${suffixCls}`]: {
fontSize: token.fontSize,
// ========================= Selector =========================
[`${selectCls}-selector`]: {
display: 'flex',
[`${selectCls}-selection-search`]: {
position: 'absolute',
top: 0,
left: inputPaddingHorizontalBase,
right: inputPaddingHorizontalBase,
bottom: 0,
'&-input': {
width: '100%',
},
},
[`
${selectCls}-selection-item,
${selectCls}-selection-placeholder
`]: {
padding: 0,
lineHeight: `${selectHeightWithoutBorder}px`,
transition: `all ${token.duration}`,
// Firefox inline-block position calculation is not same as Chrome & Safari. Patch this:
'@supports (-moz-appearance: meterbar) &': {
lineHeight: `${selectHeightWithoutBorder}px`,
},
},
[`${selectCls}-selection-item`]: {
position: 'relative',
userSelect: 'none',
},
[`${selectCls}-selection-placeholder`]: {
transition: 'none',
pointerEvents: 'none',
},
// For common baseline align
[[
'&:after',
/* For '' value baseline align */
`${selectCls}-selection-item:after`,
/* For undefined value baseline align */
`${selectCls}-selection-placeholder:after`,
].join(',')]: {
display: 'inline-block',
width: 0,
visibility: 'hidden',
content: '"\\a0"',
},
},
[`
&${selectCls}-show-arrow ${selectCls}-selection-item,
&${selectCls}-show-arrow ${selectCls}-selection-placeholder
`]: {
paddingInlineEnd: selectionItemPadding,
},
// Opacity selection if open
[`&${selectCls}-open ${selectCls}-selection-item`]: {
color: token.placeholderColor,
},
// ========================== Input ==========================
// We only change the style of non-customize input which is only support by `combobox` mode.
// Not customize
[`&:not(${selectCls}-customize-input)`]: {
[`${selectCls}-selector`]: {
width: '100%',
height: token.controlHeight,
padding: `0 ${inputPaddingHorizontalBase}px`,
[`${selectCls}-selection-search-input`]: {
height: selectHeightWithoutBorder,
},
'&:after': {
lineHeight: `${selectHeightWithoutBorder}px`,
},
},
},
[`&${selectCls}-customize-input`]: {
[`${selectCls}-selector`]: {
'&:after': {
display: 'none',
},
[`${selectCls}-selection-search`]: {
position: 'static',
width: '100%',
},
[`${selectCls}-selection-placeholder`]: {
position: 'absolute',
left: 0,
right: 0,
padding: `0 ${inputPaddingHorizontalBase}px`,
'&:after': {
display: 'none',
},
},
},
},
},
};
}
export default function genSingleStyle(token: SelectToken): CSSInterpolation {
const { selectCls } = token;
const inputPaddingHorizontalSM = token.controlPaddingHorizontalSM - token.borderWidth;
return [
genSizeStyle(token),
// ======================== Small ========================
// Shared
genSizeStyle(
{
...token,
controlHeight: token.controlHeightSM,
},
'sm',
),
// padding
{
[`${selectCls}-single${selectCls}-sm`]: {
[`&:not(${selectCls}-customize-input)`]: {
[`${selectCls}-selection-search`]: {
left: inputPaddingHorizontalSM,
right: inputPaddingHorizontalSM,
},
[`${selectCls}-selector`]: {
padding: `0 ${inputPaddingHorizontalSM}px`,
},
// With arrow should provides `padding-right` to show the arrow
[`&${selectCls}-show-arrow ${selectCls}-selection-search`]: {
insetInlineStart: 'auto',
insetInlineEnd: inputPaddingHorizontalSM + token.fontSize * 1.5,
},
[`
&${selectCls}-show-arrow ${selectCls}-selection-item,
&${selectCls}-show-arrow ${selectCls}-selection-placeholder
`]: {
paddingInlineEnd: token.fontSize * 1.5,
},
},
},
},
// ======================== Large ========================
// Shared
genSizeStyle(
{
...token,
controlHeight: token.controlHeightLG,
fontSize: token.fontSizeLG,
},
'lg',
),
];
}

View File

@ -1,129 +1,129 @@
.slide-motion(@className, @keyframeName) { // .slide-motion(@className, @keyframeName) {
@name: ~'@{ant-prefix}-@{className}'; // @name: ~'@{ant-prefix}-@{className}';
.make-motion(@name, @keyframeName); // .make-motion(@name, @keyframeName);
.@{name}-enter, // .@{name}-enter,
.@{name}-appear { // .@{name}-appear {
opacity: 0; // opacity: 0;
animation-timing-function: @ease-out-quint; // animation-timing-function: @ease-out-quint;
} // }
.@{name}-leave { // .@{name}-leave {
animation-timing-function: @ease-in-quint; // animation-timing-function: @ease-in-quint;
} // }
} // }
.slide-motion(slide-up, antSlideUp); // .slide-motion(slide-up, antSlideUp);
.slide-motion(slide-down, antSlideDown); // .slide-motion(slide-down, antSlideDown);
.slide-motion(slide-left, antSlideLeft); // .slide-motion(slide-left, antSlideLeft);
.slide-motion(slide-right, antSlideRight); // .slide-motion(slide-right, antSlideRight);
@keyframes antSlideUpIn { // @keyframes antSlideUpIn {
0% { // 0% {
transform: scaleY(0.8); // transform: scaleY(0.8);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 0; // opacity: 0;
} // }
100% { // 100% {
transform: scaleY(1); // transform: scaleY(1);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 1; // opacity: 1;
} // }
} // }
@keyframes antSlideUpOut { // @keyframes antSlideUpOut {
0% { // 0% {
transform: scaleY(1); // transform: scaleY(1);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 1; // opacity: 1;
} // }
100% { // 100% {
transform: scaleY(0.8); // transform: scaleY(0.8);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 0; // opacity: 0;
} // }
} // }
@keyframes antSlideDownIn { // @keyframes antSlideDownIn {
0% { // 0% {
transform: scaleY(0.8); // transform: scaleY(0.8);
transform-origin: 100% 100%; // transform-origin: 100% 100%;
opacity: 0; // opacity: 0;
} // }
100% { // 100% {
transform: scaleY(1); // transform: scaleY(1);
transform-origin: 100% 100%; // transform-origin: 100% 100%;
opacity: 1; // opacity: 1;
} // }
} // }
@keyframes antSlideDownOut { // @keyframes antSlideDownOut {
0% { // 0% {
transform: scaleY(1); // transform: scaleY(1);
transform-origin: 100% 100%; // transform-origin: 100% 100%;
opacity: 1; // opacity: 1;
} // }
100% { // 100% {
transform: scaleY(0.8); // transform: scaleY(0.8);
transform-origin: 100% 100%; // transform-origin: 100% 100%;
opacity: 0; // opacity: 0;
} // }
} // }
@keyframes antSlideLeftIn { // @keyframes antSlideLeftIn {
0% { // 0% {
transform: scaleX(0.8); // transform: scaleX(0.8);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 0; // opacity: 0;
} // }
100% { // 100% {
transform: scaleX(1); // transform: scaleX(1);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 1; // opacity: 1;
} // }
} // }
@keyframes antSlideLeftOut { // @keyframes antSlideLeftOut {
0% { // 0% {
transform: scaleX(1); // transform: scaleX(1);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 1; // opacity: 1;
} // }
100% { // 100% {
transform: scaleX(0.8); // transform: scaleX(0.8);
transform-origin: 0% 0%; // transform-origin: 0% 0%;
opacity: 0; // opacity: 0;
} // }
} // }
@keyframes antSlideRightIn { // @keyframes antSlideRightIn {
0% { // 0% {
transform: scaleX(0.8); // transform: scaleX(0.8);
transform-origin: 100% 0%; // transform-origin: 100% 0%;
opacity: 0; // opacity: 0;
} // }
100% { // 100% {
transform: scaleX(1); // transform: scaleX(1);
transform-origin: 100% 0%; // transform-origin: 100% 0%;
opacity: 1; // opacity: 1;
} // }
} // }
@keyframes antSlideRightOut { // @keyframes antSlideRightOut {
0% { // 0% {
transform: scaleX(1); // transform: scaleX(1);
transform-origin: 100% 0%; // transform-origin: 100% 0%;
opacity: 1; // opacity: 1;
} // }
100% { // 100% {
transform: scaleX(0.8); // transform: scaleX(0.8);
transform-origin: 100% 0%; // transform-origin: 100% 0%;
opacity: 0; // opacity: 0;
} // }
} // }

View File

@ -1964,7 +1964,11 @@ describe('Table.filter', () => {
expect(wrapper.find('.ant-tree-checkbox').at(0).hasClass('ant-tree-checkbox-checked')).toBe( expect(wrapper.find('.ant-tree-checkbox').at(0).hasClass('ant-tree-checkbox-checked')).toBe(
true, true,
); );
expect(wrapper.find('.ant-table-filter-dropdown-checkall .ant-checkbox').hasClass('ant-checkbox-indeterminate')).toBe(true); expect(
wrapper
.find('.ant-table-filter-dropdown-checkall .ant-checkbox')
.hasClass('ant-checkbox-indeterminate'),
).toBe(true);
}); });
it('select-all checkbox should change when all items are selected', () => { it('select-all checkbox should change when all items are selected', () => {
@ -1991,7 +1995,11 @@ describe('Table.filter', () => {
}); });
wrapper.find('.ant-tree-node-content-wrapper').at(0).simulate('click'); wrapper.find('.ant-tree-node-content-wrapper').at(0).simulate('click');
wrapper.find('.ant-tree-node-content-wrapper').at(1).simulate('click'); wrapper.find('.ant-tree-node-content-wrapper').at(1).simulate('click');
expect(wrapper.find('.ant-table-filter-dropdown-checkall .ant-checkbox').hasClass('ant-checkbox-checked')).toBe(true); expect(
wrapper
.find('.ant-table-filter-dropdown-checkall .ant-checkbox')
.hasClass('ant-checkbox-checked'),
).toBe(true);
}); });
}); });

View File

@ -492,7 +492,7 @@ export const genTreeStyle = (
const treeNodeCls = `${treeCls}-treenode`; const treeNodeCls = `${treeCls}-treenode`;
const treeNodePadding = token.paddingXS / 2; const treeNodePadding = token.paddingXS / 2;
const treeTitleHeight = token.heightSM; const treeTitleHeight = token.controlHeightSM;
const treeToken = { const treeToken = {
...token, ...token,