Merge pull request #48524 from ant-design/master

chore: merge master into feature
This commit is contained in:
lijianan 2024-04-18 10:11:58 +08:00 committed by GitHub
commit 27c942a00b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 3609 additions and 2495 deletions

View File

@ -1,37 +1,25 @@
import React from 'react'; import React from 'react';
import { ExclamationCircleOutlined } from '@ant-design/icons'; import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Col, Row, Tooltip } from 'antd'; import { Col, Row, Tooltip, Card, Typography } from 'antd';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';
import useLocale from '../../../hooks/useLocale'; import useLocale from '../../../hooks/useLocale';
const useStyle = createStyles(({ token, css }) => { const { Paragraph } = Typography;
const { boxShadowSecondary } = token;
return { const useStyle = createStyles(({ token, css }) => ({
card: css` card: css`
position: relative; position: relative;
display: flex; overflow: hidden;
flex-direction: column;
height: 100%;
color: inherit;
list-style: none;
border: 1px solid ${token.colorSplit};
border-radius: ${token.borderRadiusXS}px;
cursor: pointer;
transition: box-shadow ${token.motionDurationSlow};
&:hover { .ant-card-cover {
box-shadow: ${boxShadowSecondary}; overflow: hidden;
color: inherit;
} }
`, img {
image: css` transition: all ${token.motionDurationSlow} ease-out;
width: calc(100% + 2px); }
max-width: none;
height: 184px; &:hover img {
margin: -1px -1px 0; transform: scale(1.3);
object-fit: cover;
`, `,
badge: css` badge: css`
position: absolute; position: absolute;
@ -42,25 +30,12 @@ const useStyle = createStyles(({ token, css }) => {
font-size: ${token.fontSizeSM}px; font-size: ${token.fontSizeSM}px;
line-height: 1; line-height: 1;
background: rgba(0, 0, 0, 0.65); background: rgba(0, 0, 0, 0.65);
border-radius: ${token.borderRadiusXS}px; border-radius: ${token.borderRadiusLG}px;
box-shadow: 0 0 2px rgba(255, 255, 255, 0.2); box-shadow: 0 0 2px rgba(255, 255, 255, 0.2);
display: inline-flex; display: inline-flex;
column-gap: ${token.paddingXXS}px; column-gap: ${token.paddingXXS}px;
`, `,
title: css` }));
margin: ${token.margin}px ${token.marginMD}px ${token.marginXS}px;
opacity: 0.85;
font-size: ${token.fontSizeXL}px;
line-height: 28px;
`,
description: css`
margin: 0 ${token.marginMD}px ${token.marginMD}px;
opacity: 0.65;
font-size: ${token.fontSizeXL}px;
line-height: 22px;
`,
};
});
export type Resource = { export type Resource = {
title: string; title: string;
@ -91,27 +66,9 @@ const ResourceCard: React.FC<ResourceCardProps> = ({ resource }) => {
const { styles } = useStyle(); const { styles } = useStyle();
const [locale] = useLocale(locales); const [locale] = useLocale(locales);
const { title: titleStr, description, cover, src, official } = resource; const { title, description, cover, src, official } = resource;
let coverColor: string | null = null; const badge = official ? (
let title: string = titleStr;
const titleMatch = titleStr.match(/(.*)(#[\dA-Fa-f]{6})/);
if (titleMatch) {
title = titleMatch[1].trim();
// eslint-disable-next-line prefer-destructuring
coverColor = titleMatch[2];
}
return (
<Col xs={24} sm={12} md={8} lg={6} style={{ padding: 12 }}>
<a className={styles.card} target="_blank" href={src} rel="noreferrer">
<img
className={styles.image}
src={cover}
alt={title}
style={coverColor ? { backgroundColor: coverColor } : {}}
/>
{official ? (
<div className={styles.badge}>{locale.official}</div> <div className={styles.badge}>{locale.official}</div>
) : ( ) : (
<Tooltip title={locale.thirdPartDesc}> <Tooltip title={locale.thirdPartDesc}>
@ -120,9 +77,22 @@ const ResourceCard: React.FC<ResourceCardProps> = ({ resource }) => {
{locale.thirdPart} {locale.thirdPart}
</div> </div>
</Tooltip> </Tooltip>
)} );
<p className={styles?.title}>{title}</p>
<p className={styles.description}>{description}</p> return (
<Col xs={24} sm={12} md={8} lg={6}>
<a className={styles.card} target="_blank" href={src} rel="noreferrer">
<Card hoverable className={styles.card} cover={<img src={cover} alt={title} />}>
<Card.Meta
title={title}
description={
<Paragraph style={{ marginBottom: 0 }} ellipsis={{ rows: 1 }} title={description}>
{description}
</Paragraph>
}
/>
{badge}
</Card>
</a> </a>
</Col> </Col>
); );
@ -133,7 +103,7 @@ export type ResourceCardsProps = {
}; };
const ResourceCards: React.FC<ResourceCardsProps> = ({ resources }) => ( const ResourceCards: React.FC<ResourceCardsProps> = ({ resources }) => (
<Row style={{ margin: '-12px -12px 0 -12px' }}> <Row gutter={[24, 24]}>
{resources.map((item) => ( {resources.map((item) => (
<ResourceCard resource={item} key={item?.title} /> <ResourceCard resource={item} key={item?.title} />
))} ))}

View File

@ -1,11 +1,12 @@
import { createHash } from 'crypto';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { createHash } from 'crypto'; import createEmotionServer from '@emotion/server/create-instance';
import chalk from 'chalk';
import type { IApi, IRoute } from 'dumi'; import type { IApi, IRoute } from 'dumi';
import ReactTechStack from 'dumi/dist/techStacks/react'; import ReactTechStack from 'dumi/dist/techStacks/react';
import chalk from 'chalk';
import sylvanas from 'sylvanas'; import sylvanas from 'sylvanas';
import createEmotionServer from '@emotion/server/create-instance';
import localPackage from '../../package.json'; import localPackage from '../../package.json';
function extractEmotionStyle(html: string) { function extractEmotionStyle(html: string) {
@ -53,13 +54,65 @@ class AntdReactTechStack extends ReactTechStack {
if (md) { if (md) {
// extract description & css style from md file // extract description & css style from md file
const description = md.match( const blocks: Record<string, string> = {};
new RegExp(`(?:^|\\n)## ${locale}([^]+?)(\\n## [a-z]|\\n\`\`\`|\\n<style>|$)`),
)?.[1];
const style = md.match(/\r?\n(?:```css|<style>)\r?\n([^]+?)\r?\n(?:```|<\/style>)/)?.[1];
props.description ??= description?.trim(); const lines = md.split('\n');
props.style ??= style;
let blockName = '';
let cacheList: string[] = [];
// Get block name
const getBlockName = (text: string) => {
if (text.startsWith('## ')) {
return text.replace('## ', '').trim();
}
if (text.startsWith('```css') || text.startsWith('<style>')) {
return 'style';
}
return null;
};
// Fill block content
const fillBlock = (name: string, lineList: string[]) => {
if (lineList.length) {
let fullText: string;
if (name === 'style') {
fullText = lineList
.join('\n')
.replace(/<\/?style>/g, '')
.replace(/```(\s*css)/g, '');
} else {
fullText = lineList.slice(1).join('\n');
}
blocks[name] = fullText;
}
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Mark as new block
const nextBlockName = getBlockName(line);
if (nextBlockName) {
fillBlock(blockName, cacheList);
// Next Block
blockName = nextBlockName;
cacheList = [line];
} else {
cacheList.push(line);
}
}
// Last block
fillBlock(blockName, cacheList);
props.description = blocks[locale];
props.style = blocks.style;
} }
} }

View File

@ -6,7 +6,7 @@ Your pull requests will be merged after one of the collaborators approve.
Thank you! Thank you!
--> -->
[[中文版模板 / Chinese template](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md?plain=1)] [中文版模板 / Chinese template](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md?plain=1)
### 🤔 This is a ... ### 🤔 This is a ...

View File

@ -6,7 +6,7 @@
请确保填写以下 pull request 的信息,谢谢!~ 请确保填写以下 pull request 的信息,谢谢!~
--> -->
[[English Template / 英文模板](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE.md?plain=1)] [English Template / 英文模板](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE.md?plain=1)
### 🤔 这个变动的性质是? ### 🤔 这个变动的性质是?

View File

@ -5,6 +5,7 @@ on:
types: [opened, synchronize] types: [opened, synchronize]
permissions: permissions:
issues: write
contents: read contents: read
jobs: jobs:

View File

@ -287,6 +287,7 @@ jobs:
NODE_OPTIONS: "--max_old_space_size=4096" NODE_OPTIONS: "--max_old_space_size=4096"
CI: 1 CI: 1
# Artifact build files
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: github.event_name == 'push' && github.ref == 'refs/heads/master' if: github.event_name == 'push' && github.ref == 'refs/heads/master'
with: with:
@ -297,6 +298,17 @@ jobs:
es es
lib lib
- name: zip builds
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}
HEAD_SHA: ${{ github.sha }}
run: |
zip -r oss-artifacts.zip dist locale es lib
echo "🤖 Uploading"
node scripts/visual-regression/upload.js ./oss-artifacts.zip --ref=$HEAD_SHA
compiled-module-test: compiled-module-test:
name: module test name: module test
runs-on: ubuntu-latest runs-on: ubuntu-latest

1
.gitignore vendored
View File

@ -45,6 +45,7 @@ components/**/*.jsx
/.history /.history
*.tmp *.tmp
artifacts.zip artifacts.zip
oss-artifacts.zip
server/ server/
# Docs templates # Docs templates

View File

@ -1,9 +1,7 @@
const config = { const config = {
plugins: [ plugins: [
'remark-preset-lint-recommended', 'remark-preset-lint-recommended',
['remark-lint-list-item-indent', 'space'], ['remark-lint-no-undefined-references', { allow: [' ', /RFC/] }],
['remark-lint-no-literal-urls', false],
['remark-lint-no-undefined-references', false],
], ],
}; };

View File

@ -124,7 +124,9 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
[`${componentCls}-arrow`]: { [`${componentCls}-arrow`]: {
...resetIcon(), ...resetIcon(),
fontSize: fontSizeIcon, fontSize: fontSizeIcon,
// when `transform: rotate()` is applied to icon's root element
transition: `transform ${motionDurationSlow}`,
// when `transform: rotate()` is applied to icon's child element
svg: { svg: {
transition: `transform ${motionDurationSlow}`, transition: `transform ${motionDurationSlow}`,
}, },
@ -231,7 +233,7 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
const genArrowStyle: GenerateStyle<CollapseToken> = (token) => { const genArrowStyle: GenerateStyle<CollapseToken> = (token) => {
const { componentCls } = token; const { componentCls } = token;
const fixedSelector = `> ${componentCls}-item > ${componentCls}-header ${componentCls}-arrow svg`; const fixedSelector = `> ${componentCls}-item > ${componentCls}-header ${componentCls}-arrow`;
return { return {
[`${componentCls}-rtl`]: { [`${componentCls}-rtl`]: {

View File

@ -698,4 +698,30 @@ describe('ColorPicker', () => {
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'), container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeFalsy(); ).toBeFalsy();
}); });
describe('default clearValue should be changed', () => {
const Demo = () => {
const [color, setColor] = useState<string>('');
useEffect(() => {
setColor('#1677ff');
}, []);
return <ColorPicker value={color} allowClear />;
};
it('normal', () => {
const { container } = render(<Demo />);
expect(container.querySelector('.ant-color-picker-clear')).toBeFalsy();
});
it('strict', () => {
const { container } = render(
<React.StrictMode>
<Demo />
</React.StrictMode>,
);
expect(container.querySelector('.ant-color-picker-clear')).toBeFalsy();
});
});
}); });

View File

@ -4,6 +4,8 @@ import type { Color } from '../color';
import type { ColorValueType } from '../interface'; import type { ColorValueType } from '../interface';
import { generateColor } from '../util'; import { generateColor } from '../util';
const INIT_COLOR_REF = {} as ColorValueType;
function hasValue(value?: ColorValueType) { function hasValue(value?: ColorValueType) {
return value !== undefined; return value !== undefined;
} }
@ -33,7 +35,14 @@ const useColorState = (
prevColor.current = color; prevColor.current = color;
}; };
const prevValue = useRef<ColorValueType | undefined>(INIT_COLOR_REF);
useEffect(() => { useEffect(() => {
// `useEffect` will be executed twice in strict mode even if the deps are the same
// So we compare the value manually to avoid unnecessary update
if (prevValue.current === value) {
return;
}
prevValue.current = value;
if (hasValue(value)) { if (hasValue(value)) {
const newColor = generateColor(value || ''); const newColor = generateColor(value || '');
if (prevColor.current.cleared === true) { if (prevColor.current.cleared === true) {

View File

@ -42620,6 +42620,601 @@ exports[`renders components/date-picker/demo/multiple-debug.tsx extend context c
<div <div
class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical" class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical"
> >
<div
class="ant-picker ant-picker-multiple ant-picker-outlined"
>
<div
class="ant-picker-selector"
>
<div
class="ant-picker-selection-overflow"
/>
<span
class="ant-picker-selection-placeholder"
>
Bamboo
</span>
</div>
<input
class="ant-picker-multiple-input"
readonly=""
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-picker-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-picker-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-picker-panel-container ant-picker-date-panel-container"
style="margin-left: 0px; margin-right: auto;"
tabindex="-1"
>
<div
class="ant-picker-panel-layout"
>
<div>
<div
class="ant-picker-panel"
tabindex="0"
>
<div
class="ant-picker-date-panel"
>
<div
class="ant-picker-header"
>
<button
class="ant-picker-header-super-prev-btn"
tabindex="-1"
type="button"
>
<span
class="ant-picker-super-prev-icon"
/>
</button>
<button
class="ant-picker-header-prev-btn"
tabindex="-1"
type="button"
>
<span
class="ant-picker-prev-icon"
/>
</button>
<div
class="ant-picker-header-view"
>
<button
class="ant-picker-month-btn"
tabindex="-1"
type="button"
>
Nov
</button>
<button
class="ant-picker-year-btn"
tabindex="-1"
type="button"
>
2016
</button>
</div>
<button
class="ant-picker-header-next-btn"
tabindex="-1"
type="button"
>
<span
class="ant-picker-next-icon"
/>
</button>
<button
class="ant-picker-header-super-next-btn"
tabindex="-1"
type="button"
>
<span
class="ant-picker-super-next-icon"
/>
</button>
</div>
<div
class="ant-picker-body"
>
<table
class="ant-picker-content"
>
<thead>
<tr>
<th>
Su
</th>
<th>
Mo
</th>
<th>
Tu
</th>
<th>
We
</th>
<th>
Th
</th>
<th>
Fr
</th>
<th>
Sa
</th>
</tr>
</thead>
<tbody>
<tr>
<td
class="ant-picker-cell"
title="2016-10-30"
>
<div
class="ant-picker-cell-inner"
>
30
</div>
</td>
<td
class="ant-picker-cell"
title="2016-10-31"
>
<div
class="ant-picker-cell-inner"
>
31
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-01"
>
<div
class="ant-picker-cell-inner"
>
1
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-02"
>
<div
class="ant-picker-cell-inner"
>
2
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-03"
>
<div
class="ant-picker-cell-inner"
>
3
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-04"
>
<div
class="ant-picker-cell-inner"
>
4
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-05"
>
<div
class="ant-picker-cell-inner"
>
5
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-06"
>
<div
class="ant-picker-cell-inner"
>
6
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-07"
>
<div
class="ant-picker-cell-inner"
>
7
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-08"
>
<div
class="ant-picker-cell-inner"
>
8
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-09"
>
<div
class="ant-picker-cell-inner"
>
9
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-10"
>
<div
class="ant-picker-cell-inner"
>
10
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-11"
>
<div
class="ant-picker-cell-inner"
>
11
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-12"
>
<div
class="ant-picker-cell-inner"
>
12
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-13"
>
<div
class="ant-picker-cell-inner"
>
13
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-14"
>
<div
class="ant-picker-cell-inner"
>
14
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-15"
>
<div
class="ant-picker-cell-inner"
>
15
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-16"
>
<div
class="ant-picker-cell-inner"
>
16
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-17"
>
<div
class="ant-picker-cell-inner"
>
17
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-18"
>
<div
class="ant-picker-cell-inner"
>
18
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-19"
>
<div
class="ant-picker-cell-inner"
>
19
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-20"
>
<div
class="ant-picker-cell-inner"
>
20
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-21"
>
<div
class="ant-picker-cell-inner"
>
21
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view ant-picker-cell-today"
title="2016-11-22"
>
<div
class="ant-picker-cell-inner"
>
22
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-23"
>
<div
class="ant-picker-cell-inner"
>
23
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-24"
>
<div
class="ant-picker-cell-inner"
>
24
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-25"
>
<div
class="ant-picker-cell-inner"
>
25
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-26"
>
<div
class="ant-picker-cell-inner"
>
26
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-27"
>
<div
class="ant-picker-cell-inner"
>
27
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-28"
>
<div
class="ant-picker-cell-inner"
>
28
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-29"
>
<div
class="ant-picker-cell-inner"
>
29
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2016-11-30"
>
<div
class="ant-picker-cell-inner"
>
30
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-01"
>
<div
class="ant-picker-cell-inner"
>
1
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-02"
>
<div
class="ant-picker-cell-inner"
>
2
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-03"
>
<div
class="ant-picker-cell-inner"
>
3
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell"
title="2016-12-04"
>
<div
class="ant-picker-cell-inner"
>
4
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-05"
>
<div
class="ant-picker-cell-inner"
>
5
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-06"
>
<div
class="ant-picker-cell-inner"
>
6
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-07"
>
<div
class="ant-picker-cell-inner"
>
7
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-08"
>
<div
class="ant-picker-cell-inner"
>
8
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-09"
>
<div
class="ant-picker-cell-inner"
>
9
</div>
</td>
<td
class="ant-picker-cell"
title="2016-12-10"
>
<div
class="ant-picker-cell-inner"
>
10
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div <div
class="ant-picker ant-picker-multiple ant-picker-small ant-picker-outlined" class="ant-picker ant-picker-multiple ant-picker-small ant-picker-outlined"
> >

View File

@ -3315,6 +3315,50 @@ exports[`renders components/date-picker/demo/multiple-debug.tsx correctly 1`] =
<div <div
class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical" class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical"
> >
<div
class="ant-picker ant-picker-multiple ant-picker-outlined"
>
<div
class="ant-picker-selector"
>
<div
class="ant-picker-selection-overflow"
/>
<span
class="ant-picker-selection-placeholder"
>
Bamboo
</span>
</div>
<input
class="ant-picker-multiple-input"
readonly=""
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
<div <div
class="ant-picker ant-picker-multiple ant-picker-small ant-picker-outlined" class="ant-picker ant-picker-multiple ant-picker-small ant-picker-outlined"
> >

View File

@ -6,6 +6,7 @@ const defaultValue = new Array(10).fill(0).map((_, index) => dayjs('2000-01-01')
const App: React.FC = () => ( const App: React.FC = () => (
<Flex vertical gap="small"> <Flex vertical gap="small">
<DatePicker multiple placeholder="Bamboo" />
<DatePicker multiple defaultValue={defaultValue} size="small" /> <DatePicker multiple defaultValue={defaultValue} size="small" />
<DatePicker multiple defaultValue={defaultValue} /> <DatePicker multiple defaultValue={defaultValue} />
<DatePicker multiple defaultValue={defaultValue} size="large" /> <DatePicker multiple defaultValue={defaultValue} size="large" />

View File

@ -65,15 +65,33 @@ const genPickerMultipleStyle: GenerateStyle<PickerToken> = (token) => {
{ {
[`${componentCls}${componentCls}-multiple`]: { [`${componentCls}${componentCls}-multiple`]: {
width: '100%', width: '100%',
cursor: 'text',
// ==================== Selector ===================== // ==================== Selector =====================
[`${componentCls}-selector`]: { [`${componentCls}-selector`]: {
flex: 'auto', flex: 'auto',
padding: 0, padding: 0,
position: 'relative',
'&:after': { '&:after': {
margin: 0, margin: 0,
}, },
// ================== placeholder ==================
[`${componentCls}-selection-placeholder`]: {
position: 'absolute',
top: '50%',
insetInlineStart: token.inputPaddingHorizontalBase,
insetInlineEnd: 0,
transform: 'translateY(-50%)',
transition: `all ${token.motionDurationSlow}`,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
flex: 1,
color: token.colorTextPlaceholder,
pointerEvents: 'none',
},
}, },
// ===================== Overflow ==================== // ===================== Overflow ====================

View File

@ -562,7 +562,7 @@ export const genPanelStyle = (token: SharedPickerToken): CSSObject => {
'&::-webkit-scrollbar-thumb': { '&::-webkit-scrollbar-thumb': {
backgroundColor: token.colorTextTertiary, backgroundColor: token.colorTextTertiary,
borderRadius: 4, borderRadius: token.borderRadiusSM,
}, },
// For Firefox // For Firefox

View File

@ -1,7 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/dropdown/demo/arrow.tsx extend context correctly 1`] = ` exports[`renders components/dropdown/demo/arrow.tsx extend context correctly 1`] = `
Array [ <div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap: wrap;"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -9,7 +21,7 @@ Array [
<span> <span>
bottomLeft bottomLeft
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomLeft" class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -185,7 +197,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -193,7 +209,7 @@ Array [
<span> <span>
bottom bottom
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottom" class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottom"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -369,7 +385,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -377,7 +397,7 @@ Array [
<span> <span>
bottomRight bottomRight
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomRight" class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomRight"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -553,8 +573,20 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
<br />, </div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap: wrap;"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -562,7 +594,7 @@ Array [
<span> <span>
topLeft topLeft
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topLeft" class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -738,7 +770,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -746,7 +782,7 @@ Array [
<span> <span>
top top
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-top" class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-top"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -922,7 +958,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -930,7 +970,7 @@ Array [
<span> <span>
topRight topRight
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topRight" class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topRight"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -1106,14 +1146,29 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
] </div>
</div>
</div>
</div>
`; `;
exports[`renders components/dropdown/demo/arrow.tsx extend context correctly 2`] = `[]`; exports[`renders components/dropdown/demo/arrow.tsx extend context correctly 2`] = `[]`;
exports[`renders components/dropdown/demo/arrow-center.tsx extend context correctly 1`] = ` exports[`renders components/dropdown/demo/arrow-center.tsx extend context correctly 1`] = `
Array [ <div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap: wrap;"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -1121,7 +1176,7 @@ Array [
<span> <span>
bottomLeft bottomLeft
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomLeft" class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -1297,7 +1352,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -1305,7 +1364,7 @@ Array [
<span> <span>
bottom bottom
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottom" class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottom"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -1481,7 +1540,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -1489,7 +1552,7 @@ Array [
<span> <span>
bottomRight bottomRight
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomRight" class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomRight"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -1665,8 +1728,20 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
<br />, </div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap: wrap;"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -1674,7 +1749,7 @@ Array [
<span> <span>
topLeft topLeft
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topLeft" class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -1850,7 +1925,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -1858,7 +1937,7 @@ Array [
<span> <span>
top top
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-top" class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-top"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -2034,7 +2113,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -2042,7 +2125,7 @@ Array [
<span> <span>
topRight topRight
</span> </span>
</button>, </button>
<div <div
class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topRight" class="ant-dropdown ant-slide-down-appear ant-slide-down-appear-prepare ant-slide-down ant-dropdown-show-arrow ant-dropdown-placement-topRight"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
@ -2218,8 +2301,11 @@ Array [
</div> </div>
</div> </div>
</div> </div>
</div>, </div>
] </div>
</div>
</div>
</div>
`; `;
exports[`renders components/dropdown/demo/arrow-center.tsx extend context correctly 2`] = `[]`; exports[`renders components/dropdown/demo/arrow-center.tsx extend context correctly 2`] = `[]`;

View File

@ -1,7 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/dropdown/demo/arrow.tsx correctly 1`] = ` exports[`renders components/dropdown/demo/arrow.tsx correctly 1`] = `
Array [ <div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap:wrap"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -9,7 +21,11 @@ Array [
<span> <span>
bottomLeft bottomLeft
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -17,7 +33,11 @@ Array [
<span> <span>
bottom bottom
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -25,8 +45,20 @@ Array [
<span> <span>
bottomRight bottomRight
</span> </span>
</button>, </button>
<br />, </div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap:wrap"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -34,7 +66,11 @@ Array [
<span> <span>
topLeft topLeft
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -42,7 +78,11 @@ Array [
<span> <span>
top top
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -50,12 +90,27 @@ Array [
<span> <span>
topRight topRight
</span> </span>
</button>, </button>
] </div>
</div>
</div>
</div>
`; `;
exports[`renders components/dropdown/demo/arrow-center.tsx correctly 1`] = ` exports[`renders components/dropdown/demo/arrow-center.tsx correctly 1`] = `
Array [ <div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap:wrap"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -63,7 +118,11 @@ Array [
<span> <span>
bottomLeft bottomLeft
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -71,7 +130,11 @@ Array [
<span> <span>
bottom bottom
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -79,8 +142,20 @@ Array [
<span> <span>
bottomRight bottomRight
</span> </span>
</button>, </button>
<br />, </div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap:wrap"
>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -88,7 +163,11 @@ Array [
<span> <span>
topLeft topLeft
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -96,7 +175,11 @@ Array [
<span> <span>
top top
</span> </span>
</button>, </button>
</div>
<div
class="ant-space-item"
>
<button <button
class="ant-btn ant-btn-default ant-dropdown-trigger" class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button" type="button"
@ -104,8 +187,11 @@ Array [
<span> <span>
topRight topRight
</span> </span>
</button>, </button>
] </div>
</div>
</div>
</div>
`; `;
exports[`renders components/dropdown/demo/basic.tsx correctly 1`] = ` exports[`renders components/dropdown/demo/basic.tsx correctly 1`] = `

View File

@ -5,15 +5,3 @@
## en-US ## en-US
By specifying `arrow` prop with `{ pointAtCenter: true }`, the arrow will point to the center of the target element. By specifying `arrow` prop with `{ pointAtCenter: true }`, the arrow will point to the center of the target element.
```css
#components-dropdown-demo-arrow-center .ant-btn {
margin-right: 8px;
margin-bottom: 8px;
}
.ant-row-rtl #components-dropdown-demo-arrow-center .ant-btn {
margin-right: 0;
margin-bottom: 8px;
margin-left: 8px;
}
```

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { Button, Dropdown } from 'antd'; import { Button, Dropdown, Space } from 'antd';
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
{ {
@ -30,7 +30,8 @@ const items: MenuProps['items'] = [
]; ];
const App: React.FC = () => ( const App: React.FC = () => (
<> <Space direction="vertical">
<Space wrap>
<Dropdown menu={{ items }} placement="bottomLeft" arrow={{ pointAtCenter: true }}> <Dropdown menu={{ items }} placement="bottomLeft" arrow={{ pointAtCenter: true }}>
<Button>bottomLeft</Button> <Button>bottomLeft</Button>
</Dropdown> </Dropdown>
@ -40,7 +41,8 @@ const App: React.FC = () => (
<Dropdown menu={{ items }} placement="bottomRight" arrow={{ pointAtCenter: true }}> <Dropdown menu={{ items }} placement="bottomRight" arrow={{ pointAtCenter: true }}>
<Button>bottomRight</Button> <Button>bottomRight</Button>
</Dropdown> </Dropdown>
<br /> </Space>
<Space wrap>
<Dropdown menu={{ items }} placement="topLeft" arrow={{ pointAtCenter: true }}> <Dropdown menu={{ items }} placement="topLeft" arrow={{ pointAtCenter: true }}>
<Button>topLeft</Button> <Button>topLeft</Button>
</Dropdown> </Dropdown>
@ -50,7 +52,8 @@ const App: React.FC = () => (
<Dropdown menu={{ items }} placement="topRight" arrow={{ pointAtCenter: true }}> <Dropdown menu={{ items }} placement="topRight" arrow={{ pointAtCenter: true }}>
<Button>topRight</Button> <Button>topRight</Button>
</Dropdown> </Dropdown>
</> </Space>
</Space>
); );
export default App; export default App;

View File

@ -5,15 +5,3 @@
## en-US ## en-US
You could display an arrow. You could display an arrow.
```css
#components-dropdown-demo-arrow .ant-btn {
margin-right: 8px;
margin-bottom: 8px;
}
.ant-row-rtl #components-dropdown-demo-arrow .ant-btn {
margin-right: 0;
margin-bottom: 8px;
margin-left: 8px;
}
```

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { Button, Dropdown } from 'antd'; import { Button, Dropdown, Space } from 'antd';
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
{ {
@ -30,7 +30,8 @@ const items: MenuProps['items'] = [
]; ];
const App: React.FC = () => ( const App: React.FC = () => (
<> <Space direction="vertical">
<Space wrap>
<Dropdown menu={{ items }} placement="bottomLeft" arrow> <Dropdown menu={{ items }} placement="bottomLeft" arrow>
<Button>bottomLeft</Button> <Button>bottomLeft</Button>
</Dropdown> </Dropdown>
@ -40,7 +41,8 @@ const App: React.FC = () => (
<Dropdown menu={{ items }} placement="bottomRight" arrow> <Dropdown menu={{ items }} placement="bottomRight" arrow>
<Button>bottomRight</Button> <Button>bottomRight</Button>
</Dropdown> </Dropdown>
<br /> </Space>
<Space wrap>
<Dropdown menu={{ items }} placement="topLeft" arrow> <Dropdown menu={{ items }} placement="topLeft" arrow>
<Button>topLeft</Button> <Button>topLeft</Button>
</Dropdown> </Dropdown>
@ -50,7 +52,8 @@ const App: React.FC = () => (
<Dropdown menu={{ items }} placement="topRight" arrow> <Dropdown menu={{ items }} placement="topRight" arrow>
<Button>topRight</Button> <Button>topRight</Button>
</Dropdown> </Dropdown>
</> </Space>
</Space>
); );
export default App; export default App;

View File

@ -427,7 +427,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
childNode = mergedChildren(context as any); childNode = mergedChildren(context as any);
} else { } else {
warning( warning(
!mergedName.length, !mergedName.length || !!noStyle,
'usage', 'usage',
'`name` is only used for validate React element. If you are using Form.Item as layout display, please remove `name` instead.', '`name` is only used for validate React element. If you are using Form.Item as layout display, please remove `name` instead.',
); );

View File

@ -584,6 +584,15 @@ describe('Form', () => {
); );
}); });
it('No warning when use noStyle and children is empty', () => {
render(
<Form>
<Form.Item name="noWarning" noStyle />
</Form>,
);
expect(errorSpy).not.toHaveBeenCalled();
});
it('dynamic change required', async () => { it('dynamic change required', async () => {
const { container } = render( const { container } = render(
<Form> <Form>

View File

@ -148032,7 +148032,7 @@ exports[`Locale Provider should display the text as id 1`] = `
type="button" type="button"
> >
<span> <span>
OK Oke
</span> </span>
</button> </button>
</div> </div>
@ -148089,7 +148089,7 @@ exports[`Locale Provider should display the text as id 1`] = `
class="ant-transfer-list-header-selected" class="ant-transfer-list-header-selected"
> >
0 0
item data
</span> </span>
<span <span
class="ant-transfer-list-header-title" class="ant-transfer-list-header-title"
@ -148129,7 +148129,7 @@ exports[`Locale Provider should display the text as id 1`] = `
</span> </span>
<input <input
class="ant-input" class="ant-input"
placeholder="Cari" placeholder="Cari di sini"
type="text" type="text"
value="" value=""
/> />
@ -148323,7 +148323,7 @@ exports[`Locale Provider should display the text as id 1`] = `
class="ant-transfer-list-header-selected" class="ant-transfer-list-header-selected"
> >
0 0
item data
</span> </span>
<span <span
class="ant-transfer-list-header-title" class="ant-transfer-list-header-title"
@ -148363,7 +148363,7 @@ exports[`Locale Provider should display the text as id 1`] = `
</span> </span>
<input <input
class="ant-input" class="ant-input"
placeholder="Cari" placeholder="Cari di sini"
type="text" type="text"
value="" value=""
/> />
@ -149622,7 +149622,7 @@ exports[`Locale Provider should display the text as id 1`] = `
type="button" type="button"
> >
<span> <span>
OK Oke
</span> </span>
</button> </button>
</div> </div>

View File

@ -1,3 +1,4 @@
/* eslint-disable no-template-curly-in-string */
import Pagination from 'rc-pagination/lib/locale/id_ID'; import Pagination from 'rc-pagination/lib/locale/id_ID';
import type { Locale } from '.'; import type { Locale } from '.';
@ -5,45 +6,143 @@ import Calendar from '../calendar/locale/id_ID';
import DatePicker from '../date-picker/locale/id_ID'; import DatePicker from '../date-picker/locale/id_ID';
import TimePicker from '../time-picker/locale/id_ID'; import TimePicker from '../time-picker/locale/id_ID';
const typeTemplate = '${label} tidak valid ${type}';
const localeValues: Locale = { const localeValues: Locale = {
locale: 'id', locale: 'id',
Pagination, Pagination,
DatePicker, DatePicker,
TimePicker, TimePicker,
Calendar, Calendar,
global: {
placeholder: 'Silahkan pilih',
},
Table: { Table: {
filterTitle: 'Saring', filterTitle: 'Menu filter',
filterConfirm: 'OK', filterConfirm: 'Oke',
filterReset: 'Hapus', filterReset: 'Reset',
selectAll: 'Pilih semua di halaman ini', filterEmptyText: 'Tidak ada filter',
selectInvert: 'Balikkan pilihan di halaman ini', filterCheckall: 'Pilih semua item',
filterSearchPlaceholder: 'Cari di filter',
emptyText: 'Tidak ada data',
selectAll: 'Pilih halaman saat ini',
selectInvert: 'Balikkan halaman saat ini',
selectNone: 'Hapus semua data',
selectionAll: 'Pilih semua data',
sortTitle: 'Urutkan', sortTitle: 'Urutkan',
expand: 'Perluas baris',
collapse: 'Perkecil baris',
triggerDesc: 'Klik untuk mengurutkan secara menurun',
triggerAsc: 'Klik untuk mengurutkan secara menaik',
cancelSort: 'Klik untuk membatalkan pengurutan',
},
Tour: {
Next: 'Selanjutnya',
Previous: 'Sebelumnya',
Finish: 'Selesai',
}, },
Modal: { Modal: {
okText: 'OK', okText: 'Oke',
cancelText: 'Batal', cancelText: 'Batal',
justOkText: 'OK', justOkText: 'Oke',
}, },
Popconfirm: { Popconfirm: {
okText: 'OK', okText: 'Oke',
cancelText: 'Batal', cancelText: 'Batal',
}, },
Transfer: { Transfer: {
titles: ['', ''], titles: ['', ''],
searchPlaceholder: 'Cari', searchPlaceholder: 'Cari di sini',
itemUnit: 'item', itemUnit: 'data',
itemsUnit: 'item', itemsUnit: 'data',
remove: 'Hapus',
selectCurrent: 'Pilih halaman saat ini',
removeCurrent: 'Hapus halaman saat ini',
selectAll: 'Pilih semua data',
removeAll: 'Hapus semua data',
selectInvert: 'Balikkan halaman saat ini',
}, },
Upload: { Upload: {
uploading: 'Mengunggah...', uploading: 'Mengunggah...',
removeFile: 'Hapus file', removeFile: 'Hapus file',
uploadError: 'Kesalahan pengunggahan', uploadError: 'Kesalahan pengunggahan',
previewFile: 'File pratinjau', previewFile: 'Pratinjau file',
downloadFile: 'Unduh berkas', downloadFile: 'Unduh file',
}, },
Empty: { Empty: {
description: 'Tidak ada data', description: 'Tidak ada data',
}, },
Icon: {
icon: 'ikon',
},
Text: {
edit: 'Ubah',
copy: 'Salin',
copied: 'Disalin',
expand: 'Perluas',
collapse: 'Perkecil',
},
Form: {
optional: '(optional)',
defaultValidateMessages: {
default: 'Kesalahan validasi untuk ${label}',
required: 'Tolong masukkan ${label}',
enum: '${label} harus menjadi salah satu dari [${enum}]',
whitespace: '${label} tidak boleh berupa karakter kosong',
date: {
format: '${label} format tanggal tidak valid',
parse: '${label} tidak dapat diubah menjadi tanggal',
invalid: '${label} adalah tanggal yang tidak valid',
},
types: {
string: typeTemplate,
method: typeTemplate,
array: typeTemplate,
object: typeTemplate,
number: typeTemplate,
date: typeTemplate,
boolean: typeTemplate,
integer: typeTemplate,
float: typeTemplate,
regexp: typeTemplate,
email: typeTemplate,
url: typeTemplate,
hex: typeTemplate,
},
string: {
len: '${label} harus berupa ${len} karakter',
min: '${label} harus minimal ${min} karakter',
max: '${label} harus maksimal ${max} karakter',
range: '${label} harus diantara ${min}-${max} karakter',
},
number: {
len: '${label} harus sama dengan ${len}',
min: '${label} harus minimal ${min}',
max: '${label} harus maksimal ${max}',
range: '${label} harus di antara ${min}-${max}',
},
array: {
len: 'Harus ${len} ${label}',
min: 'Minimal ${min} ${label}',
max: 'Maksimal ${max} ${label}',
range: 'Jumlah ${label} harus di antara ${min}-${max}',
},
pattern: {
mismatch: '${label} tidak sesuai dengan pola ${pattern}',
},
},
},
Image: {
preview: 'Pratinjau',
},
QRCode: {
expired: 'Kode QR sudah habis masa berlakunya',
refresh: 'Segarkan',
scanned: 'Dipindai',
},
ColorPicker: {
presetEmpty: 'Kosong',
},
}; };
export default localeValues; export default localeValues;

View File

@ -128,6 +128,7 @@ const getVerticalStyle: GenerateStyle<MenuToken> = (token) => {
`border-color ${motionDurationSlow}`, `border-color ${motionDurationSlow}`,
`background ${motionDurationSlow}`, `background ${motionDurationSlow}`,
`padding ${motionDurationMid} ${motionEaseOut}`, `padding ${motionDurationMid} ${motionEaseOut}`,
`padding-inline calc(50% - ${unit(token.calc(fontSizeLG).div(2).equal())} - ${unit(itemMarginInline)})`,
].join(','), ].join(','),
[`> ${componentCls}-title-content`]: { [`> ${componentCls}-title-content`]: {

View File

@ -28,6 +28,15 @@ describe('Popover', () => {
expect(container.querySelector('.ant-popover-inner-content')).toBeTruthy(); expect(container.querySelector('.ant-popover-inner-content')).toBeTruthy();
}); });
it('should support defaultOpen', () => {
const { container } = render(
<Popover title="code" defaultOpen>
<span>show me your code</span>
</Popover>,
);
expect(container.querySelector('.ant-popover')).toBeTruthy();
});
it('shows content for render functions', () => { it('shows content for render functions', () => {
const renderTitle = () => 'some-title'; const renderTitle = () => 'some-title';
const renderContent = () => 'some-content'; const renderContent = () => 'some-content';

View File

@ -60,6 +60,7 @@ const InternalPopover = React.forwardRef<TooltipRef, PopoverProps>((props, ref)
const overlayCls = classNames(overlayClassName, hashId, cssVarCls); const overlayCls = classNames(overlayClassName, hashId, cssVarCls);
const [open, setOpen] = useMergedState(false, { const [open, setOpen] = useMergedState(false, {
value: props.open ?? props.visible, value: props.open ?? props.visible,
defaultValue: props.defaultOpen ?? props.defaultVisible,
}); });
const settingOpen = ( const settingOpen = (

View File

@ -68,4 +68,12 @@ describe('Switch', () => {
it('have static property for type detecting', () => { it('have static property for type detecting', () => {
expect(Switch.__ANT_SWITCH).toBeTruthy(); expect(Switch.__ANT_SWITCH).toBeTruthy();
}); });
it('inner element have min-height', () => {
const { container, rerender } = render(<Switch unCheckedChildren="0" size="small" />);
expect(container.querySelector('.ant-switch-inner-unchecked')).toHaveStyle('min-height: 16px');
rerender(<Switch unCheckedChildren="0" />);
expect(container.querySelector('.ant-switch-inner-unchecked')).toHaveStyle('min-height: 22px');
});
}); });

View File

@ -109,6 +109,11 @@ const genSwitchSmallStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
[`${componentCls}-inner`]: { [`${componentCls}-inner`]: {
paddingInlineStart: innerMaxMarginSM, paddingInlineStart: innerMaxMarginSM,
paddingInlineEnd: innerMinMarginSM, paddingInlineEnd: innerMinMarginSM,
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
minHeight: trackHeightSM,
},
[`${switchInnerCls}-checked`]: { [`${switchInnerCls}-checked`]: {
marginInlineStart: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`, marginInlineStart: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
marginInlineEnd: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`, marginInlineEnd: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
@ -269,6 +274,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
fontSize: token.fontSizeSM, fontSize: token.fontSizeSM,
transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`, transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`,
pointerEvents: 'none', pointerEvents: 'none',
minHeight: trackHeight,
}, },
[`${switchInnerCls}-checked`]: { [`${switchInnerCls}-checked`]: {

View File

@ -2,6 +2,8 @@ import AbstractCalculator from './calculator';
const CALC_UNIT = 'CALC_UNIT'; const CALC_UNIT = 'CALC_UNIT';
const regexp = new RegExp(CALC_UNIT, 'g');
function unit(value: string | number) { function unit(value: string | number) {
if (typeof value === 'number') { if (typeof value === 'number') {
return `${value}${CALC_UNIT}`; return `${value}${CALC_UNIT}`;
@ -77,7 +79,6 @@ export default class CSSCalculator extends AbstractCalculator {
equal(options?: { unit?: boolean }): string { equal(options?: { unit?: boolean }): string {
const { unit: cssUnit = true } = options || {}; const { unit: cssUnit = true } = options || {};
const regexp = new RegExp(`${CALC_UNIT}`, 'g');
this.result = this.result.replace(regexp, cssUnit ? 'px' : ''); this.result = this.result.replace(regexp, cssUnit ? 'px' : '');
if (typeof this.lowPriority !== 'undefined') { if (typeof this.lowPriority !== 'undefined') {
return `calc(${this.result})`; return `calc(${this.result})`;

View File

@ -5,9 +5,3 @@
## en-US ## en-US
Customize render list with Table component. Customize render list with Table component.
```css
#components-transfer-demo-table-transfer .ant-table td {
background: transparent;
}
```

View File

@ -123,7 +123,7 @@ Do it step by step:
3. Add the language support for [rc-pagination](https://github.com/react-component/pagination), for example [this](https://github.com/react-component/pagination/blob/master/src/locale/en_US.js). 3. Add the language support for [rc-pagination](https://github.com/react-component/pagination), for example [this](https://github.com/react-component/pagination/blob/master/src/locale/en_US.js).
4. Wait for `rc-picker` and `rc-pagination` to release the new version containing the above. 4. Wait for `rc-picker` and `rc-pagination` to release the new version containing the above.
5. Update the `rc-picker` and `rc-pagination` versions in `antd` and add the remaining other necessary content for the language. for example [Azerbaijani PR](https://github.com/ant-design/ant-design/pull/21387). 5. Update the `rc-picker` and `rc-pagination` versions in `antd` and add the remaining other necessary content for the language. for example [Azerbaijani PR](https://github.com/ant-design/ant-design/pull/21387).
6. Add a test case for the language in [index.test.tsx](https://github.com/ant-design/ant-design/blob/master/components/locale-provider/__tests__/index.test.tsx). 6. Add a test case for the language in [index.test.tsx](https://github.com/ant-design/ant-design/blob/master/components/locale/__tests__/index.test.tsx).
7. update snapshots, you may also need to delete `node_modules`, lock files (`yarn.lock` or `package-lock.json`) and reinstall at first. 7. update snapshots, you may also need to delete `node_modules`, lock files (`yarn.lock` or `package-lock.json`) and reinstall at first.
```bash ```bash

View File

@ -122,7 +122,7 @@ return (
3. 为 [rc-pagination](https://github.com/react-component/pagination) 添加对应语言,参考 [这个](https://github.com/react-component/pagination/blob/master/src/locale/en_US.js)。 3. 为 [rc-pagination](https://github.com/react-component/pagination) 添加对应语言,参考 [这个](https://github.com/react-component/pagination/blob/master/src/locale/en_US.js)。
4. 等待 `rc-picker``rc-pagination` 发布含上述内容的最低版本。 4. 等待 `rc-picker``rc-pagination` 发布含上述内容的最低版本。
5. 参考 [阿塞拜疆语的 PR](https://github.com/ant-design/ant-design/pull/21387) 向 `antd` 发起 PR完善对应语言的其他内容和更新 `rc-picker``rc-pagination` 版本。 5. 参考 [阿塞拜疆语的 PR](https://github.com/ant-design/ant-design/pull/21387) 向 `antd` 发起 PR完善对应语言的其他内容和更新 `rc-picker``rc-pagination` 版本。
6. 在 [index.test.tsx](https://github.com/ant-design/ant-design/blob/master/components/locale-provider/__tests__/index.test.tsx) 添加该语言的测试用例。 6. 在 [index.test.tsx](https://github.com/ant-design/ant-design/blob/master/components/locale/__tests__/index.test.tsx) 添加该语言的测试用例。
7. 更新 snapshot在这之前或许你还需要先删除 `node_modules` 和 lock 文件 `yarn.lock` or `package-lock.json` 并全新安装。 7. 更新 snapshot在这之前或许你还需要先删除 `node_modules` 和 lock 文件 `yarn.lock` or `package-lock.json` 并全新安装。
```bash ```bash

View File

@ -49,7 +49,7 @@
"build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist", "build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist",
"changelog": "npm run lint:changelog && tsx scripts/print-changelog.ts", "changelog": "npm run lint:changelog && tsx scripts/print-changelog.ts",
"check-commit": "tsx scripts/check-commit.ts", "check-commit": "tsx scripts/check-commit.ts",
"clean": "antd-tools run clean && rm -rf es lib coverage locale dist report.html artifacts.zip", "clean": "antd-tools run clean && rm -rf es lib coverage locale dist report.html artifacts.zip oss-artifacts.zip",
"clean:lockfiles": "rm -rf package-lock.json yarn.lock", "clean:lockfiles": "rm -rf package-lock.json yarn.lock",
"precompile": "npm run prestart", "precompile": "npm run prestart",
"compile": "npm run clean && antd-tools run compile", "compile": "npm run clean && antd-tools run compile",
@ -145,7 +145,7 @@
"rc-motion": "^2.9.0", "rc-motion": "^2.9.0",
"rc-notification": "~5.4.0", "rc-notification": "~5.4.0",
"rc-pagination": "~4.0.4", "rc-pagination": "~4.0.4",
"rc-picker": "~4.3.2", "rc-picker": "~4.4.1",
"rc-progress": "~4.0.0", "rc-progress": "~4.0.0",
"rc-rate": "~2.12.0", "rc-rate": "~2.12.0",
"rc-resize-observer": "^1.4.0", "rc-resize-observer": "^1.4.0",
@ -171,7 +171,7 @@
"@ant-design/tools": "^18.0.2", "@ant-design/tools": "^18.0.2",
"@antv/g6": "^4.8.24", "@antv/g6": "^4.8.24",
"@babel/eslint-plugin": "^7.23.5", "@babel/eslint-plugin": "^7.23.5",
"@biomejs/biome": "^1.6.4", "@biomejs/biome": "^1.7.0",
"@codesandbox/sandpack-react": "^2.13.8", "@codesandbox/sandpack-react": "^2.13.8",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/modifiers": "^7.0.0",
@ -214,18 +214,19 @@
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/prismjs": "^1.26.3", "@types/prismjs": "^1.26.3",
"@types/progress": "^2.0.7", "@types/progress": "^2.0.7",
"@types/qs": "^6.9.14", "@types/qs": "^6.9.15",
"@types/react": "^18.2.78", "@types/react": "^18.2.79",
"@types/react-copy-to-clipboard": "^5.0.7", "@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.2.25", "@types/react-dom": "^18.2.25",
"@types/react-highlight-words": "^0.16.7", "@types/react-highlight-words": "^0.16.7",
"@types/react-resizable": "^3.0.7", "@types/react-resizable": "^3.0.7",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"@types/spinnies": "^0.5.3",
"@types/tar": "^6.1.12", "@types/tar": "^6.1.12",
"@types/throttle-debounce": "^5.0.2", "@types/throttle-debounce": "^5.0.2",
"@types/warning": "^3.0.3", "@types/warning": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^7.6.0", "@typescript-eslint/eslint-plugin": "^7.7.0",
"@typescript-eslint/parser": "^7.6.0", "@typescript-eslint/parser": "^7.7.0",
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"ali-oss": "^6.20.0", "ali-oss": "^6.20.0",
"antd-img-crop": "^4.21.0", "antd-img-crop": "^4.21.0",
@ -266,7 +267,7 @@
"husky": "^9.0.11", "husky": "^9.0.11",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"immer": "^10.0.4", "immer": "^10.0.4",
"inquirer": "^9.2.18", "inquirer": "^9.2.19",
"is-ci": "^3.0.1", "is-ci": "^3.0.1",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"jest": "^29.7.0", "jest": "^29.7.0",
@ -319,15 +320,16 @@
"remark": "^15.0.1", "remark": "^15.0.1",
"remark-cli": "^12.0.0", "remark-cli": "^12.0.0",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"remark-lint": "^9.1.2", "remark-lint": "^10.0.0",
"remark-lint-no-undefined-references": "^4.2.1", "remark-lint-no-undefined-references": "^5.0.0",
"remark-preset-lint-recommended": "^6.1.3", "remark-preset-lint-recommended": "^7.0.0",
"remark-rehype": "^11.1.0", "remark-rehype": "^11.1.0",
"runes2": "^1.1.4", "runes2": "^1.1.4",
"semver": "^7.6.0", "semver": "^7.6.0",
"sharp": "^0.33.3", "sharp": "^0.33.3",
"simple-git": "^3.24.0", "simple-git": "^3.24.0",
"size-limit": "^11.1.2", "size-limit": "^11.1.2",
"spinnies": "^0.5.1",
"stylelint": "^16.3.1", "stylelint": "^16.3.1",
"stylelint-config-rational-order": "^0.1.2", "stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^36.0.0", "stylelint-config-standard": "^36.0.0",

View File

@ -1,18 +1,65 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase, no-async-promise-executor */
import fs from 'node:fs'; import fs from 'node:fs';
import runScript from '@npmcli/run-script'; import runScript from '@npmcli/run-script';
import { Octokit } from '@octokit/rest'; import { Octokit } from '@octokit/rest';
import AdmZip from 'adm-zip'; import AdmZip from 'adm-zip';
import axios from 'axios'; import axios from 'axios';
import chalk from 'chalk'; import chalk from 'chalk';
import cliProgress from 'cli-progress'; import Spinnies from 'spinnies';
import ora from 'ora';
import checkRepo from './check-repo'; import checkRepo from './check-repo';
const { Notification: Notifier } = require('node-notifier'); const { Notification: Notifier } = require('node-notifier');
const simpleGit = require('simple-git'); const simpleGit = require('simple-git');
const blockStatus = ['failure', 'cancelled', 'timed_out'] as const;
const spinner = { interval: 80, frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] };
const spinnies = new Spinnies({ spinner });
let spinniesId = 0;
// `spinnies` 为按条目进度,需要做简单的封装变成接近 `ora` 的形态
const showMessage = (
message: string,
status?: 'succeed' | 'fail' | 'spinning' | 'non-spinnable' | 'stopped' | true,
uniqueTitle?: string,
) => {
if (!status) {
spinnies.add(`info-${spinniesId}`, {
text: message,
status: 'non-spinnable',
});
spinniesId += 1;
} else {
const mergedId = uniqueTitle || `msg-${spinniesId}`;
let mergedMessage = uniqueTitle ? `${uniqueTitle} ${message}` : message;
// `spinnies` 对中文支持有 bug长度会按中文一半计算。我们翻个倍修复一下。
mergedMessage = `${mergedMessage}${' '.repeat(mergedMessage.length)}`;
const existSpinner = spinnies.pick(mergedId);
if (!existSpinner) {
spinnies.add(mergedId, {
text: '',
});
}
if (status === 'succeed' || status === 'fail' || status === 'stopped') {
spinnies.update(mergedId, {
text: mergedMessage,
status,
});
spinniesId += 1;
} else {
spinnies.update(mergedId, {
text: mergedMessage,
status: status === true ? 'spinning' : status,
});
}
}
};
process.on('SIGINT', () => { process.on('SIGINT', () => {
process.exit(1); process.exit(1);
}); });
@ -38,94 +85,109 @@ const emojify = (status: string = '') => {
return `${emoji || ''} ${(status || '').padEnd(15)}`; return `${emoji || ''} ${(status || '').padEnd(15)}`;
}; };
async function downloadArtifact(url: string, filepath: string) { const toMB = (bytes: number) => (bytes / 1024 / 1024).toFixed(2);
const bar = new cliProgress.SingleBar(
{ async function downloadArtifact(msgKey: string, url: string, filepath: string, token?: string) {
format: ` 下载中 [${chalk.cyan( const headers: Record<string, string> = {};
'{bar}', if (token) {
)}] {percentage}% | : {eta}s | {value}/{total}`, headers.Authorization = `token ${token}`;
}, }
cliProgress.Presets.rect,
);
bar.start(1, 0);
const response = await axios.get(url, { const response = await axios.get(url, {
headers: { headers,
Authorization: `token ${process.env.GITHUB_ACCESS_TOKEN}`,
},
responseType: 'arraybuffer', responseType: 'arraybuffer',
onDownloadProgress: (progressEvent) => { onDownloadProgress: (progressEvent) => {
bar.setTotal(progressEvent.total || 0); const { loaded, total = 0 } = progressEvent;
bar.update(progressEvent.loaded);
showMessage(
`下载进度 ${toMB(loaded)}MB/${toMB(total)}MB (${((loaded / total) * 100).toFixed(2)}%)`,
true,
msgKey,
);
}, },
}); });
fs.writeFileSync(filepath, Buffer.from(response.data)); fs.writeFileSync(filepath, Buffer.from(response.data));
return filepath;
} }
const runPrePublish = async () => { const runPrePublish = async () => {
await checkRepo(); await checkRepo();
const spinner = ora(); showMessage(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查,远程 CI 通过后方可发布'));
spinner.info(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查,远程 CI 通过后方可发布'));
const git = simpleGit(); const git = simpleGit();
const octokit = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); const octokit = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
const { current: currentBranch } = await git.branch(); const { current: currentBranch } = await git.branch();
spinner.start(`正在拉取远程分支 ${currentBranch}`); showMessage(`正在拉取远程分支 ${currentBranch}`, true);
await git.pull('origin', currentBranch); await git.pull('origin', currentBranch);
spinner.succeed(`成功拉取远程分支 ${currentBranch}`); showMessage(`成功拉取远程分支 ${currentBranch}`, 'succeed');
spinner.start(`正在推送本地分支 ${currentBranch}`); showMessage(`正在推送本地分支 ${currentBranch}`, true);
await git.push('origin', currentBranch); await git.push('origin', currentBranch);
spinner.succeed(`成功推送远程分支 ${currentBranch}`); showMessage(`成功推送远程分支 ${currentBranch}`, 'succeed');
spinner.succeed(`已经和远程分支保持同步 ${currentBranch}`); showMessage(`已经和远程分支保持同步 ${currentBranch}`, 'succeed');
const { latest } = await git.log(); const { latest } = await git.log();
spinner.succeed(`找到本地最新 commit:`); const sha = process.env.TARGET_SHA || latest.hash;
spinner.info(chalk.cyan(` hash: ${latest.hash}`));
spinner.info(chalk.cyan(` date: ${latest.date}`)); showMessage(`找到本地最新 commit:`, 'succeed');
spinner.info(chalk.cyan(` message: ${latest.message}`)); showMessage(chalk.cyan(` hash: ${sha}`));
spinner.info(chalk.cyan(` author_name: ${latest.author_name}`)); showMessage(chalk.cyan(` date: ${latest.date}`));
showMessage(chalk.cyan(` message: ${latest.message}`));
showMessage(chalk.cyan(` author_name: ${latest.author_name}`));
const owner = 'ant-design'; const owner = 'ant-design';
const repo = 'ant-design'; const repo = 'ant-design';
spinner.start(`开始检查远程分支 ${currentBranch} 的 CI 状态`); showMessage(`开始检查远程分支 ${currentBranch} 的 CI 状态`, true);
const failureUrlList: string[] = [];
const { const {
data: { check_runs }, data: { check_runs },
} = await octokit.checks.listForRef({ } = await octokit.checks.listForRef({
owner, owner,
repo, repo,
ref: latest.hash, ref: sha,
}); });
spinner.succeed(`远程分支 CI 状态:`); showMessage(`远程分支 CI 状态(${check_runs.length})`, 'succeed');
check_runs.forEach((run) => { check_runs.forEach((run) => {
spinner.info( showMessage(` ${run.name.padEnd(36)} ${emojify(run.status)} ${emojify(run.conclusion || '')}`);
` ${run.name.padEnd(36)} ${emojify(run.status)} ${emojify(run.conclusion || '')}`, if (blockStatus.some((status) => run.conclusion === status)) {
); failureUrlList.push(run.html_url!);
}
}); });
const conclusions = check_runs.map((run) => run.conclusion); const conclusions = check_runs.map((run) => run.conclusion);
if ( if (blockStatus.some((status) => conclusions.includes(status))) {
conclusions.includes('failure') || showMessage(chalk.bgRedBright('远程分支 CI 执行异常,无法继续发布,请尝试修复或重试'), 'fail');
conclusions.includes('cancelled') || showMessage(` 点此查看状态https://github.com/${owner}/${repo}/commit/${sha}`);
conclusions.includes('timed_out')
) { failureUrlList.forEach((url) => {
spinner.fail(chalk.bgRedBright('远程分支 CI 执行异常,无法继续发布,请尝试修复或重试')); showMessage(` - ${url}`);
spinner.info(` 点此查看状态https://github.com/${owner}/${repo}/commit/${latest.hash}`); });
process.exit(1); process.exit(1);
} }
const statuses = check_runs.map((run) => run.status); const statuses = check_runs.map((run) => run.status);
if (check_runs.length < 1 || statuses.includes('queued') || statuses.includes('in_progress')) { if (check_runs.length < 1 || statuses.includes('queued') || statuses.includes('in_progress')) {
spinner.fail(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试')); showMessage(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试'), 'fail');
spinner.info(` 点此查看状态https://github.com/${owner}/${repo}/commit/${latest.hash}`); showMessage(` 点此查看状态https://github.com/${owner}/${repo}/commit/${sha}`);
process.exit(1); process.exit(1);
} }
spinner.succeed(`远程分支 CI 已通过`); showMessage(`远程分支 CI 已通过`, 'succeed');
// clean up // clean up
await runScript({ event: 'clean', path: '.', stdio: 'inherit' }); await runScript({ event: 'clean', path: '.', stdio: 'inherit' });
spinner.succeed(`成功清理构建产物目录`); showMessage(`成功清理构建产物目录`, 'succeed');
spinner.start(`开始查找远程分支构建产物`);
// 从 github artifact 中下载产物
const downloadArtifactPromise = Promise.resolve().then(async () => {
showMessage('开始查找远程分支构建产物', true, '[Github]');
const { const {
data: { workflow_runs }, data: { workflow_runs },
} = await octokit.rest.actions.listWorkflowRunsForRepo({ } = await octokit.rest.actions.listWorkflowRunsForRepo({
owner, owner,
repo, repo,
head_sha: latest.hash, head_sha: sha,
per_page: 100, per_page: 100,
exclude_pull_requests: true, exclude_pull_requests: true,
event: 'push', event: 'push',
@ -135,9 +197,9 @@ const runPrePublish = async () => {
}); });
const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test'); const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test');
if (!testWorkflowRun) { if (!testWorkflowRun) {
spinner.fail(chalk.bgRedBright('找不到远程构建工作流')); throw new Error('找不到远程构建工作流');
process.exit(1);
} }
const { const {
data: { artifacts }, data: { artifacts },
} = await octokit.actions.listWorkflowRunArtifacts({ } = await octokit.actions.listWorkflowRunArtifacts({
@ -147,27 +209,69 @@ const runPrePublish = async () => {
}); });
const artifact = artifacts.find((item) => item.name === 'build artifacts'); const artifact = artifacts.find((item) => item.name === 'build artifacts');
if (!artifact) { if (!artifact) {
spinner.fail(chalk.bgRedBright('找不到远程构建产物')); throw new Error('找不到远程构建产物');
process.exit(1);
} }
spinner.info(`准备从远程分支下载构建产物`);
showMessage(`准备从远程分支下载构建产物`, true, '[Github]');
const { url } = await octokit.rest.actions.downloadArtifact.endpoint({ const { url } = await octokit.rest.actions.downloadArtifact.endpoint({
owner, owner,
repo, repo,
artifact_id: artifact.id, artifact_id: artifact.id,
archive_format: 'zip', archive_format: 'zip',
}); });
await downloadArtifact(url, 'artifacts.zip');
spinner.info(); // 返回下载后的文件路径
spinner.succeed(`成功从远程分支下载构建产物`); return downloadArtifact('[Github]', url, 'artifacts.zip', process.env.GITHUB_ACCESS_TOKEN);
});
downloadArtifactPromise
.then(() => {
showMessage(`成功下载构建产物`, 'succeed', '[Github]');
})
.catch((e: Error) => {
showMessage(chalk.bgRedBright(e.message), 'fail', '[Github]');
});
// 从 OSS 下载产物
const downloadOSSPromise = Promise.resolve().then(async () => {
const url = `https://antd-visual-diff.oss-cn-shanghai.aliyuncs.com/${sha}/oss-artifacts.zip`;
showMessage(`准备从远程 OSS 下载构建产物`, true, '[OSS]');
// 返回下载后的文件路径
return downloadArtifact('[OSS]', url, 'oss-artifacts.zip');
});
downloadOSSPromise
.then(() => {
showMessage(`成功下载构建产物`, 'succeed', '[OSS]');
})
.catch((e: Error) => {
showMessage(chalk.bgRedBright(e.message), 'fail', '[OSS]');
});
// 任意一个完成,则完成
let firstArtifactFile: string;
try {
// @ts-ignore
firstArtifactFile = await Promise.any([downloadArtifactPromise, downloadOSSPromise]);
} catch (error) {
showMessage(
chalk.bgRedBright(`下载失败,请确认你当前 ${sha.slice(0, 6)} 位于 master 分支中`),
'fail',
);
process.exit(1);
}
showMessage(`成功从远程分支下载构建产物`, 'succeed');
// unzip // unzip
spinner.start(`正在解压构建产物`); showMessage(`正在解压构建产物`, true);
const zip = new AdmZip('artifacts.zip'); const zip = new AdmZip(firstArtifactFile);
zip.extractAllTo('./', true); zip.extractAllTo('./', true);
spinner.succeed(`成功解压构建产物`); showMessage(`成功解压构建产物`, 'succeed');
await runScript({ event: 'test:dekko', path: '.', stdio: 'inherit' }); await runScript({ event: 'test:dekko', path: '.', stdio: 'inherit' });
await runScript({ event: 'test:package-diff', path: '.', stdio: 'inherit' }); await runScript({ event: 'test:package-diff', path: '.', stdio: 'inherit' });
spinner.succeed(`文件检查通过,准备发布!`); showMessage(`文件检查通过,准备发布!`, 'succeed');
new Notifier().notify({ new Notifier().notify({
title: '✅ 准备发布到 npm', title: '✅ 准备发布到 npm',

View File

@ -4,8 +4,8 @@ import React from 'react';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs'; import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import fse from 'fs-extra';
import { globSync } from 'glob'; import { globSync } from 'glob';
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import MockDate from 'mockdate'; import MockDate from 'mockdate';
import ReactDOMServer from 'react-dom/server'; import ReactDOMServer from 'react-dom/server';
@ -17,12 +17,8 @@ import { TriggerMockContext } from './demoTestContext';
jest.mock('../../components/grid/hooks/useBreakpoint', () => () => ({})); jest.mock('../../components/grid/hooks/useBreakpoint', () => () => ({}));
const toMatchImageSnapshot = configureToMatchImageSnapshot({ const snapshotPath = path.join(process.cwd(), 'imageSnapshots');
customSnapshotsDir: `${process.cwd()}/imageSnapshots`, fse.ensureDirSync(snapshotPath);
customDiffDir: `${process.cwd()}/imageDiffSnapshots`,
});
expect.extend({ toMatchImageSnapshot });
const themes = { const themes = {
default: theme.defaultAlgorithm, default: theme.defaultAlgorithm,
@ -45,7 +41,7 @@ export default function imageTest(
let doc: Document; let doc: Document;
let container: HTMLDivElement; let container: HTMLDivElement;
beforeAll(() => { beforeAll(async () => {
const dom = new JSDOM('<!DOCTYPE html><body></body></p>', { const dom = new JSDOM('<!DOCTYPE html><body></body></p>', {
url: 'http://localhost/', url: 'http://localhost/',
}); });
@ -103,6 +99,8 @@ export default function imageTest(
// Fill window // Fill window
fillWindowEnv(win); fillWindowEnv(win);
await page.setRequestInterception(true);
}); });
beforeEach(() => { beforeEach(() => {
@ -112,8 +110,8 @@ export default function imageTest(
function test(name: string, suffix: string, themedComponent: React.ReactElement) { function test(name: string, suffix: string, themedComponent: React.ReactElement) {
it(name, async () => { it(name, async () => {
await jestPuppeteer.resetPage(); await page.setViewport({ width: 800, height: 600 });
await page.setRequestInterception(true);
const onRequestHandle = (request: any) => { const onRequestHandle = (request: any) => {
if (['image'].includes(request.resourceType())) { if (['image'].includes(request.resourceType())) {
request.abort(); request.abort();
@ -212,9 +210,7 @@ export default function imageTest(
fullPage: !options.onlyViewport, fullPage: !options.onlyViewport,
}); });
expect(image).toMatchImageSnapshot({ await fse.writeFile(path.join(snapshotPath, `${identifier}${suffix}.png`), image);
customSnapshotIdentifier: `${identifier}${suffix}`,
});
MockDate.reset(); MockDate.reset();
page.off('request', onRequestHandle); page.off('request', onRequestHandle);