Skip to content

Commit

Permalink
📝 docs: 更新最佳实践文档 (#93)
Browse files Browse the repository at this point in the history
* 📝 docs: update best practice docs

* ✅ ci: fix ci lint

* 📝 docs: update best practice docs

* 📝 docs: 补充 antd 组件自定义实践案例
  • Loading branch information
arvinxx committed Aug 14, 2023
1 parent 693d1bf commit e2d0da2
Show file tree
Hide file tree
Showing 21 changed files with 549 additions and 13 deletions.
8 changes: 8 additions & 0 deletions docs/best-practice/antd-based-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: 🚧 基于 antd v5 二次封装组件库
group:
title: 组件库研发
order: 2
---

# 基于 antd v5 二开的组件库,应该如何优雅书写样式?
45 changes: 45 additions & 0 deletions docs/best-practice/antd-override.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: 自定义 antd 组件样式
group: 主题定制
---

# 如何更加优雅地覆写 antd 组件样式?

## 基于 ConfigProvider 自定义

antd 在 V5 提供了全新的 theme 属性用于自定义,因此如果需要自定义组件样式,建议优先采用 CP 上的 theme 字段。

示例 demo 如下:

<code src="./demos/ConfigProviderOverride.tsx"></code>

:::info
更多基于 ConfigProvider 的主题定制能力,详见 [聊聊 Ant Design V5 的主题(上):CSSinJS 动态主题的花活](https://www.yuque.com/antfe/featured/durxuu94nvgvgmzq#vFlnd)
:::

antd-style 的 ThemeProvider 是基于 ConfigProvider 的业务层封装,提供业务友好的定制能力,查看:[自定义主题](/guide/custom-theme)

## 基本覆写

`createStyles` 方法存在一个 `prefixCls` 参数,使用该参数可以传入组件的前缀,这样一来,任何的样式覆写都可以随着 prefixCls 的变化而自动变化。

<code src="./demos/DefaultOverride"></code>

## 抬升权重覆写

在某些组件中,直接添加类名可能因为权重不够高,导致无法覆盖样式,此时可以通过 `&` 符号来抬升相应的权重。

<code src="./demos/OverrideWeight"></code>

## 多 classNames 场景覆写

classNames 是 antd V5 的一个重头戏: [[RFC] Semantic DOM Structure for all Components](https://github.com/ant-design/ant-design/discussions/40221)
在过去,我们要做样式定义,需要找很多 dom 节点进行大量的样式覆写,而 antd 版本升级的过程中,有时候会对 dom 结构进行调整。这样一来,我们覆写的样式就会出现问题。

而 classNames 将为我们提供一个稳定的 dom 结构 API ,我们可以通过 classNames 传入的类名,将会文档指向对应的 dom 节点,进而大大降低 DOM 变化带来的 Breaking Change 风险,同时也让我们不必再 hack 式地找样式类名。

<code src="./demos/InputclassNames.tsx"></code>

## 相关讨论

- [样式权重问题](https://github.com/ant-design/antd-style/issues/24)
3 changes: 1 addition & 2 deletions docs/case/clay.md → docs/best-practice/clay.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
title: 黏土风 UI
order: 10
group: 自定义主题
group: 样式案例
---

# 黏土风格
Expand Down
53 changes: 53 additions & 0 deletions docs/best-practice/custom-token-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: 扩展自定义 Token 类型定义
group:
title: 主题定制
order: 1
---

# 如何给 antd-style 扩展 CustomToken 对象类型定义?

## 解决思路

通过给 `antd-style` 扩展 `CustomToken` 接口的类型定义,可以为 `useTheme` hooks 中增加相应的 token 类型定义。

同时,给 `ThemeProvider` 对象添加泛型,可以约束 `customToken` 的入参定义。

```tsx | pure
import { ThemeProvider, useTheme } from 'antd-style';

interface NewToken {
customBrandColor: string;
}

// 通过给 antd-style 扩展 CustomToken 对象类型定义,可以为 useTheme 中增加相应的 token 对象
declare module 'antd-style' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomToken extends NewToken {}
}

const App = () => {
const token = useTheme();
return <div>{token.customBrandColor}</div>;
};

export default () => (
// 给 ThemeProvider 对象添加泛型后可以约束 customToken 接口的入参定义
<ThemeProvider<NewToken> customToken={{ customBrandColor: '#c956df' }}>
<App />
</ThemeProvider>
);
```

:::info
由于 CustomToken 大概率是一个空 interface,如果在项目中有配置 ` @typescript-eslint/no-empty-interface` 的规则,就在代码格式化时导致接口定义被订正改为 type,而 type 是无法扩展的,会导致提示丢失(相关 issue: [#16](https://github.com/ant-design/antd-style/issues/16))。因此解决方案为如上述示例代码一样,添加禁用规则。
:::

## 参考代码

- [dumi-theme-antd-style](https://github.com/arvinxx/dumi-theme-antd-style/blob/master/src/styles/customToken.ts)
- [Ant Design 官网](https://github.com/ant-design/ant-design/blob/master/.dumi/theme/SiteThemeProvider.tsx)

## 相关讨论

- [🧐[问题] 请问一下如何给 antd-style 扩展 CustomToken 对象类型定义](https://github.com/ant-design/antd-style/issues/16)
48 changes: 48 additions & 0 deletions docs/best-practice/demos/ConfigProviderOverride.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* iframe: true
*/

import { Button, Checkbox, ConfigProvider, Popover, theme } from 'antd';
import { Flexbox } from 'react-layout-kit';

export default () => {
const { token } = theme.useToken();

return (
<ConfigProvider
theme={{
components: {
Popover: { colorText: token.colorTextLightSolid },
Checkbox: {
colorPrimary: token.blue7,
colorText: token.colorTextLightSolid,
},
Button: { colorPrimary: token.blue7 },
},
}}
>
<div style={{ marginBottom: 100 }}>
<Popover
open
content={
<div style={{ width: 300 }}>
<div>antd V5 的 Popup ,结合 结合 组件级 Token,可以非常简单地实现自定义样式</div>

<Flexbox style={{ marginTop: 24 }} horizontal distribution={'space-between'} gap={8}>
<Checkbox checked>不再显示</Checkbox>
<Button type={'primary'} size={'small'}>
我知道了
</Button>
</Flexbox>
</div>
}
color={'blue'}
arrow={{ pointAtCenter: true }}
trigger="hover"
>
antd v5 的组件级自定义样式,轻松又便捷
</Popover>
</div>
</ConfigProvider>
);
};
30 changes: 30 additions & 0 deletions docs/best-practice/demos/DefaultOverride.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* defaultShowCode: true
*/
import { Button, Space } from 'antd';
import { ThemeProvider, createStyles } from 'antd-style';

const useStyles = createStyles(({ token, css, prefixCls }) => ({
override: css`
&.${prefixCls}-btn {
background-color: ${token.colorWarning};
}
`,
}));

const Demo = ({ text }: { text?: string }) => {
const { styles } = useStyles();

return <Button className={styles.override}>{text ?? 'override to warning color'}</Button>;
};

export default () => {
return (
<Space>
<Demo />
<ThemeProvider prefixCls={'abc'}>
<Demo text={'prefixCls to abc'} />
</ThemeProvider>
</Space>
);
};
36 changes: 36 additions & 0 deletions docs/best-practice/demos/InputclassNames.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* defaultShowCode: true
*/
import { Input } from 'antd';
import { createStyles } from 'antd-style';

const useStyles = createStyles(({ token, css, prefixCls }) => ({
bg: css`
background: ${token.colorBgLayout};
padding: 24px;
`,
input: css`
background: transparent;
`,
wrapper: css`
background: transparent;
border: 2px solid ${token.colorBorder};
`,
suffix: css`
color: ${token.colorTextQuaternary};
`,
}));

export default () => {
const { styles } = useStyles();

return (
<div className={styles.bg}>
<Input
placeholder={'自定义Input classNames'}
suffix={'$'}
classNames={{ affixWrapper: styles.wrapper, input: styles.input, suffix: styles.suffix }}
/>
</div>
);
};
36 changes: 36 additions & 0 deletions docs/best-practice/demos/NestElements.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createStyles } from 'antd-style';

const useStyles = createStyles(({ css, cx }) => {
// 使用 cx 包裹 css
const child = cx(css`
background: red;
width: 100px;
height: 100px;
`);

return {
parent: css`
cursor: pointer;
&:hover {
.${child} {
background: blue;
}
}
`,
child,
};
});

const Demo = () => {
const { styles } = useStyles();

return (
<div className={styles.parent}>
<div className={styles.child} />
hover to change color
</div>
);
};

export default Demo;
32 changes: 32 additions & 0 deletions docs/best-practice/demos/OverrideWeight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* defaultShowCode: true
*/
import { App, Layout } from 'antd';
import { createStyles } from 'antd-style';

const useStyles = createStyles(({ token, css, prefixCls }) => ({
default: css`
.${prefixCls}-layout-header {
background-color: ${token.colorPrimary};
}
`,
moreWeight: css`
// ↓
&.${prefixCls}-layout-header {
background-color: ${token.colorPrimary};
}
`,
}));

export default () => {
const { styles } = useStyles();

return (
<App>
<Layout>
<Layout.Header className={styles.default}>无法覆盖</Layout.Header>
<Layout.Header className={styles.moreWeight}>可正常覆盖</Layout.Header>
</Layout>
</App>
);
};
20 changes: 20 additions & 0 deletions docs/best-practice/demos/StaticMethod/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* iframe: 240
*/
import { Button, Space } from 'antd';
import Layout from './layout';
import { showMessage, showModal, showNotification } from './request';

const App = () => (
<Layout>
<Space>
<Button type={'primary'} onClick={showMessage}>
Open message
</Button>
<Button onClick={showModal}>Open modal</Button>
<Button onClick={showNotification}>Open notification</Button>
</Space>
</Layout>
);

export default App;
32 changes: 32 additions & 0 deletions docs/best-practice/demos/StaticMethod/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* iframe: 240
*/
import { ThemeProvider } from 'antd-style';
import { MessageInstance } from 'antd/es/message/interface';
import { ModalStaticFunctions } from 'antd/es/modal/confirm';
import { NotificationInstance } from 'antd/es/notification/interface';
import { PropsWithChildren } from 'react';
import { Center } from 'react-layout-kit';

export let message: MessageInstance,
modal: Omit<ModalStaticFunctions, 'warn'>,
notification: NotificationInstance;

export default ({ children }: PropsWithChildren) => {
return (
<Center style={{ height: '100vh', background: '#f5f5f5' }}>
<ThemeProvider
theme={{
token: { colorPrimary: '#5bdbe6', colorInfo: '#5bdbe6', borderRadius: 2 },
}}
getStaticInstance={(instances) => {
message = instances.message;
modal = instances.modal;
notification = instances.notification;
}}
>
{children}
</ThemeProvider>
</Center>
);
};
24 changes: 24 additions & 0 deletions docs/best-practice/demos/StaticMethod/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* iframe: 240
*/
import { message, modal, notification } from './layout';

export const showMessage = () => {
message.success('Success!');
};

export const showNotification = () => {
notification.info({
message: `Notification`,
description: 'Hello, Ant Design Style',
});
};

export const showModal = () => {
modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
centered: true,
maskClosable: true,
});
};
15 changes: 15 additions & 0 deletions docs/best-practice/demos/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"include": ["**/*.tsx", "**/*.ts"],
"compilerOptions": {
"strict": true,
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"baseUrl": ".",
"paths": {
"antd-style": ["../../../es"]
}
}
}

0 comments on commit e2d0da2

Please sign in to comment.