feat: add useComponentStyle (#34802)

* feat: add useComponentStyle

* chore: auto generic

* feat: add overload

* feat: filter component token

* feat: genComponentStyleHook

* test: fix lint

* feat: button for test case

* chore: code clean

* chore: code clean

* feat: add componentCls into token

* feat: merge token statistic

* chore: code clean

* fix: cache token merge

* chore: code clean

* chore: add comment & rename variables

* chore: code  enhancement

* chore: FullToken<ComponentName>

* chore: clean input

* chore: code clean

* test: fix lint

* feat: inset prefixCls

Co-authored-by: zombiej <smith3816@gmail.com>
This commit is contained in:
MadCcc 2022-04-02 22:55:02 +08:00 committed by GitHub
parent bffdf0dee5
commit d0c4d7b0ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 136 deletions

View File

@ -0,0 +1,67 @@
/* eslint-disable no-redeclare */
import { CSSInterpolation, useStyleRegister } from '@ant-design/cssinjs';
import { useContext } from 'react';
import { GlobalToken, OverrideToken } from '../theme/interface';
import { mergeToken, statisticToken, UseComponentStyleResult, useToken } from '../theme';
import { ConfigContext } from '../../config-provider';
export type OverrideTokenWithoutDerivative = Omit<OverrideToken, 'derivative'>;
export type OverrideComponent = keyof OverrideTokenWithoutDerivative;
export type GlobalTokenWithComponent<ComponentName extends OverrideComponent> = GlobalToken &
OverrideToken[ComponentName];
export type StyleInfo = {
hashId: string;
prefixCls: string;
rootPrefixCls: string;
iconPrefixCls: string;
};
export type TokenWithComponentCls<T> = T & { componentCls: string; prefixCls: string };
export type FullToken<ComponentName extends OverrideComponent> = TokenWithComponentCls<
GlobalTokenWithComponent<ComponentName>
>;
function genComponentStyleHook<ComponentName extends OverrideComponent>(
component: ComponentName,
styleFn: (token: FullToken<ComponentName>, info: StyleInfo) => CSSInterpolation,
getDefaultToken?:
| OverrideTokenWithoutDerivative[ComponentName]
| ((token: GlobalToken) => OverrideTokenWithoutDerivative[ComponentName]),
) {
return (prefixCls: string): UseComponentStyleResult => {
const [theme, token, hashId] = useToken();
const { getPrefixCls, iconPrefixCls } = useContext(ConfigContext);
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => {
const { token: proxyToken, flush } = statisticToken(token);
const defaultComponentToken =
typeof getDefaultToken === 'function' ? getDefaultToken(token) : getDefaultToken;
const overrideComponentToken = token[component] as any;
// Only merge token specified in interface
const mergedComponentToken = { ...defaultComponentToken } as any;
if (overrideComponentToken) {
Object.keys(mergedComponentToken).forEach(key => {
mergedComponentToken[key] = overrideComponentToken[key] ?? mergedComponentToken[key];
});
}
const mergedToken = mergeToken<
TokenWithComponentCls<GlobalTokenWithComponent<OverrideComponent>>
>(proxyToken, { componentCls: `.${prefixCls}`, prefixCls }, mergedComponentToken);
const styleInterpolation = styleFn(mergedToken as unknown as FullToken<ComponentName>, {
hashId,
prefixCls,
rootPrefixCls: getPrefixCls(),
iconPrefixCls,
});
flush(component);
return styleInterpolation;
}),
hashId,
];
};
}
export default genComponentStyleHook;

View File

