This commit is contained in:
electroluxcode 2024-09-04 19:40:01 +08:00
commit 6900322a22
107 changed files with 2973 additions and 1177 deletions

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { Tag, version } from 'antd';
import { Space, Tag, version } from 'antd';
import { createStyles } from 'antd-style';
import classnames from 'classnames';
import { useFullSidebarData, useSidebarData } from 'dumi';
@ -22,7 +22,6 @@ const useStyle = createStyles(({ css, token }) => ({
margin-inline-end: 0;
`,
subtitle: css`
margin-inline-start: ${token.marginXS}px;
font-weight: normal;
font-size: ${token.fontSizeSM}px;
opacity: 0.8;
@ -46,10 +45,10 @@ const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
if (!before && !after) {
return (
<Link to={`${link}${search}`} className={classnames(className, { [styles.link]: tag })}>
<span>
{title}
<Space>
<span>{title}</span>
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
</span>
</Space>
{tag && (
<Tag
bordered={false}

View File

@ -5,7 +5,7 @@ import type { AnchorLinkItemProps } from 'antd/es/anchor/Anchor';
import classNames from 'classnames';
import { useRouteMeta, useTabMeta } from 'dumi';
const useStyle = createStyles(({ token, css }) => {
export const useStyle = createStyles(({ token, css }) => {
const { antCls } = token;
return {
anchorToc: css`
@ -19,13 +19,13 @@ const useStyle = createStyles(({ token, css }) => {
`,
tocWrapper: css`
position: fixed;
top: ${token.headerHeight + token.contentMarginTop - 8}px;
top: ${token.headerHeight + token.contentMarginTop - 4}px;
inset-inline-end: 0;
width: 160px;
padding: ${token.paddingXS}px;
width: 148px;
padding: 0;
border-radius: ${token.borderRadius}px;
box-sizing: border-box;
margin-inline-end: calc(16px - 100vw + 100%);
margin-inline-end: calc(8px - 100vw + 100%);
z-index: 10;
.toc-debug {
color: ${token.purple6};
@ -48,15 +48,11 @@ const useStyle = createStyles(({ token, css }) => {
}
`,
articleWrapper: css`
padding: 0 170px 32px 64px;
&.rtl {
padding: 0 64px 144px 170px;
}
padding-inline: 48px 164px;
padding-block: 0 32px;
@media only screen and (max-width: ${token.screenLG}px) {
&,
&.rtl {
& {
padding: 0 ${token.paddingLG * 2}px;
}
}

View File

@ -1,6 +1,5 @@
import React, { useContext, useLayoutEffect, useMemo, useState } from 'react';
import { Col, Flex, Space, Typography } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { FormattedMessage, useRouteMeta } from 'dumi';
@ -11,6 +10,7 @@ import type { DemoContextProps } from '../DemoContext';
import DemoContext from '../DemoContext';
import SiteContext from '../SiteContext';
import InViewSuspense from './InViewSuspense';
import { useStyle } from './DocAnchor';
const Contributors = React.lazy(() => import('./Contributors'));
const ColumnCard = React.lazy(() => import('./ColumnCard'));
@ -21,21 +21,6 @@ const PrevAndNext = React.lazy(() => import('../../common/PrevAndNext'));
const ComponentChangelog = React.lazy(() => import('../../common/ComponentChangelog'));
const EditButton = React.lazy(() => import('../../common/EditButton'));
const useStyle = createStyles(({ token, css }) => ({
articleWrapper: css`
padding: 0 170px 32px 64px;
&.rtl {
padding: 0 64px 144px 170px;
}
@media only screen and (max-width: ${token.screenLG}px) {
&,
&.rtl {
padding: 0 ${token.paddingLG * 2}px;
}
}
`,
}));
const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
const meta = useRouteMeta();
const { pathname, hash } = useLocation();
@ -107,9 +92,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
version={meta.frontmatter.tag}
/>
)}
<div style={{ minHeight: 'calc(100vh - 64px)', width: 'calc(100% - 10px)' }}>
{children}
</div>
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
<InViewSuspense>
<ColumnCard
zhihuLink={meta.frontmatter.zhihu_url}

View File

@ -53,12 +53,7 @@ const useStyle = createStyles(({ token, css }) => {
> ${antCls}-menu-item-group
> ${antCls}-menu-item-group-list
> ${antCls}-menu-item {
padding-inline-start: 40px !important;
${antCls}-row-rtl & {
padding-inline-end: 40px !important;
padding-inline-start: ${token.padding}px !important;
}
padding-inline: 36px 12px !important;
}
// Nest Category > Type > Article
@ -96,8 +91,6 @@ const useStyle = createStyles(({ token, css }) => {
`,
mainMenu: css`
z-index: 1;
.main-menu-inner {
position: sticky;
top: ${token.headerHeight + token.contentMarginTop}px;
width: 100%;
@ -106,9 +99,12 @@ const useStyle = createStyles(({ token, css }) => {
overflow: hidden;
scrollbar-width: thin;
scrollbar-color: unset;
.ant-menu {
padding: 0 4px;
}
&:hover .main-menu-inner {
&:hover {
overflow-y: auto;
}
`,
@ -144,7 +140,7 @@ const Sidebar: React.FC = () => {
<MobileMenu key="Mobile-menu">{menuChild}</MobileMenu>
) : (
<Col xxl={4} xl={5} lg={6} md={6} sm={24} xs={24} className={styles.mainMenu}>
<section className="main-menu-inner">{menuChild}</section>
{menuChild}
</Col>
);
};

View File

@ -23,6 +23,7 @@ export default defineConfig({
mfsu: false,
mako: {},
crossorigin: {},
runtimePublicPath: {},
outputPath: '_site',
favicons: ['https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png'],
resolve: {

View File

@ -20,9 +20,7 @@ jobs:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v4
with:
@ -54,7 +52,7 @@ jobs:
- name: 🎨 Diff Report
if: ${{ failure() }}
run: npx diff-yarn-lock --source=~tmpProj/yarn.lock --target=~tmpProj/yarn.lock.failed
run: bunx diff-yarn-lock --source=~tmpProj/yarn.lock --target=~tmpProj/yarn.lock.failed
- uses: actions-cool/ci-notice@v1
if: ${{ failure() }}

View File

@ -10,15 +10,13 @@ jobs:
uses: actions/checkout@v4
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: npm install
run: bun install
- name: Build
run: npm run build
run: bun run build
# ========== Prepare examples ==========
- name: Clear examples
@ -31,6 +29,6 @@ jobs:
path: examples
- name: Modify examples
run: npx tsx scripts/prepare-examples.ts
run: bunx tsx scripts/prepare-examples.ts
- run: npx pkg-pr-new publish --template './examples/examples/*'
- run: bunx pkg-pr-new publish --template './examples/examples/*'

View File

@ -36,7 +36,7 @@ jobs:
🎉 感谢您的贡献!如果您还没有加入钉钉社区群,请扫描下方二维码加入我们(加群时请提供此 PR 链接)。
<img src="https://github.com/ant-design/ant-design/assets/5378891/e24c6080-bf38-4523-b1cd-f6c43ad7375f" height="200" />
<img src="https://github.com/user-attachments/assets/cfee105e-8731-481f-a336-92b79a84d35a" height="200" />
<!-- WELCOME_CONTRIBUTION -->
body-include: '<!-- WELCOME_CONTRIBUTION -->'

View File

@ -15,73 +15,26 @@ 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@v4
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@v4
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@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: npm run site
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: bun run site
id: site
run: npm run site
run: bun run site
env:
NODE_OPTIONS: "--max_old_space_size=4096"
- name: run e2e test
run: bun run test:site
- name: upload site artifact
uses: actions/upload-artifact@v4
with:
name: site
path: _site/
retention-days: 5
# Upload PR id for next workflow use
- name: Save PR number
if: ${{ always() }}
@ -93,32 +46,3 @@ jobs:
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@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: download site artifact
uses: actions/download-artifact@v4
with:
name: site
path: _site
- name: run e2e test
run: npm run test:site

View File

@ -11,69 +11,24 @@ permissions:
contents: write
jobs:
setup:
build-and-deploy:
runs-on: ubuntu-latest
if: (startsWith(github.ref, 'refs/tags/') && (contains(github.ref_name, '-') == false)) || github.event_name == 'workflow_dispatch'
steps:
- name: checkout
uses: actions/checkout@v4
- name: cache package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- uses: oven-sh/setup-bun@v2
- 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@v4
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-and-deploy:
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- run: bun install
- name: build site
run: npm run predeploy
run: bun run predeploy
env:
NODE_OPTIONS: "--max_old_space_size=4096"
- name: build dist and bundle analyzer report
run: npm run dist
run: bun run dist
env:
ANALYZER: 1
NODE_OPTIONS: "--max_old_space_size=4096"

View File

@ -27,5 +27,4 @@ jobs:
env:
NODE_OPTIONS: "--max_old_space_size=4096"
PRODUCTION_ONLY: 1
NO_DUP_CHECK: 1
CI_JOB_NUMBER: 1

View File

@ -13,67 +13,13 @@ permissions:
contents: read
jobs:
setup:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: cache package-lock.json
uses: actions/cache@v4
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@v4
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
lint:
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: lint
run: npm run lint
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun run lint
################################ Test ################################
test-react-legacy:
@ -85,68 +31,28 @@ jobs:
env:
REACT: ${{ matrix.react }}
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: install react 16
if: ${{ matrix.react == '16' }}
run: npm run install-react-16
run: bun run bun-install-react-16
- name: install react 17
if: ${{ matrix.react == '17' }}
run: npm run install-react-17
run: bun run bun-install-react-17
# dom test
- name: dom test
run: npm test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
test-node:
name: test-node
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: install react 18
run: npm run install-react-18
- name: node test
run: npm run test:node
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun run test:node
test-react-latest:
name: test-react-latest
@ -155,33 +61,14 @@ jobs:
module: ['dom']
shard: ['1/2', '2/2']
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: install react 18
run: npm run install-react-18
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
# dom test
- name: dom test
run: npm test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
- name: persist coverages
run: |
@ -203,27 +90,9 @@ jobs:
runs-on: ubuntu-latest
needs: build
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: install react 18
run: npm run install-react-18
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: restore cache from dist
uses: actions/cache@v4
@ -233,13 +102,13 @@ jobs:
- name: dist-min test
if: ${{ matrix.module == 'dist-min' }}
run: npm test
run: bun run test
env:
LIB_DIR: dist-min
- name: dist test
if: ${{ matrix.module == 'dist' }}
run: npm test
run: bun run test
env:
LIB_DIR: dist
@ -250,10 +119,8 @@ jobs:
needs: test-react-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: oven-sh/setup-bun@v2
- run: bun install
- uses: actions/download-artifact@v4
with:
@ -262,8 +129,8 @@ jobs:
path: persist-coverage
- name: Merge Code Coverage
run: |
npx nyc merge persist-coverage/ coverage/coverage-final.json
npx nyc report --reporter text -t coverage --report-dir coverage
bunx nyc merge persist-coverage/ coverage/coverage-final.json
bunx nyc report --reporter text -t coverage --report-dir coverage
rm -rf persist-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@v4
@ -274,26 +141,10 @@ jobs:
########################### Compile & Test ###########################
build:
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: cache lib
uses: actions/cache@v4
@ -308,7 +159,7 @@ jobs:
key: es-${{ github.sha }}
- name: compile
run: npm run compile
run: bun run compile
- name: cache dist
uses: actions/cache@v4
@ -317,10 +168,9 @@ jobs:
key: dist-${{ github.sha }}
- name: dist
run: npm run dist
run: bun run dist
env:
NODE_OPTIONS: "--max_old_space_size=4096"
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CI: 1
- name: check build files
@ -351,36 +201,14 @@ jobs:
test-lib-es:
name: test lib/es module
runs-on: ubuntu-latest
needs: setup
strategy:
matrix:
module: [lib, es]
shard: ['1/2', '2/2']
steps:
- name: checkout
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: restore cache from ${{ matrix.module }}
# lib only run in master branch not in pull request
@ -393,15 +221,11 @@ jobs:
- name: compile
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
run: npm run compile
- name: install react 18
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
run: npm run install-react-18
run: bun run compile
- name: test
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
run: npm test -- --maxWorkers=2 --shard=${{matrix.shard}}
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}}
env:
LIB_DIR: ${{ matrix.module }}

View File

@ -21,10 +21,8 @@ jobs:
.ncurc.js
package.json
- name: setup node
uses: actions/setup-node@v4
with:
node-version: 20
- name: setup bun
uses: oven-sh/setup-bun@v2
- name: upgrade deps
id: upgrade
@ -32,7 +30,7 @@ jobs:
if [ ! -d .tmp ] ; then
mkdir .tmp
fi
$(npx npm-check-updates -u > .tmp/upgrade-deps-logs.txt) 2>&1 || true
$(bunx npm-check-updates -u > .tmp/upgrade-deps-logs.txt) 2>&1 || true
if [ -s .tmp/upgrade-deps-logs.txt ]; then
cat .tmp/upgrade-deps-logs.txt
echo "logs<<EOF" >> $GITHUB_OUTPUT
@ -42,7 +40,7 @@ jobs:
- name: create pull request
id: cpr
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }} # Cannot be default!!!
assignees: 'afc163, zombieJ, xrkffgg, MadCcc'

View File

@ -16,41 +16,6 @@ permissions:
contents: read
jobs:
# Prepare node modules. Reuse cache if available
setup:
name: prepare node_modules
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: cache package-lock.json
uses: actions/cache@v4
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@v4
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
############################ Generate Snapshot ###########################
visual-diff-snapshot:
name: visual-diff snapshot
@ -58,33 +23,17 @@ jobs:
matrix:
shard: ['1/2', '2/2']
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: generate image snapshots
id: test-image
run: |
node node_modules/puppeteer/install.mjs
npm run version
npm run test:image -- --shard=${{matrix.shard}}
bun run version
bun run test:image -- --shard=${{matrix.shard}}
env:
NODE_OPTIONS: "--max_old_space_size=4096"
@ -102,22 +51,8 @@ jobs:
needs: visual-diff-snapshot
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- uses: oven-sh/setup-bun@v2
- run: bun install
- uses: actions/download-artifact@v4
with:
@ -132,7 +67,7 @@ jobs:
EVENT_NUMBER: ${{ github.event.number }}
BASE_REF: ${{ github.base_ref }}
run: |
npm run test:visual-regression -- --pr-id=$EVENT_NUMBER --base-ref=$BASE_REF --max-workers=2
bun run test:visual-regression -- --pr-id=$EVENT_NUMBER --base-ref=$BASE_REF --max-workers=2
# Upload report in `visualRegressionReport`
- name: upload report artifact

View File

@ -63,8 +63,8 @@ jobs:
runs-on: ubuntu-latest
needs: [upstream-workflow-summary]
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
# We need get persist-index first
- name: download image snapshot artifact
@ -110,7 +110,7 @@ jobs:
echo "✅ Uncompress Finished"
rm package.json
npm i ali-oss --no-save
bun add ali-oss --no-save
echo "✅ Install `ali-oss` Finished"
echo "🤖 Uploading"

View File

@ -12,65 +12,18 @@ permissions:
contents: read
jobs:
# Prepare node modules. Reuse cache if available
setup:
name: prepare node_modules
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: cache package-lock.json
uses: actions/cache@v4
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@v4
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
test-image:
name: test image
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- name: restore cache from package-lock.json
uses: actions/cache@v4
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: generate image snapshots
run: |
node node_modules/puppeteer/install.mjs
npm run version
npm run test:image
bun run version
bun run test:image
tar -czvf imageSnapshots.tar.gz imageSnapshots/*
env:
NODE_OPTIONS: "--max_old_space_size=4096"

View File

@ -1,4 +1,4 @@
{
"*.{ts,tsx,js,jsx,json,css}": ["biome check --write"],
"*.{ts,tsx,js,jsx,css}": ["biome check --write"],
"*.{md,yml}": ["prettier --ignore-unknown --write"]
}

View File

@ -56,5 +56,6 @@
">= 5.16.0 <= 5.16.1": ["https://github.com/ant-design/ant-design/issues/48200"],
"5.16.3": ["https://github.com/ant-design/ant-design/issues/48568"],
"5.17.1": ["https://github.com/ant-design/ant-design/issues/48913"],
"5.18.2": ["https://github.com/ant-design/ant-design/pull/49487"]
"5.18.2": ["https://github.com/ant-design/ant-design/pull/49487"],
"5.20.4": ["https://github.com/ant-design/ant-design/issues/50687"]
}

View File

@ -16,6 +16,49 @@ tag: vVERSION
---
## 5.20.5
`2024-09-03`
- 🛠 Adjust Tree & TreeSelect `defaultExpandAll` logic to only add internal `expandedKeys` which `treeNode` has children instead to avoid perf issue when with large data or `loadData` case. [#50689](https://github.com/ant-design/ant-design/pull/50689) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Cascader not show parent option in search when using `multiple`. [#50689](https://github.com/ant-design/ant-design/pull/50689)
- 🐞 Fix Typography `ellipsis.tooltip.title` with ReactNode will cause dead loop. [#50688](https://github.com/ant-design/ant-design/pull/50688) [@zombieJ](https://github.com/zombieJ)
## 5.20.4
`2024-09-02`
- Menu
- 🐞 Fix Menu token `itemPaddingInline inoperative` not working. [#50663](https://github.com/ant-design/ant-design/pull/50663) [@coding-ice](https://github.com/coding-ice)
- 🐞 Fix Menu missing `hover` transition style. [#50624](https://github.com/ant-design/ant-design/pull/50624) [@afc163](https://github.com/afc163)
- 💄 Badge add transition effect to count node. [#50607](https://github.com/ant-design/ant-design/pull/50607) [@afc163](https://github.com/afc163)
- 💄 Fix Table column header move with unexpected transition. [#50605](https://github.com/ant-design/ant-design/pull/50605) [@afc163](https://github.com/afc163)
- 🛠 Refactor Typography code to optimize internal logic. [#50561](https://github.com/ant-design/ant-design/pull/50561) [@afc163](https://github.com/afc163)
- 🐞 Disable the Rate component within Form.Item when the form is disabled. [#50594](https://github.com/ant-design/ant-design/pull/50594) [@nikzanda](https://github.com/nikzanda)
- 🌐 Patch tr_TR `Transfer.deselectAll` locale. [#50672](https://github.com/ant-design/ant-design/pull/50672) [@coding-ice](https://github.com/coding-ice)
## 5.20.3
`2024-08-26`
- 🐞 Refactor Typography native css ellipsis measure logic to handle precision edge case. [#50514](https://github.com/ant-design/ant-design/pull/50514) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix ColorPicker `onChangeComplete` not correct when click directly without move on the picker panel. [#50501](https://github.com/ant-design/ant-design/pull/50501) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix FloatButton.Group with controlled mode warning for nest updating issue. [#50500](https://github.com/ant-design/ant-design/pull/50500) [@zombieJ](https://github.com/zombieJ)
## 5.20.2
`2024-08-19`
- 💄 Fix the suffix style problem of InputNumber without control. [#50450](https://github.com/ant-design/ant-design/pull/50450) [@coding-ice](https://github.com/coding-ice)
- 🆕 Form `rule.message` supports skipping variable substitution through `\\${}`. [#50412](https://github.com/ant-design/ant-design/pull/50412) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fixed the issue where the rounded corners of the trigger element are missing when the FloatButton component has shape="square" and in menu mode when the menu pops up. [#50408](https://github.com/ant-design/ant-design/pull/50408) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 Fixed the problem that Upload.Dragger does not work when dragging and dropping upload folders. [#50394](https://github.com/ant-design/ant-design/pull/50394) [@huiliangShen](https://github.com/huiliangShen)
- 🐞 Fixed the issue where the arrow icon disappears after hovering when Select specifies `getPopcontainer={node=node.parentNode}`. [#50382](https://github.com/ant-design/ant-design/pull/50382) [@afc163](https://github.com/afc163)
- 🐞 Fixed the arrow misalignment error when Popover sets the `arrow.pointAtCenter` property. [#50260](https://github.com/ant-design/ant-design/pull/50260) [@Wxh16144](https://github.com/Wxh16144)
- 📖 Transfer adds Russian and Ukrainian localization copy. [#50429](https://github.com/ant-design/ant-design/pull/50429) [@alexlag](https://github.com/alexlag)
- TypeScript
- 🤖 Roll back the Table partial generic constraint object to any to reduce break changes caused by [#50351](https://github.com/ant-design/ant-design/pull/50351). [#50372](https://github.com/ant-design/ant-design/pull/50372) [@crazyair](https://github.com/crazyair)
## 5.20.1
`2024-08-11`

View File

@ -15,6 +15,49 @@ tag: vVERSION
---
## 5.20.5
`2024-09-03`
- 🛠 调整 Tree 与 TreeSelect 的 `defaultExpandAll` 的行为,仅将有子节点的 `treeNode` 加入 `expandedKeys` 以防止在大数据与 `loadData` 异步的情况下引发的性能问题。[#50689](https://github.com/ant-design/ant-design/pull/50689) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Cascader 在 `multiple` 下搜索不会显示父节点作为选项的问题。[#50689](https://github.com/ant-design/ant-design/pull/50689)
- 🐞 修复 Typography `ellipsis.tooltip.title` 配置 ReactNode 会导致死循环的问题。[#50688](https://github.com/ant-design/ant-design/pull/50688) [@zombieJ](https://github.com/zombieJ)
## 5.20.4
`2024-09-02`
- Menu
- 🐞 修复 Menu 的 `itemPaddingInline` token 不生效的问题。[#50663](https://github.com/ant-design/ant-design/pull/50663) [@coding-ice](https://github.com/coding-ice)
- 🐞 修复 Menu `hover` 时背景色切换渐变效果丢失的问题。[#50624](https://github.com/ant-design/ant-design/pull/50624) [@afc163](https://github.com/afc163)
- 💄 给 Badge 增加一个动画缓动效果。[#50607](https://github.com/ant-design/ant-design/pull/50607) [@afc163](https://github.com/afc163)
- 💄 修复 Table 列头切换状态时多余的的移动缓动动画。[#50605](https://github.com/ant-design/ant-design/pull/50605) [@afc163](https://github.com/afc163)
- 🛠 重构 Typography 代码以优化内部实现逻辑。[#50561](https://github.com/ant-design/ant-design/pull/50561) [@afc163](https://github.com/afc163)
- 🐞 当表单被禁用时,禁用 Form.Item 中的 Rate 组件。[#50594](https://github.com/ant-design/ant-design/pull/50594) [@nikzanda](https://github.com/nikzanda)
- 🌐 补充土耳其 `Transfer.deselectAll` 本地化文本。[#50672](https://github.com/ant-design/ant-design/pull/50672) [@coding-ice](https://github.com/coding-ice)
## 5.20.3
`2024-08-26`
- 🐞 重构 Typography 在使用 css 原生省略时的检查逻辑,以解决屏幕缩放等情况下的精度问题。[#50514](https://github.com/ant-design/ant-design/pull/50514) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 ColorPicker 组件在面板上不拖拽直接点击的时候,`onChangeComplete` 返回值不正确的问题。[#50501](https://github.com/ant-design/ant-design/pull/50501) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 FloatButton.Group 在受控模式下 React 会警告递归更新的问题。[#50500](https://github.com/ant-design/ant-design/pull/50500) [@zombieJ](https://github.com/zombieJ)
## 5.20.2
`2024-08-19`
- 💄 修复 InputNumber 没有控件的后缀样式问题。[#50450](https://github.com/ant-design/ant-design/pull/50450) [@coding-ice](https://github.com/coding-ice)
- 🆕 Form `rule.message` 支持通过 `\\${}` 跳过变量替换。[#50412](https://github.com/ant-design/ant-design/pull/50412) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复了 FloatButton 组件当 shape=“square” 时,并且在菜单模式下,菜单弹出时 trigger 元素圆角缺失的问题。[#50408](https://github.com/ant-design/ant-design/pull/50408) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 修复 Upload.Dragger 拖拽上传文件夹时不工作问题。[#50394](https://github.com/ant-design/ant-design/pull/50394) [@huiliangShen](https://github.com/huiliangShen)
- 🐞 修复 Select 指定 `getPopcontainer={node=node.parentNode}` 时箭头图标 hover 后会消失的问题。[#50382](https://github.com/ant-design/ant-design/pull/50382) [@afc163](https://github.com/afc163)
- 🐞 修复 Popover 设置 `arrow.pointAtCenter` 属性时箭头未对齐错误。[#50260](https://github.com/ant-design/ant-design/pull/50260) [@Wxh16144](https://github.com/Wxh16144)
- 📖 Transfer 补充俄罗斯语和乌克兰语本地化文案。[#50429](https://github.com/ant-design/ant-design/pull/50429) [@alexlag](https://github.com/alexlag)
- TypeScript
- 🤖 将 Table 部分泛型约束 object 回滚为 any以减少 [#50351](https://github.com/ant-design/ant-design/pull/50351) 造成的 break change。[#50372](https://github.com/ant-design/ant-design/pull/50372) [@crazyair](https://github.com/crazyair)
## 5.20.1
`2024-08-11`

View File

@ -57,6 +57,8 @@
## 🖥 兼容环境
支持范围https://browsersl.ist/#q=defaults
- 现代浏览器。
- 支持服务端渲染。
- [Electron](https://www.electronjs.org/)

View File

@ -1,5 +1,6 @@
{
"files": {
"ignoreUnknown": true,
"ignore": [
".dumi/tmp*",
".dumi/scripts/clarity.js",
@ -9,7 +10,8 @@
"_site/**/*",
"node_modules",
"server",
"scripts/previewEditor/**/*"
"scripts/previewEditor/**/*",
"package.json"
]
},
"formatter": {

6
codecov.yml Normal file
View File

@ -0,0 +1,6 @@
coverage:
status:
project: #add everything under here, more options at https://docs.codecov.com/docs/commit-status
default:
target: 100%
threshold: 0%

View File

@ -6,10 +6,12 @@ import pickAttrs from 'rc-util/lib/pickAttrs';
export type BaseClosableType = { closeIcon?: React.ReactNode } & React.AriaAttributes;
export type ClosableType = boolean | BaseClosableType;
export type ContextClosable<T extends { closable?: ClosableType; closeIcon?: ReactNode } = any> =
Partial<Pick<T, 'closable' | 'closeIcon'>>;
export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode };
export type ContextClosable<T extends BaseContextClosable = any> = Partial<
Pick<T, 'closable' | 'closeIcon'>
>;
export function pickClosable<T extends { closable?: ClosableType; closeIcon?: ReactNode }>(
export function pickClosable<T extends BaseContextClosable>(
context?: ContextClosable<T>,
): ContextClosable<T> | undefined {
if (!context) {

View File

@ -344,6 +344,7 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => {
},
[numberPrefixCls]: {
overflow: 'hidden',
transition: `all ${token.motionDurationMid} ${token.motionEaseOutBack}`,
[`${numberPrefixCls}-only`]: {
position: 'relative',
display: 'inline-block',

View File

@ -2343,7 +2343,7 @@ exports[`renders components/button/demo/linear-gradient.tsx extend context corre
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary ant-btn-lg css-ykstnd"
class="ant-btn ant-btn-primary ant-btn-lg acss-9mi5l3"
type="button"
>
<span
@ -2378,7 +2378,7 @@ exports[`renders components/button/demo/linear-gradient.tsx extend context corre
class="ant-space-item"
>
<button
class="ant-btn ant-btn-default ant-btn-lg css-ykstnd"
class="ant-btn ant-btn-default ant-btn-lg acss-9mi5l3"
type="button"
>
<span>

View File

@ -1973,7 +1973,7 @@ exports[`renders components/button/demo/linear-gradient.tsx correctly 1`] = `
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary ant-btn-lg css-ykstnd"
class="ant-btn ant-btn-primary ant-btn-lg acss-9mi5l3"
type="button"
>
<span
@ -2008,7 +2008,7 @@ exports[`renders components/button/demo/linear-gradient.tsx correctly 1`] = `
class="ant-space-item"
>
<button
class="ant-btn ant-btn-default ant-btn-lg css-ykstnd"
class="ant-btn ant-btn-default ant-btn-lg acss-9mi5l3"
type="button"
>
<span>

View File

@ -1,13 +1,11 @@
import React, { useContext } from 'react';
import { Button, ConfigProvider, Space } from 'antd';
import React from 'react';
import { AntDesignOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { Button, ConfigProvider, Space } from 'antd';
import { createStyles } from 'antd-style';
const App: React.FC = () => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const rootPrefixCls = getPrefixCls();
const linearGradientButton = css`
&.${rootPrefixCls}-btn-primary:not([disabled]):not(.${rootPrefixCls}-btn-dangerous) {
const useStyle = createStyles(({ prefixCls, css }) => ({
linearGradientButton: css`
&.${prefixCls}-btn-primary:not([disabled]):not(.${prefixCls}-btn-dangerous) {
border-width: 0;
> span {
@ -16,7 +14,7 @@ const App: React.FC = () => {
&::before {
content: '';
background: linear-gradient(135deg, #6253E1, #04BEFE);
background: linear-gradient(135deg, #6253e1, #04befe);
position: absolute;
inset: 0;
opacity: 1;
@ -28,11 +26,16 @@ const App: React.FC = () => {
opacity: 0;
}
}
`;
`,
}));
const App: React.FC = () => {
const { styles } = useStyle();
return (
<ConfigProvider
button={{
className: linearGradientButton,
className: styles.linearGradientButton,
}}
>
<Space>

View File

@ -51,7 +51,7 @@ Common props ref[Common props](/docs/react/common-props)
| allowClear | Show clear button | boolean \| { clearIcon?: ReactNode } | true | 5.8.0: Support object type |
| autoClearSearchValue | Whether the current search will be cleared on selecting an item. Only applies when `multiple` is `true` | boolean | true | 5.9.0 |
| autoFocus | If get focus when component mounted | boolean | false | |
| changeOnSelect | (Work on single select) Change value on each selection if set to true, see above demo for details | boolean | false | |
| changeOnSelect | Change value on each selection if set to true, see above demo for details | boolean | false | |
| className | The additional css class | string | - | |
| defaultValue | Initial selected value | string\[] \| number\[] | \[] | |
| disabled | Whether disabled select | boolean | false | |

View File

@ -52,7 +52,7 @@ demo:
| allowClear | 支持清除 | boolean \| { clearIcon?: ReactNode } | true | 5.8.0: 支持对象形式 |
| autoClearSearchValue | 是否在选中项后清空搜索框,只在 `multiple``true` 时有效 | boolean | true | 5.9.0 |
| autoFocus | 自动获取焦点 | boolean | false | |
| changeOnSelect | (单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false | |
| changeOnSelect | 单选时生效multiple 下始终都可以选择),点选每级菜单选项值都会发生变化。 | boolean | false | |
| className | 自定义类名 | string | - | |
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
| disabled | 禁用 | boolean | false | |

View File

@ -34,12 +34,15 @@ function doMouseMove(
});
fireEvent(ele, mouseDown);
// Drag
if (start !== end) {
const mouseMove: any = new Event('mousemove');
mouseMove.pageX = end;
mouseMove.pageY = end;
fireEvent(document, mouseMove);
}
const mouseUp = createEvent.mouseUp(document);
fireEvent(document, mouseUp);
@ -848,4 +851,32 @@ describe('ColorPicker', () => {
);
});
});
it('onChangeComplete with default empty color should not be alpha', async () => {
const spyRect = spyElementPrototypes(HTMLElement, {
getBoundingClientRect: () => ({
x: 0,
y: 100,
width: 100,
height: 100,
}),
});
const handleChangeComplete = jest.fn();
const { container } = render(<ColorPicker open onChangeComplete={handleChangeComplete} />);
// Move
doMouseMove(container, 50, 50);
expect(handleChangeComplete).toHaveBeenCalledTimes(1);
const color = handleChangeComplete.mock.calls[0][0];
expect(color.toRgb()).toEqual({
r: 255,
g: 128,
b: 128,
a: 1,
});
spyRect.mockRestore();
});
});

View File

@ -17,6 +17,11 @@ const components = {
slider: ColorSlider,
};
type Info = {
type?: 'hue' | 'alpha';
value?: number;
};
const PanelPicker: FC = () => {
const panelPickerContext = useContext(PanelPickerContext);
@ -81,32 +86,10 @@ const PanelPicker: FC = () => {
}, [value, activeIndex, isSingle, lockedColor, gradientDragging]);
// ============================ Change ============================
const fillColor = (nextColor: AggregationColor) => {
if (mode === 'single') {
return nextColor;
}
const nextColors = [...colors];
nextColors[activeIndex] = {
...nextColors[activeIndex],
color: nextColor,
};
return new AggregationColor(nextColors);
};
const onInternalChange = (
colorValue: AggregationColor | Color,
fromPicker?: boolean,
info?: {
type?: 'hue' | 'alpha';
value?: number;
},
) => {
const nextColor = generateColor(colorValue);
let submitColor = nextColor;
const fillColor = (nextColor: AggregationColor | Color, info?: Info) => {
let submitColor = generateColor(nextColor);
// Fill alpha color to 100% if origin is cleared color
if (value.cleared) {
const rgb = submitColor.toRgb();
@ -125,11 +108,29 @@ const PanelPicker: FC = () => {
}
}
onChange(fillColor(submitColor), fromPicker);
if (mode === 'single') {
return submitColor;
}
const nextColors = [...colors];
nextColors[activeIndex] = {
...nextColors[activeIndex],
color: submitColor,
};
const onInternalChangeComplete = (nextColor: AggregationColor) => {
onChangeComplete(fillColor(nextColor));
return new AggregationColor(nextColors);
};
const onInternalChange = (
colorValue: AggregationColor | Color,
fromPicker?: boolean,
info?: Info,
) => {
onChange(fillColor(colorValue, info), fromPicker);
};
const onInternalChangeComplete = (nextColor: Color, info?: Info) => {
onChangeComplete(fillColor(nextColor, info));
};
// ============================ Render ============================
@ -170,8 +171,8 @@ const PanelPicker: FC = () => {
onChange={(colorValue, info) => {
onInternalChange(colorValue, true, info);
}}
onChangeComplete={(colorValue) => {
onInternalChangeComplete(generateColor(colorValue));
onChangeComplete={(colorValue, info) => {
onInternalChangeComplete(colorValue, info);
}}
components={components}
/>

View File

@ -60,7 +60,7 @@ Common props ref[Common props](/docs/react/common-props)
| size | Setting the trigger size | `large` \| `middle` \| `small` | `middle` | 5.7.0 |
| trigger | ColorPicker trigger mode | `hover` \| `click` | `click` | |
| value | Value of color | string \| `Color` | - | |
| onChange | Callback when `value` is changed | `(value: Color, hex: string) => void` | - | |
| onChange | Callback when `value` is changed | `(value: Color, css: string) => void` | - | |
| onChangeComplete | Called when color pick ends | `(value: Color) => void` | - | 5.7.0 |
| onFormatChange | Callback when `format` is changed | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - | |
| onOpenChange | Callback when `open` is changed | `(open: boolean) => void` | - | |

View File

@ -61,7 +61,7 @@ group:
| size | 设置触发器大小 | `large` \| `middle` \| `small` | `middle` | 5.7.0 |
| trigger | 颜色选择器的触发模式 | `hover` \| `click` | `click` | |
| value | 颜色的值 | string \| `Color` | - | |
| onChange | 颜色变化的回调 | `(value: Color, hex: string) => void` | - | |
| onChange | 颜色变化的回调 | `(value: Color, css: string) => void` | - | |
| onChangeComplete | 颜色选择完成的回调 | `(value: Color) => void` | - | 5.7.0 |
| onFormatChange | 颜色格式变化的回调 | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - | |
| onOpenChange | 当 `open` 被改变时的回调 | `(open: boolean) => void` | - | |

View File

@ -85,7 +85,7 @@ export type ColorPickerProps = Omit<
[key: `data-${string}`]: string;
onOpenChange?: (open: boolean) => void;
onFormatChange?: (format?: ColorFormatType) => void;
onChange?: (value: AggregationColor, hex: string) => void;
onChange?: (value: AggregationColor, css: string) => void;
onClear?: () => void;
onChangeComplete?: (value: AggregationColor) => void;
} & Pick<PopoverProps, 'getPopupContainer' | 'autoAdjustOverflow' | 'destroyTooltipOnHide'>;

View File

@ -22680,9 +22680,9 @@ exports[`ConfigProvider components Rate configProvider 1`] = `
exports[`ConfigProvider components Rate configProvider componentDisabled 1`] = `
<ul
class="config-rate"
class="config-rate config-rate-disabled"
role="radiogroup"
tabindex="0"
tabindex="-1"
>
<li
class="config-rate-star config-rate-star-zero"
@ -22692,7 +22692,7 @@ exports[`ConfigProvider components Rate configProvider componentDisabled 1`] = `
aria-posinset="1"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="config-rate-star-first"
@ -22750,7 +22750,7 @@ exports[`ConfigProvider components Rate configProvider componentDisabled 1`] = `
aria-posinset="2"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="config-rate-star-first"
@ -22808,7 +22808,7 @@ exports[`ConfigProvider components Rate configProvider componentDisabled 1`] = `
aria-posinset="3"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="config-rate-star-first"
@ -22866,7 +22866,7 @@ exports[`ConfigProvider components Rate configProvider componentDisabled 1`] = `
aria-posinset="4"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="config-rate-star-first"
@ -22924,7 +22924,7 @@ exports[`ConfigProvider components Rate configProvider componentDisabled 1`] = `
aria-posinset="5"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="config-rate-star-first"

View File

@ -45,8 +45,8 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
Common props ref[Common props](/docs/react/common-props)
<!-- prettier-ignore -->
:::info{title=注意}
v5 use `rootClassName` & `rootStyle` to config wrapper style instead of `className` & `style` in v4 to align the API with Modal.
:::info{title=Note}
v5 uses `rootClassName` & `rootStyle` to configure the outermost element style, instead of `className` & `style` from v4. This is done to align the API with Modal.
:::
| Props | Description | Type | Default | Version |

View File

@ -16,7 +16,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*obM7S5lIxeMAAA
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">Basic</code>
<code src="./demo/simple.tsx">Chose image</code>
<code src="./demo/simple.tsx">Choose image</code>
<code src="./demo/customize.tsx">Customize</code>
<code src="./demo/config-provider.tsx">ConfigProvider</code>
<code src="./demo/description.tsx">No description</code>

View File

@ -1,8 +1,9 @@
import React, { memo, useCallback, useContext, useEffect } from 'react';
import React, { memo, useContext } from 'react';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import FileTextOutlined from '@ant-design/icons/FileTextOutlined';
import classNames from 'classnames';
import CSSMotion from 'rc-motion';
import { useEvent } from 'rc-util';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import { devUseWarning } from '../_util/warning';
@ -11,7 +12,7 @@ import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import { FloatButtonGroupProvider } from './context';
import FloatButton, { floatButtonPrefixCls } from './FloatButton';
import type { FloatButtonGroupProps, FloatButtonRef } from './interface';
import type { FloatButtonGroupProps } from './interface';
import useStyle from './style';
const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
@ -28,6 +29,7 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
children,
onOpenChange,
open: customOpen,
onClick: onTriggerButtonClick,
...floatButtonProps
} = props;
@ -53,53 +55,61 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
const floatButtonGroupRef = React.useRef<HTMLDivElement>(null);
const floatButtonRef = React.useRef<FloatButtonRef['nativeElement']>(null);
// ========================== Open ==========================
const hoverTrigger = trigger === 'hover';
const clickTrigger = trigger === 'click';
const hoverAction = React.useMemo<React.DOMAttributes<HTMLDivElement>>(() => {
const hoverTypeAction = {
onMouseEnter() {
setOpen(true);
onOpenChange?.(true);
},
onMouseLeave() {
setOpen(false);
onOpenChange?.(false);
},
};
return trigger === 'hover' ? hoverTypeAction : {};
}, [trigger]);
const handleOpenChange = () => {
setOpen((prevState) => {
onOpenChange?.(!prevState);
return !prevState;
});
};
const onClick = useCallback(
(e: MouseEvent) => {
if (floatButtonGroupRef.current?.contains(e.target as Node)) {
if (floatButtonRef.current?.contains(e.target as Node)) {
handleOpenChange();
const triggerOpen = useEvent((nextOpen: boolean) => {
if (open !== nextOpen) {
setOpen(nextOpen);
onOpenChange?.(nextOpen);
}
});
// ===================== Trigger: Hover =====================
const onMouseEnter = () => {
if (hoverTrigger) {
triggerOpen(true);
}
};
const onMouseLeave = () => {
if (hoverTrigger) {
triggerOpen(false);
}
};
// ===================== Trigger: Click =====================
const onInternalTriggerButtonClick: FloatButtonGroupProps['onClick'] = (e) => {
if (clickTrigger) {
triggerOpen(!open);
}
onTriggerButtonClick?.(e);
};
React.useEffect(() => {
if (clickTrigger) {
const onDocClick = (e: MouseEvent) => {
// Skip if click on the group
if (floatButtonGroupRef.current?.contains(e.target as Node)) {
return;
}
setOpen(false);
onOpenChange?.(false);
},
[trigger],
);
useEffect(() => {
if (trigger === 'click') {
document.addEventListener('click', onClick);
triggerOpen(false);
};
document.addEventListener('click', onDocClick, {
capture: true,
});
return () => {
document.removeEventListener('click', onClick);
document.removeEventListener('click', onDocClick, {
capture: true,
});
};
}
}, [trigger]);
}, [clickTrigger]);
// =================== Warning =====================
// ======================== Warning =========================
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('FloatButton.Group');
@ -110,9 +120,17 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
);
}
// ========================= Render =========================
return wrapCSSVar(
<FloatButtonGroupProvider value={shape}>
<div ref={floatButtonGroupRef} className={groupCls} style={style} {...hoverAction}>
<div
ref={floatButtonGroupRef}
className={groupCls}
style={style}
// Hover trigger
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{trigger && ['click', 'hover'].includes(trigger) ? (
<>
<CSSMotion visible={open} motionName={`${groupPrefixCls}-wrap`}>
@ -121,12 +139,12 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
)}
</CSSMotion>
<FloatButton
ref={floatButtonRef}
type={type}
icon={open ? mergedCloseIcon : icon}
description={description}
aria-label={props['aria-label']}
className={`${groupPrefixCls}-trigger`}
onClick={onInternalTriggerButtonClick}
{...floatButtonProps}
/>
</>

View File

@ -5650,6 +5650,332 @@ Array [
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-col-4 ant-form-item-label"
>
<label
class=""
title="Rate"
>
Rate
</label>
</div>
<div
class="ant-col ant-col-14 ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<ul
class="ant-rate ant-rate-disabled"
role="radiogroup"
tabindex="-1"
>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="1"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="2"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="3"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="4"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="5"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</form>,
]
`;
@ -30400,6 +30726,89 @@ exports[`renders components/form/demo/variant.tsx extend context correctly 1`] =
class="ant-form ant-form-horizontal"
style="max-width: 600px;"
>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label ant-col-xs-24 ant-col-sm-6"
>
<label
class=""
for="variant"
title="Form variant"
>
Form variant
</label>
</div>
<div
class="ant-col ant-form-item-control ant-col-xs-24 ant-col-sm-14"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<div
class="ant-segmented"
id="variant"
>
<div
class="ant-segmented-group"
>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="outlined"
>
outlined
</div>
</label>
<label
class="ant-segmented-item ant-segmented-item-selected"
>
<input
checked=""
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="filled"
>
filled
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="borderless"
>
borderless
</div>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>

View File

@ -2765,6 +2765,332 @@ Array [
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-col-4 ant-form-item-label"
>
<label
class=""
title="Rate"
>
Rate
</label>
</div>
<div
class="ant-col ant-col-14 ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<ul
class="ant-rate ant-rate-disabled"
role="radiogroup"
tabindex="-1"
>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="1"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="2"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="3"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="4"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="5"
aria-setsize="5"
role="radio"
tabindex="-1"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="star"
class="anticon anticon-star"
role="img"
>
<svg
aria-hidden="true"
data-icon="star"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"
/>
</svg>
</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</form>,
]
`;
@ -12619,6 +12945,89 @@ exports[`renders components/form/demo/variant.tsx correctly 1`] = `
class="ant-form ant-form-horizontal"
style="max-width:600px"
>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label ant-col-xs-24 ant-col-sm-6"
>
<label
class=""
for="variant"
title="Form variant"
>
Form variant
</label>
</div>
<div
class="ant-col ant-form-item-control ant-col-xs-24 ant-col-sm-14"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<div
class="ant-segmented"
id="variant"
>
<div
class="ant-segmented-group"
>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="outlined"
>
outlined
</div>
</label>
<label
class="ant-segmented-item ant-segmented-item-selected"
>
<input
checked=""
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="filled"
>
filled
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="borderless"
>
borderless
</div>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>

View File

@ -10,6 +10,7 @@ import {
Input,
InputNumber,
Radio,
Rate,
Select,
Slider,
Switch,
@ -117,6 +118,9 @@ const FormDisabledDemo: React.FC = () => {
<Form.Item label="ColorPicker">
<ColorPicker />
</Form.Item>
<Form.Item label="Rate">
<Rate />
</Form.Item>
</Form>
</>
);

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
Button,
Cascader,
@ -9,7 +9,9 @@ import {
Mentions,
Select,
TreeSelect,
Segmented,
} from 'antd';
import type { FormProps } from 'antd';
const { RangePicker } = DatePicker;
@ -24,8 +26,24 @@ const formItemLayout = {
},
};
const App: React.FC = () => (
<Form {...formItemLayout} variant="filled" style={{ maxWidth: 600 }}>
const App: React.FC = () => {
const [componentVariant, setComponentVariant] = useState<FormProps['variant']>('filled');
const onFormVariantChange = ({ variant }: { variant: FormProps['variant'] }) => {
setComponentVariant(variant);
};
return (
<Form
{...formItemLayout}
onValuesChange={onFormVariantChange}
variant={componentVariant}
style={{ maxWidth: 600 }}
initialValues={{ variant: componentVariant }}
>
<Form.Item label="Form variant" name="variant">
<Segmented options={['outlined', 'filled', 'borderless']} />
</Form.Item>
<Form.Item label="Input" name="Input" rules={[{ required: true, message: 'Please input!' }]}>
<Input />
</Form.Item>
@ -54,7 +72,11 @@ const App: React.FC = () => (
<Mentions />
</Form.Item>
<Form.Item label="Select" name="Select" rules={[{ required: true, message: 'Please input!' }]}>
<Form.Item
label="Select"
name="Select"
rules={[{ required: true, message: 'Please input!' }]}
>
<Select />
</Form.Item>
@ -97,5 +119,6 @@ const App: React.FC = () => (
</Form.Item>
</Form>
);
};
export default App;

View File

@ -16,4 +16,12 @@ describe('suffix', () => {
fireEvent.click(container.querySelector('i')!);
expect(mockFocus).toHaveBeenCalled();
});
it('should has classname when without controls', () => {
const { container } = render(<InputNumber suffix={<i>antd</i>} controls={false} />);
expect(
container.querySelector('.ant-input-number-affix-wrapper-without-controls'),
).toBeTruthy();
});
});

View File

@ -171,6 +171,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
[`${prefixCls}-affix-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-affix-wrapper-lg`]: mergedSize === 'large',
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-without-controls`]: controls === false,
},
hashId,
),

View File

@ -469,7 +469,7 @@ const genAffixWrapperStyles: GenerateStyle<InputNumberToken> = (token: InputNumb
width: token.handleWidth,
opacity: 1,
},
[`&:hover ${componentCls}-suffix`]: {
[`&:not(${componentCls}-affix-wrapper-without-controls):hover ${componentCls}-suffix`]: {
marginInlineEnd: token.calc(token.handleWidth).add(paddingInline).equal(),
},
},

View File

@ -11,6 +11,7 @@ import { ConfigContext } from '../config-provider';
import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout';
import type { InputProps, InputRef } from './Input';
import Input from './Input';
import DisabledContext from '../config-provider/DisabledContext';
const defaultIconRender = (visible: boolean): React.ReactNode =>
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
@ -36,12 +37,16 @@ type IconPropsType = React.HTMLAttributes<HTMLSpanElement> & React.Attributes;
const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
const {
disabled,
disabled: customDisabled,
action = 'click',
visibilityToggle = true,
iconRender = defaultIconRender,
} = props;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
const visibilityControlled =
typeof visibilityToggle === 'object' && visibilityToggle.visible !== undefined;
const [visible, setVisible] = useState(() =>
@ -59,7 +64,7 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
const removePasswordTimeout = useRemovePasswordTimeout(inputRef);
const onVisibleChange = () => {
if (disabled) {
if (mergedDisabled) {
return;
}
if (visible) {

View File

@ -10601,6 +10601,48 @@ exports[`renders components/input/demo/password-input.tsx extend context correct
</div>
</div>
</div>
<div
class="ant-space-item"
>
<span
class="ant-input-affix-wrapper ant-input-disabled ant-input-affix-wrapper-disabled ant-input-outlined ant-input-password"
>
<input
class="ant-input ant-input-disabled"
disabled=""
placeholder="disabled input password"
type="password"
value=""
/>
<span
class="ant-input-suffix"
>
<span
aria-label="eye-invisible"
class="anticon anticon-eye-invisible ant-input-password-icon"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="eye-invisible"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z"
/>
<path
d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z"
/>
</svg>
</span>
</span>
</span>
</div>
</div>
`;

View File

@ -3947,6 +3947,48 @@ exports[`renders components/input/demo/password-input.tsx correctly 1`] = `
</div>
</div>
</div>
<div
class="ant-space-item"
>
<span
class="ant-input-affix-wrapper ant-input-disabled ant-input-affix-wrapper-disabled ant-input-outlined ant-input-password"
>
<input
class="ant-input ant-input-disabled"
disabled=""
placeholder="disabled input password"
type="password"
value=""
/>
<span
class="ant-input-suffix"
>
<span
aria-label="eye-invisible"
class="anticon anticon-eye-invisible ant-input-password-icon"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="eye-invisible"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z"
/>
<path
d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z"
/>
</svg>
</span>
</span>
</span>
</div>
</div>
`;

View File

@ -21,6 +21,7 @@ const App: React.FC = () => {
{passwordVisible ? 'Hide' : 'Show'}
</Button>
</Space>
<Input.Password disabled placeholder="disabled input password" />
</Space>
);
};

View File

@ -466,6 +466,7 @@ const genAffixStyle: GenerateStyle<InputToken> = (token: InputToken) => {
} = token;
const affixCls = `${componentCls}-affix-wrapper`;
const affixClsDisabled = `${componentCls}-affix-wrapper-disabled`;
return {
[affixCls]: {
@ -552,6 +553,17 @@ const genAffixStyle: GenerateStyle<InputToken> = (token: InputToken) => {
},
},
},
[affixClsDisabled]: {
// password disabled
[`${iconCls}${componentCls}-password-icon`]: {
color: colorIcon,
cursor: 'not-allowed',
'&:hover': {
color: colorIcon,
},
},
},
};
};

View File

@ -50,6 +50,7 @@ const localeValues: Locale = {
selectCurrent: 'Tüm sayfayı seç',
removeCurrent: 'Sayfayı kaldır',
selectAll: 'Tümünü seç',
deselectAll: 'Tümünün seçimini kaldır',
removeAll: 'Tümünü kaldır',
selectInvert: 'Tersini seç',
},

View File

@ -15,6 +15,7 @@ const getVerticalInlineStyle: GenerateStyle<MenuToken, CSSObject> = (token) => {
marginXS,
itemMarginBlock,
itemWidth,
itemPaddingInline,
} = token;
const paddingWithArrow = token.calc(menuArrowSize).add(padding).add(marginXS).equal();
@ -28,7 +29,7 @@ const getVerticalInlineStyle: GenerateStyle<MenuToken, CSSObject> = (token) => {
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
height: itemHeight,
lineHeight: unit(itemHeight),
paddingInline: padding,
paddingInline: itemPaddingInline,
overflow: 'hidden',
textOverflow: 'ellipsis',
marginInline: itemMarginInline,
@ -128,9 +129,6 @@ const getVerticalStyle: GenerateStyle<MenuToken> = (token) => {
`border-color ${motionDurationSlow}`,
`background ${motionDurationSlow}`,
`padding ${motionDurationMid} ${motionEaseOut}`,
`padding-inline calc(50% - ${unit(token.calc(fontSizeLG).div(2).equal())} - ${unit(
itemMarginInline,
)})`,
].join(','),
[`> ${componentCls}-title-content`]: {

View File

@ -8,8 +8,8 @@ import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
import Button from '../../button';
describe('Popconfirm', () => {
mountTest(Popconfirm as any);
rtlTest(Popconfirm as any);
mountTest(() => <Popconfirm title="test" />);
rtlTest(() => <Popconfirm title="test" />);
const eventObject = expect.objectContaining({
target: expect.anything(),

View File

@ -8,6 +8,7 @@ import type { StarProps as RcStarProps } from 'rc-rate/lib/Star';
import { ConfigContext } from '../config-provider';
import Tooltip from '../tooltip';
import useStyle from './style';
import DisabledContext from '../config-provider/DisabledContext';
export interface RateProps extends RcRateProps {
rootClassName?: string;
@ -22,6 +23,7 @@ const Rate = React.forwardRef<RateRef, RateProps>((props, ref) => {
style,
tooltips,
character = <StarFilled />,
disabled: customDisabled,
...rest
} = props;
@ -40,11 +42,16 @@ const Rate = React.forwardRef<RateRef, RateProps>((props, ref) => {
const mergedStyle: React.CSSProperties = { ...rate?.style, ...style };
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
return wrapCSSVar(
<RcRate
ref={ref}
character={character}
characterRender={characterRender}
disabled={mergedDisabled}
{...rest}
className={classNames(className, rootClassName, hashId, cssVarCls, rate?.className)}
style={mergedStyle}

View File

@ -29,8 +29,8 @@ function expectMatchChecked(container: HTMLElement, checkedList: boolean[]) {
}
describe('Segmented', () => {
mountTest(Segmented as any);
rtlTest(Segmented as any);
mountTest(() => <Segmented options={[]} />);
rtlTest(() => <Segmented options={[]} />);
beforeAll(() => {
jest.useFakeTimers();

View File

@ -8459,7 +8459,7 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -8490,7 +8490,7 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -8521,7 +8521,7 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -8539,7 +8539,7 @@ exports[`renders components/space/demo/compact.tsx extend context correctly 1`]
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-selected"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-selected"
draggable="false"
>
<span
@ -8569,7 +8569,7 @@ exports[`renders components/space/demo/compact.tsx extend context correctly 1`]
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -8646,7 +8646,7 @@ exports[`renders components/space/demo/compact.tsx extend context correctly 1`]
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -10324,7 +10324,7 @@ exports[`renders components/table/demo/filter-in-tree.tsx extend context correct
>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -10404,7 +10404,7 @@ exports[`renders components/table/demo/filter-in-tree.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -10438,7 +10438,7 @@ exports[`renders components/table/demo/filter-in-tree.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -10522,7 +10522,7 @@ exports[`renders components/table/demo/filter-in-tree.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -10556,7 +10556,7 @@ exports[`renders components/table/demo/filter-in-tree.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -11264,7 +11264,7 @@ exports[`renders components/table/demo/filter-search.tsx extend context correctl
>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -11294,7 +11294,7 @@ exports[`renders components/table/demo/filter-search.tsx extend context correctl
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -11324,7 +11324,7 @@ exports[`renders components/table/demo/filter-search.tsx extend context correctl
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -10,7 +10,9 @@ const genSorterStyle: GenerateStyle<TableToken, CSSObject> = (token) => {
[`${componentCls}-thead th${componentCls}-column-has-sorters`]: {
outline: 'none',
cursor: 'pointer',
transition: `all ${token.motionDurationSlow}`,
// why left 0s? Avoid column header move with transition when left is changed
// https://github.com/ant-design/ant-design/issues/50588
transition: `all ${token.motionDurationSlow}, left 0s`,
'&:hover': {
background: token.tableHeaderSortHoverBg,

View File

@ -38,7 +38,7 @@ export interface ComponentToken {
*/
cardPaddingLG: string;
/**
* @desc
* @desc
* @descEN Font size of title
*/
titleFontSize: number;

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import type { DragEndEvent } from '@dnd-kit/core/dist/types/index';
import type { DragEndEvent } from '@dnd-kit/core';
import {
arrayMove,
horizontalListSortingStrategy,

View File

@ -4,6 +4,7 @@ import omit from 'rc-util/lib/omit';
import type { PresetColorType, PresetStatusColorType } from '../_util/colors';
import { isPresetColor, isPresetStatusColor } from '../_util/colors';
import type { ClosableType } from '../_util/hooks/useClosable';
import useClosable, { pickClosable } from '../_util/hooks/useClosable';
import { replaceElement } from '../_util/reactNode';
import type { LiteralUnion } from '../_util/type';
@ -22,8 +23,8 @@ export interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
className?: string;
rootClassName?: string;
color?: LiteralUnion<PresetColorType | PresetStatusColorType>;
closable?: boolean | ({ closeIcon?: React.ReactNode } & React.AriaAttributes);
/** Advised to use closeIcon instead. */
closable?: ClosableType;
closeIcon?: React.ReactNode;
/** @deprecated `visible` will be removed in next major version. */
visible?: boolean;

View File

@ -1,7 +1,11 @@
/* eslint-disable import/prefer-default-export */
import getDesignToken from './getDesignToken';
import type { GlobalToken, MappingAlgorithm } from './interface';
import { defaultConfig, useToken as useInternalToken } from './internal';
import {
defaultConfig,
DesignTokenContext as InternalDesignTokenContext,
useToken as useInternalToken,
} from './internal';
import compactAlgorithm from './themes/compact';
import darkAlgorithm from './themes/dark';
import defaultAlgorithm from './themes/default';
@ -19,15 +23,21 @@ function useToken() {
export type { GlobalToken, MappingAlgorithm };
export default {
/** @private Test Usage. Do not use in production. */
defaultConfig,
/** Default seedToken */
defaultSeed: defaultConfig.token,
useToken,
defaultAlgorithm,
darkAlgorithm,
compactAlgorithm,
getDesignToken,
/**
* @private Private variable
* @warring 🔥 Do not use in production. 🔥
*/
defaultConfig,
/**
* @private Private variable
* @warring 🔥 Do not use in production. 🔥
*/
_internalContext: InternalDesignTokenContext,
};

View File

@ -172,6 +172,366 @@ Array [
exports[`renders components/tour/demo/basic.tsx extend context correctly 2`] = `[]`;
exports[`renders components/tour/demo/gap.tsx extend context correctly 1`] = `
<div>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Begin Tour
</span>
</button>
<div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
style="display: flex; margin-top: 12px;"
>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
Radius:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left: 0%; width: 8%;"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="8"
class="ant-slider-handle"
role="slider"
style="left: 8%; transform: translateX(-50%);"
tabindex="0"
/>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-slider-tooltip ant-tooltip-placement-top"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; bottom: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
>
8
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
offset:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left: 0%; width: 4%;"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="2"
class="ant-slider-handle"
role="slider"
style="left: 4%; transform: translateX(-50%);"
tabindex="0"
/>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-slider-tooltip ant-tooltip-placement-top"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; bottom: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
>
2
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
Horizontal offset:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left: 0%; width: 4%;"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="2"
class="ant-slider-handle"
role="slider"
style="left: 4%; transform: translateX(-50%);"
tabindex="0"
/>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-slider-tooltip ant-tooltip-placement-top"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; bottom: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
>
2
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
Vertical offset:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left: 0%; width: 4%;"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="2"
class="ant-slider-handle"
role="slider"
style="left: 4%; transform: translateX(-50%);"
tabindex="0"
/>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-slider-tooltip ant-tooltip-placement-top"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; bottom: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
>
2
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-tour ant-tour-placement-bottom"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1001;"
>
<div
class="ant-tour-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tour-content"
>
<div
class="ant-tour-inner"
>
<button
class="ant-tour-close"
type="button"
>
<span
aria-label="close"
class="anticon anticon-close ant-tour-close-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
/>
</svg>
</span>
</button>
<div
class="ant-tour-cover"
>
<img
alt="tour.png"
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
/>
</div>
<div
class="ant-tour-header"
>
<div
class="ant-tour-title"
>
Upload File
</div>
</div>
<div
class="ant-tour-description"
>
Put your files here.
</div>
<div
class="ant-tour-footer"
>
<div
class="ant-tour-buttons"
>
<button
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn"
type="button"
>
<span>
Finish
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/tour/demo/gap.tsx extend context correctly 2`] = `[]`;
exports[`renders components/tour/demo/indicator.tsx extend context correctly 1`] = `
Array [
<button

View File

@ -77,6 +77,208 @@ Array [
]
`;
exports[`renders components/tour/demo/gap.tsx correctly 1`] = `
<div>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Begin Tour
</span>
</button>
<div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
style="display:flex;margin-top:12px"
>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
Radius:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;width:8%"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="8"
class="ant-slider-handle"
role="slider"
style="left:8%;transform:translateX(-50%)"
tabindex="0"
/>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
offset:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;width:4%"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="2"
class="ant-slider-handle"
role="slider"
style="left:4%;transform:translateX(-50%)"
tabindex="0"
/>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
Horizontal offset:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;width:4%"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="2"
class="ant-slider-handle"
role="slider"
style="left:4%;transform:translateX(-50%)"
tabindex="0"
/>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-row"
>
<div
class="ant-col ant-col-6"
>
<span
class="ant-typography"
>
Vertical offset:
</span>
</div>
<div
class="ant-col ant-col-12"
>
<div
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;width:4%"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-orientation="horizontal"
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="2"
class="ant-slider-handle"
role="slider"
style="left:4%;transform:translateX(-50%)"
tabindex="0"
/>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/tour/demo/indicator.tsx correctly 1`] = `
Array [
<button

View File

@ -430,6 +430,169 @@ exports[`Tour custom step pre btn & next btn className & style 1`] = `
exports[`Tour rtl render component should be rendered correctly in RTL direction 1`] = `null`;
exports[`Tour should support gap.offset 1`] = `
<body>
<div>
<button
type="button"
>
Show
</button>
<div
class="ant-tour ant-tour-placement-bottom"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1001;"
>
<div
class="ant-tour-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tour-content"
>
<div
class="ant-tour-inner"
>
<button
class="ant-tour-close"
type="button"
>
<span
aria-label="close"
class="anticon anticon-close ant-tour-close-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
/>
</svg>
</span>
</button>
<div
class="ant-tour-header"
>
<div
class="ant-tour-title"
>
Show in Center
</div>
</div>
<div
class="ant-tour-description"
>
Here is the content of Tour.
</div>
<div
class="ant-tour-footer"
>
<div
class="ant-tour-buttons"
>
<button
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn"
type="button"
>
<span>
Finish
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div
class="ant-tour-mask"
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 1001; pointer-events: none;"
>
<svg
style="width: 100%; height: 100%;"
>
<defs>
<mask
id="ant-tour-mask-test-id"
>
<rect
fill="white"
height="100vh"
width="100vw"
x="0"
y="0"
/>
<rect
class="ant-tour-placeholder-animated"
fill="black"
height="200"
rx="2"
width="250"
x="90"
y="190"
/>
</mask>
</defs>
<rect
fill="rgba(0,0,0,0.5)"
height="100%"
mask="url(#ant-tour-mask-test-id)"
width="100%"
x="0"
y="0"
/>
<rect
fill="transparent"
height="190"
pointer-events="auto"
width="100%"
x="0"
y="0"
/>
<rect
fill="transparent"
height="100%"
pointer-events="auto"
width="90"
x="0"
y="0"
/>
<rect
fill="transparent"
height="calc(100vh - 390px)"
pointer-events="auto"
width="100%"
x="0"
y="390"
/>
<rect
fill="transparent"
height="100%"
pointer-events="auto"
width="calc(100vw - 340px)"
x="340"
y="0"
/>
</svg>
</div>
</div>
<div>
<div
class="ant-tour-target-placeholder"
style="left: 90px; top: 190px; width: 250px; height: 200px; position: fixed; pointer-events: none;"
/>
</div>
</body>
`;
exports[`Tour single 1`] = `
<body>
<div>

View File

@ -1,5 +1,6 @@
/* eslint-disable react/no-unstable-nested-components */
import React, { useEffect, useRef } from 'react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import Tour from '..';
import mountTest from '../../../tests/shared/mountTest';
@ -7,6 +8,24 @@ import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, screen } from '../../../tests/utils';
import type { TourProps } from '../interface';
const mockBtnRect = (
rect: { x: number; y: number; width: number; height: number },
scrollIntoViewCb?: () => void,
) => {
spyElementPrototypes(HTMLButtonElement, {
getBoundingClientRect: {
get(): any {
return () => ({ ...rect, left: rect.x, top: rect.y });
},
},
scrollIntoView: {
get(): any {
scrollIntoViewCb?.();
return (val: boolean | ScrollIntoViewOptions) => val;
},
},
});
};
describe('Tour', () => {
mountTest(Tour);
rtlTest(Tour);
@ -590,6 +609,86 @@ describe('Tour', () => {
expect(onClose).toHaveBeenLastCalledWith(1);
});
it('should support gap.radius', () => {
const App: React.FC<{ gap: TourProps['gap'] }> = ({ gap }) => {
const ref = useRef<HTMLButtonElement>(null);
const [show, setShow] = React.useState<boolean>();
const steps: TourProps['steps'] = [
{
title: 'Show in Center',
description: 'Here is the content of Tour.',
target: () => ref.current!,
},
];
return (
<>
<button type="button" onClick={() => setShow(true)} ref={ref}>
Show
</button>
<Tour open={show} steps={steps} gap={gap} />
</>
);
};
const { rerender, baseElement } = render(<App gap={{ radius: 4 }} />);
fireEvent.click(screen.getByRole('button', { name: 'Show' }));
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toBeTruthy();
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute('rx', '4');
rerender(<App gap={{ radius: 0 }} />);
fireEvent.click(screen.getByRole('button', { name: 'Show' }));
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toBeTruthy();
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute('rx', '0');
});
it('should support gap.offset', () => {
const gap = { offset: 10 };
const pos = { x: 100, y: 200, width: 230, height: 180 };
mockBtnRect(pos);
const App: React.FC = () => {
const ref = useRef<HTMLButtonElement>(null);
const [show, setShow] = React.useState<boolean>();
const steps: TourProps['steps'] = [
{
title: 'Show in Center',
description: 'Here is the content of Tour.',
target: () => ref.current!,
},
];
return (
<>
<button type="button" onClick={() => setShow(true)} ref={ref}>
Show
</button>
<Tour steps={steps} gap={gap} open={show} />
</>
);
};
const { baseElement } = render(<App />);
const targetBtn = screen.getByRole('button', { name: 'Show' });
fireEvent.click(targetBtn);
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
'width',
String(pos.width + gap.offset * 2),
);
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
'height',
String(pos.height + gap.offset * 2),
);
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
'x',
String(pos.x - gap.offset),
);
expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
'y',
String(pos.y - gap.offset),
);
expect(baseElement).toMatchSnapshot();
});
// This test is for PurePanel which means safe to remove.
describe('PurePanel', () => {
const PurePanel = Tour._InternalPanelDoNotUseOrYouWillBeFired;

View File

@ -0,0 +1,11 @@
## zh-CN
使用 `gap` 参数来控制高亮区域的边距和圆角。
- `5.9.0` 之前不支持单独设置两个方向上的边距和数组类型的 `offset` 参数。
## en-US
Using `gap` to control the radius of highlight area and the offset between highlight area and the element.
- Setting offset in two directions individually and `offset` with array type is not supported until `5.9.0`.

View File

@ -0,0 +1,100 @@
import React, { useRef, useState } from 'react';
import { Button, Col, Row, Slider, Space, Tour, Typography } from 'antd';
import type { TourProps } from 'antd';
const { Text } = Typography;
const App: React.FC = () => {
const tourNodeRef = useRef(null);
const [radius, setRadius] = useState(8);
const [offsetX, setOffsetX] = useState(2);
const [offsetY, setOffsetY] = useState(2);
const [offset, setOffset] = useState(2);
const [open, setOpen] = useState(false);
const [offsetDirection, setOffsetDirection] = useState<'both' | 'individual'>('individual');
const steps: TourProps['steps'] = [
{
title: 'Upload File',
description: 'Put your files here.',
cover: (
<img
alt="tour.png"
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
/>
),
target: () => tourNodeRef.current,
},
];
const offsetGap =
offsetDirection === 'both'
? { offset }
: {
offset: [offsetX, offsetY] as [number, number],
};
return (
<div ref={tourNodeRef}>
<Button type="primary" onClick={() => setOpen(true)}>
Begin Tour
</Button>
<Space style={{ display: 'flex', marginTop: 12 }} direction="vertical">
<Row>
<Col span={6}>
<Text>Radius:</Text>
</Col>
<Col span={12}>
<Slider value={radius} onChange={(val) => val && setRadius(val)} />
</Col>
</Row>
<Row>
<Col span={6}>
<Text> offset:</Text>
</Col>
<Col span={12}>
<Slider
value={offset}
max={50}
onChange={(val) => val && setOffset(val)}
onFocus={() => setOffsetDirection('both')}
/>
</Col>
</Row>
<Row>
<Col span={6}>
<Text>Horizontal offset:</Text>
</Col>
<Col span={12}>
<Slider
value={offsetX}
max={50}
onChange={(val) => val && setOffsetX(val)}
onFocus={() => setOffsetDirection('individual')}
/>
</Col>
</Row>
<Row>
<Col span={6}>
<Text>Vertical offset:</Text>
</Col>
<Col span={12}>
<Slider
value={offsetY}
max={50}
onChange={(val) => val && setOffsetY(val)}
onFocus={() => setOffsetDirection('individual')}
/>
</Col>
</Row>
</Space>
<Tour
open={open}
onClose={() => setOpen(false)}
steps={steps}
gap={{ ...offsetGap, radius }}
/>
</div>
);
};
export default App;

View File

@ -22,6 +22,7 @@ Use when you want to guide users through a product.
<code src="./demo/placement.tsx">Placement</code>
<code src="./demo/mask.tsx">Custom mask style</code>
<code src="./demo/indicator.tsx">Custom indicator</code>
<code src="./demo/gap.tsx">Custom highlighted area style</code>
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
## API
@ -35,6 +36,7 @@ Common props ref[Common props](/docs/react/common-props)
| arrow | Whether to show the arrow, including the configuration whether to point to the center of the element | `boolean`\|`{ pointAtCenter: boolean}` | `true` | |
| closeIcon | Customize close icon | `React.ReactNode` | `true` | 5.9.0 |
| disabledInteraction | Disable interaction on highlighted area. | `boolean` | `false` | 5.13.0 |
| gap | Control the radius of the highlighted area and the offset between highlighted area and the element. | `{ offset?: number \| [number, number]; radius?: number }` | `{ offset?: 6 ; radius?: 2 }` | 5.0.0 (array type `offset`: 5.9.0) |
| placement | Position of the guide card relative to the target element | `center` `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` | `bottom` | |
| onClose | Callback function on shutdown | `Function` | - | |
| mask | Whether to enable masking, change mask style and fill color by pass custom props | `boolean \| { style?: React.CSSProperties; color?: string; }` | `true` | |

View File

@ -23,6 +23,7 @@ tag: 5.0.0
<code src="./demo/placement.tsx">位置</code>
<code src="./demo/mask.tsx">自定义遮罩样式</code>
<code src="./demo/indicator.tsx">自定义指示器</code>
<code src="./demo/gap.tsx">自定义高亮区域的样式</code>
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
## API
@ -36,6 +37,7 @@ tag: 5.0.0
| arrow | 是否显示箭头,包含是否指向元素中心的配置 | `boolean` \| `{ pointAtCenter: boolean}` | `true` | |
| closeIcon | 自定义关闭按钮 | `React.ReactNode` | `true` | 5.9.0 |
| disabledInteraction | 禁用高亮区域交互 | `boolean` | `false` | 5.13.0 |
| gap | 控制高亮区域的圆角边框和显示间距 | `{ offset?: number \| [number, number]; radius?: number }` | `{ offset?: 6 ; radius?: 2 }` | 5.0.0 (数组类型的 `offset`: 5.9.0 ) |
| placement | 引导卡片相对于目标元素的位置 | `center` `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` | `bottom` | |
| onClose | 关闭引导时的回调函数 | `Function` | - | |
| onFinish | 引导完成时的回调 | `Function` | - | |

View File

@ -11861,7 +11861,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx extend context corre
>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -11941,7 +11941,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx extend context corre
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -11975,7 +11975,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx extend context corre
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -12009,7 +12009,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx extend context corre
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -12039,7 +12039,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx extend context corre
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -12069,7 +12069,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx extend context corre
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -7907,7 +7907,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx correctly 1`] = `
>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -7987,7 +7987,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -8021,7 +8021,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -8055,7 +8055,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -8085,7 +8085,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -8115,7 +8115,7 @@ exports[`renders components/transfer/demo/tree-transfer.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -393,7 +393,7 @@ exports[`renders components/tree-select/demo/basic.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -423,7 +423,7 @@ exports[`renders components/tree-select/demo/basic.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -453,7 +453,7 @@ exports[`renders components/tree-select/demo/basic.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -483,7 +483,7 @@ exports[`renders components/tree-select/demo/basic.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -513,7 +513,7 @@ exports[`renders components/tree-select/demo/basic.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -543,7 +543,7 @@ exports[`renders components/tree-select/demo/basic.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -620,7 +620,7 @@ exports[`renders components/tree-select/demo/basic.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -1145,7 +1145,7 @@ exports[`renders components/tree-select/demo/multiple.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -1175,7 +1175,7 @@ exports[`renders components/tree-select/demo/multiple.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -1252,7 +1252,7 @@ exports[`renders components/tree-select/demo/multiple.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -1573,7 +1573,7 @@ Array [
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -1603,7 +1603,7 @@ Array [
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -1680,7 +1680,7 @@ Array [
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -2388,7 +2388,7 @@ exports[`renders components/tree-select/demo/suffix.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -2418,7 +2418,7 @@ exports[`renders components/tree-select/demo/suffix.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -2495,7 +2495,7 @@ exports[`renders components/tree-select/demo/suffix.tsx extend context correctly
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -2689,7 +2689,7 @@ exports[`renders components/tree-select/demo/treeData.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -2716,7 +2716,7 @@ exports[`renders components/tree-select/demo/treeData.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -2743,7 +2743,7 @@ exports[`renders components/tree-select/demo/treeData.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-open ant-select-tree-treenode-leaf-last"
class="ant-select-tree-treenode ant-select-tree-treenode-switcher-close ant-select-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -951,7 +951,7 @@ exports[`renders components/tree/demo/block-node.tsx extend context correctly 1`
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-disabled ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-disabled ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -985,7 +985,7 @@ exports[`renders components/tree/demo/block-node.tsx extend context correctly 1`
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-selected ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-selected ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -1132,7 +1132,7 @@ exports[`renders components/tree/demo/customized-icon.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-selected"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-selected"
draggable="false"
>
<span
@ -1182,7 +1182,7 @@ exports[`renders components/tree/demo/customized-icon.tsx extend context correct
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -947,7 +947,7 @@ exports[`renders components/tree/demo/block-node.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-disabled ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-disabled ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -981,7 +981,7 @@ exports[`renders components/tree/demo/block-node.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-selected ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-selected ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -1126,7 +1126,7 @@ exports[`renders components/tree/demo/customized-icon.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-selected"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-selected"
draggable="false"
>
<span
@ -1176,7 +1176,7 @@ exports[`renders components/tree/demo/customized-icon.tsx correctly 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span

View File

@ -698,7 +698,7 @@ exports[`Tree switcherIcon in Tree could be string 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -725,7 +725,7 @@ exports[`Tree switcherIcon in Tree could be string 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -822,7 +822,7 @@ exports[`Tree switcherIcon should be loading icon when loadData 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-loading"
class="ant-tree-treenode ant-tree-treenode-switcher-close"
draggable="false"
>
<span
@ -834,35 +834,14 @@ exports[`Tree switcherIcon should be loading icon when loadData 1`] = `
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher_open"
class="ant-tree-switcher ant-tree-switcher_close"
>
<span
aria-label="loading"
class="anticon anticon-loading anticon-spin ant-tree-switcher-loading-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="loading"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
/>
</svg>
</span>
switcherIcon
</span>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-open"
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-close"
title="node1"
>
<span
class="ant-tree-iconEle ant-tree-icon__open ant-tree-icon_loading"
/>
<span
class="ant-tree-title"
>
@ -872,7 +851,7 @@ exports[`Tree switcherIcon should be loading icon when loadData 1`] = `
</div>
<div
aria-grabbed="false"
class="ant-tree-treenode ant-tree-treenode-switcher-open ant-tree-treenode-loading ant-tree-treenode-leaf-last"
class="ant-tree-treenode ant-tree-treenode-switcher-close ant-tree-treenode-leaf-last"
draggable="false"
>
<span
@ -884,35 +863,14 @@ exports[`Tree switcherIcon should be loading icon when loadData 1`] = `
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher_open"
class="ant-tree-switcher ant-tree-switcher_close"
>
<span
aria-label="loading"
class="anticon anticon-loading anticon-spin ant-tree-switcher-loading-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="loading"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
/>
</svg>
</span>
switcherIcon
</span>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-open"
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-close"
title="node2"
>
<span
class="ant-tree-iconEle ant-tree-icon__open ant-tree-icon_loading"
/>
<span
class="ant-tree-title"
>

View File

@ -125,7 +125,7 @@ describe('Tree', () => {
<Tree
switcherIcon="switcherIcon"
loadData={onLoadData}
defaultExpandAll
defaultExpandedKeys={['0-0-2', '0-0-3']}
switcherLoadingIcon={<div>loading...</div>}
>
<TreeNode icon="icon">

View File

@ -29,7 +29,10 @@ const App: React.FC = () => (
treeData={treeData}
height={233}
defaultExpandAll
titleRender={(item) => <MemoTooltip title={item.title as any}>{item.title as any}</MemoTooltip>}
titleRender={(item) => {
const title = item.title as React.ReactNode;
return <MemoTooltip title={title}>{title}</MemoTooltip>;
}}
/>
);

View File

@ -19,32 +19,26 @@ export interface CopyBtnProps extends Omit<CopyConfig, 'onCopy'> {
loading: boolean;
}
const CopyBtn: React.FC<CopyBtnProps> = (props) => {
const {
const CopyBtn: React.FC<CopyBtnProps> = ({
prefixCls,
copied,
locale,
iconOnly,
tooltips,
icon,
loading: btnLoading,
tabIndex,
onCopy,
} = props;
loading: btnLoading,
}) => {
const tooltipNodes = toList(tooltips);
const iconNodes = toList(icon);
const { copied: copiedText, copy: copyText } = locale ?? {};
const copyTitle = copied
? getNode(tooltipNodes[1], copiedText)
: getNode(tooltipNodes[0], copyText);
const systemStr = copied ? copiedText : copyText;
const copyTitle = getNode(tooltipNodes[copied ? 1 : 0], systemStr);
const ariaLabel = typeof copyTitle === 'string' ? copyTitle : systemStr;
return (
<Tooltip key="copy" title={copyTitle}>
<Tooltip title={copyTitle}>
<TransButton
className={classNames(`${prefixCls}-copy`, {
[`${prefixCls}-copy-success`]: copied,

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import toArray from 'rc-util/lib/Children/toArray';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
import { isValidText } from './util';
interface MeasureTextProps {
style?: React.CSSProperties;
@ -44,24 +45,8 @@ const MeasureText = React.forwardRef<MeasureTextRef, MeasureTextProps>(
},
);
function cuttable(node: React.ReactElement) {
const type = typeof node;
return type === 'string' || type === 'number';
}
function getNodesLen(nodeList: React.ReactElement[]) {
let totalLen = 0;
nodeList.forEach((node) => {
if (cuttable(node)) {
totalLen += String(node).length;
} else {
totalLen += 1;
}
});
return totalLen;
}
const getNodesLen = (nodeList: React.ReactElement[]) =>
nodeList.reduce((totalLen, node) => totalLen + (isValidText(node) ? String(node).length : 1), 0);
function sliceNodes(nodeList: React.ReactElement[], len: number) {
let currLen = 0;
@ -74,7 +59,7 @@ function sliceNodes(nodeList: React.ReactElement[], len: number) {
}
const node = nodeList[i];
const canCut = cuttable(node);
const canCut = isValidText(node);
const nodeLen = canCut ? String(node).length : 1;
const nextLen = currLen + nodeLen;
@ -97,7 +82,6 @@ export interface EllipsisProps {
enableMeasure?: boolean;
text?: React.ReactNode;
width: number;
// fontSize: number;
rows: number;
children: (
cutChildren: React.ReactNode[],
@ -142,9 +126,7 @@ export default function EllipsisMeasure(props: EllipsisProps) {
// ========================= NeedEllipsis =========================
const measureWhiteSpaceRef = React.useRef<HTMLElement>(null);
const needEllipsisRef = React.useRef<MeasureTextRef>(null);
// Measure for `rows-1` height, to avoid operation exceed the line height
const descRowsEllipsisRef = React.useRef<MeasureTextRef>(null);
const symbolRowEllipsisRef = React.useRef<MeasureTextRef>(null);
@ -187,9 +169,11 @@ export default function EllipsisMeasure(props: EllipsisProps) {
// Get the height of `rows - 1` + symbol height
const descRowsEllipsisHeight = rows === 1 ? 0 : descRowsEllipsisRef.current?.getHeight() || 0;
const symbolRowEllipsisHeight = symbolRowEllipsisRef.current?.getHeight() || 0;
const rowsWithEllipsisHeight = descRowsEllipsisHeight + symbolRowEllipsisHeight;
const maxRowsHeight = Math.max(baseRowsEllipsisHeight, rowsWithEllipsisHeight);
const maxRowsHeight = Math.max(
baseRowsEllipsisHeight,
// height of rows with ellipsis
descRowsEllipsisHeight + symbolRowEllipsisHeight,
);
setEllipsisHeight(maxRowsHeight + 1);
@ -209,34 +193,31 @@ export default function EllipsisMeasure(props: EllipsisProps) {
const isOverflow = midHeight > ellipsisHeight;
let targetMidIndex = cutMidIndex;
if (maxIndex - minIndex === 1) {
targetMidIndex = isOverflow ? minIndex : maxIndex;
}
if (isOverflow) {
setEllipsisCutIndex([minIndex, targetMidIndex]);
} else {
setEllipsisCutIndex([targetMidIndex, maxIndex]);
}
setEllipsisCutIndex(isOverflow ? [minIndex, targetMidIndex] : [targetMidIndex, maxIndex]);
}
}, [ellipsisCutIndex, cutMidIndex]);
// ========================= Text Content =========================
const finalContent = React.useMemo(() => {
// Skip everything if `enableMeasure` is disabled
if (!enableMeasure) {
return children(nodeList, false);
}
if (
needEllipsis !== STATUS_MEASURE_NEED_ELLIPSIS ||
!ellipsisCutIndex ||
ellipsisCutIndex[0] !== ellipsisCutIndex[1]
) {
const content = children(nodeList, false);
// Limit the max line count to avoid scrollbar blink
// Limit the max line count to avoid scrollbar blink unless no need ellipsis
// https://github.com/ant-design/ant-design/issues/42958
if (
needEllipsis !== STATUS_MEASURE_NO_NEED_ELLIPSIS &&
needEllipsis !== STATUS_MEASURE_NONE
) {
if ([STATUS_MEASURE_NO_NEED_ELLIPSIS, STATUS_MEASURE_NONE].includes(needEllipsis)) {
return content;
}
return (
<span
style={{
@ -249,9 +230,6 @@ export default function EllipsisMeasure(props: EllipsisProps) {
);
}
return content;
}
return children(expanded ? nodeList : sliceNodes(nodeList, ellipsisCutIndex[0]), canEllipsis);
}, [expanded, needEllipsis, ellipsisCutIndex, nodeList, ...miscDeps]);

View File

@ -4,7 +4,7 @@ import classNames from 'classnames';
import ResizeObserver from 'rc-resize-observer';
import type { AutoSizeType } from 'rc-textarea';
import toArray from 'rc-util/lib/Children/toArray';
import useIsomorphicLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import omit from 'rc-util/lib/omit';
import { composeRef } from 'rc-util/lib/ref';
@ -19,13 +19,13 @@ import Editable from '../Editable';
import useCopyClick from '../hooks/useCopyClick';
import useMergedConfig from '../hooks/useMergedConfig';
import usePrevious from '../hooks/usePrevious';
import useUpdatedEffect from '../hooks/useUpdatedEffect';
import useTooltipProps from '../hooks/useTooltipProps';
import type { TypographyProps } from '../Typography';
import Typography from '../Typography';
import CopyBtn from './CopyBtn';
import Ellipsis from './Ellipsis';
import EllipsisTooltip from './EllipsisTooltip';
import { getEleSize } from './util';
import { isEleEllipsis, isValidText } from './util';
export type BaseType = 'secondary' | 'success' | 'warning' | 'danger';
@ -162,7 +162,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
// Focus edit icon when back
const prevEditing = usePrevious(editing);
useUpdatedEffect(() => {
useLayoutEffect(() => {
if (!editing && prevEditing) {
editIconRef.current?.focus();
}
@ -223,7 +223,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
[mergedEnableEllipsis, ellipsisConfig, enableEdit, enableCopy],
);
useIsomorphicLayoutEffect(() => {
useLayoutEffect(() => {
if (enableEllipsis && !needMeasureEllipsis) {
setIsLineClampSupport(isStyleSupport('webkitLineClamp'));
setIsTextOverflowSupport(isStyleSupport('textOverflow'));
@ -246,7 +246,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
// We use effect to change from css ellipsis to js ellipsis.
// To make SSR still can see the ellipsis.
useIsomorphicLayoutEffect(() => {
useLayoutEffect(() => {
setCssEllipsis(canUseCssEllipsis && mergedEnableEllipsis);
}, [canUseCssEllipsis, mergedEnableEllipsis]);
@ -281,11 +281,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
const textEle = typographyRef.current;
if (enableEllipsis && cssEllipsis && textEle) {
const [offsetWidth, offsetHeight] = getEleSize(textEle);
const currentEllipsis = cssLineClamp
? offsetHeight < textEle.scrollHeight
: offsetWidth < textEle.scrollWidth;
const currentEllipsis = isEleEllipsis(textEle);
if (isNativeEllipsis !== currentEllipsis) {
setIsNativeEllipsis(currentEllipsis);
@ -318,40 +314,13 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
}, [cssEllipsis, mergedEnableEllipsis]);
// ========================== Tooltip ===========================
let tooltipProps: TooltipProps = {};
if (ellipsisConfig.tooltip === true) {
tooltipProps = { title: editConfig.text ?? children };
} else if (React.isValidElement(ellipsisConfig.tooltip)) {
tooltipProps = { title: ellipsisConfig.tooltip };
} else if (typeof ellipsisConfig.tooltip === 'object') {
tooltipProps = { title: editConfig.text ?? children, ...ellipsisConfig.tooltip };
} else {
tooltipProps = { title: ellipsisConfig.tooltip };
}
const topAriaLabel = React.useMemo(() => {
const isValid = (val: any): val is string | number => ['string', 'number'].includes(typeof val);
const tooltipProps = useTooltipProps(ellipsisConfig.tooltip, editConfig.text, children);
const topAriaLabel = React.useMemo(() => {
if (!enableEllipsis || cssEllipsis) {
return undefined;
}
if (isValid(editConfig.text)) {
return editConfig.text;
}
if (isValid(children)) {
return children;
}
if (isValid(title)) {
return title;
}
if (isValid(tooltipProps.title)) {
return tooltipProps.title;
}
return undefined;
return [editConfig.text, children, title, tooltipProps.title].find(isValidText);
}, [enableEllipsis, cssEllipsis, title, tooltipProps.title, isMergedEllipsis]);
// =========================== Render ===========================
@ -379,15 +348,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
// Expand
const renderExpand = () => {
const { expandable, symbol } = ellipsisConfig;
if (!expandable) {
return null;
}
if (expanded && expandable !== 'collapsible') {
return null;
}
return (
return expandable ? (
<TransButton
key="expand"
className={`${prefixCls}-${expanded ? 'collapse' : 'expand'}`}
@ -396,7 +357,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
>
{typeof symbol === 'function' ? symbol(expanded) : symbol}
</TransButton>
);
) : null;
};
// Edit
@ -446,7 +407,6 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
};
const renderOperations = (canEllipsis: boolean) => [
// (renderExpanded || ellipsisConfig.collapsible) && renderExpand(),
canEllipsis && renderExpand(),
renderEdit(),
renderCopy(),

View File

@ -13,20 +13,37 @@ export function getNode(dom: React.ReactNode, defaultNode: React.ReactNode, need
}
/**
* Get React of element with precision.
* ref: https://github.com/ant-design/ant-design/issues/50143
* Check for element is native ellipsis
* ref:
* - https://github.com/ant-design/ant-design/issues/50143
* - https://github.com/ant-design/ant-design/issues/50414
*/
export function getEleSize(ele: HTMLElement): [width: number, height: number] {
export function isEleEllipsis(ele: HTMLElement): boolean {
// Create a new div to get the size
const childDiv = document.createElement('em');
ele.appendChild(childDiv);
// For test case
if (process.env.NODE_ENV !== 'production') {
childDiv.className = 'ant-typography-css-ellipsis-content-measure';
}
const rect = ele.getBoundingClientRect();
const { offsetWidth, offsetHeight } = ele;
const childRect = childDiv.getBoundingClientRect();
let returnWidth = offsetWidth;
let returnHeight = offsetHeight;
// Reset
ele.removeChild(childDiv);
if (Math.abs(offsetWidth - rect.width) < 1 && Math.abs(offsetHeight - rect.height) < 1) {
returnWidth = rect.width;
returnHeight = rect.height;
// Range checker
return (
// Horizontal out of range
rect.left > childRect.left ||
childRect.right > rect.right ||
// Vertical out of range
rect.top > childRect.top ||
childRect.bottom > rect.bottom
);
}
return [returnWidth, returnHeight];
}
export const isValidText = (val: any): val is string | number =>
['string', 'number'].includes(typeof val);

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import EnterOutlined from '@ant-design/icons/EnterOutlined';
import classNames from 'classnames';
import type { AutoSizeType } from 'rc-textarea';
import type { TextAreaProps } from 'rc-textarea';
import KeyCode from 'rc-util/lib/KeyCode';
import { cloneElement } from '../_util/reactNode';
@ -21,7 +21,7 @@ interface EditableProps {
style?: React.CSSProperties;
direction?: DirectionType;
maxLength?: number;
autoSize?: boolean | AutoSizeType;
autoSize?: TextAreaProps['autoSize'];
enterIcon?: React.ReactNode;
component?: string;
}
@ -94,28 +94,27 @@ const Editable: React.FC<EditableProps> = (props) => {
}) => {
// Check if it's a real key
if (
lastKeyCode.current === keyCode &&
!inComposition.current &&
!ctrlKey &&
!altKey &&
!metaKey &&
!shiftKey
lastKeyCode.current !== keyCode ||
inComposition.current ||
ctrlKey ||
altKey ||
metaKey ||
shiftKey
) {
return;
}
if (keyCode === KeyCode.ENTER) {
confirmChange();
onEnd?.();
} else if (keyCode === KeyCode.ESC) {
onCancel();
}
}
};
const onBlur: React.FocusEventHandler<HTMLTextAreaElement> = () => {
confirmChange();
};
const textClassName = component ? `${prefixCls}-${component}` : '';
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
const textAreaClassName = classNames(
@ -123,9 +122,9 @@ const Editable: React.FC<EditableProps> = (props) => {
`${prefixCls}-edit-content`,
{
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-${component}`]: !!component,
},
className,
textClassName,
hashId,
cssVarCls,
);

View File

@ -19,7 +19,6 @@ const Text: React.ForwardRefRenderFunction<HTMLSpanElement, TextProps> = (
if (ellipsis && typeof ellipsis === 'object') {
return omit(ellipsis as EllipsisConfig, ['expandable', 'rows']);
}
return ellipsis;
}, [ellipsis]);

View File

@ -17,8 +17,6 @@ export interface TitleProps
const Title = React.forwardRef<HTMLElement, TitleProps>((props, ref) => {
const { level = 1, ...restProps } = props;
let component: keyof JSX.IntrinsicElements;
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Typography.Title');
@ -28,13 +26,9 @@ const Title = React.forwardRef<HTMLElement, TitleProps>((props, ref) => {
'Title only accept `1 | 2 | 3 | 4 | 5` as `level` value. And `5` need 4.6.0+ version.',
);
}
if (TITLE_ELE_LIST.includes(level)) {
component = `h${level}`;
} else {
component = 'h1';
}
const component: keyof JSX.IntrinsicElements = TITLE_ELE_LIST.includes(level)
? `h${level}`
: `h1`;
return <Base ref={ref} {...restProps} component={component} />;
});

View File

@ -1,7 +1,6 @@
import * as React from 'react';
import classNames from 'classnames';
import { composeRef } from 'rc-util/lib/ref';
import { devUseWarning } from '../_util/warning';
import type { ConfigConsumerProps, DirectionType } from '../config-provider';
import { ConfigContext } from '../config-provider';
@ -42,6 +41,7 @@ const Typography = React.forwardRef<
style,
...restProps
} = props;
const {
getPrefixCls,
direction: contextDirection,
@ -49,23 +49,16 @@ const Typography = React.forwardRef<
} = React.useContext<ConfigConsumerProps>(ConfigContext);
const direction = typographyDirection ?? contextDirection;
let mergedRef = ref;
if (setContentRef) {
mergedRef = composeRef(ref, setContentRef);
}
const mergedRef = setContentRef ? composeRef(ref, setContentRef) : ref;
const prefixCls = getPrefixCls('typography', customizePrefixCls);
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Typography');
warning.deprecated(!setContentRef, 'setContentRef', 'ref');
}
const prefixCls = getPrefixCls('typography', customizePrefixCls);
// Style
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
const componentClassName = classNames(
prefixCls,
typography?.className,
@ -92,5 +85,4 @@ if (process.env.NODE_ENV !== 'production') {
Typography.displayName = 'Typography';
}
// es default export should use const instead of let
export default Typography;

View File

@ -364,29 +364,48 @@ describe('Typography.Ellipsis', () => {
describe('should tooltip support', () => {
let domSpy: ReturnType<typeof spyElementPrototypes>;
let containerWidth = 100;
let contentWidth = 200;
let rectContainerWidth = 100;
let containerRect = {
left: 0,
top: 0,
right: 100,
bottom: 22,
};
let measureRect = {
left: 200,
top: 0,
};
beforeAll(() => {
domSpy = spyElementPrototypes(HTMLElement, {
offsetWidth: {
get: () => containerWidth,
getBoundingClientRect() {
if (
(this as unknown as HTMLElement).classList.contains(
'ant-typography-css-ellipsis-content-measure',
)
) {
return {
...measureRect,
right: measureRect.left,
bottom: measureRect.top + 22,
};
}
return containerRect;
},
scrollWidth: {
get: () => contentWidth,
},
getBoundingClientRect: () => ({
width: rectContainerWidth,
height: 0,
}),
});
});
beforeEach(() => {
containerWidth = 100;
contentWidth = 200;
rectContainerWidth = 100;
containerRect = {
left: 0,
top: 0,
right: 100,
bottom: 22,
};
measureRect = {
left: 200,
top: 0,
};
});
afterAll(() => {
@ -453,11 +472,11 @@ describe('Typography.Ellipsis', () => {
});
});
describe('precision', () => {
// https://github.com/ant-design/ant-design/issues/50143
it('precision', async () => {
containerWidth = 100;
contentWidth = 100;
rectContainerWidth = 99.9;
it('should show', async () => {
containerRect.right = 99.9;
measureRect.left = 100;
const { container, baseElement } = await getWrapper({
title: true,
@ -465,10 +484,28 @@ describe('Typography.Ellipsis', () => {
});
fireEvent.mouseEnter(container.firstChild!);
await waitFor(() => {
await waitFakeTimer();
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
// https://github.com/ant-design/ant-design/issues/50414
it('should not show', async () => {
containerRect.right = 48.52;
measureRect.left = 48.52;
const { container, baseElement } = await getWrapper({
title: true,
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild!);
await waitFakeTimer();
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).toBeFalsy();
});
});
});
@ -490,22 +527,26 @@ describe('Typography.Ellipsis', () => {
it('should display tooltip if line clamp', async () => {
mockRectSpy = spyElementPrototypes(HTMLElement, {
scrollHeight: {
get() {
let html = (this as any).innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
},
offsetHeight: {
get: () => 32,
},
offsetWidth: {
get: () => 100,
},
scrollWidth: {
get: () => 100,
getBoundingClientRect() {
if (
(this as unknown as HTMLElement).classList.contains(
'ant-typography-css-ellipsis-content-measure',
)
) {
return {
left: 0,
right: 0,
top: 100,
bottom: 122,
};
}
return {
left: 0,
right: 100,
top: 0,
bottom: 22 * 3,
};
},
});
@ -527,6 +568,32 @@ describe('Typography.Ellipsis', () => {
// https://github.com/ant-design/ant-design/issues/46580
it('dynamic to be ellipsis should show tooltip', async () => {
let dynamicWidth = 100;
mockRectSpy = spyElementPrototypes(HTMLElement, {
getBoundingClientRect() {
if (
(this as unknown as HTMLElement).classList.contains(
'ant-typography-css-ellipsis-content-measure',
)
) {
return {
left: 0,
right: dynamicWidth,
top: 0,
bottom: 22,
};
}
return {
left: 100,
right: 100,
top: 0,
bottom: 22,
};
},
});
const ref = React.createRef<HTMLElement>();
render(
<Base ellipsis={{ tooltip: 'bamboo' }} component="p" ref={ref}>
@ -535,8 +602,7 @@ describe('Typography.Ellipsis', () => {
);
// Force to narrow
offsetWidth = 1;
scrollWidth = 100;
dynamicWidth = 50;
triggerResize(ref.current!);
await waitFakeTimer();
@ -544,6 +610,8 @@ describe('Typography.Ellipsis', () => {
fireEvent.mouseEnter(ref.current!);
await waitFakeTimer();
expect(document.querySelector('.ant-tooltip')).toBeTruthy();
mockRectSpy.mockRestore();
});
it('not force single line if expanded', async () => {
@ -565,4 +633,15 @@ describe('Typography.Ellipsis', () => {
rerender(renderDemo(true));
expect(container.querySelector('.ant-typography-collapse')).toBeTruthy();
});
it('no dead loop', () => {
const tooltipObj: any = {};
tooltipObj.loop = tooltipObj;
render(
<Base ellipsis={{ tooltip: tooltipObj }} component="p">
{fullStr}
</Base>,
);
});
});

View File

@ -0,0 +1,22 @@
import { isValidElement, useMemo } from 'react';
import type { TooltipProps } from '../../tooltip';
const useTooltipProps = (
tooltip: React.ReactNode | TooltipProps,
editConfigText: React.ReactNode,
children: React.ReactNode,
) =>
useMemo(() => {
if (tooltip === true) {
return { title: editConfigText ?? children };
}
if (isValidElement(tooltip)) {
return { title: tooltip };
}
if (typeof tooltip === 'object') {
return { title: editConfigText ?? children, ...tooltip };
}
return { title: tooltip };
}, [tooltip, editConfigText, children]);
export default useTooltipProps;

View File

@ -1,16 +0,0 @@
import * as React from 'react';
/** Similar with `useEffect` but only trigger after mounted */
const useUpdatedEffect = (callback: () => void, conditions?: React.DependencyList) => {
const mountRef = React.useRef(false);
React.useEffect(() => {
if (mountRef.current) {
callback();
} else {
mountRef.current = true;
}
}, conditions);
};
export default useUpdatedEffect;

View File

@ -176,8 +176,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
node-version: 16

View File

@ -180,7 +180,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup Node.js (设置 node 版本)
uses: actions/setup-node@v3
uses: oven-sh/setup-bun@v2
with:
node-version: 16

View File

@ -5,37 +5,24 @@ order: 1
title: CSS Compatible
---
Ant Design supports the last 2 versions of modern browsers. If you need to be compatible with legacy browsers, please perform downgrade processing according to actual needs:
### Default Style Compatibility
## StyleProvider
Ant Design supports the [last 2 versions of modern browsers](https://browsersl.ist/#q=defaults). If you need to be compatible with legacy browsers, please perform downgrade processing according to actual needs:
Please ref [`@ant-design/cssinjs`](https://github.com/ant-design/cssinjs#styleprovider).
| Feature | antd version | Compatibility | Minimum Chrome Version | Compatibility workaround |
| --- | --- | --- | --- | --- |
| [:where Selector](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) | `>=5.0.0` | [caniuse](https://caniuse.com/?search=%3Awhere) | Chrome 88 | `<StyleProvider hashPriority="high">` |
| [CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) | `>=5.0.0` | [caniuse](https://caniuse.com/css-logical-props) | Chrome 89 | `<StyleProvider transformers={[legacyLogicalPropertiesTransformer]}>` |
## `layer` Downgrade
If you need to support older browsers, please use [StyleProvider](https://github.com/ant-design/cssinjs#styleprovider) for degradation handling according to your actual requirements.
Ant Design supports configuring `layer` for unified downgrade since `5.17.0`. After the downgrade, the style of antd will always be lower than the default CSS selector priority, so that users can override the style (please be sure to check the browser compatibility of `@layer`):
## `:where` in selector
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd styles will be encapsulated in `@layer` to lower the priority:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
## Compatible adjustment
- antd version: `>=5.0.0`
- MDN: [:where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where)
- Browser Compatibility: [caniuse](https://caniuse.com/?search=%3Awhere)
- Minimum Chrome Version Supported: 88
- Default Enabled: Yes
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, you can use `@ant-design/cssinjs` to disable the default lowering of specificity (please ensure version consistency with antd).
@ -77,6 +64,12 @@ Raise priority through plugin:
## CSS Logical Properties
- antd version: `>=5.0.0`
- MDN[CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties)
- Browser Compatibility: [caniuse](https://caniuse.com/css-logical-props)
- Minimum Chrome Version Supported: 89
- Default Enabled: Yes
To unify LTR and RTL styles, Ant Design uses CSS logical properties. For example, the original `margin-left` is replaced by `margin-inline-start`, so that it is the starting position spacing under both LTR and RTL. If you need to be compatible with older browsers, you can configure `transformers` through the `StyleProvider` of `@ant-design/cssinjs`:
```tsx
@ -102,6 +95,36 @@ When toggled, styles will downgrade CSS logical properties:
}
```
## `@layer`
- antd version: `>=5.17.0`
- MDN[CSS @layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer)
- Browser Compatibility: [caniuse](https://caniuse.com/css-at-rule-layer)
- Minimum Chrome Version Supported: 99
- Default Enabled: No
Ant Design supports configuring `@layer` for unified css priority downgrade since `5.17.0`. After the downgrade, the style of antd will always be lower than the default CSS selector priority, so that users can override the style (please be sure to check the browser compatibility of `@layer`):
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd styles will be encapsulated in `@layer` to lower the priority:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
## Rem Adaptation
In responsive web development, there is a need for a convenient and flexible way to achieve page adaptation and responsive design. The `px2remTransformer` transformer can quickly and accurately convert pixel units in style sheets to rem units relative to the root element (HTML tag), enabling the implementation of adaptive and responsive layouts.

View File

@ -5,38 +5,25 @@ order: 1
title: 样式兼容
---
Ant Design 支持最近 2 个版本的现代浏览器。如果你需要兼容旧版浏览器,请根据实际需求进行降级处理:
## 默认样式兼容性说明
## StyleProvider
Ant Design 5.x 支持[最近 2 个版本的现代浏览器](https://browsersl.ist/#q=defaults)。默认情况下,我们使用了一些现代 CSS 特性来提高样式的可维护性和可扩展性,这些特性在旧版浏览器中可能不被支持,好在我们可以通过一些降级兼容方案来解决。
查看 [`@ant-design/cssinjs`](https://github.com/ant-design/cssinjs#styleprovider).
| 特性 | antd 版本 | 兼容性 | 最低 Chrome 版本 | 降级兼容方案 |
| --- | --- | --- | --- | --- |
| [:where 选择器](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) | `>=5.0.0` | [caniuse](https://caniuse.com/?search=%3Awhere) | Chrome 88 | `<StyleProvider hashPriority="high">` |
| [CSS 逻辑属性](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) | `>=5.0.0` | [caniuse](https://caniuse.com/css-logical-props) | Chrome 89 | `<StyleProvider transformers={[legacyLogicalPropertiesTransformer]}>` |
## `layer` 降权
Ant Design 从 `5.17.0` 起支持配置 `layer` 进行统一降权。经过降权后antd 的样式将始终低于默认的 CSS 选择器优先级,以便于用户进行样式覆盖(请务必注意检查 `@layer` 浏览器兼容性):
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd 的样式会被封装在 `@layer` 中,以降低优先级:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
如果你需要兼容旧版浏览器,请根据实际需求使用 [StyleProvider](https://github.com/ant-design/cssinjs#styleprovider) 降级处理。
## `:where` 选择器
- 支持版本:`>=5.0.0`
- MDN 文档:[:where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where)
- 浏览器兼容性:[caniuse](https://caniuse.com/?search=%3Awhere)
- Chrome 最低支持版本88
- 默认启用:是
Ant Design 的 CSS-in-JS 默认通过 `:where` 选择器降低 CSS Selector 优先级,以减少用户升级时额外调整自定义样式的成本,不过 `:where` 语法的[兼容性](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility)在低版本浏览器比较差。在某些场景下你如果需要支持旧版浏览器,你可以使用 `@ant-design/cssinjs` 取消默认的降权操作(请注意版本保持与 antd 一致):
```tsx
@ -77,9 +64,15 @@ export default () => (
## CSS 逻辑属性
- 支持版本:`>=5.0.0`
- MDN 文档:[:where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where)
- 浏览器兼容性:[caniuse](https://caniuse.com/css-logical-props)
- Chrome 最低支持版本89
- 默认启用:是
为了统一 LTR 和 RTL 样式Ant Design 使用了 CSS 逻辑属性。例如原 `margin-left` 使用 `margin-inline-start` 代替,使其在 LTR 和 RTL 下都为起始位置间距。如果你需要兼容旧版浏览器(如 360 浏览器、QQ 浏览器 等等),可以通过 `@ant-design/cssinjs``StyleProvider` 配置 `transformers` 将其转换:
```tsx
```tsx | pure
import { legacyLogicalPropertiesTransformer, StyleProvider } from '@ant-design/cssinjs';
// `transformers` 提供预处理功能将样式进行转换
@ -102,11 +95,41 @@ export default () => (
}
```
## `@layer` 样式优先级降权
- 支持版本:`>=5.17.0`
- MDN 文档:[@layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer)
- 浏览器兼容性:[caniuse](https://caniuse.com/?search=%40layer)
- Chrome 最低支持版本99
- 默认启用:否
Ant Design 从 `5.17.0` 起支持配置 `layer` 进行统一降权。经过降权后antd 的样式将始终低于默认的 CSS 选择器优先级,以便于用户进行样式覆盖(请务必注意检查 `@layer` 浏览器兼容性):
```tsx | pure
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd 的样式会被封装在 `@layer` 中,以降低优先级:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
## rem 适配
在响应式网页开发中,需要一种方便且灵活的方式来实现页面的适配和响应式设计。`px2remTransformer` 转换器可以快速而准确地将样式表中的像素单位转换为相对于根元素HTML 标签)的 rem 单位,实现页面的自适应和响应式布局。
```tsx
```tsx | pure
import { px2remTransformer, StyleProvider } from '@ant-design/cssinjs';
const px2rem = px2remTransformer({

View File

@ -58,7 +58,7 @@ However, after enabling CSS variables, the component styles of the same antd ver
</ConfigProvider>
```
By the way, we strongly recommend using `extractStyle` to extract static styles, which will bring a certain amount of performance improvement to the application.
By the way, we strongly recommend using [extractStyle](/docs/react/server-side-rendering) to extract static styles, which will bring a certain amount of performance improvement to the application.
### Customize Theme

View File

@ -58,7 +58,7 @@ hash 是 Ant Design 5.0 以来的特性之一,其功能是为每一份主题
</ConfigProvider>
```
同时我们非常推荐使用 `extractStyle` 来抽取静态样式,这样会为应用性能带来一定量的提升。
同时我们非常推荐使用 [extractStyle](/docs/react/server-side-rendering-cn) 来抽取静态样式,这样会为应用性能带来一定量的提升。
### 修改主题

View File

@ -221,7 +221,7 @@ const App: React.FC = () => {
return (
<>
<ColorPicker showText value={primary} onChangeComplete={(color) => setPrimary(color.toHexString())} />
<ColorPicker showText value={primary} onChange={(color) => setPrimary(color.toHexString())} />
<Divider />
<ConfigProvider
theme={{

View File

@ -221,7 +221,7 @@ const App: React.FC = () => {
return (
<>
<ColorPicker showText value={primary} onChangeComplete={(color) => setPrimary(color.toHexString())} />
<ColorPicker showText value={primary} onChange={(color) => setPrimary(color.toHexString())} />
<Divider />
<ConfigProvider
theme={{

Some files were not shown because too many files have changed in this diff Show More