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

📝 docs: 更新最佳实践文档 #93

Merged
merged 4 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"]
}
}
}
Loading