Merge branch 'feature' into refactor-z-index-context

This commit is contained in:
kiner-tang(文辉) 2023-10-20 10:16:32 +08:00
commit d51de67bc8
35 changed files with 546 additions and 293 deletions

View File

@ -63,6 +63,7 @@ const CustomTheme = () => {
<Suspense fallback={<Skeleton style={{ margin: 24 }} />}>
<ThemeEditor
advanced
hideAdvancedSwitcher
theme={{ name: 'Custom Theme', key: 'test', config: theme }}
style={{ height: 'calc(100vh - 64px)' }}
onThemeChange={(newTheme) => {

122
.github/workflows/preview-build.yml vendored Normal file
View File

@ -0,0 +1,122 @@
# 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
site-test:
name: site E2E test
runs-on: ubuntu-latest
needs: [setup, 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

106
.github/workflows/preview-deploy.yml vendored Normal file
View File

@ -0,0 +1,106 @@
# 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 }}

31
.github/workflows/preview-start.yml vendored Normal file
View File

@ -0,0 +1,31 @@
# 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 -->'

View File

@ -1,208 +0,0 @@
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

@ -90,7 +90,7 @@ exports[`renders components/affix/demo/on-change.tsx extend context correctly 2`
exports[`renders components/affix/demo/target.tsx extend context correctly 1`] = `
<div
style="width: 100%; height: 100px; border-radius: 6px; overflow: auto; border: 1px solid #40a9ff;"
style="width: 100%; height: 100px; overflow: auto; border: 1px solid #40a9ff;"
>
<div
style="width: 100%; height: 1000px;"

View File

@ -84,7 +84,7 @@ exports[`renders components/affix/demo/on-change.tsx correctly 1`] = `
exports[`renders components/affix/demo/target.tsx correctly 1`] = `
<div
style="width:100%;height:100px;border-radius:6px;overflow:auto;border:1px solid #40a9ff"
style="width:100%;height:100px;overflow:auto;border:1px solid #40a9ff"
>
<div
style="width:100%;height:1000px"

View File

@ -4,7 +4,6 @@ import { Affix, Button } from 'antd';
const containerStyle: React.CSSProperties = {
width: '100%',
height: 100,
borderRadius: 6,
overflow: 'auto',
border: '1px solid #40a9ff',
};

View File

@ -264,4 +264,28 @@ describe('Dropdown', () => {
expect(divRef.current).toBeTruthy();
});
it('should trigger open event when click on item', () => {
const onOpenChange = jest.fn();
render(
<Dropdown
onOpenChange={onOpenChange}
open
menu={{
items: [
{
label: <div className="bamboo" />,
key: 1,
},
],
}}
>
<a />
</Dropdown>,
);
fireEvent.click(document.body.querySelector('.bamboo')!);
expect(onOpenChange).toHaveBeenCalledWith(false, { source: 'menu' });
});
});

View File

@ -45,7 +45,7 @@ export interface DropdownProps {
arrow?: boolean | DropdownArrowOptions;
trigger?: ('click' | 'hover' | 'contextMenu')[];
dropdownRender?: (originNode: React.ReactNode) => React.ReactNode;
onOpenChange?: (open: boolean) => void;
onOpenChange?: (open: boolean, info: { source: 'trigger' | 'menu' }) => void;
open?: boolean;
disabled?: boolean;
destroyPopupOnHide?: boolean;
@ -194,7 +194,7 @@ const Dropdown: CompoundedComponent = (props) => {
});
const onInnerOpenChange = useEvent((nextOpen: boolean) => {
onOpenChange?.(nextOpen);
onOpenChange?.(nextOpen, { source: 'trigger' });
onVisibleChange?.(nextOpen);
setOpen(nextOpen);
});
@ -213,6 +213,7 @@ const Dropdown: CompoundedComponent = (props) => {
});
const onMenuClick = React.useCallback(() => {
onOpenChange?.(false, { source: 'menu' });
setOpen(false);
}, []);

View File

@ -42,7 +42,7 @@ Common props ref[Common props](/docs/react/common-props)
### Dropdown
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- | --- |
| arrow | Whether the dropdown arrow should be visible | boolean \| { pointAtCenter: boolean } | false | |
| autoAdjustOverflow | Whether to adjust dropdown placement automatically when dropdown is off screen | boolean | true | 5.2.0 |
| autoFocus | Focus element in `overlay` when opened | boolean | false | 4.21.0 |
@ -56,7 +56,7 @@ Common props ref[Common props](/docs/react/common-props)
| placement | Placement of popup menu: `bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
| trigger | The trigger mode which executes the dropdown action. Note that hover can't be used on touchscreens | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| open | Whether the dropdown menu is currently open. Use `visible` under 4.23.0 ([why?](/docs/react/faq#why-open)) | boolean | - | 4.23.0 |
| onOpenChange | Called when the open state is changed. Not trigger when hidden by click item. Use `onVisibleChange` under 4.23.0 ([why?](/docs/react/faq#why-open)) | (open: boolean) => void | - | 4.23.0 |
| onOpenChange | Called when the open state is changed. Not trigger when hidden by click item. Use `onVisibleChange` under 4.23.0 ([why?](/docs/react/faq#why-open)) | (open: boolean, info: { source: 'trigger' | 'menu' }) => void | - | `info.source`: 5.11.0 |
### Dropdown.Button

View File

@ -46,7 +46,7 @@ demo:
### Dropdown
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- | --- |
| arrow | 下拉框箭头是否显示 | boolean \| { pointAtCenter: boolean } | false | |
| autoAdjustOverflow | 下拉框被遮挡时自动调整位置 | boolean | true | 5.2.0 |
| autoFocus | 打开后自动聚焦下拉框 | boolean | false | 4.21.0 |
@ -60,7 +60,7 @@ demo:
| placement | 菜单弹出位置:`bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
| trigger | 触发下拉的行为,移动端不支持 hover | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| open | 菜单是否显示,小于 4.23.0 使用 `visible`[为什么?](/docs/react/faq#弹层类组件为什么要统一至-open-属性) | boolean | - | 4.23.0 |
| onOpenChange | 菜单显示状态改变时调用,点击菜单按钮导致的消失不会触发。小于 4.23.0 使用 `onVisibleChange`[为什么?](/docs/react/faq#弹层类组件为什么要统一至-open-属性) | (open: boolean) => void | - | 4.23.0 |
| onOpenChange | 菜单显示状态改变时调用,点击菜单按钮导致的消失不会触发。小于 4.23.0 使用 `onVisibleChange`[为什么?](/docs/react/faq#弹层类组件为什么要统一至-open-属性) | (open: boolean, info: { source: 'trigger' | 'menu' }) => void | - | `info.source`: 5.11.0 |
### Dropdown.Button

View File

@ -308,9 +308,23 @@ Provide linkage between forms. If a sub form with `name` prop update, it will au
| setFieldValue | Set fields value(Will directly pass to form store and **reset validation message**. If you do not want to modify passed object, please clone first) | (name: [NamePath](#namepath), value: any) => void | 4.22.0 |
| setFieldsValue | Set fields value(Will directly pass to form store and **reset validation message**. If you do not want to modify passed object, please clone first). Use `setFieldValue` instead if you want to only config single value in Form.List | (values) => void | |
| submit | Submit the form. It's same as click `submit` button | () => void | |
| validateFields | Validate fields. Use `recursive` to validate all the field in the path | (nameList?: [NamePath](#namepath)\[], { validateOnly?: boolean }) => Promise | `validateOnly`: 5.5.0, `recursive`: 5.9.0 |
| validateFields | Validate fields. Use `recursive` to validate all the field in the path | (nameList?: [NamePath](#namepath)\[], config?: [ValidateConfig](#validateFields)) => Promise | |
#### validateFields return sample
#### validateFields
```tsx
export interface ValidateConfig {
// New in 5.5.0. Only validate content and not show error message on UI.
validateOnly?: boolean;
// New in 5.9.0. Recursively validate the provided `nameList` and its sub-paths.
recursive?: boolean;
// New in 5.11.0. Validate dirty fields (touched + validated).
// It's useful to validate fields only when they are touched or validated.
dirty?: boolean;
}
```
return sample:
```jsx
validateFields()

View File

@ -307,9 +307,23 @@ Form.List 渲染表单相关操作函数。
| setFieldValue | 设置表单的值(该值将直接传入 form store 中并且**重置错误信息**。如果你不希望传入对象被修改,请克隆后传入) | (name: [NamePath](#namepath), value: any) => void | 4.22.0 |
| setFieldsValue | 设置表单的值(该值将直接传入 form store 中并且**重置错误信息**。如果你不希望传入对象被修改,请克隆后传入)。如果你只想修改 Form.List 中单项值,请通过 `setFieldValue` 进行指定 | (values) => void | |
| submit | 提交表单,与点击 `submit` 按钮效果相同 | () => void | |
| validateFields | 触发表单验证,设置 `recursive` 时会递归校验所有包含的路径 | (nameList?: [NamePath](#namepath)\[], { validateOnly?: boolean, recursive?: boolean }) => Promise | `validateOnly`: 5.5.0, `recursive`: 5.9.0 |
| validateFields | 触发表单验证,设置 `recursive` 时会递归校验所有包含的路径 | (nameList?: [NamePath](#namepath)\[], config?: [ValidateConfig](#validateFields)) => Promise | |
#### validateFields 返回示例
#### validateFields
```tsx
export interface ValidateConfig {
// 5.5.0 新增。仅校验内容而不会将错误信息展示到 UI 上。
validateOnly?: boolean;
// 5.9.0 新增。对提供的 `nameList` 与其子路径进行递归校验。
recursive?: boolean;
// 5.11.0 新增。校验 dirty 的字段touched + validated
// 使用 `dirty` 可以很方便的仅校验用户操作过和被校验过的字段。
dirty?: boolean;
}
```
返回示例:
```jsx
validateFields()

View File

@ -42,6 +42,7 @@ Common props ref[Common props](/docs/react/common-props)
| addonBefore | The label text displayed before (on the left side of) the input field | ReactNode | - | |
| autoFocus | If get focus when component mounted | boolean | false | - |
| bordered | Whether has border style | boolean | true | 4.12.0 |
| changeOnBlur | Trigger `onChange` when blur. e.g. reset value in range by blur | boolean | true | 5.11.0 |
| controls | Whether to show `+-` controls, or set custom arrows icon | boolean \| { upIcon?: React.ReactNode; downIcon?: React.ReactNode; } | - | 4.19.0 |
| decimalSeparator | Decimal separator | string | - | - |
| defaultValue | The initial value | number | - | - |

View File

@ -43,6 +43,7 @@ demo:
| addonBefore | 带标签的 input设置前置标签 | ReactNode | - | 4.17.0 |
| autoFocus | 自动获取焦点 | boolean | false | - |
| bordered | 是否有边框 | boolean | true | 4.12.0 |
| changeOnBlur | 是否在失去焦点时,触发 `onChange` 事件(例如值超出范围时,重新限制回范围并触发事件) | boolean | true | 5.11.0 |
| controls | 是否显示增减按钮,也可设置自定义箭头图标 | boolean \| { upIcon?: React.ReactNode; downIcon?: React.ReactNode; } | - | 4.19.0 |
| decimalSeparator | 小数点 | string | - | - |
| defaultValue | 初始值 | number | - | - |

View File

@ -1,14 +1,14 @@
import * as React from 'react';
import { useContext, useEffect, useRef, useState } from 'react';
import BarsOutlined from '@ant-design/icons/BarsOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import * as React from 'react';
import { useContext, useEffect, useRef, useState } from 'react';
import isNumeric from '../_util/isNumeric';
import { ConfigContext } from '../config-provider';
import { LayoutContext } from './layout';
import { LayoutContext } from './context';
const dimensionMaxMap = {
xs: '479.98px',

View File

@ -115,7 +115,7 @@ exports[`renders components/layout/demo/basic.tsx correctly 1`] = `
class="ant-space-item"
>
<div
class="ant-layout"
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
@ -172,7 +172,7 @@ exports[`renders components/layout/demo/component-token.tsx correctly 1`] = `
</div>
</header>
<div
class="ant-layout"
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
@ -429,7 +429,7 @@ exports[`renders components/layout/demo/component-token.tsx correctly 1`] = `
exports[`renders components/layout/demo/custom-trigger.tsx correctly 1`] = `
<div
class="ant-layout"
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
@ -1256,7 +1256,7 @@ exports[`renders components/layout/demo/fixed-sider.tsx correctly 1`] = `
exports[`renders components/layout/demo/responsive.tsx correctly 1`] = `
<div
class="ant-layout"
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
@ -1434,7 +1434,7 @@ exports[`renders components/layout/demo/responsive.tsx correctly 1`] = `
exports[`renders components/layout/demo/side.tsx correctly 1`] = `
<div
class="ant-layout"
class="ant-layout ant-layout-has-sider"
style="min-height:100vh"
>
<aside
@ -2154,7 +2154,7 @@ exports[`renders components/layout/demo/top-side.tsx correctly 1`] = `
</ol>
</nav>
<div
class="ant-layout"
class="ant-layout ant-layout-has-sider"
style="padding:24px 0;background:#ffffff"
>
<aside
@ -2469,7 +2469,7 @@ exports[`renders components/layout/demo/top-side-2.tsx correctly 1`] = `
/>
</header>
<div
class="ant-layout"
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"

View File

@ -1,6 +1,8 @@
import { UserOutlined } from '@ant-design/icons';
import React, { useState } from 'react';
import { UserOutlined } from '@ant-design/icons';
import { renderToString } from 'react-dom/server';
import { act } from 'react-dom/test-utils';
import Layout from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -332,4 +334,16 @@ describe('Sider', () => {
expect(ref.current instanceof HTMLElement).toBe(true);
});
});
it('auto check hasSider', () => {
const htmlContent = renderToString(
<Layout>
<div />
<Sider />
<div />
</Layout>,
);
expect(htmlContent).toContain('ant-layout-has-sider');
});
});

View File

@ -0,0 +1,15 @@
import * as React from 'react';
export interface LayoutContextProps {
siderHook: {
addSider: (id: string) => void;
removeSider: (id: string) => void;
};
}
export const LayoutContext = React.createContext<LayoutContextProps>({
siderHook: {
addSider: () => null,
removeSider: () => null,
},
});

View File

@ -0,0 +1,22 @@
import type * as React from 'react';
import toArray from 'rc-util/lib/Children/toArray';
import Sider from '../Sider';
export default function useHasSider(
siders: string[],
children?: React.ReactNode,
hasSider?: boolean,
) {
if (typeof hasSider === 'boolean') {
return hasSider;
}
if (siders.length) {
return true;
}
const childNodes = toArray(children);
return childNodes.some((node) => node.type === Sider);
}

View File

@ -1,7 +1,10 @@
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 { LayoutContext } from './context';
import useHasSider from './hooks/useHasSider';
import useStyle from './style';
export interface GeneratorProps {
@ -16,19 +19,6 @@ export interface BasicProps extends React.HTMLAttributes<HTMLDivElement> {
hasSider?: boolean;
}
export interface LayoutContextProps {
siderHook: {
addSider: (id: string) => void;
removeSider: (id: string) => void;
};
}
export const LayoutContext = React.createContext<LayoutContextProps>({
siderHook: {
addSider: () => null,
removeSider: () => null,
},
});
interface BasicPropsWithTagName extends BasicProps {
tagName: 'header' | 'footer' | 'main' | 'div';
}
@ -91,11 +81,13 @@ const BasicLayout = React.forwardRef<HTMLDivElement, BasicPropsWithTagName>((pro
const { getPrefixCls, layout } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('layout', customizePrefixCls);
const mergedHasSider = useHasSider(siders, children, hasSider);
const [wrapSSR, hashId] = useStyle(prefixCls);
const classString = classNames(
prefixCls,
{
[`${prefixCls}-has-sider`]: typeof hasSider === 'boolean' ? hasSider : siders.length > 0,
[`${prefixCls}-has-sider`]: mergedHasSider,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
layout?.className,

View File

@ -118,6 +118,19 @@ exports[`renders components/spin/demo/delayAndDebounce.tsx extend context correc
exports[`renders components/spin/demo/delayAndDebounce.tsx extend context correctly 2`] = `[]`;
exports[`renders components/spin/demo/fullscreen.tsx extend context correctly 1`] = `
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Show fullscreen for 2s
</span>
</button>
`;
exports[`renders components/spin/demo/fullscreen.tsx extend context correctly 2`] = `[]`;
exports[`renders components/spin/demo/inside.tsx extend context correctly 1`] = `
<div
class="example"

View File

@ -112,6 +112,17 @@ exports[`renders components/spin/demo/delayAndDebounce.tsx correctly 1`] = `
</div>
`;
exports[`renders components/spin/demo/fullscreen.tsx correctly 1`] = `
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Show fullscreen for 2s
</span>
</button>
`;
exports[`renders components/spin/demo/inside.tsx correctly 1`] = `
<div
class="example"

View File

@ -1,5 +1,6 @@
import { render } from '@testing-library/react';
import React from 'react';
import { render } from '@testing-library/react';
import Spin from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -19,6 +20,15 @@ describe('Spin', () => {
expect(container.querySelector<HTMLElement>('.ant-spin')?.style.background).toBe('red');
});
it('should not apply nested styles when full screen', () => {
const { container } = render(
<Spin fullscreen>
<div>content</div>
</Spin>,
);
expect(container.querySelector<HTMLElement>('ant-spin-nested-loading')).toBeNull();
});
it("should render custom indicator when it's set", () => {
const customIndicator = <div className="custom-indicator" />;
const { asFragment } = render(<Spin indicator={customIndicator} />);

View File

@ -0,0 +1,7 @@
## zh-CN
`fullscreen` 属性非常适合创建流畅的页面加载器。它添加了半透明覆盖层,并在其中心放置了一个旋转加载符号。
## en-US
The `fullscreen` mode is perfect for creating page loaders. It adds a dimmed overlay with a centered spinner.

View File

@ -0,0 +1,23 @@
import React, { useState } from 'react';
import { Button, Spin } from 'antd';
const App: React.FC = () => {
const [show, setShow] = useState(false);
const showLoader = () => {
setShow(true);
setTimeout(() => {
setShow(false);
}, 2000);
};
return (
<>
<Button onClick={showLoader}>Show fullscreen for 2s</Button>
{show && <Spin fullscreen size="large" />}
</>
);
};
export default App;

View File

@ -17,26 +17,28 @@ When part of the page is waiting for asynchronous data or during a rendering pro
## Examples
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">basic Usage</code>
<code src="./demo/basic.tsx">Basic Usage</code>
<code src="./demo/size.tsx">Size</code>
<code src="./demo/inside.tsx">Inside a container</code>
<code src="./demo/nested.tsx">Embedded mode</code>
<code src="./demo/tip.tsx">Customized description</code>
<code src="./demo/delayAndDebounce.tsx">delay</code>
<code src="./demo/delayAndDebounce.tsx">Delay</code>
<code src="./demo/custom-indicator.tsx">Custom spinning indicator</code>
<code src="./demo/fullscreen.tsx">Fullscreen</code>
## API
Common props ref[Common props](/docs/react/common-props)
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| delay | Specifies a delay in milliseconds for loading state (prevent flush) | number (milliseconds) | - |
| indicator | React node of the spinning indicator | ReactNode | - |
| size | The size of Spin, options: `small`, `default` and `large` | string | `default` |
| spinning | Whether Spin is visible | boolean | true |
| tip | Customize description content when Spin has children | ReactNode | - |
| wrapperClassName | The className of wrapper when Spin has children | string | - |
| fullscreen | Display a backdrop with the `Spin` component | boolean | false | 5.11.0 |
### Static Method

View File

@ -25,6 +25,7 @@ export interface SpinProps {
wrapperClassName?: string;
indicator?: SpinIndicator;
children?: React.ReactNode;
fullscreen?: boolean;
}
export interface SpinClassProps extends SpinProps {
@ -87,6 +88,7 @@ const Spin: React.FC<SpinClassProps> = (props) => {
style,
children,
hashId,
fullscreen,
...restProps
} = props;
@ -108,7 +110,10 @@ const Spin: React.FC<SpinClassProps> = (props) => {
setSpinning(false);
}, [delay, customSpinning]);
const isNestedPattern = React.useMemo<boolean>(() => typeof children !== 'undefined', [children]);
const isNestedPattern = React.useMemo<boolean>(
() => typeof children !== 'undefined' && !fullscreen,
[children, fullscreen],
);
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Spin');
@ -126,6 +131,7 @@ const Spin: React.FC<SpinClassProps> = (props) => {
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-spinning`]: spinning,
[`${prefixCls}-show-text`]: !!tip,
[`${prefixCls}-fullscreen`]: fullscreen,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
@ -151,7 +157,9 @@ const Spin: React.FC<SpinClassProps> = (props) => {
aria-busy={spinning}
>
{renderIndicator(prefixCls, props)}
{tip && isNestedPattern ? <div className={`${prefixCls}-text`}>{tip}</div> : null}
{tip && (isNestedPattern || fullscreen) ? (
<div className={`${prefixCls}-text`}>{tip}</div>
) : null}
</div>
);

View File

@ -25,19 +25,21 @@ demo:
<code src="./demo/tip.tsx">自定义描述文案</code>
<code src="./demo/delayAndDebounce.tsx">延迟</code>
<code src="./demo/custom-indicator.tsx">自定义指示符</code>
<code src="./demo/fullscreen.tsx">全屏</code>
## API
通用属性参考:[通用属性](/docs/react/common-props)
| 参数 | 说明 | 类型 | 默认值 |
| ---------------- | -------------------------------------------- | ------------- | --------- |
| delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - |
| indicator | 加载指示符 | ReactNode | - |
| size | 组件大小,可选值为 `small` `default` `large` | string | `default` |
| spinning | 是否为加载中状态 | boolean | true |
| tip | 当作为包裹元素时,可以自定义描述文案 | ReactNode | - |
| wrapperClassName | 包装器的类属性 | string | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - |
| indicator | 加载指示符 | ReactNode | - |
| size | 组件大小,可选值为 `small` `default` `large` | string | `default` |
| spinning | 是否为加载中状态 | boolean | true |
| tip | 当作为包裹元素时,可以自定义描述文案 | ReactNode | - |
| wrapperClassName | 包装器的类属性 | string | - |
| fullscreen | 显示带有 `Spin` 组件的背景 | boolean | false | 5.11.0 |
### 静态方法

View File

@ -1,5 +1,6 @@
import type { CSSObject } from '@ant-design/cssinjs';
import { Keyframes } from '@ant-design/cssinjs';
import { resetComponent } from '../../style';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
@ -39,6 +40,8 @@ const antRotate = new Keyframes('antRotate', {
to: { transform: 'rotate(405deg)' },
});
const dotPadding = (token: SpinToken) => (token.dotSize - token.fontSize) / 2 + 2;
const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject => ({
[`${token.componentCls}`]: {
...resetComponent(token),
@ -57,6 +60,30 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
opacity: 1,
},
[`${token.componentCls}-text`]: {
fontSize: token.fontSize,
paddingTop: dotPadding(token),
},
'&-fullscreen': {
position: 'fixed',
width: '100vw',
height: '100vh',
backgroundColor: token.colorBgMask,
zIndex: token.zIndexPopupBase,
inset: 0,
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'center',
[`${token.componentCls}-dot ${token.componentCls}-dot-item`]: {
backgroundColor: token.colorWhite,
},
[`${token.componentCls}-text`]: {
color: token.colorTextLightSolid,
},
},
'&-nested-loading': {
position: 'relative',
[`> div > ${token.componentCls}`]: {
@ -80,9 +107,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
position: 'absolute',
top: '50%',
width: '100%',
paddingTop: (token.dotSize - token.fontSize) / 2 + 2,
textShadow: `0 1px 2px ${token.colorBgContainer}`, // FIXME: shadow
fontSize: token.fontSize,
},
[`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {

View File

@ -11,7 +11,7 @@ import Button from '../../../button';
import type { CheckboxChangeEvent } from '../../../checkbox';
import Checkbox from '../../../checkbox';
import { ConfigContext } from '../../../config-provider/context';
import Dropdown from '../../../dropdown';
import Dropdown, { type DropdownProps } from '../../../dropdown';
import Empty from '../../../empty';
import type { MenuProps } from '../../../menu';
import Menu from '../../../menu';
@ -295,17 +295,19 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
internalTriggerFilter(getFilteredKeysSync());
};
const onVisibleChange = (newVisible: boolean) => {
if (newVisible && propFilteredKeys !== undefined) {
// Sync filteredKeys on appear in controlled mode (propFilteredKeys !== undefined)
setFilteredKeysSync(wrapStringListType(propFilteredKeys));
}
const onVisibleChange: DropdownProps['onOpenChange'] = (newVisible, info) => {
if (info.source === 'trigger') {
if (newVisible && propFilteredKeys !== undefined) {
// Sync filteredKeys on appear in controlled mode (propFilteredKeys !== undefined)
setFilteredKeysSync(wrapStringListType(propFilteredKeys));
}
triggerVisible(newVisible);
triggerVisible(newVisible);
// Default will filter when closed
if (!newVisible && !column.filterDropdown) {
onConfirm();
// Default will filter when closed
if (!newVisible && !column.filterDropdown) {
onConfirm();
}
}
};

View File

@ -74,15 +74,15 @@ More option at [rc-tabs tabs](https://github.com/react-component/tabs#tabs)
### TabItemType
| Property | Description | Type | Default |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| closeIcon | Customize close icon in TabPane's head. Only works while `type="editable-card"`. 5.7.0: close button will be hidden when setting to `null` or `false` | boolean \| ReactNode | - |
| closeIcon | Customize close icon in TabPane's head. Only works while `type="editable-card"`. 5.7.0: close button will be hidden when setting to `null` or `false` | boolean \| ReactNode | - | |
| destroyInactiveTabPane | Whether destroy inactive TabPane when change tab | boolean | false | 5.11.0 |
| disabled | Set TabPane disabled | boolean | false |
| forceRender | Forced render of content in tabs, not lazy render after clicking on tabs | boolean | false |
| key | TabPane's key | string | - |
| label | TabPane's head display text | ReactNode | - |
| children | TabPane's head display content | ReactNode | - |
| disabled | Set TabPane disabled | boolean | false | |
| forceRender | Forced render of content in tabs, not lazy render after clicking on tabs | boolean | false | |
| key | TabPane's key | string | - | |
| label | TabPane's head display text | ReactNode | - | |
| children | TabPane's head display content | ReactNode | - | |
## Design Token

View File

@ -76,15 +76,15 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
### TabItemType
| 参数 | 说明 | 类型 | 默认值 |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| closeIcon | 自定义关闭图标,在 `type="editable-card"` 时有效。5.7.0:设置为 `null``false` 时隐藏关闭按钮 | boolean \| ReactNode | - |
| closeIcon | 自定义关闭图标,在 `type="editable-card"` 时有效。5.7.0:设置为 `null``false` 时隐藏关闭按钮 | boolean \| ReactNode | - | |
| destroyInactiveTabPane | 被隐藏时是否销毁 DOM 结构 | boolean | false | 5.11.0 |
| disabled | 禁用某一项 | boolean | false |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
| key | 对应 activeKey | string | - |
| label | 选项卡头显示文字 | ReactNode | - |
| children | 选项卡头显示内容 | ReactNode | - |
| disabled | 禁用某一项 | boolean | false | |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false | |
| key | 对应 activeKey | string | - | |
| label | 选项卡头显示文字 | ReactNode | - | |
| children | 选项卡头显示内容 | ReactNode | - | |
## 主题变量Design Token

View File

@ -1,7 +1,7 @@
{
"name": "antd",
"version": "5.10.1",
"packageManager": "^npm@9.0.0",
"packageManager": "npm@9.0.0",
"description": "An enterprise-class UI design language and React components implementation",
"title": "Ant Design",
"keywords": [
@ -95,6 +95,7 @@
"test-node": "npm run version && jest --config .jest.node.js --no-cache",
"tsc": "tsc --noEmit",
"site:test": "jest --config .jest.site.js",
"site:test:update": "npm run site && npm run site:test -- -u",
"test-image": "jest --config .jest.image.js --no-cache -i -u",
"argos": "tsx scripts/argos-upload.ts",
"version": "tsx scripts/generate-version.ts",
@ -129,10 +130,10 @@
"rc-dialog": "~9.3.3",
"rc-drawer": "~6.5.2",
"rc-dropdown": "~4.1.0",
"rc-field-form": "~1.39.0",
"rc-field-form": "~1.40.0",
"rc-image": "~7.3.1",
"rc-input": "~1.3.5",
"rc-input-number": "~8.3.0",
"rc-input-number": "~8.4.0",
"rc-mentions": "~2.9.1",
"rc-menu": "~9.12.2",
"rc-motion": "^2.9.0",
@ -148,7 +149,7 @@
"rc-steps": "~6.0.1",
"rc-switch": "~4.1.0",
"rc-table": "~7.35.1",
"rc-tabs": "~12.13.0",
"rc-tabs": "~12.13.1",
"rc-textarea": "~1.5.1",
"rc-tooltip": "~6.1.1",
"rc-tree": "~5.7.12",
@ -209,7 +210,7 @@
"@typescript-eslint/parser": "^6.5.0",
"antd-img-crop": "^4.9.0",
"antd-style": "^3.5.0",
"antd-token-previewer": "^2.0.1",
"antd-token-previewer": "^2.0.4",
"chalk": "^4.0.0",
"cheerio": "1.0.0-rc.12",
"circular-dependency-plugin": "^5.2.2",