mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
feat: css var (#45589)
* feat: css variables theme * chore: temp * chore temp * chore: temp * chore: temp * chore: tmp * chore: temp * feat: full css variables * feat: css var * chore: code clean * chore: code clean * chore: bump cssinjs * test: fix lint * feat: better key logic * feat: useStyle add param rootCls for cssVar scope * chore: fix lint * chore: code clean * chore: fix lint * perf: minimize component token size * chore: make useId compatible * chore: code clean * chore: fix lint * chore: code clean * chore: update test case * feat: genCSSVarRegister * feat: RPN Calculator * chore: add test for css var * chore: code clean * test: add test for calc * feat: better calc type * chore: code clean * chore: update size limit * feat: better useCSSVar * chore: better useCSSVar * test: add cov * feat: better calc logic * test: add test case * chore: code clean --------- Signed-off-by: MadCcc <madccc@foxmail.com>
This commit is contained in:
parent
99138cb93e
commit
5f1dd427df
@ -5,7 +5,7 @@ import stackblitzSdk from '@stackblitz/sdk';
|
||||
import { Alert, Badge, Space, Tooltip } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage, useSiteData, LiveContext } from 'dumi';
|
||||
import { FormattedMessage, LiveContext, useSiteData } from 'dumi';
|
||||
import LZString from 'lz-string';
|
||||
|
||||
import type { AntdPreviewerProps } from './Previewer';
|
||||
|
@ -6,7 +6,7 @@ import DayJS from 'dayjs';
|
||||
import { FormattedMessage, useIntl, useRouteMeta, useTabMeta } from 'dumi';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Anchor, Avatar, Col, Skeleton, Space, Tooltip, Typography } from 'antd';
|
||||
import { Anchor, Avatar, Col, ConfigProvider, Skeleton, Space, Tooltip, Typography } from 'antd';
|
||||
import useLayoutState from '../../../hooks/useLayoutState';
|
||||
import useLocation from '../../../hooks/useLocation';
|
||||
import EditButton from '../../common/EditButton';
|
||||
@ -275,7 +275,9 @@ const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
</Typography.Paragraph>
|
||||
) : null}
|
||||
{!meta.frontmatter.__autoDescription && meta.frontmatter.description}
|
||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
|
||||
<ConfigProvider theme={{ cssVar: {} }}>
|
||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
|
||||
</ConfigProvider>
|
||||
{(meta.frontmatter?.zhihu_url ||
|
||||
meta.frontmatter?.yuque_url ||
|
||||
meta.frontmatter?.juejin_url) && (
|
||||
|
@ -62,7 +62,7 @@ const validateBreakpoints = (token: GlobalToken) => {
|
||||
};
|
||||
|
||||
export default function useResponsiveObserver() {
|
||||
const [, token] = useToken();
|
||||
const [, , , token] = useToken();
|
||||
const responsiveMap: BreakpointMap = getResponsiveMap(validateBreakpoints(token));
|
||||
|
||||
// To avoid repeat create instance, we add `useMemo` here.
|
||||
|
@ -26,6 +26,7 @@ import IconWrapper from './IconWrapper';
|
||||
import LoadingIcon from './LoadingIcon';
|
||||
import useStyle from './style';
|
||||
import CompactCmp from './style/compactCmp';
|
||||
import useCSSVar from './style/cssVar';
|
||||
|
||||
export type LegacyButtonType = ButtonType | 'danger';
|
||||
|
||||
@ -118,7 +119,8 @@ const InternalButton: React.ForwardRefRenderFunction<
|
||||
const { getPrefixCls, autoInsertSpaceInButton, direction, button } = useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('btn', customizePrefixCls);
|
||||
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
const [, hashId] = useStyle(prefixCls);
|
||||
const wrapCSSVar = useCSSVar(prefixCls);
|
||||
|
||||
const disabled = useContext(DisabledContext);
|
||||
const mergedDisabled = customDisabled ?? disabled;
|
||||
@ -254,7 +256,7 @@ const InternalButton: React.ForwardRefRenderFunction<
|
||||
children || children === 0 ? spaceChildren(children, needInserted && autoInsertSpace) : null;
|
||||
|
||||
if (linkButtonRestProps.href !== undefined) {
|
||||
return wrapSSR(
|
||||
return wrapCSSVar(
|
||||
<a
|
||||
{...linkButtonRestProps}
|
||||
className={classNames(classes, {
|
||||
@ -296,7 +298,7 @@ const InternalButton: React.ForwardRefRenderFunction<
|
||||
);
|
||||
}
|
||||
|
||||
return wrapSSR(buttonNode);
|
||||
return wrapCSSVar(buttonNode);
|
||||
};
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(
|
||||
|
4
components/button/style/cssVar.ts
Normal file
4
components/button/style/cssVar.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { genCSSVarRegister } from '../../theme/internal';
|
||||
import { prepareComponentToken } from '.';
|
||||
|
||||
export default genCSSVarRegister('Button', prepareComponentToken);
|
@ -1,9 +1,9 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
|
||||
|
||||
import { genFocusStyle } from '../../style';
|
||||
import type { GlobalToken } from '../../theme';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import type { GenStyleFn } from '../../theme/util/genComponentStyleHook';
|
||||
import genGroupStyle from './group';
|
||||
@ -90,6 +90,21 @@ export interface ComponentToken {
|
||||
* @descEN Horizontal padding of small button
|
||||
*/
|
||||
paddingInlineSM: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 按钮横向内间距
|
||||
* @descEN Horizontal padding of button
|
||||
*/
|
||||
paddingBlock: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 大号按钮横向内间距
|
||||
* @descEN Horizontal padding of large button
|
||||
*/
|
||||
paddingBlockLG: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 小号按钮横向内间距
|
||||
* @descEN Horizontal padding of small button
|
||||
*/
|
||||
paddingBlockSM: CSSProperties['paddingInline'];
|
||||
/**
|
||||
* @desc 只有图标的按钮图标尺寸
|
||||
* @descEN Icon size of button which only contains icon
|
||||
@ -139,6 +154,7 @@ export interface ComponentToken {
|
||||
|
||||
export interface ButtonToken extends FullToken<'Button'> {
|
||||
buttonPaddingHorizontal: CSSProperties['paddingInline'];
|
||||
buttonPaddingVertical: CSSProperties['paddingBlock'];
|
||||
buttonIconOnlyFontSize: number;
|
||||
}
|
||||
|
||||
@ -156,7 +172,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
textAlign: 'center',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
border: `${token.lineWidth}px ${token.lineType} transparent`,
|
||||
border: `${unit(token.lineWidth)} ${token.lineType} transparent`,
|
||||
cursor: 'pointer',
|
||||
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
|
||||
userSelect: 'none',
|
||||
@ -220,7 +236,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
insetInlineStart: -token.lineWidth,
|
||||
display: 'inline-block',
|
||||
width: token.lineWidth,
|
||||
height: `calc(100% + ${token.lineWidth * 2}px)`,
|
||||
height: `calc(100% + ${unit(token.lineWidth)} * 2)`,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
},
|
||||
@ -238,7 +254,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
top: -token.lineWidth,
|
||||
insetInlineStart: -token.lineWidth,
|
||||
display: 'inline-block',
|
||||
width: `calc(100% + ${token.lineWidth * 2}px)`,
|
||||
width: `calc(100% + ${unit(token.lineWidth)} * 2)`,
|
||||
height: token.lineWidth,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
@ -271,8 +287,8 @@ const genCircleButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => (
|
||||
|
||||
const genRoundButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({
|
||||
borderRadius: token.controlHeight,
|
||||
paddingInlineStart: token.controlHeight / 2,
|
||||
paddingInlineEnd: token.controlHeight / 2,
|
||||
paddingInlineStart: token.calc(token.controlHeight).div(2).equal(),
|
||||
paddingInlineEnd: token.calc(token.controlHeight).div(2).equal(),
|
||||
});
|
||||
|
||||
// =============================== Type ===============================
|
||||
@ -569,15 +585,12 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
|
||||
componentCls,
|
||||
controlHeight,
|
||||
fontSize,
|
||||
lineHeight,
|
||||
lineWidth,
|
||||
borderRadius,
|
||||
buttonPaddingHorizontal,
|
||||
iconCls,
|
||||
buttonPaddingVertical,
|
||||
} = token;
|
||||
|
||||
const paddingVertical = Math.max(0, (controlHeight - fontSize * lineHeight) / 2 - lineWidth);
|
||||
|
||||
const iconOnlyCls = `${componentCls}-icon-only`;
|
||||
|
||||
return [
|
||||
@ -586,7 +599,7 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
|
||||
[`${componentCls}${sizePrefixCls}`]: {
|
||||
fontSize,
|
||||
height: controlHeight,
|
||||
padding: `${paddingVertical}px ${buttonPaddingHorizontal}px`,
|
||||
padding: `${unit(buttonPaddingVertical!)} ${unit(buttonPaddingHorizontal!)}`,
|
||||
borderRadius,
|
||||
|
||||
[`&${iconOnlyCls}`]: {
|
||||
@ -635,7 +648,8 @@ const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = (token) => {
|
||||
controlHeight: token.controlHeightSM,
|
||||
fontSize: token.contentFontSizeSM,
|
||||
padding: token.paddingXS,
|
||||
buttonPaddingHorizontal: token.paddingInlineSM, // Fixed padding
|
||||
buttonPaddingHorizontal: token.paddingInlineSM,
|
||||
buttonPaddingVertical: token.paddingBlockSM,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
buttonIconOnlyFontSize: token.onlyIconSizeSM,
|
||||
});
|
||||
@ -648,6 +662,7 @@ const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = (token) => {
|
||||
controlHeight: token.controlHeightLG,
|
||||
fontSize: token.contentFontSizeLG,
|
||||
buttonPaddingHorizontal: token.paddingInlineLG,
|
||||
buttonPaddingVertical: token.paddingBlockLG,
|
||||
borderRadius: token.borderRadiusLG,
|
||||
buttonIconOnlyFontSize: token.onlyIconSizeLG,
|
||||
});
|
||||
@ -670,44 +685,63 @@ const genBlockButtonStyle: GenerateStyle<ButtonToken> = (token) => {
|
||||
export const prepareToken: (token: Parameters<GenStyleFn<'Button'>>[0]) => ButtonToken = (
|
||||
token,
|
||||
) => {
|
||||
const { paddingInline, onlyIconSize } = token;
|
||||
const { paddingInline, onlyIconSize, paddingBlock } = token;
|
||||
|
||||
const buttonToken = mergeToken<ButtonToken>(token, {
|
||||
buttonPaddingHorizontal: paddingInline,
|
||||
buttonPaddingVertical: paddingBlock,
|
||||
buttonIconOnlyFontSize: onlyIconSize,
|
||||
});
|
||||
|
||||
return buttonToken;
|
||||
};
|
||||
|
||||
export const prepareComponentToken = (token: GlobalToken) => ({
|
||||
fontWeight: 400,
|
||||
defaultShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
|
||||
primaryShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
|
||||
dangerShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
|
||||
primaryColor: token.colorTextLightSolid,
|
||||
dangerColor: token.colorTextLightSolid,
|
||||
borderColorDisabled: token.colorBorder,
|
||||
defaultGhostColor: token.colorBgContainer,
|
||||
ghostBg: 'transparent',
|
||||
defaultGhostBorderColor: token.colorBgContainer,
|
||||
paddingInline: token.paddingContentHorizontal - token.lineWidth,
|
||||
paddingInlineLG: token.paddingContentHorizontal - token.lineWidth,
|
||||
paddingInlineSM: 8 - token.lineWidth,
|
||||
onlyIconSize: token.fontSizeLG,
|
||||
onlyIconSizeSM: token.fontSizeLG - 2,
|
||||
onlyIconSizeLG: token.fontSizeLG + 2,
|
||||
groupBorderColor: token.colorPrimaryHover,
|
||||
linkHoverBg: 'transparent',
|
||||
textHoverBg: token.colorBgTextHover,
|
||||
defaultColor: token.colorText,
|
||||
defaultBg: token.colorBgContainer,
|
||||
defaultBorderColor: token.colorBorder,
|
||||
defaultBorderColorDisabled: token.colorBorder,
|
||||
contentFontSize: token.fontSize,
|
||||
contentFontSizeSM: token.fontSize,
|
||||
contentFontSizeLG: token.fontSizeLG,
|
||||
});
|
||||
export const prepareComponentToken: GetDefaultToken<'Button'> = (token) => {
|
||||
const contentFontSize = token.fontSize;
|
||||
const contentFontSizeSM = token.fontSize;
|
||||
const contentFontSizeLG = token.fontSizeLG;
|
||||
|
||||
return {
|
||||
fontWeight: 400,
|
||||
defaultShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
|
||||
primaryShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
|
||||
dangerShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
|
||||
primaryColor: token.colorTextLightSolid,
|
||||
dangerColor: token.colorTextLightSolid,
|
||||
borderColorDisabled: token.colorBorder,
|
||||
defaultGhostColor: token.colorBgContainer,
|
||||
ghostBg: 'transparent',
|
||||
defaultGhostBorderColor: token.colorBgContainer,
|
||||
paddingInline: token.paddingContentHorizontal - token.lineWidth,
|
||||
paddingInlineLG: token.paddingContentHorizontal - token.lineWidth,
|
||||
paddingInlineSM: 8 - token.lineWidth,
|
||||
paddingBlock: Math.max(
|
||||
(token.controlHeight - contentFontSize * token.lineHeight) / 2 - token.lineWidth,
|
||||
0,
|
||||
),
|
||||
paddingBlockSM: Math.max(
|
||||
(token.controlHeightSM - contentFontSizeSM * token.lineHeight) / 2 - token.lineWidth,
|
||||
0,
|
||||
),
|
||||
paddingBlockLG: Math.max(
|
||||
(token.controlHeightLG - contentFontSizeLG * token.lineHeight) / 2 - token.lineWidth,
|
||||
0,
|
||||
),
|
||||
onlyIconSize: token.fontSizeLG,
|
||||
onlyIconSizeSM: token.fontSizeLG - 2,
|
||||
onlyIconSizeLG: token.fontSizeLG + 2,
|
||||
groupBorderColor: token.colorPrimaryHover,
|
||||
linkHoverBg: 'transparent',
|
||||
textHoverBg: token.colorBgTextHover,
|
||||
defaultColor: token.colorText,
|
||||
defaultBg: token.colorBgContainer,
|
||||
defaultBorderColor: token.colorBorder,
|
||||
defaultBorderColorDisabled: token.colorBorder,
|
||||
contentFontSize,
|
||||
contentFontSizeSM,
|
||||
contentFontSizeLG,
|
||||
};
|
||||
};
|
||||
|
||||
export default genComponentStyleHook(
|
||||
'Button',
|
||||
|
@ -3,9 +3,9 @@ import kebabCase from 'lodash/kebabCase';
|
||||
import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
||||
|
||||
import ConfigProvider from '..';
|
||||
import { InputNumber } from '../..';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
import { InputNumber, Button } from '../..';
|
||||
import { render } from '../../../tests/utils';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
import theme from '../../theme';
|
||||
import { useToken } from '../../theme/internal';
|
||||
|
||||
@ -197,4 +197,53 @@ describe('ConfigProvider.Theme', () => {
|
||||
);
|
||||
expect(tokenRef?.colorPrimaryText).toBe('#1677ff');
|
||||
});
|
||||
|
||||
describe('cssVar', () => {
|
||||
it('should work', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider theme={{ cssVar: { key: 'foo' } }}>
|
||||
<Button>Button</Button>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
const button = container.querySelector('button')!;
|
||||
|
||||
expect(button).toHaveClass('foo');
|
||||
expect(button).toHaveStyle({
|
||||
'--antd-color-text': 'rgba(0, 0, 0, 0.88)',
|
||||
boxShadow: 'var(--antd-button-default-shadow)',
|
||||
'line-height': 'var(--antd-line-height)',
|
||||
});
|
||||
});
|
||||
|
||||
it('prefix', () => {
|
||||
const { container } = render(
|
||||
<>
|
||||
<ConfigProvider theme={{ cssVar: { key: 'foo' }, hashed: true }}>
|
||||
<Button className="button-foo">Button</Button>
|
||||
</ConfigProvider>
|
||||
<ConfigProvider theme={{ cssVar: { key: 'bar', prefix: 'bar' }, hashed: true }}>
|
||||
<Button className="button-bar">Button</Button>
|
||||
</ConfigProvider>
|
||||
</>,
|
||||
);
|
||||
|
||||
const fooBtn = container.querySelector('.button-foo')!;
|
||||
const barBtn = container.querySelector('.button-bar')!;
|
||||
|
||||
expect(fooBtn).toHaveClass('foo');
|
||||
expect(fooBtn).toHaveStyle({
|
||||
'--antd-color-text': 'rgba(0, 0, 0, 0.88)',
|
||||
boxShadow: 'var(--antd-button-default-shadow)',
|
||||
'line-height': 'var(--antd-line-height)',
|
||||
});
|
||||
|
||||
expect(barBtn).toHaveClass('bar');
|
||||
expect(barBtn).toHaveStyle({
|
||||
'--bar-color-text': 'rgba(0, 0, 0, 0.88)',
|
||||
boxShadow: 'var(--bar-button-default-shadow)',
|
||||
'line-height': 'var(--bar-line-height)',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -47,6 +47,16 @@ export interface ThemeConfig {
|
||||
algorithm?: MappingAlgorithm | MappingAlgorithm[];
|
||||
hashed?: boolean;
|
||||
inherit?: boolean;
|
||||
cssVar?: {
|
||||
/**
|
||||
* Prefix for css variable, default to `antd`.
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* Unique key for theme, should be set manually < react@18.
|
||||
*/
|
||||
key?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ComponentStyleConfig {
|
||||
|
@ -3,15 +3,31 @@ import isEqual from 'rc-util/lib/isEqual';
|
||||
import type { OverrideToken } from '../../theme/interface';
|
||||
import type { ThemeConfig } from '../context';
|
||||
import { defaultConfig } from '../../theme/internal';
|
||||
import useThemeKey from './useThemeKey';
|
||||
import { devUseWarning } from '../../_util/warning';
|
||||
|
||||
export default function useTheme(
|
||||
theme?: ThemeConfig,
|
||||
parentTheme?: ThemeConfig,
|
||||
): ThemeConfig | undefined {
|
||||
const warning = devUseWarning('ConfigProvider');
|
||||
|
||||
const themeConfig = theme || {};
|
||||
const parentThemeConfig: ThemeConfig =
|
||||
themeConfig.inherit === false || !parentTheme ? defaultConfig : parentTheme;
|
||||
|
||||
const themeKey = useThemeKey();
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const cssVarEnabled = themeConfig.cssVar || parentThemeConfig.cssVar;
|
||||
const validKey = !!(themeConfig.cssVar?.key || themeKey);
|
||||
warning(
|
||||
!cssVarEnabled || validKey,
|
||||
'breaking',
|
||||
'Missing key in `cssVar` config. Please upgrade to React 18 or set `cssVar.key` manually in each ConfigProvider inside `cssVar` enabled ConfigProvider.',
|
||||
);
|
||||
}
|
||||
|
||||
return useMemo<ThemeConfig | undefined>(
|
||||
() => {
|
||||
if (!theme) {
|
||||
@ -30,6 +46,14 @@ export default function useTheme(
|
||||
} as any;
|
||||
});
|
||||
|
||||
const cssVarKey = `css-var-${themeKey.replace(/:/g, '')}`;
|
||||
const mergedCssVar = (themeConfig.cssVar || parentThemeConfig.cssVar) && {
|
||||
prefix: 'antd', // Default to antd
|
||||
...parentThemeConfig.cssVar,
|
||||
...themeConfig.cssVar,
|
||||
key: themeConfig.cssVar?.key || cssVarKey,
|
||||
};
|
||||
|
||||
// Base token
|
||||
return {
|
||||
...parentThemeConfig,
|
||||
@ -40,6 +64,7 @@ export default function useTheme(
|
||||
...themeConfig.token,
|
||||
},
|
||||
components: mergedComponents,
|
||||
cssVar: mergedCssVar,
|
||||
};
|
||||
},
|
||||
[themeConfig, parentThemeConfig],
|
||||
|
7
components/config-provider/hooks/useThemeKey.ts
Normal file
7
components/config-provider/hooks/useThemeKey.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
const useEmptyId = () => '';
|
||||
|
||||
const useThemeKey = typeof useId === 'undefined' ? useEmptyId : useId;
|
||||
|
||||
export default useThemeKey;
|
@ -5,7 +5,7 @@ import type { DrawerClassNames, DrawerStyles } from '../DrawerPanel';
|
||||
|
||||
const useStyle = createStyles(({ token }) => ({
|
||||
'my-drawer-body': {
|
||||
background: token['blue-1'],
|
||||
background: token.blue1,
|
||||
},
|
||||
'my-drawer-mask': {
|
||||
boxShadow: `inset 0 0 15px #fff`,
|
||||
|
@ -4,7 +4,7 @@ import { createStyles, useTheme } from 'antd-style';
|
||||
|
||||
const useStyle = createStyles(({ token }) => ({
|
||||
'my-modal-body': {
|
||||
background: token['blue-1'],
|
||||
background: token.blue1,
|
||||
padding: token.paddingSM,
|
||||
},
|
||||
'my-modal-mask': {
|
||||
|
@ -25,6 +25,7 @@ describe('Theme', () => {
|
||||
);
|
||||
delete token._hashId;
|
||||
delete token._tokenKey;
|
||||
delete token._themeKey;
|
||||
return token;
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import getAlphaColor from '../util/getAlphaColor';
|
||||
import genCalc from '../util/calc';
|
||||
import type AbstractCalculator from 'antd/es/theme/util/calc/calculator';
|
||||
|
||||
describe('util', () => {
|
||||
describe('getAlphaColor', () => {
|
||||
@ -6,4 +8,129 @@ describe('util', () => {
|
||||
expect(getAlphaColor('rgba(0, 0, 0, 0.5)', 'rgba(255, 255, 255)')).toBe('rgba(0, 0, 0, 0.5)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculator', () => {
|
||||
const cases: [
|
||||
(calc: (num: number | AbstractCalculator) => AbstractCalculator) => string | number,
|
||||
{ js: number; css: string },
|
||||
][] = [
|
||||
[
|
||||
// 1 + 1
|
||||
(calc) => calc(1).add(1).equal(),
|
||||
{
|
||||
js: 2,
|
||||
css: 'calc(1px + 1px)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// (1 + 1) * 4
|
||||
(calc) => calc(1).add(1).mul(4).equal(),
|
||||
{
|
||||
js: 8,
|
||||
css: 'calc((1px + 1px) * 4)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// (2 + 4) / 2 - 2
|
||||
(calc) => calc(2).add(4).div(2).sub(2).equal(),
|
||||
{
|
||||
js: 1,
|
||||
css: 'calc((2px + 4px) / 2 - 2px)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// Bad case
|
||||
// (2 + 4) / (3 - 2) - 2
|
||||
(calc) => calc(2).add(4).div(calc(3).sub(2)).sub(2).equal(),
|
||||
{
|
||||
js: 4,
|
||||
css: 'calc((2px + 4px) / (3px - 2px) - 2px)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// Bad case
|
||||
// 2 * (2 + 3)
|
||||
(calc) => calc(2).mul(calc(2).add(3)).equal(),
|
||||
{
|
||||
js: 10,
|
||||
css: 'calc(2px * (2px + 3px))',
|
||||
},
|
||||
],
|
||||
[
|
||||
// (1 + 2) * 3
|
||||
(calc) => calc(calc(1).add(2)).mul(3).equal(),
|
||||
{
|
||||
js: 9,
|
||||
css: 'calc((1px + 2px) * 3)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// 1 + (2 - 1)
|
||||
(calc) => calc(1).add(calc(2).sub(1)).equal(),
|
||||
{
|
||||
js: 2,
|
||||
css: 'calc(1px + (2px - 1px))',
|
||||
},
|
||||
],
|
||||
[
|
||||
// 1 + 2 * 2
|
||||
(calc) => calc(1).add(calc(2).mul(2)).equal(),
|
||||
{
|
||||
js: 5,
|
||||
css: 'calc(1px + 2px * 2)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// 5 - (2 - 1)
|
||||
(calc) => calc(5).sub(calc(2).sub(1)).equal(),
|
||||
{
|
||||
js: 4,
|
||||
css: 'calc(5px - (2px - 1px))',
|
||||
},
|
||||
],
|
||||
[
|
||||
// 2 * 6 / 3
|
||||
(calc) => calc(2).mul(6).div(3).equal(),
|
||||
{
|
||||
js: 4,
|
||||
css: 'calc(2px * 6 / 3)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// 6 / 3 * 2
|
||||
(calc) => calc(6).div(3).mul(2).equal(),
|
||||
{
|
||||
js: 4,
|
||||
css: 'calc(6px / 3 * 2)',
|
||||
},
|
||||
],
|
||||
[
|
||||
// Bad case
|
||||
// 6 / (3 * 2)
|
||||
(calc) => calc(6).div(calc(3).mul(2)).equal(),
|
||||
{
|
||||
js: 1,
|
||||
css: 'calc(6px / (3px * 2))',
|
||||
},
|
||||
],
|
||||
[
|
||||
// 6
|
||||
(calc) => calc(6).equal(),
|
||||
{
|
||||
js: 6,
|
||||
css: '6px',
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
cases.forEach(([exp, { js, css }], index) => {
|
||||
it(`js calc ${index + 1}`, () => {
|
||||
expect(exp(genCalc('js'))).toBe(js);
|
||||
});
|
||||
|
||||
it(`css calc ${index + 1}`, () => {
|
||||
expect(exp(genCalc('css'))).toBe(css);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -29,6 +29,10 @@ export interface DesignTokenProviderProps {
|
||||
/** Just merge `token` & `override` at top to save perf */
|
||||
override: { override: Partial<AliasToken> } & ComponentsToken;
|
||||
hashed?: string | boolean;
|
||||
cssVar?: {
|
||||
prefix?: string;
|
||||
key?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const DesignTokenContext = React.createContext<DesignTokenProviderProps>(defaultConfig);
|
||||
|
@ -28,7 +28,6 @@ export type {
|
||||
export { PresetColors } from './presetColors';
|
||||
export type {
|
||||
ColorPalettes,
|
||||
LegacyColorPalettes,
|
||||
PresetColorKey,
|
||||
PresetColorType,
|
||||
} from './presetColors';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { ColorPalettes, LegacyColorPalettes } from '../presetColors';
|
||||
import type { ColorPalettes } from '../presetColors';
|
||||
import type { SeedToken } from '../seeds';
|
||||
import type { ColorMapToken } from './colors';
|
||||
import type { FontMapToken } from './font';
|
||||
@ -36,7 +36,6 @@ export interface CommonMapToken extends StyleMapToken {
|
||||
|
||||
export interface MapToken
|
||||
extends SeedToken,
|
||||
LegacyColorPalettes,
|
||||
ColorPalettes,
|
||||
ColorMapToken,
|
||||
SizeMapToken,
|
||||
|
@ -20,13 +20,6 @@ export type PresetColorType = Record<PresetColorKey, string>;
|
||||
|
||||
type ColorPaletteKeyIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
||||
|
||||
export type LegacyColorPalettes = {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
[key in `${keyof PresetColorType}-${ColorPaletteKeyIndex}`]: string;
|
||||
};
|
||||
|
||||
export type ColorPalettes = {
|
||||
[key in `${keyof PresetColorType}${ColorPaletteKeyIndex}`]: string;
|
||||
};
|
||||
|
@ -10,20 +10,26 @@ import type {
|
||||
} from './interface';
|
||||
import { PresetColors } from './interface';
|
||||
import useToken from './useToken';
|
||||
import type { FullToken } from './util/genComponentStyleHook';
|
||||
import genComponentStyleHook, { genSubStyleComponent } from './util/genComponentStyleHook';
|
||||
import type { FullToken, GetDefaultToken } from './util/genComponentStyleHook';
|
||||
import genComponentStyleHook, {
|
||||
genSubStyleComponent,
|
||||
genCSSVarRegister,
|
||||
} from './util/genComponentStyleHook';
|
||||
import genPresetColor from './util/genPresetColor';
|
||||
import statisticToken, { merge as mergeToken } from './util/statistic';
|
||||
import useResetIconStyle from './util/useResetIconStyle';
|
||||
import calc from './util/calc';
|
||||
|
||||
export { DesignTokenContext, defaultConfig } from './context';
|
||||
export {
|
||||
PresetColors,
|
||||
genComponentStyleHook,
|
||||
genSubStyleComponent,
|
||||
genCSSVarRegister,
|
||||
genPresetColor,
|
||||
mergeToken,
|
||||
statisticToken,
|
||||
calc,
|
||||
// hooks
|
||||
useResetIconStyle,
|
||||
useStyleRegister,
|
||||
@ -39,4 +45,5 @@ export type {
|
||||
PresetColorType,
|
||||
SeedToken,
|
||||
UseComponentStyleResult,
|
||||
GetDefaultToken,
|
||||
};
|
||||
|
@ -1,12 +1,6 @@
|
||||
import { generate } from '@ant-design/colors';
|
||||
import type { DerivativeFunc } from '@ant-design/cssinjs';
|
||||
import type {
|
||||
ColorPalettes,
|
||||
LegacyColorPalettes,
|
||||
MapToken,
|
||||
PresetColorType,
|
||||
SeedToken,
|
||||
} from '../../interface';
|
||||
import type { ColorPalettes, MapToken, PresetColorType, SeedToken } from '../../interface';
|
||||
import { defaultPresetColors } from '../seed';
|
||||
import genColorMapToken from '../shared/genColorMapToken';
|
||||
import { generateColorPalettes, generateNeutralColorPalettes } from './colors';
|
||||
@ -18,7 +12,6 @@ const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
|
||||
const colors = generate(token[colorKey], { theme: 'dark' });
|
||||
|
||||
return new Array(10).fill(1).reduce((prev, _, i) => {
|
||||
prev[`${colorKey}-${i + 1}`] = colors[i];
|
||||
prev[`${colorKey}${i + 1}`] = colors[i];
|
||||
return prev;
|
||||
}, {}) as ColorPalettes;
|
||||
@ -29,7 +22,7 @@ const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
|
||||
...cur,
|
||||
};
|
||||
return prev;
|
||||
}, {} as ColorPalettes & LegacyColorPalettes);
|
||||
}, {} as ColorPalettes);
|
||||
|
||||
const mergedMapToken = mapToken ?? defaultAlgorithm(token);
|
||||
|
||||
|
@ -1,13 +1,7 @@
|
||||
import { generate } from '@ant-design/colors';
|
||||
import genControlHeight from '../shared/genControlHeight';
|
||||
import genSizeMapToken from '../shared/genSizeMapToken';
|
||||
import type {
|
||||
ColorPalettes,
|
||||
LegacyColorPalettes,
|
||||
MapToken,
|
||||
PresetColorType,
|
||||
SeedToken,
|
||||
} from '../../interface';
|
||||
import type { ColorPalettes, MapToken, PresetColorType, SeedToken } from '../../interface';
|
||||
import { defaultPresetColors } from '../seed';
|
||||
import genColorMapToken from '../shared/genColorMapToken';
|
||||
import genCommonMapToken from '../shared/genCommonMapToken';
|
||||
@ -20,10 +14,9 @@ export default function derivative(token: SeedToken): MapToken {
|
||||
const colors = generate(token[colorKey]);
|
||||
|
||||
return new Array(10).fill(1).reduce((prev, _, i) => {
|
||||
prev[`${colorKey}-${i + 1}`] = colors[i];
|
||||
prev[`${colorKey}${i + 1}`] = colors[i];
|
||||
return prev;
|
||||
}, {}) as ColorPalettes & LegacyColorPalettes;
|
||||
}, {}) as ColorPalettes;
|
||||
})
|
||||
.reduce((prev, cur) => {
|
||||
prev = {
|
||||
@ -31,7 +24,7 @@ export default function derivative(token: SeedToken): MapToken {
|
||||
...cur,
|
||||
};
|
||||
return prev;
|
||||
}, {} as ColorPalettes & LegacyColorPalettes);
|
||||
}, {} as ColorPalettes);
|
||||
|
||||
return {
|
||||
...token,
|
||||
|
@ -9,6 +9,41 @@ import type { AliasToken, GlobalToken, MapToken, SeedToken } from './interface';
|
||||
import defaultSeedToken from './themes/seed';
|
||||
import formatToken from './util/alias';
|
||||
|
||||
export const unitless: {
|
||||
[key in keyof AliasToken]?: boolean;
|
||||
} = {
|
||||
lineHeight: true,
|
||||
lineHeightSM: true,
|
||||
lineHeightLG: true,
|
||||
lineHeightHeading1: true,
|
||||
lineHeightHeading2: true,
|
||||
lineHeightHeading3: true,
|
||||
lineHeightHeading4: true,
|
||||
lineHeightHeading5: true,
|
||||
opacityLoading: true,
|
||||
fontWeightStrong: true,
|
||||
zIndexPopupBase: true,
|
||||
zIndexBase: true,
|
||||
};
|
||||
|
||||
export const ignore: {
|
||||
[key in keyof AliasToken]?: boolean;
|
||||
} = {
|
||||
size: true,
|
||||
sizeSM: true,
|
||||
sizeLG: true,
|
||||
sizeMD: true,
|
||||
sizeXS: true,
|
||||
sizeXXS: true,
|
||||
sizeMS: true,
|
||||
sizeXL: true,
|
||||
sizeXXL: true,
|
||||
sizeUnit: true,
|
||||
sizeStep: true,
|
||||
motionBase: true,
|
||||
motionUnit: true,
|
||||
};
|
||||
|
||||
export const getComputedToken = (
|
||||
originToken: SeedToken,
|
||||
overrideToken: DesignTokenProviderProps['components'] & {
|
||||
@ -57,14 +92,22 @@ export default function useToken(): [
|
||||
theme: Theme<SeedToken, MapToken>,
|
||||
token: GlobalToken,
|
||||
hashId: string,
|
||||
realToken: GlobalToken,
|
||||
cssVar?: DesignTokenProviderProps['cssVar'],
|
||||
] {
|
||||
const { token: rootDesignToken, hashed, theme, override } = React.useContext(DesignTokenContext);
|
||||
const {
|
||||
token: rootDesignToken,
|
||||
hashed,
|
||||
theme,
|
||||
override,
|
||||
cssVar,
|
||||
} = React.useContext(DesignTokenContext);
|
||||
|
||||
const salt = `${version}-${hashed || ''}`;
|
||||
|
||||
const mergedTheme = theme || defaultTheme;
|
||||
|
||||
const [token, hashId] = useCacheToken<GlobalToken, SeedToken>(
|
||||
const [token, hashId, realToken] = useCacheToken<GlobalToken, SeedToken>(
|
||||
mergedTheme,
|
||||
[defaultSeedToken, rootDesignToken],
|
||||
{
|
||||
@ -74,8 +117,14 @@ export default function useToken(): [
|
||||
// formatToken will not be consumed after 1.15.0 with getComputedToken.
|
||||
// But token will break if @ant-design/cssinjs is under 1.15.0 without it
|
||||
formatToken,
|
||||
cssVar: cssVar && {
|
||||
prefix: cssVar.prefix,
|
||||
key: cssVar.key,
|
||||
unitless,
|
||||
ignore,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return [mergedTheme, token, hashed ? hashId : ''];
|
||||
return [mergedTheme, token, hashed ? hashId : '', realToken, cssVar];
|
||||
}
|
||||
|
74
components/theme/util/calc/CSSCalculator.ts
Normal file
74
components/theme/util/calc/CSSCalculator.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import AbstractCalculator from './calculator';
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
|
||||
export default class CSSCalculator extends AbstractCalculator {
|
||||
result: string = '';
|
||||
|
||||
lowPriority?: boolean;
|
||||
|
||||
constructor(num: number | AbstractCalculator) {
|
||||
super();
|
||||
if (num instanceof CSSCalculator) {
|
||||
this.result = `(${num.result})`;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result = unit(num);
|
||||
}
|
||||
}
|
||||
|
||||
add(num: number | AbstractCalculator): this {
|
||||
if (num instanceof CSSCalculator) {
|
||||
this.result = `${this.result} + ${num.getResult()}`;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result = `${this.result} + ${unit(num)}`;
|
||||
}
|
||||
this.lowPriority = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
sub(num: number | AbstractCalculator): this {
|
||||
if (num instanceof CSSCalculator) {
|
||||
this.result = `${this.result} - ${num.getResult()}`;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result = `${this.result} - ${unit(num)}`;
|
||||
}
|
||||
this.lowPriority = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
mul(num: number | AbstractCalculator): this {
|
||||
if (this.lowPriority) {
|
||||
this.result = `(${this.result})`;
|
||||
}
|
||||
if (num instanceof CSSCalculator) {
|
||||
this.result = `${this.result} * ${num.getResult(true)}`;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result = `${this.result} * ${num}`;
|
||||
}
|
||||
this.lowPriority = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
div(num: number | AbstractCalculator): this {
|
||||
if (this.lowPriority) {
|
||||
this.result = `(${this.result})`;
|
||||
}
|
||||
if (num instanceof CSSCalculator) {
|
||||
this.result = `${this.result} / ${num.getResult(true)}`;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result = `${this.result} / ${num}`;
|
||||
}
|
||||
this.lowPriority = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
getResult(force?: boolean): string {
|
||||
return this.lowPriority || force ? `(${this.result})` : this.result;
|
||||
}
|
||||
|
||||
equal(): string {
|
||||
if (typeof this.lowPriority !== 'undefined') {
|
||||
return `calc(${this.result})`;
|
||||
}
|
||||
return this.result;
|
||||
}
|
||||
}
|
54
components/theme/util/calc/NumCalculator.ts
Normal file
54
components/theme/util/calc/NumCalculator.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import AbstractCalculator from './calculator';
|
||||
|
||||
export default class NumCalculator extends AbstractCalculator {
|
||||
result: number = 0;
|
||||
|
||||
constructor(num: number | string | AbstractCalculator) {
|
||||
super();
|
||||
if (num instanceof NumCalculator) {
|
||||
this.result = num.result;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result = num;
|
||||
}
|
||||
}
|
||||
|
||||
add(num: number | AbstractCalculator): this {
|
||||
if (num instanceof NumCalculator) {
|
||||
this.result += num.result;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result += num;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
sub(num: number | AbstractCalculator): this {
|
||||
if (num instanceof NumCalculator) {
|
||||
this.result -= num.result;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result -= num;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
mul(num: number | AbstractCalculator): this {
|
||||
if (num instanceof NumCalculator) {
|
||||
this.result *= num.result;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result *= num;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
div(num: number | AbstractCalculator): this {
|
||||
if (num instanceof NumCalculator) {
|
||||
this.result /= num.result;
|
||||
} else if (typeof num === 'number') {
|
||||
this.result /= num;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
equal(): number {
|
||||
return this.result;
|
||||
}
|
||||
}
|
11
components/theme/util/calc/calculator.ts
Normal file
11
components/theme/util/calc/calculator.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export default abstract class AbstractCalculator {
|
||||
abstract add(num: number | AbstractCalculator): this;
|
||||
|
||||
abstract sub(num: number | AbstractCalculator): this;
|
||||
|
||||
abstract mul(num: number | AbstractCalculator): this;
|
||||
|
||||
abstract div(num: number | AbstractCalculator): this;
|
||||
|
||||
abstract equal(): string | number;
|
||||
}
|
11
components/theme/util/calc/index.ts
Normal file
11
components/theme/util/calc/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import NumCalculator from './NumCalculator';
|
||||
import CSSCalculator from './CSSCalculator';
|
||||
import type AbstractCalculator from './calculator';
|
||||
|
||||
const genCalc = (type: 'css' | 'js') => {
|
||||
const Calculator = type === 'css' ? CSSCalculator : NumCalculator;
|
||||
|
||||
return (num: number | AbstractCalculator) => new Calculator(num);
|
||||
};
|
||||
|
||||
export default genCalc;
|
@ -1,216 +0,0 @@
|
||||
/* eslint-disable no-redeclare */
|
||||
import { useContext, type ComponentType } from 'react';
|
||||
import type { CSSInterpolation } from '@ant-design/cssinjs';
|
||||
import { useStyleRegister } from '@ant-design/cssinjs';
|
||||
import { warning } from 'rc-util';
|
||||
|
||||
import { ConfigContext } from '../../config-provider/context';
|
||||
import { genCommonStyle, genLinkStyle } from '../../style';
|
||||
import type {
|
||||
ComponentTokenMap,
|
||||
GlobalToken,
|
||||
OverrideToken,
|
||||
UseComponentStyleResult,
|
||||
} from '../interface';
|
||||
import useToken from '../useToken';
|
||||
import statisticToken, { merge as mergeToken } from './statistic';
|
||||
import useResetIconStyle from './useResetIconStyle';
|
||||
|
||||
export type OverrideTokenWithoutDerivative = ComponentTokenMap;
|
||||
export type OverrideComponent = keyof OverrideTokenWithoutDerivative;
|
||||
export type GlobalTokenWithComponent<ComponentName extends OverrideComponent> = GlobalToken &
|
||||
ComponentTokenMap[ComponentName];
|
||||
|
||||
type ComponentToken<ComponentName extends OverrideComponent> = Exclude<
|
||||
OverrideToken[ComponentName],
|
||||
undefined
|
||||
>;
|
||||
type ComponentTokenKey<ComponentName extends OverrideComponent> =
|
||||
keyof ComponentToken<ComponentName>;
|
||||
|
||||
export interface StyleInfo<ComponentName extends OverrideComponent> {
|
||||
hashId: string;
|
||||
prefixCls: string;
|
||||
rootPrefixCls: string;
|
||||
iconPrefixCls: string;
|
||||
overrideComponentToken: ComponentTokenMap[ComponentName];
|
||||
}
|
||||
|
||||
export type TokenWithCommonCls<T> = T & {
|
||||
/** Wrap component class with `.` prefix */
|
||||
componentCls: string;
|
||||
/** Origin prefix which do not have `.` prefix */
|
||||
prefixCls: string;
|
||||
/** Wrap icon class with `.` prefix */
|
||||
iconCls: string;
|
||||
/** Wrap ant prefixCls class with `.` prefix */
|
||||
antCls: string;
|
||||
};
|
||||
export type FullToken<ComponentName extends OverrideComponent> = TokenWithCommonCls<
|
||||
GlobalTokenWithComponent<ComponentName>
|
||||
>;
|
||||
|
||||
export type GenStyleFn<ComponentName extends OverrideComponent> = (
|
||||
token: FullToken<ComponentName>,
|
||||
info: StyleInfo<ComponentName>,
|
||||
) => CSSInterpolation;
|
||||
|
||||
export default function genComponentStyleHook<ComponentName extends OverrideComponent>(
|
||||
componentName: ComponentName | [ComponentName, string],
|
||||
styleFn: GenStyleFn<ComponentName>,
|
||||
getDefaultToken?:
|
||||
| null
|
||||
| OverrideTokenWithoutDerivative[ComponentName]
|
||||
| ((token: GlobalToken) => OverrideTokenWithoutDerivative[ComponentName]),
|
||||
options: {
|
||||
resetStyle?: boolean;
|
||||
// Deprecated token key map [["oldTokenKey", "newTokenKey"], ["oldTokenKey", "newTokenKey"]]
|
||||
deprecatedTokens?: [ComponentTokenKey<ComponentName>, ComponentTokenKey<ComponentName>][];
|
||||
/**
|
||||
* Only use component style in client side. Ignore in SSR.
|
||||
*/
|
||||
clientOnly?: boolean;
|
||||
/**
|
||||
* Set order of component style. Default is -999.
|
||||
*/
|
||||
order?: number;
|
||||
} = {},
|
||||
) {
|
||||
const cells = (Array.isArray(componentName) ? componentName : [componentName, componentName]) as [
|
||||
ComponentName,
|
||||
string,
|
||||
];
|
||||
|
||||
const [component] = cells;
|
||||
const concatComponent = cells.join('-');
|
||||
|
||||
return (prefixCls: string): UseComponentStyleResult => {
|
||||
const [theme, token, hashId] = useToken();
|
||||
const { getPrefixCls, iconPrefixCls, csp } = useContext(ConfigContext);
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
|
||||
// Shared config
|
||||
const sharedConfig: Omit<Parameters<typeof useStyleRegister>[0], 'path'> = {
|
||||
theme,
|
||||
token,
|
||||
hashId,
|
||||
nonce: () => csp?.nonce!,
|
||||
clientOnly: options.clientOnly,
|
||||
|
||||
// antd is always at top of styles
|
||||
order: options.order || -999,
|
||||
};
|
||||
|
||||
// Generate style for all a tags in antd component.
|
||||
useStyleRegister(
|
||||
{ ...sharedConfig, clientOnly: false, path: ['Shared', rootPrefixCls] },
|
||||
() => [
|
||||
{
|
||||
// Link
|
||||
'&': genLinkStyle(token),
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
// Generate style for icons
|
||||
useResetIconStyle(iconPrefixCls, csp);
|
||||
|
||||
return [
|
||||
useStyleRegister(
|
||||
{ ...sharedConfig, path: [concatComponent, prefixCls, iconPrefixCls] },
|
||||
() => {
|
||||
const { token: proxyToken, flush } = statisticToken(token);
|
||||
|
||||
const customComponentToken = { ...(token[component] as ComponentToken<ComponentName>) };
|
||||
if (options.deprecatedTokens) {
|
||||
const { deprecatedTokens } = options;
|
||||
deprecatedTokens.forEach(([oldTokenKey, newTokenKey]) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warning(
|
||||
!customComponentToken?.[oldTokenKey],
|
||||
`The token '${String(oldTokenKey)}' of ${component} had deprecated, use '${String(
|
||||
newTokenKey,
|
||||
)}' instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Should wrap with `if` clause, or there will be `undefined` in object.
|
||||
if (customComponentToken?.[oldTokenKey] || customComponentToken?.[newTokenKey]) {
|
||||
customComponentToken[newTokenKey] ??= customComponentToken?.[oldTokenKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
const defaultComponentToken =
|
||||
typeof getDefaultToken === 'function'
|
||||
? getDefaultToken(mergeToken(proxyToken, customComponentToken ?? {}))
|
||||
: getDefaultToken;
|
||||
|
||||
const mergedComponentToken = { ...defaultComponentToken, ...customComponentToken };
|
||||
|
||||
const componentCls = `.${prefixCls}`;
|
||||
const mergedToken = mergeToken<
|
||||
TokenWithCommonCls<GlobalTokenWithComponent<OverrideComponent>>
|
||||
>(
|
||||
proxyToken,
|
||||
{
|
||||
componentCls,
|
||||
prefixCls,
|
||||
iconCls: `.${iconPrefixCls}`,
|
||||
antCls: `.${rootPrefixCls}`,
|
||||
},
|
||||
mergedComponentToken,
|
||||
);
|
||||
|
||||
const styleInterpolation = styleFn(mergedToken as unknown as FullToken<ComponentName>, {
|
||||
hashId,
|
||||
prefixCls,
|
||||
rootPrefixCls,
|
||||
iconPrefixCls,
|
||||
overrideComponentToken: customComponentToken as any,
|
||||
});
|
||||
flush(component, mergedComponentToken);
|
||||
return [
|
||||
options.resetStyle === false ? null : genCommonStyle(token, prefixCls),
|
||||
styleInterpolation,
|
||||
];
|
||||
},
|
||||
),
|
||||
hashId,
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SubStyleComponentProps {
|
||||
prefixCls: string;
|
||||
}
|
||||
|
||||
// Get from second argument
|
||||
type RestParameters<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
|
||||
|
||||
export const genSubStyleComponent: <ComponentName extends OverrideComponent>(
|
||||
componentName: [ComponentName, string],
|
||||
...args: RestParameters<Parameters<typeof genComponentStyleHook<ComponentName>>>
|
||||
) => ComponentType<SubStyleComponentProps> = (componentName, styleFn, getDefaultToken, options) => {
|
||||
const useStyle = genComponentStyleHook(componentName, styleFn, getDefaultToken, {
|
||||
resetStyle: false,
|
||||
|
||||
// Sub Style should default after root one
|
||||
order: -998,
|
||||
...options,
|
||||
});
|
||||
|
||||
const StyledComponent: ComponentType<SubStyleComponentProps> = ({
|
||||
prefixCls,
|
||||
}: SubStyleComponentProps) => {
|
||||
useStyle(prefixCls);
|
||||
return null;
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
StyledComponent.displayName = `SubStyle_${
|
||||
Array.isArray(componentName) ? componentName.join('.') : componentName
|
||||
}`;
|
||||
}
|
||||
|
||||
return StyledComponent;
|
||||
};
|
327
components/theme/util/genComponentStyleHook.tsx
Normal file
327
components/theme/util/genComponentStyleHook.tsx
Normal file
@ -0,0 +1,327 @@
|
||||
/* eslint-disable no-redeclare */
|
||||
import type { ComponentType, FC, ReactElement } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import type { CSSInterpolation } from '@ant-design/cssinjs';
|
||||
import { token2CSSVar, useCSSVarRegister, useStyleRegister } from '@ant-design/cssinjs';
|
||||
import { warning } from 'rc-util';
|
||||
|
||||
import { ConfigContext } from '../../config-provider/context';
|
||||
import { genCommonStyle, genLinkStyle } from '../../style';
|
||||
import type {
|
||||
ComponentTokenMap,
|
||||
GlobalToken,
|
||||
OverrideToken,
|
||||
UseComponentStyleResult,
|
||||
} from '../interface';
|
||||
import useToken, { ignore, unitless } from '../useToken';
|
||||
import statisticToken, { merge as mergeToken } from './statistic';
|
||||
import useResetIconStyle from './useResetIconStyle';
|
||||
import genCalc from './calc';
|
||||
import type AbstractCalculator from './calc/calculator';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export type OverrideTokenWithoutDerivative = ComponentTokenMap;
|
||||
export type OverrideComponent = keyof OverrideTokenWithoutDerivative;
|
||||
export type GlobalTokenWithComponent<C extends OverrideComponent> = GlobalToken &
|
||||
ComponentTokenMap[C];
|
||||
|
||||
type ComponentToken<C extends OverrideComponent> = Exclude<OverrideToken[C], undefined>;
|
||||
type ComponentTokenKey<C extends OverrideComponent> = keyof ComponentToken<C>;
|
||||
|
||||
export interface StyleInfo {
|
||||
hashId: string;
|
||||
prefixCls: string;
|
||||
rootPrefixCls: string;
|
||||
iconPrefixCls: string;
|
||||
}
|
||||
|
||||
export type CSSUtil = {
|
||||
calc: (number: any) => AbstractCalculator;
|
||||
};
|
||||
|
||||
export type TokenWithCommonCls<T> = T & {
|
||||
/** Wrap component class with `.` prefix */
|
||||
componentCls: string;
|
||||
/** Origin prefix which do not have `.` prefix */
|
||||
prefixCls: string;
|
||||
/** Wrap icon class with `.` prefix */
|
||||
iconCls: string;
|
||||
/** Wrap ant prefixCls class with `.` prefix */
|
||||
antCls: string;
|
||||
} & CSSUtil;
|
||||
|
||||
export type FullToken<C extends OverrideComponent> = TokenWithCommonCls<
|
||||
GlobalTokenWithComponent<C>
|
||||
>;
|
||||
|
||||
export type GenStyleFn<C extends OverrideComponent> = (
|
||||
token: FullToken<C>,
|
||||
info: StyleInfo,
|
||||
) => CSSInterpolation;
|
||||
|
||||
export type GetDefaultToken<C extends OverrideComponent> =
|
||||
| null
|
||||
| OverrideTokenWithoutDerivative[C]
|
||||
| ((token: GlobalToken) => OverrideTokenWithoutDerivative[C]);
|
||||
|
||||
const getDefaultComponentToken = <C extends OverrideComponent>(
|
||||
component: C,
|
||||
token: GlobalToken,
|
||||
getDefaultToken: GetDefaultToken<C>,
|
||||
) => {
|
||||
if (typeof getDefaultToken === 'function') {
|
||||
return getDefaultToken(mergeToken<GlobalToken>(token, token[component] ?? {}));
|
||||
}
|
||||
return getDefaultToken ?? {};
|
||||
};
|
||||
|
||||
const getComponentToken = <C extends OverrideComponent>(
|
||||
component: C,
|
||||
token: GlobalToken,
|
||||
defaultToken: OverrideTokenWithoutDerivative[C],
|
||||
options?: { prefix?: boolean; deprecatedTokens?: [ComponentTokenKey<C>, ComponentTokenKey<C>][] },
|
||||
) => {
|
||||
const customToken = { ...(token[component] as ComponentToken<C>) };
|
||||
if (options?.deprecatedTokens) {
|
||||
const { deprecatedTokens } = options;
|
||||
deprecatedTokens.forEach(([oldTokenKey, newTokenKey]) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warning(
|
||||
!customToken?.[oldTokenKey],
|
||||
`The token '${String(oldTokenKey)}' of ${component} had deprecated, use '${String(
|
||||
newTokenKey,
|
||||
)}' instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Should wrap with `if` clause, or there will be `undefined` in object.
|
||||
if (customToken?.[oldTokenKey] || customToken?.[newTokenKey]) {
|
||||
customToken[newTokenKey] ??= customToken?.[oldTokenKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
const mergedToken: any = { ...defaultToken, ...customToken };
|
||||
|
||||
// Remove same value as global token to minimize size
|
||||
Object.keys(mergedToken).forEach((key) => {
|
||||
if (mergedToken[key] === token[key as keyof GlobalToken]) {
|
||||
delete mergedToken[key];
|
||||
}
|
||||
});
|
||||
|
||||
if (options?.prefix && defaultToken) {
|
||||
// Prefix component token with component name
|
||||
Object.keys(defaultToken).forEach((key) => {
|
||||
const newKey = `${component}${key.slice(0, 1).toUpperCase()}${key.slice(1)}`;
|
||||
mergedToken[newKey] = mergedToken[key];
|
||||
delete mergedToken[key];
|
||||
});
|
||||
}
|
||||
|
||||
return mergedToken;
|
||||
};
|
||||
|
||||
export default function genComponentStyleHook<C extends OverrideComponent>(
|
||||
componentName: C | [C, string],
|
||||
styleFn: GenStyleFn<C>,
|
||||
getDefaultToken?:
|
||||
| null
|
||||
| OverrideTokenWithoutDerivative[C]
|
||||
| ((token: GlobalToken) => OverrideTokenWithoutDerivative[C]),
|
||||
options: {
|
||||
resetStyle?: boolean;
|
||||
// Deprecated token key map [["oldTokenKey", "newTokenKey"], ["oldTokenKey", "newTokenKey"]]
|
||||
deprecatedTokens?: [ComponentTokenKey<C>, ComponentTokenKey<C>][];
|
||||
/**
|
||||
* Only use component style in client side. Ignore in SSR.
|
||||
*/
|
||||
clientOnly?: boolean;
|
||||
/**
|
||||
* Set order of component style. Default is -999.
|
||||
*/
|
||||
order?: number;
|
||||
} = {},
|
||||
) {
|
||||
const cells = (Array.isArray(componentName) ? componentName : [componentName, componentName]) as [
|
||||
C,
|
||||
string,
|
||||
];
|
||||
|
||||
const [component] = cells;
|
||||
const concatComponent = cells.join('-');
|
||||
|
||||
return (prefixCls: string): UseComponentStyleResult => {
|
||||
const [theme, token, hashId, realToken, cssVar] = useToken();
|
||||
const { getPrefixCls, iconPrefixCls, csp } = useContext(ConfigContext);
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
|
||||
const calculator = genCalc(cssVar ? 'css' : 'js');
|
||||
|
||||
// Shared config
|
||||
const sharedConfig: Omit<Parameters<typeof useStyleRegister>[0], 'path'> = {
|
||||
theme,
|
||||
token,
|
||||
hashId,
|
||||
nonce: () => csp?.nonce!,
|
||||
clientOnly: options.clientOnly,
|
||||
|
||||
// antd is always at top of styles
|
||||
order: options.order || -999,
|
||||
};
|
||||
|
||||
// Generate style for all a tags in antd component.
|
||||
useStyleRegister(
|
||||
{ ...sharedConfig, clientOnly: false, path: ['Shared', rootPrefixCls] },
|
||||
() => [
|
||||
{
|
||||
// Link
|
||||
'&': genLinkStyle(token),
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
// Generate style for icons
|
||||
useResetIconStyle(iconPrefixCls, csp);
|
||||
|
||||
const wrapSSR = useStyleRegister(
|
||||
{ ...sharedConfig, path: [concatComponent, prefixCls, iconPrefixCls] },
|
||||
() => {
|
||||
const { token: proxyToken, flush } = statisticToken(token);
|
||||
|
||||
const defaultComponentToken = getDefaultComponentToken(
|
||||
component,
|
||||
realToken,
|
||||
getDefaultToken,
|
||||
);
|
||||
|
||||
const componentCls = `.${prefixCls}`;
|
||||
const componentToken = getComponentToken(component, realToken, defaultComponentToken, {
|
||||
deprecatedTokens: options.deprecatedTokens,
|
||||
});
|
||||
|
||||
if (cssVar) {
|
||||
Object.keys(defaultComponentToken).forEach((key) => {
|
||||
defaultComponentToken[key] = `var(${token2CSSVar(
|
||||
key,
|
||||
`${cssVar?.prefix}-${component}`,
|
||||
)})`;
|
||||
});
|
||||
}
|
||||
const mergedToken = mergeToken<
|
||||
TokenWithCommonCls<GlobalTokenWithComponent<OverrideComponent>>
|
||||
>(
|
||||
proxyToken,
|
||||
{
|
||||
componentCls,
|
||||
prefixCls,
|
||||
iconCls: `.${iconPrefixCls}`,
|
||||
antCls: `.${rootPrefixCls}`,
|
||||
calc: calculator,
|
||||
},
|
||||
cssVar ? defaultComponentToken : componentToken,
|
||||
);
|
||||
|
||||
const styleInterpolation = styleFn(mergedToken as unknown as FullToken<C>, {
|
||||
hashId,
|
||||
prefixCls,
|
||||
rootPrefixCls,
|
||||
iconPrefixCls,
|
||||
});
|
||||
flush(component, componentToken);
|
||||
return [
|
||||
options.resetStyle === false ? null : genCommonStyle(token, prefixCls),
|
||||
styleInterpolation,
|
||||
];
|
||||
},
|
||||
);
|
||||
|
||||
return [wrapSSR, classNames(hashId, cssVar?.key)];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SubStyleComponentProps {
|
||||
prefixCls: string;
|
||||
}
|
||||
|
||||
// Get from second argument
|
||||
type RestParameters<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
|
||||
|
||||
export const genSubStyleComponent: <C extends OverrideComponent>(
|
||||
componentName: [C, string],
|
||||
...args: RestParameters<Parameters<typeof genComponentStyleHook<C>>>
|
||||
) => ComponentType<SubStyleComponentProps> = (componentName, styleFn, getDefaultToken, options) => {
|
||||
const useStyle = genComponentStyleHook(componentName, styleFn, getDefaultToken, {
|
||||
resetStyle: false,
|
||||
|
||||
// Sub Style should default after root one
|
||||
order: -998,
|
||||
...options,
|
||||
});
|
||||
|
||||
const StyledComponent: ComponentType<SubStyleComponentProps> = ({
|
||||
prefixCls,
|
||||
}: SubStyleComponentProps) => {
|
||||
useStyle(prefixCls);
|
||||
return null;
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
StyledComponent.displayName = `SubStyle_${
|
||||
Array.isArray(componentName) ? componentName.join('.') : componentName
|
||||
}`;
|
||||
}
|
||||
|
||||
return StyledComponent;
|
||||
};
|
||||
|
||||
export type CSSVarRegisterProps = {
|
||||
rootCls: string;
|
||||
component: string;
|
||||
cssVar: {
|
||||
prefix?: string;
|
||||
key?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const genCSSVarRegister = <C extends OverrideComponent>(
|
||||
component: C,
|
||||
getDefaultToken: GetDefaultToken<C>,
|
||||
) => {
|
||||
const CSSVarRegister: FC<CSSVarRegisterProps> = ({ rootCls, cssVar }) => {
|
||||
const [, , , realToken] = useToken();
|
||||
useCSSVarRegister(
|
||||
{
|
||||
path: [component],
|
||||
prefix: cssVar?.prefix,
|
||||
key: cssVar?.key!,
|
||||
unitless,
|
||||
ignore,
|
||||
token: realToken,
|
||||
scope: rootCls,
|
||||
},
|
||||
() => {
|
||||
const defaultToken = getDefaultComponentToken(component, realToken, getDefaultToken);
|
||||
return getComponentToken(component, realToken, defaultToken, {
|
||||
prefix: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
const useCSSVar = (rootCls: string) => {
|
||||
const [, , , , cssVar] = useToken();
|
||||
|
||||
return (node: ReactElement): ReactElement =>
|
||||
cssVar ? (
|
||||
<>
|
||||
<CSSVarRegister rootCls={rootCls} cssVar={cssVar} component={component} />
|
||||
{node}
|
||||
</>
|
||||
) : (
|
||||
node
|
||||
);
|
||||
};
|
||||
|
||||
return useCSSVar;
|
||||
};
|
@ -56,7 +56,7 @@ Raise priority through plugin:
|
||||
To unify LTR and RTL styles, Ant Design uses CSS logical properties. For example, the original `margin-left` is replaced by `margin-inline-start`, so that it is the starting position spacing under both LTR and RTL. If you need to be compatible with older browsers, you can configure `transformers` through the `StyleProvider` of `@ant-design/cssinjs`:
|
||||
|
||||
```tsx
|
||||
import { StyleProvider, legacyLogicalPropertiesTransformer } from '@ant-design/cssinjs';
|
||||
import { legacyLogicalPropertiesTransformer, StyleProvider } from '@ant-design/cssinjs';
|
||||
|
||||
// `transformers` provides a way to transform CSS properties
|
||||
export default () => (
|
||||
@ -83,7 +83,7 @@ When toggled, styles will downgrade CSS logical properties:
|
||||
In responsive web development, there is a need for a convenient and flexible way to achieve page adaptation and responsive design. The `px2remTransformer` transformer can quickly and accurately convert pixel units in style sheets to rem units relative to the root element (HTML tag), enabling the implementation of adaptive and responsive layouts.
|
||||
|
||||
```tsx
|
||||
import { StyleProvider, px2remTransformer } from '@ant-design/cssinjs';
|
||||
import { px2remTransformer, StyleProvider } from '@ant-design/cssinjs';
|
||||
|
||||
const px2rem = px2remTransformer({
|
||||
rootValue: 32, // 32px = 1rem; @default 16
|
||||
|
@ -113,7 +113,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.0.0",
|
||||
"@ant-design/cssinjs": "^1.17.2",
|
||||
"@ant-design/cssinjs": "^2.0.0-alpha.1",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/react-slick": "~1.0.2",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
@ -329,11 +329,11 @@
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "./dist/antd.min.js",
|
||||
"limit": "401 KiB"
|
||||
"limit": "405 KiB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/antd-with-locales.min.js",
|
||||
"limit": "460 KiB"
|
||||
"limit": "465 KiB"
|
||||
}
|
||||
],
|
||||
"title": "Ant Design",
|
||||
|
Loading…
Reference in New Issue
Block a user