Skip to content

Commit

Permalink
feat: support css var (#147)
Browse files Browse the repository at this point in the history
* feat: css var

* chore: code clean

* feat: update regexp

* feat: more feature

* test: add basic test case

* test: could mix

* feat: generate css var string when transform

* test: cssVarRegister

* test: add dynamic test

* chore: code clean

* test: auto clear test

* chore: code clean
  • Loading branch information
MadCcc committed Oct 25, 2023
1 parent 92dce00 commit 2f5c7b3
Show file tree
Hide file tree
Showing 14 changed files with 711 additions and 32 deletions.
8 changes: 8 additions & 0 deletions docs/demo/css-var.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: CSS Variables
nav:
title: Demo
path: /demo
---

<code src="../examples/css-var.tsx"></code>
11 changes: 6 additions & 5 deletions docs/examples/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { useStyleRegister } from '@ant-design/cssinjs';
import { unit, useStyleRegister } from '@ant-design/cssinjs';
import classNames from 'classnames';
import React from 'react';
import type { DerivativeToken } from './theme';
Expand All @@ -14,6 +14,7 @@ const genSharedButtonStyle = (
borderColor: token.borderColor,
borderWidth: token.borderWidth,
borderRadius: token.borderRadius,
lineHeight: token.lineHeight,

cursor: 'pointer',

Expand Down Expand Up @@ -65,7 +66,7 @@ const genPrimaryButtonStyle = (
): CSSInterpolation =>
genSolidButtonStyle(prefixCls, token, () => ({
backgroundColor: token.primaryColor,
border: `${token.borderWidth}px solid ${token.primaryColor}`,
border: `${unit(token.borderWidth)} solid ${token.primaryColor}`,
color: token.reverseTextColor,

'&:hover': {
Expand All @@ -83,7 +84,7 @@ const genGhostButtonStyle = (
[`.${prefixCls}`]: {
backgroundColor: 'transparent',
color: token.primaryColor,
border: `${token.borderWidth}px solid ${token.primaryColor}`,
border: `${unit(token.borderWidth)} solid ${token.primaryColor}`,

'&:hover': {
borderColor: token.primaryColor,
Expand All @@ -102,7 +103,7 @@ const Button = ({ className, type, ...restProps }: ButtonProps) => {
const prefixCls = 'ant-btn';

// 【自定义】制造样式
const [theme, token, hashId] = useToken();
const [theme, token, hashId, cssVarKey] = useToken();

// default 添加默认样式选择器后可以省很多冲突解决问题
const defaultCls = `${prefixCls}-default`;
Expand All @@ -129,7 +130,7 @@ const Button = ({ className, type, ...restProps }: ButtonProps) => {

return wrapSSR(
<button
className={classNames(prefixCls, typeCls, hashId, className)}
className={classNames(prefixCls, typeCls, hashId, className, cssVarKey)}
{...restProps}
/>,
);
Expand Down
36 changes: 30 additions & 6 deletions docs/examples/components/theme.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { TinyColor } from '@ctrl/tinycolor';
import type { CSSObject, Theme } from '@ant-design/cssinjs';
import { createTheme, useCacheToken } from '@ant-design/cssinjs';
import { TinyColor } from '@ctrl/tinycolor';
import React from 'react';

export type GetStyle = (prefixCls: string, token: DerivativeToken) => CSSObject;

Expand All @@ -15,6 +15,9 @@ export interface DesignToken {
borderRadius: number;
borderColor: string;
borderWidth: number;

lineHeight: number;
lineHeightBase: number;
}

export interface DerivativeToken extends DesignToken {
Expand All @@ -31,6 +34,9 @@ const defaultDesignToken: DesignToken = {
borderRadius: 2,
borderColor: 'black',
borderWidth: 1,

lineHeight: 1.5,
lineHeightBase: 1.5,
};

// 模拟推导过程
Expand All @@ -48,21 +54,39 @@ export const ThemeContext = React.createContext(createTheme(derivative));
export const DesignTokenContext = React.createContext<{
token?: Partial<DesignToken>;
hashed?: string | boolean;
cssVar?: {
key: string;
};
}>({
token: defaultDesignToken,
});

export function useToken(): [Theme<any, any>, DerivativeToken, string] {
const { token: rootDesignToken = {}, hashed } =
React.useContext(DesignTokenContext);
export function useToken(): [
Theme<any, any>,
DerivativeToken,
string,
string | undefined,
] {
const {
token: rootDesignToken = {},
hashed,
cssVar,
} = React.useContext(DesignTokenContext);
const theme = React.useContext(ThemeContext);

const [token, hashId] = useCacheToken<DerivativeToken, DesignToken>(
theme,
[defaultDesignToken, rootDesignToken],
{
salt: typeof hashed === 'string' ? hashed : '',
cssVar: cssVar && {
prefix: 'rc',
key: cssVar.key,
unitless: {
lineHeight: true,
},
},
},
);
return [theme, token, hashed ? hashId : ''];
return [theme, token, hashed ? hashId : '', cssVar?.key];
}
52 changes: 52 additions & 0 deletions docs/examples/css-var.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import './basic.less';
import Button from './components/Button';
import { DesignTokenContext } from './components/theme';

export default function App() {
const [show, setShow] = React.useState(true);

const [, forceUpdate] = React.useState({});
React.useEffect(() => {
forceUpdate({});
}, []);

return (
<div style={{ background: 'rgba(0,0,0,0.1)', padding: 16 }}>
<h3>默认情况下不会自动删除添加的样式</h3>

<label>
<input type="checkbox" checked={show} onChange={() => setShow(!show)} />
Show Components
</label>

{show && (
<div>
<DesignTokenContext.Provider
value={{ cssVar: { key: 'default' }, hashed: true }}
>
<Button>Default</Button>
<Button type="primary">Primary</Button>
<Button type="ghost">Ghost</Button>

<Button className="btn-override">Override By ClassName</Button>
</DesignTokenContext.Provider>
<br />
<DesignTokenContext.Provider
value={{
token: { primaryColor: 'green' },
cssVar: { key: 'default2' },
hashed: true,
}}
>
<Button>Default</Button>
<Button type="primary">Primary</Button>
<Button type="ghost">Ghost</Button>

<Button className="btn-override">Override By ClassName</Button>
</DesignTokenContext.Provider>
</div>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion docs/examples/ssr-hydrate-file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createCache, StyleProvider } from '@ant-design/cssinjs';
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import { Demo } from './ssr-advanced';
import './ssr-hydrate-file.css';
// import './ssr-hydrate-file.css';

// Copy from `ssr-advanced-hydrate.tsx`
const HTML = `
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"license": "MIT",
"scripts": {
"start": "dumi dev",
"dev": "father dev",
"docs:build": "dumi build",
"docs:deploy": "gh-pages -d .doc",
"compile": "father build",
Expand Down
70 changes: 70 additions & 0 deletions src/hooks/useCSSVarRegister.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import hash from '@emotion/hash';
import { removeCSS, updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import { useContext } from 'react';
import StyleContext, {
ATTR_MARK,
ATTR_TOKEN,
CSS_IN_JS_INSTANCE,
} from '../StyleContext';
import { isClientSide } from '../util';
import type { TokenWithCSSVar } from '../util/css-variables';
import { transformToken } from '../util/css-variables';
import useGlobalCache from './useGlobalCache';

const useCSSVarRegister = <V, T extends Record<string, V>>(
config: {
path: string[];
key: string;
prefix?: string;
unitless?: Record<string, boolean>;
token: any;
},
fn: () => T,
) => {
const { key, prefix, unitless, token } = config;
const {
cache: { instanceId },
container,
autoClear,
} = useContext(StyleContext);
const { _tokenKey: tokenKey } = token;

const cache = useGlobalCache<[TokenWithCSSVar<T>, string, T, string]>(
'variables',
[...config.path, key, tokenKey],
() => {
const styleId = hash([...config.path, key].join('%'));
const originToken = fn();
const [mergedToken, cssVarsStr] = transformToken(originToken, key, {
prefix,
unitless,
});
return [mergedToken, cssVarsStr, originToken, styleId];
},
([, , , styleId], fromHMR) => {
if ((fromHMR || autoClear) && isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
([, cssVarsStr, , styleId]) => {
if (!cssVarsStr) {
return;
}
const style = updateCSS(cssVarsStr, styleId, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
priority: -999,
});

(style as any)[CSS_IN_JS_INSTANCE] = instanceId;

// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, key);
},
);

return cache;
};

export default useCSSVarRegister;
Loading

0 comments on commit 2f5c7b3

Please sign in to comment.