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

feat: support css var #147

Merged
merged 13 commits into from
Oct 25, 2023
Merged
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
Loading