@ -154,12 +154,11 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
...rest ...rest
} = props; } = props;
const { getPrefixCls, autoInsertSpaceInButton, direction, iconPrefixCls } = const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext);
React.useContext(ConfigContext);
const prefixCls = getPrefixCls('btn', customizePrefixCls); const prefixCls = getPrefixCls('btn', customizePrefixCls);
// Style // Style
const [wrapSSR, hashId] = useStyle(prefixCls, iconPrefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
const size = React.useContext(SizeContext); const size = React.useContext(SizeContext);
const [innerLoading, setLoading] = React.useState<Loading>(!!loading); const [innerLoading, setLoading] = React.useState<Loading>(!!loading);

View File

@ -1,15 +1,8 @@
// deps-lint-skip-all // deps-lint-skip-all
import { CSSInterpolation, CSSObject } from '@ant-design/cssinjs'; import { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { TinyColor } from '@ctrl/tinycolor'; import { TinyColor } from '@ctrl/tinycolor';
import { import genComponentStyleHook, { FullToken } from '../../_util/hooks/genComponentStyleHook';
AliasToken, import { GenerateStyle, mergeToken } from '../../_util/theme';
UseComponentStyleResult,
useStyleRegister,
useToken,
GenerateStyle,
statisticToken,
mergeToken,
} from '../../_util/theme';
/** Component only token. Which will handle additional calculation of alias token */ /** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken { export interface ComponentToken {
@ -17,17 +10,16 @@ export interface ComponentToken {
colorBgTextActive: string; colorBgTextActive: string;
} }
interface ButtonToken extends AliasToken, ComponentToken { interface ButtonToken extends FullToken<'Button'> {
btnCls: string;
iconPrefixCls: string; iconPrefixCls: string;
} }
// ============================== Shared ============================== // ============================== Shared ==============================
const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => { const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => {
const { btnCls, iconPrefixCls } = token; const { componentCls, iconPrefixCls } = token;
return { return {
[btnCls]: { [componentCls]: {
outline: 'none', outline: 'none',
position: 'relative', position: 'relative',
display: 'inline-block', display: 'inline-block',
@ -53,7 +45,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
marginInlineStart: token.marginXS, marginInlineStart: token.marginXS,
}, },
[`&${btnCls}-block`]: { [`&${componentCls}-block`]: {
width: '100%', width: '100%',
}, },
}, },
@ -148,14 +140,14 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
), ),
...genGhostButtonStyle( ...genGhostButtonStyle(
token.btnCls, token.componentCls,
token.colorBgComponent, token.colorBgComponent,
token.colorBgComponent, token.colorBgComponent,
token.colorTextDisabled, token.colorTextDisabled,
token.colorBorder, token.colorBorder,
), ),
[`&${token.btnCls}-dangerous`]: { [`&${token.componentCls}-dangerous`]: {
color: token.colorError, color: token.colorError,
borderColor: token.colorError, borderColor: token.colorError,
@ -171,7 +163,7 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
), ),
...genGhostButtonStyle( ...genGhostButtonStyle(
token.btnCls, token.componentCls,
token.colorError, token.colorError,
token.colorError, token.colorError,
token.colorTextDisabled, token.colorTextDisabled,
@ -200,14 +192,14 @@ const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
), ),
...genGhostButtonStyle( ...genGhostButtonStyle(
token.btnCls, token.componentCls,
token.colorPrimary, token.colorPrimary,
token.colorPrimary, token.colorPrimary,
token.colorTextDisabled, token.colorTextDisabled,
token.colorBorder, token.colorBorder,
), ),
[`&${token.btnCls}-dangerous`]: { [`&${token.componentCls}-dangerous`]: {
backgroundColor: token.colorError, backgroundColor: token.colorError,
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`, boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
@ -221,7 +213,7 @@ const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
), ),
...genGhostButtonStyle( ...genGhostButtonStyle(
token.btnCls, token.componentCls,
token.colorError, token.colorError,
token.colorError, token.colorError,
token.colorTextDisabled, token.colorTextDisabled,
@ -253,7 +245,7 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genPureDisabledButtonStyle(token), ...genPureDisabledButtonStyle(token),
[`&${token.btnCls}-dangerous`]: { [`&${token.componentCls}-dangerous`]: {
color: token.colorError, color: token.colorError,
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
@ -284,7 +276,7 @@ const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genPureDisabledButtonStyle(token), ...genPureDisabledButtonStyle(token),
[`&${token.btnCls}-dangerous`]: { [`&${token.componentCls}-dangerous`]: {
color: token.colorError, color: token.colorError,
...genPureDisabledButtonStyle(token), ...genPureDisabledButtonStyle(token),
@ -292,20 +284,20 @@ const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
}); });
const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => { const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => {
const { btnCls } = token; const { componentCls } = token;
return { return {
[`${btnCls}-default`]: genDefaultButtonStyle(token), [`${componentCls}-default`]: genDefaultButtonStyle(token),
[`${btnCls}-primary`]: genPrimaryButtonStyle(token), [`${componentCls}-primary`]: genPrimaryButtonStyle(token),
[`${btnCls}-dashed`]: genDashedButtonStyle(token), [`${componentCls}-dashed`]: genDashedButtonStyle(token),
[`${btnCls}-link`]: genLinkButtonStyle(token), [`${componentCls}-link`]: genLinkButtonStyle(token),
[`${btnCls}-text`]: genTextButtonStyle(token), [`${componentCls}-text`]: genTextButtonStyle(token),
}; };
}; };
// =============================== Size =============================== // =============================== Size ===============================
const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSSInterpolation => { const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSSInterpolation => {
const { btnCls, iconPrefixCls } = token; const { componentCls, iconPrefixCls } = token;
const paddingVertical = Math.max( const paddingVertical = Math.max(
0, 0,
@ -313,12 +305,12 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
); );
const paddingHorizontal = token.padding - token.controlLineWidth; const paddingHorizontal = token.padding - token.controlLineWidth;
const iconOnlyCls = `${btnCls}-icon-only`; const iconOnlyCls = `${componentCls}-icon-only`;
return [ return [
// Size // Size
{ {
[`${btnCls}${sizePrefixCls}`]: { [`${componentCls}${sizePrefixCls}`]: {
fontSize: token.fontSize, fontSize: token.fontSize,
height: token.controlHeight, height: token.controlHeight,
padding: `${paddingVertical}px ${paddingHorizontal}px`, padding: `${paddingVertical}px ${paddingHorizontal}px`,
@ -334,27 +326,28 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
}, },
// Loading // Loading
[`&${btnCls}-loading`]: { [`&${componentCls}-loading`]: {
opacity: 0.65, opacity: 0.65,
cursor: 'default', cursor: 'default',
}, },
[`${btnCls}-loading-icon`]: { [`${componentCls}-loading-icon`]: {
transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`, transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`,
}, },
[`&:not(${iconOnlyCls}) ${btnCls}-loading-icon:not(:only-child) > .${iconPrefixCls}`]: { [`&:not(${iconOnlyCls}) ${componentCls}-loading-icon:not(:only-child) > .${iconPrefixCls}`]:
marginInlineEnd: token.marginXS, {
}, marginInlineEnd: token.marginXS,
},
}, },
}, },
// Shape - patch prefixCls again to override solid border radius style // Shape - patch prefixCls again to override solid border radius style
{ {
[`${btnCls}${btnCls}-circle${sizePrefixCls}`]: genCircleButtonStyle(token), [`${componentCls}${componentCls}-circle${sizePrefixCls}`]: genCircleButtonStyle(token),
}, },
{ {
[`${btnCls}${btnCls}-round${sizePrefixCls}`]: genRoundButtonStyle(token), [`${componentCls}${componentCls}-round${sizePrefixCls}`]: genRoundButtonStyle(token),
}, },
]; ];
}; };
@ -367,7 +360,7 @@ const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => {
padding: token.paddingXS, padding: token.paddingXS,
}); });
return genSizeButtonStyle(largeToken, `${token.btnCls}-sm`); return genSizeButtonStyle(largeToken, `${token.componentCls}-sm`);
}; };
const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = token => { const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = token => {
@ -376,60 +369,44 @@ const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = token => {
fontSize: token.fontSizeLG, fontSize: token.fontSizeLG,
}); });
return genSizeButtonStyle(largeToken, `${token.btnCls}-lg`); return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`);
}; };
// ============================== Export ============================== // ============================== Export ==============================
export default function useStyle( export default genComponentStyleHook(
prefixCls: string, 'Button',
iconPrefixCls: string, (token, { iconPrefixCls }) => {
): UseComponentStyleResult { const buttonToken: ButtonToken = {
const [theme, token, hashId] = useToken(); ...token,
iconPrefixCls,
};
return [ return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => { // Shared
const { colorText, Button = {} } = token; genSharedButtonStyle(buttonToken),
const textColor = new TinyColor(colorText);
const { token: proxyToken, flush } = statisticToken(token); // Size
genSizeSmallButtonStyle(buttonToken),
genSizeBaseButtonStyle(buttonToken),
genSizeLargeButtonStyle(buttonToken),
const buttonToken = mergeToken<ButtonToken>( // Group (type, ghost, danger, disabled, loading)
proxyToken, genTypeButtonStyle(buttonToken),
{ ];
colorBgTextHover: textColor },
.clone() token => {
.setAlpha(textColor.getAlpha() * 0.02) const { colorText } = token;
.toRgbString(), const textColor = new TinyColor(colorText);
colorBgTextActive: textColor
.clone()
.setAlpha(textColor.getAlpha() * 0.03)
.toRgbString(),
iconPrefixCls, return {
btnCls: `.${prefixCls}`, colorBgTextHover: textColor
}, .clone()
.setAlpha(textColor.getAlpha() * 0.02)
// Override .toRgbString(),
Button, colorBgTextActive: textColor
); .clone()
.setAlpha(textColor.getAlpha() * 0.03)
const styles = [ .toRgbString(),
// Shared };
genSharedButtonStyle(buttonToken), },
);
// Size
genSizeSmallButtonStyle(buttonToken),
genSizeBaseButtonStyle(buttonToken),
genSizeLargeButtonStyle(buttonToken),
// Group (type, ghost, danger, disabled, loading)
genTypeButtonStyle(buttonToken),
];
flush('Button');
return styles;
}),
hashId,
];
}

View File

@ -8,13 +8,8 @@
// // deps-lint-skip: form // // deps-lint-skip: form
// deps-lint-skip-all // deps-lint-skip-all
import { import genComponentStyleHook, { FullToken } from '../../_util/hooks/genComponentStyleHook';
DerivativeToken, import { GenerateStyle } from '../../_util/theme';
useStyleRegister,
useToken,
UseComponentStyleResult,
GenerateStyle,
} from '../../_util/theme';
import { getStyle as getCheckboxStyle } from '../../checkbox/style'; import { getStyle as getCheckboxStyle } from '../../checkbox/style';
export interface ComponentToken { export interface ComponentToken {
@ -23,15 +18,12 @@ export interface ComponentToken {
dropdownHeight: number; dropdownHeight: number;
} }
interface CascaderToken extends DerivativeToken, ComponentToken { type CascaderToken = FullToken<'Cascader'>;
prefixCls: string;
cascaderCls: string;
}
// =============================== Base =============================== // =============================== Base ===============================
const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => { const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => {
const { prefixCls, cascaderCls } = token; const { prefixCls, componentCls } = token;
const cascaderMenuItemCls = `${cascaderCls}-menu-item`; const cascaderMenuItemCls = `${componentCls}-menu-item`;
const iconCls = ` const iconCls = `
&${cascaderMenuItemCls}-expand ${cascaderMenuItemCls}-expand-icon, &${cascaderMenuItemCls}-expand ${cascaderMenuItemCls}-expand-icon,
${cascaderMenuItemCls}-loading-icon ${cascaderMenuItemCls}-loading-icon
@ -46,7 +38,7 @@ const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => {
// == Control == // == Control ==
// ===================================================== // =====================================================
{ {
[cascaderCls]: { [componentCls]: {
width: token.controlWidth, width: token.controlWidth,
}, },
}, },
@ -55,11 +47,11 @@ const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => {
// == Popup == // == Popup ==
// ===================================================== // =====================================================
{ {
[`${cascaderCls}-dropdown`]: [ [`${componentCls}-dropdown`]: [
// ==================== Checkbox ==================== // ==================== Checkbox ====================
getCheckboxStyle(`${prefixCls}-checkbox`, token, hashId!), getCheckboxStyle(`${prefixCls}-checkbox`, token, hashId!),
{ {
[cascaderCls]: { [componentCls]: {
// ================== Checkbox ================== // ================== Checkbox ==================
'&-checkbox': { '&-checkbox': {
top: 0, top: 0,
@ -73,8 +65,8 @@ const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => {
flexWrap: 'nowrap', flexWrap: 'nowrap',
alignItems: 'flex-start', alignItems: 'flex-start',
[`&${cascaderCls}-menu-empty`]: { [`&${componentCls}-menu-empty`]: {
[`${cascaderCls}-menu`]: { [`${componentCls}-menu`]: {
width: '100%', width: '100%',
height: 'auto', height: 'auto',
@ -157,7 +149,7 @@ const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => {
// == RTL == // == RTL ==
// ===================================================== // =====================================================
{ {
[`${cascaderCls}-dropdown-rtl`]: { [`${componentCls}-dropdown-rtl`]: {
direction: 'rtl', direction: 'rtl',
}, },
}, },
@ -165,28 +157,12 @@ const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => {
}; };
// ============================== Export ============================== // ============================== Export ==============================
export default function useStyle(prefixCls: string): UseComponentStyleResult { export default genComponentStyleHook(
const [theme, token, hashId] = useToken(); 'Cascader',
(token, { hashId }) => [genBaseStyle(token, hashId)],
return [ {
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => { controlWidth: 184,
const { Cascader = {} } = token; controlItemWidth: 111,
dropdownHeight: 180,
const cascaderToken: CascaderToken = { },
...token, );
prefixCls,
cascaderCls: `.${prefixCls}`,
controlWidth: 184,
controlItemWidth: 111,
dropdownHeight: 180,
// Override
...Cascader,
};
return [genBaseStyle(cascaderToken, hashId)];
}),
hashId,
];
}