Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Component Style to Adapt CSS Variables #45618

Closed
65 tasks done
MadCcc opened this issue Nov 1, 2023 · 11 comments
Closed
65 tasks done

Refactor Component Style to Adapt CSS Variables #45618

MadCcc opened this issue Nov 1, 2023 · 11 comments

Comments

@MadCcc
Copy link
Member

MadCcc commented Nov 1, 2023

Better to read before:

Why need Refactor

Currently we generate different hash and tokens for each theme, which are used in styles to get different styles, and sometimes we will use tokens to calculate a new value. Since we will replace actual value of token with CSS variables like var(--color-primary), tokens could not be used to calculate directly any more. That's the major reason that we must refactor antd component style.

Refactor Plan

We will put component refactoring tasks in this issue, and these tasks should be all done in November, which means the feature of CSS variables will be released in 5.12.0. Assigned task that do not have response for more than one week will be called back.

All PRs should be based on feature branch. In feature branch, all demos are enabled with CSS variables.

The first PR: #45589 . This PR contains feature of CSS variables and refactor of Button. We will accpect task requestment after this PR is merged.

How and What to Refactor

Turn Component Token into CSS variables

  1. Create cssVar.ts in components/xxx/style/ directory, and fill in with code below (demonstrated with Button)
import { genCSSVarRegister } from '../../theme/internal';
import { prepareComponentToken } from '.';

export default genCSSVarRegister('Button', prepareComponentToken);

prepareComponentToken is the third param of genComponentStyleHook. If it exist, we should extract and export it from style/index.ts.

This file will export a hook, we will use it in component.

  1. Same as useStyle, import useCSSVar in component:
import useStyle from './style';
++  import useCSSVar from './style/cssVar';

const Component = () => {
  const prefixCls = 'ant-comp';
  
--  const [wrapSSR, hashId] = useStyle();
++  const [, hashId] = useStyle(prefixCls);
++  const wrapCSSVar = useCSSVar(prefixCls);

--  return wrapSSR(<div />);
++  return wrapCSSVar(<div />);
}

wrapSSR is useless, we could safely remove it. useCSSVar need one param, which should be rootCls of the component. In most cases, this rootCls could be prefixCls. But if prefixCls is not always on the root element of component, we could use useCSSVarCls to generate a new class and apply it to where rootClassName is applied to.

import useStyle from './style';
++  import useCSSVar from './style/cssVar';
++  import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';

const Component = () => {
  const prefixCls = 'ant-comp';
++  const cssVarCls = useCSSVarCls(prefixCls);
  
--  const [wrapSSR, hashId] = useStyle();
++  const [, hashId] = useStyle(prefixCls);
++  const wrapCSSVar = useCSSVar(cssVarCls);

--  return wrapSSR(<div className={rootClassName} />);
++  return wrapCSSVar(<div className={classNames(cssVarCls, rootClassName)} />);
}

In this way, all component token will be transformed into CSS variables. If you customize component token, you could find styles like this:

image

Refactor Token Calculation

As is said before, we need to refactor token calculation in order to get right value. With CSS variables, all calculation should be transformed with calc(), or it should become a component token.

  1. Turn Calculation into component token.

In prepareComponentToken, tokens remain actual value, which could be directly calculated with js code, so we do not need to refactor this part of code, and can even add more component tokens to avoid transforming. Some calculation for color can not be transformed with calc() neither, so we should add component token for them.

