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
} = props;
const { getPrefixCls, autoInsertSpaceInButton, direction, iconPrefixCls } =
React.useContext(ConfigContext);
const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('btn', customizePrefixCls);
// Style
const [wrapSSR, hashId] = useStyle(prefixCls, iconPrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const size = React.useContext(SizeContext);
const [innerLoading, setLoading] = React.useState<Loading>(!!loading);

View File

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

View File

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