Merge pull request #45271 from ant-design/master

chore: feature merge master
This commit is contained in:
二货爱吃白萝卜 2023-10-12 10:58:20 +08:00 committed by GitHub
commit 012e8781c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 686 additions and 382 deletions

View File

@ -35,6 +35,7 @@ const useStyle = createStyles(({ token, css }) => ({
}));
export interface ColorPickerProps {
id?: string;
children?: React.ReactNode;
value?: string | Color;
onChange?: (value?: Color | string) => void;
@ -66,7 +67,7 @@ const DebouncedColorPicker: React.FC<ColorPickerProps> = (props) => {
);
};
const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange }) => {
const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange, id }) => {
const { styles } = useStyle();
const matchColors = React.useMemo(() => {
@ -95,6 +96,7 @@ const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange }) => {
value={typeof value === 'string' ? value : value?.toHexString()}
onChange={(event) => onChange?.(event.target.value)}
style={{ width: 120 }}
id={id}
/>
<Space size="middle">

View File

@ -1,12 +1,13 @@
import React from 'react';
import { InputNumber, Space, Slider } from 'antd';
import { InputNumber, Slider, Space } from 'antd';
export interface RadiusPickerProps {
id?: string;
value?: number;
onChange?: (value: number | null) => void;
}
export default function RadiusPicker({ value, onChange }: RadiusPickerProps) {
export default function RadiusPicker({ value, onChange, id }: RadiusPickerProps) {
return (
<Space size="large">
<InputNumber
@ -16,6 +17,7 @@ export default function RadiusPicker({ value, onChange }: RadiusPickerProps) {
min={0}
formatter={(val) => `${val}px`}
parser={(str) => (str ? parseFloat(str) : (str as any))}
id={id}
/>
<Slider

View File

@ -1,7 +1,8 @@
import { createStyles, useTheme } from 'antd-style';
import * as React from 'react';
import classNames from 'classnames';
import { Space } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import classNames from 'classnames';
import useLocale from '../../../../hooks/useLocale';
export const THEMES = {
@ -33,48 +34,54 @@ const locales = {
const useStyle = createStyles(({ token, css }) => ({
themeCard: css`
border-radius: ${token.borderRadius}px;
cursor: pointer;
transition: all ${token.motionDurationSlow};
overflow: hidden;
display: inline-block;
border-radius: ${token.borderRadius}px;
cursor: pointer;
transition: all ${token.motionDurationSlow};
overflow: hidden;
display: inline-block;
& > input[type="radio"] {
width: 0;
height: 0;
opacity: 0;
position: absolute;
}
& > input[type='radio'] {
width: 0;
height: 0;
opacity: 0;
position: absolute;
}
img {
vertical-align: top;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
}
img {
vertical-align: top;
box-shadow:
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
}
&:focus-within,
&:hover {
transform: scale(1.04);
}
`,
&:focus-within,
&:hover {
transform: scale(1.04);
}
`,
themeCardActive: css`
box-shadow: 0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
box-shadow:
0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
&,
&:hover:not(:focus-within) {
transform: scale(1);
}
`,
&,
&:hover:not(:focus-within) {
transform: scale(1);
}
`,
}));
export interface ThemePickerProps {
id?: string;
value?: string;
onChange?: (value: string) => void;
}
export default function ThemePicker({ value, onChange }: ThemePickerProps) {
export default function ThemePicker(props: ThemePickerProps) {
const { value, onChange, id } = props;
const token = useTheme();
const { styles } = useStyle();
@ -82,7 +89,7 @@ export default function ThemePicker({ value, onChange }: ThemePickerProps) {
return (
<Space size={token.paddingLG}>
{Object.keys(THEMES).map((theme) => {
{Object.keys(THEMES).map((theme, index) => {
const url = THEMES[theme as THEME];
return (
@ -94,7 +101,7 @@ export default function ThemePicker({ value, onChange }: ThemePickerProps) {
onChange?.(theme);
}}
>
<input type="radio" name="theme" />
<input type="radio" name="theme" id={index === 0 ? id : null} />
<img src={url} alt={theme} />
</label>
<span>{locale[theme as keyof typeof locale]}</span>

View File

@ -534,11 +534,20 @@ export default function Theme() {
<Form.Item label={locale.titleBorderRadius} name="borderRadius">
<RadiusPicker />
</Form.Item>
<Form.Item label={locale.titleCompact} name="compact">
<Radio.Group>
<Radio value="default">{locale.default}</Radio>
<Radio value="compact">{locale.compact}</Radio>
</Radio.Group>
<Form.Item label={locale.titleCompact} name="compact" htmlFor="compact_default">
<Radio.Group
options={[
{
label: locale.default,
value: 'default',
id: 'compact_default',
},
{
label: locale.compact,
value: 'compact',
},
]}
/>
</Form.Item>
</Form>
</Card>

View File

@ -8,7 +8,7 @@ import classNames from 'classnames';
import { FormattedMessage, useSiteData } from 'dumi';
import LZString from 'lz-string';
import type { AntdPreviewerProps } from '.';
import type { AntdPreviewerProps } from './Previewer';
import useLocation from '../../../hooks/useLocation';
import BrowserFrame from '../../common/BrowserFrame';
import ClientOnly from '../../common/ClientOnly';
@ -171,7 +171,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
});
const localizedTitle = title;
const introChildren = <div dangerouslySetInnerHTML={{ __html: description }} />;
const highlightClass = classNames('highlight-wrapper', {
'highlight-wrapper-expand': codeExpand,
});
@ -205,7 +204,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
const suffix = codeType === 'tsx' ? 'tsx' : 'js';
const dependencies: Record<PropertyKey, string> = jsx.split('\n').reduce(
const dependencies = (jsx as string).split('\n').reduce<Record<PropertyKey, string>>(
(acc, line) => {
const matches = line.match(/import .+? from '(.+)';$/);
if (matches?.[1]) {
@ -377,7 +376,9 @@ createRoot(document.getElementById('container')).render(<Demo />);
</Tooltip>
<EditButton title={<FormattedMessage id="app.content.edit-demo" />} filename={filename} />
</div>
<div className="code-box-description">{introChildren}</div>
{description && (
<div className="code-box-description" dangerouslySetInnerHTML={{ __html: description }} />
)}
<Space wrap size="middle" className="code-box-actions">
{showOnlineUrl && (
<Tooltip title={<FormattedMessage id="app.demo.online" />}>

View File

@ -1,7 +1,7 @@
import classNames from 'classnames';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import { Helmet, useOutlet } from 'dumi';
import { Helmet, useOutlet, useSiteData } from 'dumi';
import React, { useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import zhCN from 'antd/es/locale/zh_CN';
import ConfigProvider from 'antd/es/config-provider';
@ -30,10 +30,11 @@ const locales = {
const DocLayout: React.FC = () => {
const outlet = useOutlet();
const location = useLocation();
const { pathname, search } = location;
const { pathname, search, hash } = location;
const [locale, lang] = useLocale(locales);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const { direction } = useContext(SiteContext);
const { loading } = useSiteData();
useLayoutEffect(() => {
if (lang === 'cn') {
@ -52,6 +53,13 @@ const DocLayout: React.FC = () => {
}
}, []);
// handle hash change or visit page hash from Link component, and jump after async chunk loaded
useEffect(() => {
const id = hash.replace('#', '');
if (id) document.getElementById(decodeURIComponent(id))?.scrollIntoView();
}, [loading, hash]);
useEffect(() => {
if (typeof (window as any).ga !== 'undefined') {
(window as any).ga('send', 'pageview', pathname + search);

View File

@ -1,96 +0,0 @@
# Each PR will build preview site that help to check code is work as expect.
name: Preview Build
on:
pull_request:
types: [opened, synchronize, reopened]
# Cancel prev CI if new commit come
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Prepare node modules. Reuse cache if available
setup:
name: prepare preview
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: cache package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |
if [ ! -d "package-temp-dir" ]; then
mkdir package-temp-dir
fi
cp package-lock.json package-temp-dir
- name: cache node_modules
id: node_modules_cache_id
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: install
if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
run: npm ci
build-site:
name: build preview
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- name: restore cache from package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: npm run site
id: site
run: npm run site
env:
SITE_ENV: development
NODE_OPTIONS: "--max_old_space_size=4096 --openssl-legacy-provider"
- name: upload site artifact
uses: actions/upload-artifact@v3
with:
name: site
path: _site/
retention-days: 5
# Upload PR id for next workflow use
- name: Save PR number
if: ${{ always() }}
run: echo ${{ github.event.number }} > ./pr-id.txt
- name: Upload PR number
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: pr
path: ./pr-id.txt

View File

@ -1,106 +0,0 @@
# Each PR will build preview site that help to check code is work as expect.
name: Preview Deploy
on:
workflow_run:
workflows: ["Preview Build"]
types:
- completed
permissions:
contents: read
jobs:
deploy-site:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
name: deploy preview
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
# We need get PR id first
- name: download pr artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
name: pr
# Save PR id to output
- name: save PR id
id: pr
run: echo "id=$(<pr-id.txt)" >> $GITHUB_OUTPUT
# Download site artifact
- name: download site artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
name: site
- name: upload surge service
id: deploy
run: |
export DEPLOY_DOMAIN=https://preview-${{ steps.pr.outputs.id }}-ant-design.surge.sh
npx surge --project ./ --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
- name: update status comment
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
[<img width="306" src="https://user-images.githubusercontent.com/5378891/72400743-23dbb200-3785-11ea-9d13-1a2d92743846.png">](https://preview-${{ steps.pr.outputs.id }}-ant-design.surge.sh)
<!-- AUTO_PREVIEW_HOOK -->
body-include: '<!-- AUTO_PREVIEW_HOOK -->'
number: ${{ steps.pr.outputs.id }}
- name: The job has failed
if: ${{ failure() }}
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
<img width="534" src="https://user-images.githubusercontent.com/5378891/75333447-1e63a280-58c1-11ea-975d-235367fd1522.png">
<!-- AUTO_PREVIEW_HOOK -->
body-include: '<!-- AUTO_PREVIEW_HOOK -->'
number: ${{ steps.pr.outputs.id }}
build-site-failed:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
name: build preview failed
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'failure'
steps:
# We need get PR id first
- name: download pr artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
name: pr
# Save PR id to output
- name: save PR id
id: pr
run: echo "id=$(<pr-id.txt)" >> $GITHUB_OUTPUT
- name: The job has failed
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
<img width="534" src="https://user-images.githubusercontent.com/5378891/75333447-1e63a280-58c1-11ea-975d-235367fd1522.png">
<!-- AUTO_PREVIEW_HOOK -->
body-include: '<!-- AUTO_PREVIEW_HOOK -->'
number: ${{ steps.pr.outputs.id }}

View File

@ -1,31 +0,0 @@
# When `preview-build` start. Leave a message on the PR
#
# 🚨🚨🚨 Important 🚨🚨🚨
# Never do any `checkout` or `npm install` action!
# `pull_request_target` will enable PR to access the secrets!
name: Preview Start
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
preview-start:
permissions:
issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
name: start preview info
runs-on: ubuntu-latest
steps:
- name: update status comment
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
![Prepare preview](https://user-images.githubusercontent.com/5378891/72351368-2c979e00-371b-11ea-9652-eb4e825d745e.gif)
<!-- AUTO_PREVIEW_HOOK -->
body-include: '<!-- AUTO_PREVIEW_HOOK -->'

208
.github/workflows/preview.yml vendored Normal file
View File

@ -0,0 +1,208 @@
name: PR Preview
on:
pull_request_target:
types: [opened, synchronize, reopened]
# Cancel prev CI if new commit come
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
preview-start:
permissions:
issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
name: Prepare preview
runs-on: ubuntu-latest
steps:
- name: update status comment
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
![Prepare preview](https://user-images.githubusercontent.com/5378891/72351368-2c979e00-371b-11ea-9652-eb4e825d745e.gif)
<!-- AUTO_PREVIEW_HOOK -->
body-include: "<!-- AUTO_PREVIEW_HOOK -->"
setup:
name: Setup
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: cache package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |
if [ ! -d "package-temp-dir" ]; then
mkdir package-temp-dir
fi
cp package-lock.json package-temp-dir
- name: cache node_modules
id: node_modules_cache_id
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: install
if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
run: npm ci
build-site:
name: Build Preview Site
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- name: restore cache from package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: npm run site
id: site
run: npm run site
env:
SITE_ENV: development
NODE_OPTIONS: "--max_old_space_size=4096 --openssl-legacy-provider"
- name: upload site artifact
uses: actions/upload-artifact@v3
with:
name: site
path: _site/
retention-days: 5
# Upload PR id for next workflow use
- name: Save PR number
if: ${{ always() }}
run: echo ${{ github.event.number }} > ./pr-id.txt
- name: Upload PR number
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: pr
path: ./pr-id.txt
site-test:
name: Site E2E Test
runs-on: ubuntu-latest
needs: build-site
steps:
- name: checkout
uses: actions/checkout@v4
- name: restore cache from package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: download site artifact
uses: actions/download-artifact@v3
with:
name: site
path: _site
- name: run e2e test
run: npm run site:test
preview-deploy:
name: Deploy Preview
runs-on: ubuntu-latest
needs: build-site
steps:
# We need get PR id first
- name: download pr artifact
uses: actions/download-artifact@v3
with:
name: pr
# Save PR id to output
- name: save PR id
id: pr
run: echo "id=$(<pr-id.txt)" >> $GITHUB_OUTPUT
# Download site artifact
- name: download site artifact
uses: actions/download-artifact@v3
with:
name: site
- name: upload surge service
id: deploy
run: |
export DEPLOY_DOMAIN=https://preview-${{ steps.pr.outputs.id }}-ant-design.surge.sh
npx surge --project ./ --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
preview-end:
name: Preview End
runs-on: ubuntu-latest
needs:
- build-site
- preview-deploy
if: always()
permissions:
issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
steps:
- name: download pr artifact
uses: actions/download-artifact@v3
with:
name: pr
- name: save PR id
id: pr
run: echo "id=$(<pr-id.txt)" >> $GITHUB_OUTPUT
- name: success comment
if: needs.build-site.result == 'success' && needs.preview-deploy.result == 'success'
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
[<img width="306" alt="Preview Is ready" src="https://user-images.githubusercontent.com/5378891/72400743-23dbb200-3785-11ea-9d13-1a2d92743846.png">](https://preview-${{ steps.pr.outputs.id }}-ant-design.surge.sh)
<!-- AUTO_PREVIEW_HOOK -->
body-include: "<!-- AUTO_PREVIEW_HOOK -->"
number: ${{ steps.pr.outputs.id }}
- name: failed comment
if: needs.build-site.result == 'failure' || needs.preview-deploy.result == 'failure'
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
<img width="534" alt="Preview Failed" src="https://user-images.githubusercontent.com/5378891/75333447-1e63a280-58c1-11ea-975d-235367fd1522.png">
<!-- AUTO_PREVIEW_HOOK -->
body-include: "<!-- AUTO_PREVIEW_HOOK -->"
number: ${{ steps.pr.outputs.id }}

View File

@ -1,47 +0,0 @@
name: Site E2E Test
on:
workflow_run:
workflows: ["Preview Build"]
types:
- completed
permissions:
contents: read
jobs:
e2e-test:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
name: E2E Test
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: checkout
uses: actions/checkout@v4
- name: restore cache from package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
# Download site artifact
- name: download site artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
name: site
path: _site
- name: Run E2E Test
run: npm run site:test

View File

@ -16,6 +16,39 @@ tag: vVERSION
---
## 5.10.0
`2023-10-10`
- 🔥 New component Flex, used to set flexible layout. [#44362](https://github.com/ant-design/ant-design/pull/44362)
- 🔥 Notification component supports `stack` configuration. By default, more than three notifications will be stacked. [#44618](https://github.com/ant-design/ant-design/pull/44618)
- 🔥 Update the active styles of Input, InputNumber, Select, Cascader, TreeSelect, DatePicker, and ColorPicker. [#45009](https://github.com/ant-design/ant-design/pull/45009)
- 🆕 Watermark supports setting the text alignment direction through `textAlign`. [#44888](https://github.com/ant-design/ant-design/pull/44888) [@Yuiai01](https://github.com/Yuiai01)
- 🆕 Slider supports any number of nodes and migrates xxxStyles to semantic `styles` and `classNames` properties. [#45000](https://github.com/ant-design/ant-design/pull/45000)
- 🆕 Cascader supports the Cascader.Panel component for inline use. [#45089](https://github.com/ant-design/ant-design/pull/45089)
- 🆕 Tooltip adds `fresh` attribute to support scenarios where content still needs to be updated when closed. [#45020](https://github.com/ant-design/ant-design/pull/45020)
- 🆕 Drawer supports customizing the `className` of built-in modules through `classNames`. [#44935](https://github.com/ant-design/ant-design/pull/44935)
- 🆕 ConfigProvider supports the `warning` attribute to configure warning levels (e.g. filter out deprecated API warnings). [#44809](https://github.com/ant-design/ant-design/pull/44809)
- Modal
- 🆕 Modal supports customizing the `className` of built-in modules through `classNames`. [#44934](https://github.com/ant-design/ant-design/pull/44934)
- 🐞 Fixed the content overflow problem when Modal.confirm `description` is a long text. [#45212](https://github.com/ant-design/ant-design/pull/45212)
- 🐞 Fix the problem that the nested Typography of Menu.Item cannot be vertically centered when `ellipsis` is true. [#41146](https://github.com/ant-design/ant-design/pull/41146) [@Yuiai01](https://github.com/Yuiai01)
- 🐞 Fix Select internal input not being able to apply fontFamily. [#45197](https://github.com/ant-design/ant-design/pull/45197) [@Yuiai01](https://github.com/Yuiai01)
- 🐞 Fix InputNumber border issue when using `addonBefore` in Space.Compact. [#45004](https://github.com/ant-design/ant-design/pull/45004) [@Yuiai01](https://github.com/Yuiai01)
- 🐞 Fix the problem that Tag.CheckableTag does not support ref. [#45164](https://github.com/ant-design/ant-design/pull/45164) [@mingming-ma](https://github.com/mingming-ma)
- 🐞 Fixed the issue where the font in the Avatar.Group component does not support responsiveness. [#34722](https://github.com/ant-design/ant-design/pull/34722) [@laishiwen](https://github.com/laishiwen)
- 🛠 Refactor Affix into a functional component. [#42674](https://github.com/ant-design/ant-design/pull/42674)
- 🛠 The Popover component deprecates the `minWidth` component token and adds `titleMinWidth` as a replacement. [#44750](https://github.com/ant-design/ant-design/pull/44750)
- 🌈 Token
- 🆕 Input adds `hoverBg` `activeBg` token to set the input box hover and activation background color. [#44752](https://github.com/ant-design/ant-design/pull/44752) [@Pan-yongyong](https://github.com/Pan-yongyong)
- 🆕 Descriptions Added `titleColor` and `contentColor` to set the title color and content area text color. [#44729](https://github.com/ant-design/ant-design/pull/44729) [@Child-qjj](https://github.com/Child-qjj)
- 🐞 Fixed the issue where the Input component Token `addonBg` is invalid. [#45222](https://github.com/ant-design/ant-design/pull/45222)
- TypeScript
- 🤖 The ArgsProps type for exported Notification is NotificationArgsProps. [#45147](https://github.com/ant-design/ant-design/pull/45147)
- 🌐 Locales
- 🇵🇱 Added Tour locales for pl_PL. [#45166](https://github.com/ant-design/ant-design/pull/45166) [@antonisierakowski](https://github.com/antonisierakowski)
- 🇰🇷 Optimize ko_KR locales. [#45150](https://github.com/ant-design/ant-design/pull/45150) [@owjs3901](https://github.com/owjs3901)
## 5.9.4
`2023-09-28`

View File

@ -16,6 +16,39 @@ tag: vVERSION
---
## 5.10.0
`2023-10-10`
- 🔥 新增 Flex 组件,用于设置弹性布局。[#44362](https://github.com/ant-design/ant-design/pull/44362)
- 🔥 Notification 组件支持 `stack` 配置,默认超过三个以上的提示会堆叠起来。[#44618](https://github.com/ant-design/ant-design/pull/44618)
- 🔥 更新 Input、InputNumber、Select、Cascader、TreeSelect、DatePicker、ColorPicker 的激活态样式。[#45009](https://github.com/ant-design/ant-design/pull/45009)
- 🆕 Watermark 支持通过 `textAlign` 设置文本对齐方向。[#44888](https://github.com/ant-design/ant-design/pull/44888) [@Yuiai01](https://github.com/Yuiai01)
- 🆕 Slider 支持任意节点数并且将 xxxStyle 迁移至语义化 `styles``classNames` 属性中。[#45000](https://github.com/ant-design/ant-design/pull/45000)
- 🆕 Cascader 支持 Cascader.Panel 组件供内联使用。[#45089](https://github.com/ant-design/ant-design/pull/45089)
- 🆕 Tooltip 添加 `fresh` 属性以支持在关闭时仍然需要更新内容的场景。[#45020](https://github.com/ant-design/ant-design/pull/45020)
- 🆕 Drawer 支持通过 `classNames` 自定义内置模块的 `className`。[#44935](https://github.com/ant-design/ant-design/pull/44935)
- 🆕 ConfigProvider 支持 `warning` 属性以配置警告等级(如过滤掉废弃 API 警告)。[#44809](https://github.com/ant-design/ant-design/pull/44809)
- Modal
- 🆕 Modal 支持通过 `classNames` 自定义内置模块的 `className`。[#44934](https://github.com/ant-design/ant-design/pull/44934)
- 🐞 修复 Modal.confirm `description` 是长文本时的内容溢出问题。[#45212](https://github.com/ant-design/ant-design/pull/45212)
- 🐞 修复 Menu.Item 嵌套的 Typography 其 `ellipsis` 为 true 时无法垂直居中的问题。[#41146](https://github.com/ant-design/ant-design/pull/41146) [@Yuiai01](https://github.com/Yuiai01)
- 🐞 修复 Select 内部 input 无法应用 fontFamily 的问题。[#45197](https://github.com/ant-design/ant-design/pull/45197) [@Yuiai01](https://github.com/Yuiai01)
- 🐞 修复 InputNumber 在 Space.Compact 中使用 `addonBefore` 时的边框问题。[#45004](https://github.com/ant-design/ant-design/pull/45004) [@Yuiai01](https://github.com/Yuiai01)
- 🐞 修复 Tag.CheckableTag 不支持 ref 的问题。[#45164](https://github.com/ant-design/ant-design/pull/45164) [@mingming-ma](https://github.com/mingming-ma)
- 🐞 修复 Avatar.Group 组件内的字体不支持响应式的问题。[#34722](https://github.com/ant-design/ant-design/pull/34722) [@laishiwen](https://github.com/laishiwen)
- 🛠 重构 Affix 为函数组件。[#42674](https://github.com/ant-design/ant-design/pull/42674)
- 🛠 Popover 组件废弃 `minWidth` 组件 token并添加 `titleMinWidth` 作为替代。[#44750](https://github.com/ant-design/ant-design/pull/44750)
- 🌈 Token
- 🆕 Input 新增 `hoverBg` `activeBg` token 用以设置输入框 hover 和 激活时背景颜色。[#44752](https://github.com/ant-design/ant-design/pull/44752) [@Pan-yongyong](https://github.com/Pan-yongyong)
- 🆕 Descriptions 新增 `titleColor` `contentColor` 用以设置标题颜色和内容区域文字颜色。[#44729](https://github.com/ant-design/ant-design/pull/44729) [@Child-qjj](https://github.com/Child-qjj)
- 🐞 修复 Input 组件 Token `addonBg` 失效的问题。[#45222](https://github.com/ant-design/ant-design/pull/45222)
- TypeScript
- 🤖 导出 Notification 的 ArgsProps 类型为 NotificationArgsProps。[#45147](https://github.com/ant-design/ant-design/pull/45147)
- 🌐 国际化
- 🇵🇱 为 pl_PL 补充 Tour 国际化。[#45166](https://github.com/ant-design/ant-design/pull/45166) [@antonisierakowski](https://github.com/antonisierakowski)
- 🇰🇷 优化 ko_KR 国际化。[#45150](https://github.com/ant-design/ant-design/pull/45150) [@owjs3901](https://github.com/owjs3901)
## 5.9.4
`2023-09-28`

View File

@ -1190,7 +1190,7 @@ Array [
class="ant-card-head-wrapper"
/>
<div
class="ant-tabs ant-tabs-top ant-tabs-large ant-card-head-tabs"
class="ant-tabs ant-tabs-top ant-tabs-middle ant-card-head-tabs"
>
<div
class="ant-tabs-nav"

View File

@ -1124,7 +1124,7 @@ Array [
class="ant-card-head-wrapper"
/>
<div
class="ant-tabs ant-tabs-top ant-tabs-large ant-card-head-tabs"
class="ant-tabs ant-tabs-top ant-tabs-middle ant-card-head-tabs"
>
<div
class="ant-tabs-nav"

View File

@ -69,6 +69,9 @@ const App: React.FC = () => {
activeTabKey={activeTabKey2}
tabBarExtraContent={<a href="#">More</a>}
onTabChange={onTab2Change}
tabProps={{
size: 'middle',
}}
>
{contentListNoTitle[activeTabKey2]}
</Card>

View File

@ -364,6 +364,7 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => {
[`${componentCls}-contain-tabs`]: {
[`> ${componentCls}-head`]: {
minHeight: 0,
[`${componentCls}-head-title, ${componentCls}-extra`]: {
paddingTop: cardHeadPadding,
},
@ -405,7 +406,6 @@ const genCardSizeStyle: GenerateStyle<CardToken> = (token): CSSObject => {
[`${componentCls}-small${componentCls}-contain-tabs`]: {
[`> ${componentCls}-head`]: {
[`${componentCls}-head-title, ${componentCls}-extra`]: {
minHeight: headerHeightSM,
paddingTop: 0,
display: 'flex',
alignItems: 'center',

View File

@ -1,11 +1,11 @@
import * as React from 'react';
import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import type { CheckboxChangeEvent } from './Checkbox';
import Checkbox from './Checkbox';
import GroupContext from './GroupContext';
import useStyle from './style';
export type CheckboxValueType = string | number | boolean;
@ -16,6 +16,7 @@ export interface CheckboxOptionType {
style?: React.CSSProperties;
disabled?: boolean;
title?: string;
id?: string;
onChange?: (e: CheckboxChangeEvent) => void;
}
@ -124,6 +125,7 @@ const InternalGroup: React.ForwardRefRenderFunction<HTMLDivElement, CheckboxGrou
className={`${groupPrefixCls}-item`}
style={option.style}
title={option.title}
id={option.id}
>
{option.label}
</Checkbox>

View File

@ -1,4 +1,5 @@
import React, { useState } from 'react';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
@ -268,4 +269,11 @@ describe('CheckboxGroup', () => {
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(onChange).toHaveBeenCalledWith(['A']);
});
it('options support id', () => {
const { container } = render(
<Checkbox.Group options={[{ label: 'bamboo', id: 'bamboo', value: 'bamboo' }]} />,
);
expect(container.querySelector('#bamboo')).toBeTruthy();
});
});

View File

@ -14,6 +14,11 @@ Flex. Available since `5.10.0`.
- Good for setting spacing between elements.
- Suitable for setting various horizontal and vertical alignments.
### Difference with Space component
- Space is used to set the spacing between inline elements. It will add a wrapper element for each child element for inline alignment. Suitable for equidistant arrangement of multiple child elements in rows and columns.
- Flex is used to set the layout of block-level elements. It does not add a wrapper element. Suitable for layout of child elements in vertical or horizontal direction, and provides more flexibility and control.
## Examples
<!-- prettier-ignore -->

View File

@ -15,6 +15,11 @@ tag: New
- 适合设置元素之间的间距。
- 适合设置各种水平、垂直对齐方式。
### 与 Space 组件的区别
- Space 为内联元素提供间距,其本身会为每一个子元素添加包裹元素用于内联对齐。适用于行、列中多个子元素的等距排列。
- Flex 为块级元素提供间距,其本身不会添加包裹元素。适用于垂直或水平方向上的子元素布局,并提供了更多的灵活性和控制能力。
## 代码演示
<!-- prettier-ignore -->

View File

@ -1,5 +1,6 @@
import type { RefAttributes } from 'react';
import React from 'react';
import type { RadioGroupProps } from '..';
import Radio from '..';
import { fireEvent, render } from '../../../tests/utils';
@ -252,4 +253,11 @@ describe('Radio Group', () => {
fireEvent.blur(container.firstChild!);
expect(handleBlur).toHaveBeenCalledTimes(1);
});
it('options support id', () => {
const { container } = render(
<Radio.Group options={[{ label: 'bamboo', id: 'bamboo', value: 'bamboo' }]} />,
);
expect(container.querySelector('#bamboo')).toBeTruthy();
});
});

View File

@ -79,6 +79,7 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref
checked={value === option.value}
title={option.title}
style={option.style}
id={option.id}
>
{option.label}
</Radio>

View File

@ -13,6 +13,11 @@ Set components spacing.
- Avoid components clinging together and set a unified space.
- Use Space.Compact when child form components are compactly connected and the border is collapsed (After version `antd@4.24.0` Supported).
### Difference with Flex component
- Space is used to set the spacing between inline elements. It will add a wrapper element for each child element for inline alignment. Suitable for equidistant arrangement of multiple child elements in rows and columns.
- Flex is used to set the layout of block-level elements. It does not add a wrapper element. Suitable for layout of child elements in vertical or horizontal direction, and provides more flexibility and control.
## Examples
<!-- prettier-ignore -->
@ -55,6 +60,7 @@ Use Space.Compact when child form components are compactly connected and the bor
- Cascader
- DatePicker
- Input/Input.Search
- InputNumber
- Select
- TimePicker
- TreeSelect

View File

@ -17,6 +17,11 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
- 可以设置各种水平对齐方式。
- 需要表单组件之间紧凑连接且合并边框时,使用 Space.Compact`antd@4.24.0` 版本开始提供该组件)。
### 与 Flex 组件的区别
- Space 为内联元素提供间距,其本身会为每一个子元素添加包裹元素用于内联对齐。适用于行、列中多个子元素的等距排列。
- Flex 为块级元素提供间距,其本身不会添加包裹元素。适用于垂直或水平方向上的子元素布局,并提供了更多的灵活性和控制能力。
## 代码演示
<!-- prettier-ignore -->
@ -63,6 +68,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
- Cascader
- DatePicker
- Input/Input.Search
- InputNumber
- Select
- TimePicker
- TreeSelect

View File

@ -1,3 +1,4 @@
import * as React from 'react';
import FileOutlined from '@ant-design/icons/FileOutlined';
import FolderOpenOutlined from '@ant-design/icons/FolderOpenOutlined';
import FolderOutlined from '@ant-design/icons/FolderOutlined';
@ -7,9 +8,8 @@ import type { BasicDataNode } from 'rc-tree';
import type { DataNode, EventDataNode, Key } from 'rc-tree/lib/interface';
import { conductExpandParent } from 'rc-tree/lib/util';
import { convertDataToEntities, convertTreeToData } from 'rc-tree/lib/utils/treeUtil';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import { ConfigContext } from '../config-provider';
import type { AntdTreeNodeAttribute, TreeProps } from './Tree';
import Tree from './Tree';
import { calcRangeKeys, convertDirectoryKeysToNodes } from './utils/dictUtil';
@ -113,7 +113,7 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
nativeEvent: MouseEvent;
},
) => {
const { multiple } = props;
const { multiple, fieldNames } = props;
const { node, nativeEvent } = event;
const { key = '' } = node;
@ -137,7 +137,7 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
newSelectedKeys = keys;
lastSelectedKey.current = key;
cachedSelectedKeys.current = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys);
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys, fieldNames);
} else if (multiple && shiftPick) {
// Shift click
newSelectedKeys = Array.from(
@ -148,16 +148,17 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
expandedKeys,
startKey: key,
endKey: lastSelectedKey.current,
fieldNames,
}),
]),
);
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys);
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys, fieldNames);
} else {
// Single click
newSelectedKeys = [key];
lastSelectedKey.current = key;
cachedSelectedKeys.current = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys);
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys, fieldNames);
}
props.onSelect?.(newSelectedKeys, newEvent);

View File

@ -264,4 +264,38 @@ describe('Directory Tree', () => {
render(createTree({ ref: treeRef }));
expect('scrollTo' in treeRef.current!).toBeTruthy();
});
it('fieldNames support', () => {
const treeData = [
{
id: '0-0-0',
label: 'Folder',
child: [
{
label: 'Folder2',
id: '0-0-1',
child: [
{
label: 'File',
id: '0-0-2',
isLeaf: true,
},
],
},
],
},
];
const onSelect = jest.fn();
const { container } = render(
createTree({
defaultExpandAll: true,
// @ts-ignore
treeData,
onSelect,
fieldNames: { key: 'id', title: 'label', children: 'child' },
}),
);
fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[0]);
expect(onSelect.mock.calls[0][1].selectedNodes.length).toBe(1);
});
});

View File

@ -1,4 +1,7 @@
import type { DataNode, Key } from 'rc-tree/lib/interface';
import { fillFieldNames } from 'rc-tree/lib/utils/treeUtil';
import type { TreeProps } from '../Tree';
enum Record {
None,
@ -6,14 +9,20 @@ enum Record {
End,
}
type FieldNames = TreeProps['fieldNames'];
function traverseNodesKey(
treeData: DataNode[],
callback: (key: Key | number | null, node: DataNode) => boolean,
fieldNames: Required<NonNullable<FieldNames>>,
) {
function processNode(dataNode: DataNode) {
const { key, children } = dataNode;
const { key: fieldKey, children: fieldChildren } = fieldNames;
function processNode(dataNode: DataNode & FieldNames[keyof FieldNames]) {
const key = dataNode[fieldKey];
const children = dataNode[fieldChildren];
if (callback(key, dataNode) !== false) {
traverseNodesKey(children || [], callback);
traverseNodesKey(children || [], callback, fieldNames);
}
}
@ -26,11 +35,13 @@ export function calcRangeKeys({
expandedKeys,
startKey,
endKey,
fieldNames,
}: {
treeData: DataNode[];
expandedKeys: Key[];
startKey?: Key;
endKey?: Key;
fieldNames?: FieldNames;
}): Key[] {
const keys: Key[] = [];
let record: Record = Record.None;
@ -46,42 +57,54 @@ export function calcRangeKeys({
return key === startKey || key === endKey;
}
traverseNodesKey(treeData, (key: Key) => {
if (record === Record.End) {
return false;
}
if (matchKey(key)) {
// Match test
keys.push(key);
if (record === Record.None) {
record = Record.Start;
} else if (record === Record.Start) {
record = Record.End;
traverseNodesKey(
treeData,
(key: Key) => {
if (record === Record.End) {
return false;
}
} else if (record === Record.Start) {
// Append selection
keys.push(key);
}
return expandedKeys.includes(key);
});
if (matchKey(key)) {
// Match test
keys.push(key);
if (record === Record.None) {
record = Record.Start;
} else if (record === Record.Start) {
record = Record.End;
return false;
}
} else if (record === Record.Start) {
// Append selection
keys.push(key);
}
return expandedKeys.includes(key);
},
fillFieldNames(fieldNames),
);
return keys;
}
export function convertDirectoryKeysToNodes(treeData: DataNode[], keys: Key[]) {
export function convertDirectoryKeysToNodes(
treeData: DataNode[],
keys: Key[],
fieldNames?: FieldNames,
) {
const restKeys: Key[] = [...keys];
const nodes: DataNode[] = [];
traverseNodesKey(treeData, (key: Key, node: DataNode) => {
const index = restKeys.indexOf(key);
if (index !== -1) {
nodes.push(node);
restKeys.splice(index, 1);
}
traverseNodesKey(
treeData,
(key: Key, node: DataNode) => {
const index = restKeys.indexOf(key);
if (index !== -1) {
nodes.push(node);
restKeys.splice(index, 1);
}
return !!restKeys.length;
});
return !!restKeys.length;
},
fillFieldNames(fieldNames),
);
return nodes;
}

View File

@ -0,0 +1,83 @@
---
title: Historical Debt of API
date: 2023-10-11
author: zombieJ
---
You may have received this warning when upgrading Ant Design:
```text
Warning: [antd: XXX] `old prop` is deprecated. Please use `new prop` instead.
```
This is because antd has some historical debt in API design. For example, in antd v3 and before, the code of TreeSelect was directly copied from Select and extended on this basis. There are differences in their search styles:
<img alt="Select" height="162" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*uDbxSKTLU8YAAAAAAAAAAAAADrJ8AQ/original" />
<img alt="TreeSelect" height="316" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ggTeQqbnFVkAAAAAAAAAAAAADrJ8AQ/original" />
And in the maintenance process, developers want to control the content of the search box. Unfortunately, this requirement was PR by different developers at different times. So two different properties were added, one called `inputValue` and the other called `searchValue`:
```tsx
// Select in combobox mode, the search box is the input box, `inputValue` looks reasonable
<Select inputValue="search" />
// TreeSelect's search box is in the popup layer, `searchValue` is also reasonable
<TreeSelect searchValue="search" />
```
In multiple mode, the Select component will clear the search box content after selecting the content. But in some scenarios, developers want to keep it. So TreeSelect and Select added the `autoClearSearchValue` property.
Wait! Select is called `inputValue`, why should it be called `autoClearSearchValue`? Obviously it should be called `autoClearInputValue`. If we continue to grow other similar API styles on the existing API. You will find that the component's props are getting more and more split. This will also cause bad smells in code maintenance. For example, in the above example, we later extracted the Select component into a unified UI layer and merged it into the `rc-select` component. `rc-tree-select` only needs to implement the content of the popup layer, and the structure and style of the input box can be completely reused with Select. But because the APIs of the two are inconsistent, we need to do extra processing, so we need to refactor these API debts and unify them during the iteration process. (In v4, we merged it into `searchValue` and unified the design)
However there is no silver bullet in the world, we can't design a perfect API at the beginning. Some APIs seem very reasonable at the beginning of the design, but as the iteration progresses, they will be found to be more or less inappropriate. For example, the popup layer was named dropdown in the early days, which corresponds to the popup content of Dropdown and Select components. But for Tooltip, dropdown is obviously not suitable. From a unified perspective, popup will be more suitable.
### Deprecated Warning
We gradually unified the API naming rules during the maintenance process ([API Naming rules](https://github.com/ant-design/ant-design/wiki/API-Naming-rules)). When adding new features, we prefer to find similar APIs from existing ones. For existing APIs, deprecated warnings are gradually added. In order to maintain compatibility, our strategy is that the deprecated warnings provided by each version will continue to be compatible with a major version, and it will be removed in the next major version. For example, in v4, deprecated warnings are added, so they can still be used in v5, but they will be removed in v6. In this way, developers have enough time to migrate.
But from the developer's point of view, this is not reasonable. Developers only upgraded antd, but they have to suffer from the intrusion of console because of the API design mistakes of the component library. If a few usage warnings are mixed into the deprecated warnings, developers often have difficulty finding them. This situation is particularly prominent in major version upgrades. The business may not give you enough time to do the upgrade migration, so you have to use compatible packages and other technical means to make it run first. For long deprecated warnings, developers have to choose to temporarily (or permanently) ignore them. For this situation, usage warnings will be more important, so we propose the [Warning Filter RFC](https://github.com/ant-design/ant-design/discussions/44551).
#### Warning Filter
You can aggregate the deprecated information through the `warning` property of ConfigProvider:
```tsx
<ConfigProvider warning={{ strict: false }} />
```
After aggregation, the original flattened deprecated information will be merged into an array and displayed in the console. And it will not affect the usage warnings:
![Merged Message](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*MG-rQ4NSbbcAAAAAAAAAAAAADrJ8AQ/original)
#### Extension Problem
As mentioned above, there is no silver bullet in API design. In order to prevent breaking change, we generally will not change the existing API implementation. But for some conventions, this will cause trouble. For example, the `ref` component is a typical convention. As long as it is a React developer, you can understand that you can get the DOM node through `ref` and do some basic operations such as `focus`. But for composite components, the calling method and DOM may not be unified. For example, the `ref` of the Table component should obviously be the outermost div, but the `scrollTo` method should correspond to the scroll container (if it is VirtualTable, it should be handled by the internal `rc-virtual-list`). In antd mobile, `ref` is designed as a composite structure, and the DOM node is always returned through `nativeElement`:
```tsx
export interface SampleRef {
nativeElement: HTMLElement;
focus(): void;
blur(): void;
}
```
But in antd, because we did not make a convention for `ref` early, we encountered difficulties in implementing the method. But fortunately, there is Proxy support, we can intercept `ref` through Proxy and return the results we want:
```tsx
useImperativeHandle(
ref,
() =>
new Proxy(divRef.current, {
get(target, key) {
// ...
},
}),
);
```
We can continue to be compatible with previous usage in this way. It is still a DOM node, but it also supports the definition call of SampleRef.
## Summary
API design is a difficult problem. As the iteration of technology stack and components themselves. Some designs will gradually decay, and API upgrades themselves are also painful for developers. We hope that through this article, you can understand our design ideas and some problems in the upgrade process. If you have any suggestions or ideas, welcome to discuss on Github.

View File

@ -0,0 +1,83 @@
---
title: API 的历史债务
date: 2023-10-11
author: zombieJ
---
在升级 Ant Design 的过程中,你或许收到过这种警告:
```text
Warning: [antd: XXX] `old prop` is deprecated. Please use `new prop` instead.
```
这是因为 antd 在开发过程中,有一些 API 设计的不合理导致的历史债务。举个例子,在 antd v3 及以前TreeSelect 的代码是从 Select 中直接复制出来并在此基础上做的拓展。他们的搜索样式存在差异:
<img alt="Select" height="162" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*uDbxSKTLU8YAAAAAAAAAAAAADrJ8AQ/original" />
<img alt="TreeSelect" height="316" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ggTeQqbnFVkAAAAAAAAAAAAADrJ8AQ/original" />
而后的维护过程中,开发者希望可以受控控制搜索框的内容。而不巧的是,这个需求是不同的机缘巧合被提出并由不同的开发者贡献了代码。于是两者添加了不同的属性,一个叫做 `inputValue` 而另一个叫做 `searchValue`
```tsx
// Select 在 combobox 模式下,搜索框就是输入框,`inputValue` 看起来很合理
<Select inputValue="search" />
// TreeSelect 的搜索框在弹出层,`searchValue` 也很合理
<TreeSelect searchValue="search" />
```
在多选模式下,类 Select 组件在选择内容后会清除搜索框内容。但是有些场景下,开发者希望能够保留。因而 TreeSelect 和 Select 又添加了 `autoClearSearchValue` 属性。
等等Select 明明叫 `inputValue`,为什么要叫 `autoClearSearchValue`?明显应该叫做 `autoClearInputValue` 呐。如果我们在现有的 API 上继续生长其他的同类 API 风格。你会发现组件的 prop 变得越来越分裂。这也会导致代码维护出现坏味道。例如上面这个例子,在之后我们对类 Select 组件抽成了统一的 UI 层并将其合并到 `rc-select` 组件中。`rc-tree-select` 只需要实现弹出层的内容,而输入框的结构和样式可以和 Select 完全复用。但是由于两者的 API 不一致,导致我们需要额外的处理,所以我们在迭代过程中需要对这些 API 债务进行重构并将其统一起来。(在 v4 中,我们将其合并为了 `searchValue` 并且对设计也进行了统一)
然而世上没有银弹,我们无法在一开始就设计出完美的 API。有一些 API 在设计之初显得非常合理,而随着迭代又会发现或多或少不合时宜。比如说弹出层早期起名为 dropdown这对应了 Dropdown 以及类 Select 组件的弹出内容。但是对于 Tooltip 而言dropdown 显然是不适合的。从统一的角度看popup 会更适合。
### 废弃警告
在维护过程中,我们逐渐统一了 API 命名规范([API Naming rules](https://github.com/ant-design/ant-design/wiki/API-Naming-rules))。在添加新的 feature 时,优先从现存的 API 中寻找接近。对于现存的 API逐步添加废弃警告。为了保持兼容我们的策略是每个版本提供的废弃警告会继续兼容一个大版本而在下下个大版本中移除它。例如在 v4 中添加了废弃警告,那么在 v5 中仍然可以使用,但是在 v6 中将会被移除。以此确保开发者有足够的时间进行迁移。
但是从开发者角度看,这也并不合理。开发者本身只是对 antd 进行了升级,却要因为组件库 API 设计的失误而遭受 console 的侵扰。如果在废弃警告中混入几个使用警告,开发者往往很难发现它们。这种情况在大版本升级中尤为显著,业务可能并没有给你足够的时间去做升级迁移,因而不得不使用兼容包以及其他的一些技术手段让它先跑起来。而对于冗长的废弃警告,开发者不得不选择暂时(或者永远)无视它们。针对这种情况,使用警告会更为重要,因而我们提出了 [Warning Filter RFC](https://github.com/ant-design/ant-design/discussions/44551)。
#### 警告过滤
通过 ConfigProvider 的 `warning` 属性,可以将废弃信息进行聚合:
```tsx
<ConfigProvider warning={{ strict: false }} />
```
聚合后,原本打平的废弃信息会合并为一个数组在 console 中展示。而对于使用警告则不会影响:
![Merged Message](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*MG-rQ4NSbbcAAAAAAAAAAAAADrJ8AQ/original)
### 拓展问题
如上所述API 设计不存在银弹。为了防止 breaking change我们一般不会改动现有的 API 实现。但是对于一些约定的内容,这就会造成麻烦。比如说 `ref` 组件是很典型的约定,只要是 React 的开发者就能明白,通过 `ref` 可以获取 DOM 节点以及做一些诸如 `focus` 的基本操作。但是对于复合组件而言,调用方法和 DOM 不一定能够统一。比如说 Table 组件的 `ref` 显然应该是最外层的 div但是对于 `scrollTo` 方法则应该对应到滚动容器上(如果是 VirtualTable 则应该交由内部的 `rc-virtual-list` 进行处理)。在 antd mobile 中 `ref` 被设计为复合结构DOM 节点总是通过 `nativeElement` 返回:
```tsx
export interface SampleRef {
nativeElement: HTMLElement;
focus(): void;
blur(): void;
}
```
而在 antd 中,由于我们早期没有对 `ref` 进行约定,导致在实现方法是就遇到了难题。不过好在有 Proxy 支持,我们可以通过 Proxy 对 `ref` 进行拦截并返回我们想要的结果:
```tsx
useImperativeHandle(
ref,
() =>
new Proxy(divRef.current, {
get(target, key) {
// ...
},
}),
);
```
通过这种方式,我们可以继续兼容之前的使用。它仍然是一个 DOM 节点,但是同样也支持了 SampleRef 的定义调用。
## 总结
API 设计是个难题,随着技术栈以及组件本身的迭代。一些设计会逐渐腐朽,而 API 升级本身对于开发者也是痛苦的。我们希望通过这篇文章,让开发者能够理解我们的设计思路以及在升级过程中的一些问题。如果你有任何的建议或者想法,欢迎在 Github 中讨论。

View File

@ -13,7 +13,7 @@ Please ref [`@ant-design/cssinjs`](https://github.com/ant-design/cssinjs#stylepr
## Compatible adjustment
Ant Design default using CSS-in-JS with `:where` Selector to reduce priority to avoid user additional adjust style cost when updating. If you want to support old browser (or some other CSS framework selector priority conflict like TailwindCSS), you can use `@ant-design/cssinjs` to adjust this behavior (Please note keep version align with antd):
The CSS-in-JS feature of Ant Design uses the ":where" selector by default to lower the CSS selector specificity, reducing the additional cost of adjusting custom styles when upgrading for users. However, the compatibility of the ":where" syntax is relatively poor in older browsers ([compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility)). In certain scenarios, if you need to support older browsers (or encounter priority conflicts like TailwindCSS), you can use `@ant-design/cssinjs` to disable the default lowering of specificity (please ensure version consistency with antd).
```tsx
import { StyleProvider } from '@ant-design/cssinjs';

View File

@ -13,7 +13,7 @@ Ant Design 支持最近 2 个版本的现代浏览器。如果你需要兼容旧
## `:where` 选择器
Ant Design 的 CSS-in-JS 默认通过 `:where` 选择器降低 CSS Selector 优先级,以减少用户升级时额外调整自定义样式成本。在某些场景下你如果需要支持的旧版浏览器(或者如 TailwindCSS 优先级冲突),你可以使用 `@ant-design/cssinjs` 取消默认的降权操作(请注意版本保持与 antd 一致):
Ant Design 的 CSS-in-JS 默认通过 `:where` 选择器降低 CSS Selector 优先级,以减少用户升级时额外调整自定义样式成本,不过 `:where` 语法的[兼容性](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility)在低版本浏览器比较差。在某些场景下你如果需要支持的旧版浏览器(或者如 TailwindCSS 优先级冲突),你可以使用 `@ant-design/cssinjs` 取消默认的降权操作(请注意版本保持与 antd 一致):
```tsx
import { StyleProvider } from '@ant-design/cssinjs';

View File

@ -17,10 +17,6 @@ We could configure global token and component token for each component through t
import React from 'react';
import { Checkbox, ConfigProvider, Radio } from 'antd';
import { Checkbox, ConfigProvider, Radio } from 'antd';
import { Checkbox, ConfigProvider, Radio } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{

View File

@ -17,10 +17,6 @@ title: 从 Less 变量到 Design Token
import React from 'react';
import { Checkbox, ConfigProvider, Radio } from 'antd';
import { Checkbox, ConfigProvider, Radio } from 'antd';
import { Checkbox, ConfigProvider, Radio } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{

View File

@ -78,6 +78,10 @@ Please find below some of the design resources and tools about Ant Design that w
- https://mastergo-local-default.oss-cn-beijing.aliyuncs.com/ant-design-mastergo.svg
- Use fully components and templates on MasterGo
- https://mastergo.com/community/?utm_source=antdesign&utm_medium=link&utm_campaign=resource&cata_name=AntDesign
- AntBlocks UI for Figma
- https://uploads-ssl.webflow.com/64dc925e7cb893427a5c9cdc/64e4610f7818dcc7501057ad_antblocks-ui-card-img.svg
- High-quality, responsive, and customizable React components built on Ant Design
- https://www.antblocksui.com/#figma
</ResourceCards>
## Articles

View File

@ -163,6 +163,23 @@ toc: false
- 负责 Ant Design 前端基础设施研发。
- 负责中后台设计/前端工具体系建设。
### Node.js 工程师
简历请投递zhubin.gzb@antgroup.com
> 注明简历来自 ant.design 官网
- 岗位级别P5/P6/P7/P8
- 岗位地点:杭州/上海
- 岗位要求:
- 在 Node.js 技术栈持续耕耘,情有独钟。
- 热爱开源。
- 坚持和善于用技术和工具解决其他问题。
- 丰富的 Node.js 研发经验。
- 岗位职责:
- 负责 Node.js 前端基础设施研发。
- 负责大前端工具体系建设。
### ADIArtificial Design Intelligence 工程师
简历和作品集请投递jiayin.liu#antgroup.com

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.9.4",
"version": "5.10.0",
"packageManager": "^npm@9.0.0",
"description": "An enterprise-class UI design language and React components implementation",
"title": "Ant Design",
@ -94,7 +94,7 @@
"test-all": "sh -e ./scripts/test-all.sh",
"test-node": "npm run version && jest --config .jest.node.js --no-cache",
"tsc": "tsc --noEmit",
"site:test": "jest --config .jest.site.js --no-cache --force-exit",
"site:test": "jest --config .jest.site.js",
"test-image": "jest --config .jest.image.js --no-cache -i -u",
"argos": "tsx scripts/argos-upload.ts",
"version": "tsx scripts/generate-version.ts",
@ -129,7 +129,7 @@
"rc-dialog": "~9.3.3",
"rc-drawer": "~6.5.2",
"rc-dropdown": "~4.1.0",
"rc-field-form": "~1.38.2",
"rc-field-form": "~1.39.0",
"rc-image": "~7.3.1",
"rc-input": "~1.3.5",
"rc-input-number": "~8.2.0",