export const prepareComponentToken: GetDefaultToken<'Button'> = (token) => {
  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}`,
  };
};
  1. Replace Calculation with token.calc

Outside of prepareComponentToken, we must replace all calculation with token.calc. This is a function that will differ by configuration of CSS variables. So we could use one code to get different value with different configuration. For example:

const genRoundButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({
  borderRadius: token.controlHeight,
  paddingInlineStart: token.calc(token.controlHeight).div(2).equal(), // `32 / 2` or `calc(32px / 2)`
  paddingInlineEnd: token.calc(token.controlHeight).div(2).equal(),
});

token.calc is chainable, and it will do calculation by the order. For more example, take a look at the test cases:

https://github.com/ant-design/ant-design/pull/45589/files#diff-474a355b4973b5cd9ab68c695f77e8fc28504f72f7af6297c8396151bbfff873R13

Replace Math.max and Math.min with token.max and token.min

-- borderRadius: Math.max(token.borderRadius, 4),
++ borderRadius: token.max(token.borderRadius, 4),

Same as token.calc, the result with css var will be min(var(--border-radius), 4px)

Replace px with unit()

For some css properties like border, we used to write like this:

border: `${token.lineWidth}px ${token.lineStyle} ${token.borderColor}`

But with CSS varaibles, `${token.lineWidth}px` will not work. So we should use unit() from @ant-deisng/cssinjs.

import { unit } from '@ant-design/cssinjs';

const genStyle = (token) => ({
  border: `${unit(token.lineWidth)} ${token.lineStyle} ${token.borderColor}`
});

Replace fontSize * lineHeight with fontHeight

We used to write Math.round(fontSize * lineHeight) in style to get integer, but round() of CSS is not available now. So fontHeight is added into token, which is calculated already with fontSize and lineHeight, and so as fontHeightSM and fontHeightLG.

Task List

If you are interested in contributing, please comment in this issue and pick one component that is not assigned to anyone.

Simple PR to reference: #45716
Example with custom rootCls and Math.ceil: #45727

@MadCcc

@li-jia-nan

@RedJue

@c0dedance

@kiner-tang

@JarvisArt

@cc-hearts

@MadCcc MadCcc added 💡 Feature Request 🎱 Collaborate PR only Need confirm with Designer or Core member labels Nov 1, 2023
@MadCcc MadCcc removed the unconfirmed label Nov 1, 2023
@MadCcc MadCcc pinned this issue Nov 1, 2023
@afc163

This comment was marked as resolved.

@MadCcc
Copy link
Member Author

MadCcc commented Nov 1, 2023

这是大动作,建议一开始先用一个实验性配置开启。

为了方便重构,在重构分支是默认开启的。整个方案和原先是兼容的,可以配置局部开启

@RedJue
Copy link
Member

RedJue commented Nov 1, 2023

Calendar
Card
Carousel
Cascader
Checkbox
Collapse
ColorPicker

@c0dedance
Copy link
Contributor

认领 Watermark Statistic Empty Popconfirm skeleton

@dancerphil
Copy link
Contributor

dancerphil commented Nov 2, 2023

想要讨论一个场景,看看在新的 css-variables 模式下怎么支持:

如果两个组件(比如 Button 和 Input 好了)都使用了 --antd-color-primary,但是我的设计规范中,希望它们两个是不同的颜色,看起来我需要这样写:

.ant-button {
	--antd-color-primary: red;
}

.ant-input {
	--antd-color-primary: blue;
}

然而,我作为一个设计规范的指定者,更希望在一个非常全局的范围定义他们,并且把组件级别的自定义能力交给业务开发者,比如:

/* 设计规范 */
.app {
	--antd-button-color-primary: red; /* 当然,此时可以取更好的名字 */
	--antd-input-color-primary: blue;
}

/* 业务层面覆盖 */
.ant-button {
	--antd-button-color-primary: pink;
}

这个提案可能可行吗?似乎会产生大量的 component token。(不过我觉得是不是可以在 map token 的层面还是用 theme,然后把 css var 仅作为 component token?)


我期望的 API 可能是这样的:

  1. 移除 theme 里面的 component 系列

  2. 提供一个额外的 calculateComponentTokenClassName

const className = calculateComponentTokenClassName(theme); // .css-var-hashxxx.ant-theme

return <div className={className}>{children}</div>
.css-var-hashxxx.ant-theme {
	--ant-button-primary-color: #1976d2;
    --ant-input-border-color: #1976d2;
    --ant-input-placeholder-color: #eeeeee;
    /* ... */
}
  1. 组件层面默认只消费 css vars,消费的优先级是 dom 嵌套的层级
  2. ant 的 css vars 放在 :root 上,像我这样的团队设计规范放在 App 那一层上,业务层面的放在页面上或者组件上

@MadCcc
Copy link
Member Author

MadCcc commented Nov 2, 2023

@dancerphil 我们目前希望用户还是使用 ConfigProvider 去修改主题——也就是说是否开启 css 变量对于主题的使用是没有任何影响的。

如果需要在 react 生命周期之外修改 css 变量,自定义主题的 key(用于替代 css-var-hash),再加上组件的 root class 就可以做到覆盖。

无论如何,我们想尽量淡化用户对 css 变量的认知,有些后续的功能或者拓展仍然可以在此之上进行。

@kiner-tang
Copy link
Member

kiner-tang commented Nov 3, 2023

我想认领一下:
Layout
List
Mentions
Menu
Message
Modal
Notification
Pagination
Tabs
Transfer
Tree
TreeSelect
Typography
Upload

@poyiding
Copy link
Contributor

poyiding commented Nov 8, 2023

认领
Badge
Breadcrumb
Descriptions
Drawer

@JarvisArt
Copy link
Contributor

认领
FloatButton
Image
Progress
Radio
Rate
Result
Space
Switch
Timeline

@MadCcc
Copy link
Member Author

MadCcc commented Nov 8, 2023

Task updated for above volunteers ❤️.

@cc-hearts
Copy link
Contributor

认领
Steps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants