mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 14:13:37 +08:00
Merge pull request #48524 from ant-design/master
chore: merge master into feature
This commit is contained in:
commit
27c942a00b
@ -1,39 +1,27 @@
|
||||
import React from 'react';
|
||||
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 useLocale from '../../../hooks/useLocale';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const { boxShadowSecondary } = token;
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
return {
|
||||
card: css`
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
card: css`
|
||||
position: relative;
|
||||
display: flex;
|
||||
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};
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
box-shadow: ${boxShadowSecondary};
|
||||
color: inherit;
|
||||
.ant-card-cover {
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
image: css`
|
||||
width: calc(100% + 2px);
|
||||
max-width: none;
|
||||
height: 184px;
|
||||
margin: -1px -1px 0;
|
||||
object-fit: cover;
|
||||
`,
|
||||
badge: css`
|
||||
img {
|
||||
transition: all ${token.motionDurationSlow} ease-out;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
transform: scale(1.3);
|
||||
`,
|
||||
badge: css`
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
@ -42,25 +30,12 @@ const useStyle = createStyles(({ token, css }) => {
|
||||
font-size: ${token.fontSizeSM}px;
|
||||
line-height: 1;
|
||||
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);
|
||||
display: inline-flex;
|
||||
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 = {
|
||||
title: string;
|
||||
@ -91,38 +66,33 @@ const ResourceCard: React.FC<ResourceCardProps> = ({ resource }) => {
|
||||
const { styles } = useStyle();
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
const { title: titleStr, description, cover, src, official } = resource;
|
||||
const { title, description, cover, src, official } = resource;
|
||||
|
||||
let coverColor: string | null = null;
|
||||
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];
|
||||
}
|
||||
const badge = official ? (
|
||||
<div className={styles.badge}>{locale.official}</div>
|
||||
) : (
|
||||
<Tooltip title={locale.thirdPartDesc}>
|
||||
<div className={styles.badge}>
|
||||
<ExclamationCircleOutlined />
|
||||
{locale.thirdPart}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<Col xs={24} sm={12} md={8} lg={6} style={{ padding: 12 }}>
|
||||
<Col xs={24} sm={12} md={8} lg={6}>
|
||||
<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>
|
||||
) : (
|
||||
<Tooltip title={locale.thirdPartDesc}>
|
||||
<div className={styles.badge}>
|
||||
<ExclamationCircleOutlined />
|
||||
{locale.thirdPart}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<p className={styles?.title}>{title}</p>
|
||||
<p className={styles.description}>{description}</p>
|
||||
<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>
|
||||
</Col>
|
||||
);
|
||||
@ -133,7 +103,7 @@ export type ResourceCardsProps = {
|
||||
};
|
||||
|
||||
const ResourceCards: React.FC<ResourceCardsProps> = ({ resources }) => (
|
||||
<Row style={{ margin: '-12px -12px 0 -12px' }}>
|
||||
<Row gutter={[24, 24]}>
|
||||
{resources.map((item) => (
|
||||
<ResourceCard resource={item} key={item?.title} />
|
||||
))}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { createHash } from 'crypto';
|
||||
import fs from 'fs';
|
||||
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 ReactTechStack from 'dumi/dist/techStacks/react';
|
||||
import chalk from 'chalk';
|
||||
import sylvanas from 'sylvanas';
|
||||
import createEmotionServer from '@emotion/server/create-instance';
|
||||
|
||||
import localPackage from '../../package.json';
|
||||
|
||||
function extractEmotionStyle(html: string) {
|
||||
@ -53,13 +54,65 @@ class AntdReactTechStack extends ReactTechStack {
|
||||
|
||||
if (md) {
|
||||
// extract description & css style from md file
|
||||
const description = md.match(
|
||||
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];
|
||||
const blocks: Record<string, string> = {};
|
||||
|
||||
props.description ??= description?.trim();
|
||||
props.style ??= style;
|
||||
const lines = md.split('\n');
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -6,7 +6,7 @@ Your pull requests will be merged after one of the collaborators approve.
|
||||
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 ...
|
||||
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE/pr_cn.md
vendored
2
.github/PULL_REQUEST_TEMPLATE/pr_cn.md
vendored
@ -6,7 +6,7 @@
|
||||
请确保填写以下 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)
|
||||
|
||||
### 🤔 这个变动的性质是?
|
||||
|
||||
|
1
.github/workflows/size-limit.yml
vendored
1
.github/workflows/size-limit.yml
vendored
@ -5,6 +5,7 @@ on:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@ -287,6 +287,7 @@ jobs:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
CI: 1
|
||||
|
||||
# Artifact build files
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
with:
|
||||
@ -297,6 +298,17 @@ jobs:
|
||||
es
|
||||
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:
|
||||
name: module test
|
||||
runs-on: ubuntu-latest
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -45,6 +45,7 @@ components/**/*.jsx
|
||||
/.history
|
||||
*.tmp
|
||||
artifacts.zip
|
||||
oss-artifacts.zip
|
||||
server/
|
||||
|
||||
# Docs templates
|
||||
|
@ -1,9 +1,7 @@
|
||||
const config = {
|
||||
plugins: [
|
||||
'remark-preset-lint-recommended',
|
||||
['remark-lint-list-item-indent', 'space'],
|
||||
['remark-lint-no-literal-urls', false],
|
||||
['remark-lint-no-undefined-references', false],
|
||||
['remark-lint-no-undefined-references', { allow: [' ', /RFC/] }],
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -307,7 +307,7 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => {
|
||||
|
||||
[`${componentCls}-body`]: {
|
||||
padding: cardPaddingBase,
|
||||
borderRadius: ` 0 0 ${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)}`,
|
||||
borderRadius: `0 0 ${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)}`,
|
||||
...clearFix(),
|
||||
},
|
||||
|
||||
|
@ -124,7 +124,9 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
|
||||
[`${componentCls}-arrow`]: {
|
||||
...resetIcon(),
|
||||
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: {
|
||||
transition: `transform ${motionDurationSlow}`,
|
||||
},
|
||||
@ -231,7 +233,7 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
|
||||
const genArrowStyle: GenerateStyle<CollapseToken> = (token) => {
|
||||
const { componentCls } = token;
|
||||
|
||||
const fixedSelector = `> ${componentCls}-item > ${componentCls}-header ${componentCls}-arrow svg`;
|
||||
const fixedSelector = `> ${componentCls}-item > ${componentCls}-header ${componentCls}-arrow`;
|
||||
|
||||
return {
|
||||
[`${componentCls}-rtl`]: {
|
||||
|
@ -698,4 +698,30 @@ describe('ColorPicker', () => {
|
||||
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
|
||||
).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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,6 +4,8 @@ import type { Color } from '../color';
|
||||
import type { ColorValueType } from '../interface';
|
||||
import { generateColor } from '../util';
|
||||
|
||||
const INIT_COLOR_REF = {} as ColorValueType;
|
||||
|
||||
function hasValue(value?: ColorValueType) {
|
||||
return value !== undefined;
|
||||
}
|
||||
@ -33,7 +35,14 @@ const useColorState = (
|
||||
prevColor.current = color;
|
||||
};
|
||||
|
||||
const prevValue = useRef<ColorValueType | undefined>(INIT_COLOR_REF);
|
||||
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)) {
|
||||
const newColor = generateColor(value || '');
|
||||
if (prevColor.current.cleared === true) {
|
||||
|
@ -42620,6 +42620,601 @@ exports[`renders components/date-picker/demo/multiple-debug.tsx extend context c
|
||||
<div
|
||||
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
|
||||
class="ant-picker ant-picker-multiple ant-picker-small ant-picker-outlined"
|
||||
>
|
||||
|
@ -3315,6 +3315,50 @@ exports[`renders components/date-picker/demo/multiple-debug.tsx correctly 1`] =
|
||||
<div
|
||||
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 ant-picker-multiple ant-picker-small ant-picker-outlined"
|
||||
>
|
||||
|
@ -6,6 +6,7 @@ const defaultValue = new Array(10).fill(0).map((_, index) => dayjs('2000-01-01')
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex vertical gap="small">
|
||||
<DatePicker multiple placeholder="Bamboo" />
|
||||
<DatePicker multiple defaultValue={defaultValue} size="small" />
|
||||
<DatePicker multiple defaultValue={defaultValue} />
|
||||
<DatePicker multiple defaultValue={defaultValue} size="large" />
|
||||
|
@ -65,15 +65,33 @@ const genPickerMultipleStyle: GenerateStyle<PickerToken> = (token) => {
|
||||
{
|
||||
[`${componentCls}${componentCls}-multiple`]: {
|
||||
width: '100%',
|
||||
cursor: 'text',
|
||||
|
||||
// ==================== Selector =====================
|
||||
[`${componentCls}-selector`]: {
|
||||
flex: 'auto',
|
||||
padding: 0,
|
||||
position: 'relative',
|
||||
|
||||
'&:after': {
|
||||
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 ====================
|
||||
|
@ -562,7 +562,7 @@ export const genPanelStyle = (token: SharedPickerToken): CSSObject => {
|
||||
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: token.colorTextTertiary,
|
||||
borderRadius: 4,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
},
|
||||
|
||||
// For Firefox
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,111 +1,197 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders components/dropdown/demo/arrow.tsx correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
<div
|
||||
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
<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
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottom
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span>
|
||||
bottom
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</button>,
|
||||
<br />,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
top
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</button>,
|
||||
]
|
||||
<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
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
top
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/dropdown/demo/arrow-center.tsx correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
<div
|
||||
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
<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
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottom
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span>
|
||||
bottom
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</button>,
|
||||
<br />,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
top
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</button>,
|
||||
]
|
||||
<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
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
top
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/dropdown/demo/basic.tsx correctly 1`] = `
|
||||
|
@ -5,15 +5,3 @@
|
||||
## en-US
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Button, Dropdown } from 'antd';
|
||||
import { Button, Dropdown, Space } from 'antd';
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
@ -30,27 +30,30 @@ const items: MenuProps['items'] = [
|
||||
];
|
||||
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<Dropdown menu={{ items }} placement="bottomLeft" arrow={{ pointAtCenter: true }}>
|
||||
<Button>bottomLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottom" arrow={{ pointAtCenter: true }}>
|
||||
<Button>bottom</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottomRight" arrow={{ pointAtCenter: true }}>
|
||||
<Button>bottomRight</Button>
|
||||
</Dropdown>
|
||||
<br />
|
||||
<Dropdown menu={{ items }} placement="topLeft" arrow={{ pointAtCenter: true }}>
|
||||
<Button>topLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="top" arrow={{ pointAtCenter: true }}>
|
||||
<Button>top</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="topRight" arrow={{ pointAtCenter: true }}>
|
||||
<Button>topRight</Button>
|
||||
</Dropdown>
|
||||
</>
|
||||
<Space direction="vertical">
|
||||
<Space wrap>
|
||||
<Dropdown menu={{ items }} placement="bottomLeft" arrow={{ pointAtCenter: true }}>
|
||||
<Button>bottomLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottom" arrow={{ pointAtCenter: true }}>
|
||||
<Button>bottom</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottomRight" arrow={{ pointAtCenter: true }}>
|
||||
<Button>bottomRight</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<Dropdown menu={{ items }} placement="topLeft" arrow={{ pointAtCenter: true }}>
|
||||
<Button>topLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="top" arrow={{ pointAtCenter: true }}>
|
||||
<Button>top</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="topRight" arrow={{ pointAtCenter: true }}>
|
||||
<Button>topRight</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
@ -5,15 +5,3 @@
|
||||
## en-US
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Button, Dropdown } from 'antd';
|
||||
import { Button, Dropdown, Space } from 'antd';
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
@ -30,27 +30,30 @@ const items: MenuProps['items'] = [
|
||||
];
|
||||
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<Dropdown menu={{ items }} placement="bottomLeft" arrow>
|
||||
<Button>bottomLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottom" arrow>
|
||||
<Button>bottom</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottomRight" arrow>
|
||||
<Button>bottomRight</Button>
|
||||
</Dropdown>
|
||||
<br />
|
||||
<Dropdown menu={{ items }} placement="topLeft" arrow>
|
||||
<Button>topLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="top" arrow>
|
||||
<Button>top</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="topRight" arrow>
|
||||
<Button>topRight</Button>
|
||||
</Dropdown>
|
||||
</>
|
||||
<Space direction="vertical">
|
||||
<Space wrap>
|
||||
<Dropdown menu={{ items }} placement="bottomLeft" arrow>
|
||||
<Button>bottomLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottom" arrow>
|
||||
<Button>bottom</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="bottomRight" arrow>
|
||||
<Button>bottomRight</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<Dropdown menu={{ items }} placement="topLeft" arrow>
|
||||
<Button>topLeft</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="top" arrow>
|
||||
<Button>top</Button>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items }} placement="topRight" arrow>
|
||||
<Button>topRight</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
@ -427,7 +427,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
||||
childNode = mergedChildren(context as any);
|
||||
} else {
|
||||
warning(
|
||||
!mergedName.length,
|
||||
!mergedName.length || !!noStyle,
|
||||
'usage',
|
||||
'`name` is only used for validate React element. If you are using Form.Item as layout display, please remove `name` instead.',
|
||||
);
|
||||
|
@ -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 () => {
|
||||
const { container } = render(
|
||||
<Form>
|
||||
|
@ -148032,7 +148032,7 @@ exports[`Locale Provider should display the text as id 1`] = `
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
OK
|
||||
Oke
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -148089,7 +148089,7 @@ exports[`Locale Provider should display the text as id 1`] = `
|
||||
class="ant-transfer-list-header-selected"
|
||||
>
|
||||
0
|
||||
item
|
||||
data
|
||||
</span>
|
||||
<span
|
||||
class="ant-transfer-list-header-title"
|
||||
@ -148129,7 +148129,7 @@ exports[`Locale Provider should display the text as id 1`] = `
|
||||
</span>
|
||||
<input
|
||||
class="ant-input"
|
||||
placeholder="Cari"
|
||||
placeholder="Cari di sini"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
@ -148323,7 +148323,7 @@ exports[`Locale Provider should display the text as id 1`] = `
|
||||
class="ant-transfer-list-header-selected"
|
||||
>
|
||||
0
|
||||
item
|
||||
data
|
||||
</span>
|
||||
<span
|
||||
class="ant-transfer-list-header-title"
|
||||
@ -148363,7 +148363,7 @@ exports[`Locale Provider should display the text as id 1`] = `
|
||||
</span>
|
||||
<input
|
||||
class="ant-input"
|
||||
placeholder="Cari"
|
||||
placeholder="Cari di sini"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
@ -149622,7 +149622,7 @@ exports[`Locale Provider should display the text as id 1`] = `
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
OK
|
||||
Oke
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
import Pagination from 'rc-pagination/lib/locale/id_ID';
|
||||
|
||||
import type { Locale } from '.';
|
||||
@ -5,45 +6,143 @@ import Calendar from '../calendar/locale/id_ID';
|
||||
import DatePicker from '../date-picker/locale/id_ID';
|
||||
import TimePicker from '../time-picker/locale/id_ID';
|
||||
|
||||
const typeTemplate = '${label} tidak valid ${type}';
|
||||
|
||||
const localeValues: Locale = {
|
||||
locale: 'id',
|
||||
Pagination,
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
Calendar,
|
||||
global: {
|
||||
placeholder: 'Silahkan pilih',
|
||||
},
|
||||
Table: {
|
||||
filterTitle: 'Saring',
|
||||
filterConfirm: 'OK',
|
||||
filterReset: 'Hapus',
|
||||
selectAll: 'Pilih semua di halaman ini',
|
||||
selectInvert: 'Balikkan pilihan di halaman ini',
|
||||
filterTitle: 'Menu filter',
|
||||
filterConfirm: 'Oke',
|
||||
filterReset: 'Reset',
|
||||
filterEmptyText: 'Tidak ada filter',
|
||||
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',
|
||||
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: {
|
||||
okText: 'OK',
|
||||
okText: 'Oke',
|
||||
cancelText: 'Batal',
|
||||
justOkText: 'OK',
|
||||
justOkText: 'Oke',
|
||||
},
|
||||
Popconfirm: {
|
||||
okText: 'OK',
|
||||
okText: 'Oke',
|
||||
cancelText: 'Batal',
|
||||
},
|
||||
Transfer: {
|
||||
titles: ['', ''],
|
||||
searchPlaceholder: 'Cari',
|
||||
itemUnit: 'item',
|
||||
itemsUnit: 'item',
|
||||
searchPlaceholder: 'Cari di sini',
|
||||
itemUnit: 'data',
|
||||
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: {
|
||||
uploading: 'Mengunggah...',
|
||||
removeFile: 'Hapus file',
|
||||
uploadError: 'Kesalahan pengunggahan',
|
||||
previewFile: 'File pratinjau',
|
||||
downloadFile: 'Unduh berkas',
|
||||
previewFile: 'Pratinjau file',
|
||||
downloadFile: 'Unduh file',
|
||||
},
|
||||
Empty: {
|
||||
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;
|
||||
|
@ -128,6 +128,7 @@ 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`]: {
|
||||
|
@ -28,6 +28,15 @@ describe('Popover', () => {
|
||||
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', () => {
|
||||
const renderTitle = () => 'some-title';
|
||||
const renderContent = () => 'some-content';
|
||||
|
@ -60,6 +60,7 @@ const InternalPopover = React.forwardRef<TooltipRef, PopoverProps>((props, ref)
|
||||
const overlayCls = classNames(overlayClassName, hashId, cssVarCls);
|
||||
const [open, setOpen] = useMergedState(false, {
|
||||
value: props.open ?? props.visible,
|
||||
defaultValue: props.defaultOpen ?? props.defaultVisible,
|
||||
});
|
||||
|
||||
const settingOpen = (
|
||||
|
@ -68,4 +68,12 @@ describe('Switch', () => {
|
||||
it('have static property for type detecting', () => {
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
@ -109,6 +109,11 @@ const genSwitchSmallStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
|
||||
[`${componentCls}-inner`]: {
|
||||
paddingInlineStart: innerMaxMarginSM,
|
||||
paddingInlineEnd: innerMinMarginSM,
|
||||
|
||||
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
|
||||
minHeight: trackHeightSM,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
marginInlineStart: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
|
||||
marginInlineEnd: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
|
||||
@ -269,6 +274,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
|
||||
fontSize: token.fontSizeSM,
|
||||
transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`,
|
||||
pointerEvents: 'none',
|
||||
minHeight: trackHeight,
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
|
@ -2,6 +2,8 @@ import AbstractCalculator from './calculator';
|
||||
|
||||
const CALC_UNIT = 'CALC_UNIT';
|
||||
|
||||
const regexp = new RegExp(CALC_UNIT, 'g');
|
||||
|
||||
function unit(value: string | number) {
|
||||
if (typeof value === 'number') {
|
||||
return `${value}${CALC_UNIT}`;
|
||||
@ -77,7 +79,6 @@ export default class CSSCalculator extends AbstractCalculator {
|
||||
|
||||
equal(options?: { unit?: boolean }): string {
|
||||
const { unit: cssUnit = true } = options || {};
|
||||
const regexp = new RegExp(`${CALC_UNIT}`, 'g');
|
||||
this.result = this.result.replace(regexp, cssUnit ? 'px' : '');
|
||||
if (typeof this.lowPriority !== 'undefined') {
|
||||
return `calc(${this.result})`;
|
||||
|
@ -5,9 +5,3 @@
|
||||
## en-US
|
||||
|
||||
Customize render list with Table component.
|
||||
|
||||
```css
|
||||
#components-transfer-demo-table-transfer .ant-table td {
|
||||
background: transparent;
|
||||
}
|
||||
```
|
||||
|
@ -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).
|
||||
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).
|
||||
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.
|
||||
|
||||
```bash
|
||||
|
@ -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)。
|
||||
4. 等待 `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`) 并全新安装。
|
||||
|
||||
```bash
|
||||
|
24
package.json
24
package.json
@ -49,7 +49,7 @@
|
||||
"build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist",
|
||||
"changelog": "npm run lint:changelog && tsx scripts/print-changelog.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",
|
||||
"precompile": "npm run prestart",
|
||||
"compile": "npm run clean && antd-tools run compile",
|
||||
@ -145,7 +145,7 @@
|
||||
"rc-motion": "^2.9.0",
|
||||
"rc-notification": "~5.4.0",
|
||||
"rc-pagination": "~4.0.4",
|
||||
"rc-picker": "~4.3.2",
|
||||
"rc-picker": "~4.4.1",
|
||||
"rc-progress": "~4.0.0",
|
||||
"rc-rate": "~2.12.0",
|
||||
"rc-resize-observer": "^1.4.0",
|
||||
@ -171,7 +171,7 @@
|
||||
"@ant-design/tools": "^18.0.2",
|
||||
"@antv/g6": "^4.8.24",
|
||||
"@babel/eslint-plugin": "^7.23.5",
|
||||
"@biomejs/biome": "^1.6.4",
|
||||
"@biomejs/biome": "^1.7.0",
|
||||
"@codesandbox/sandpack-react": "^2.13.8",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
@ -214,18 +214,19 @@
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/prismjs": "^1.26.3",
|
||||
"@types/progress": "^2.0.7",
|
||||
"@types/qs": "^6.9.14",
|
||||
"@types/react": "^18.2.78",
|
||||
"@types/qs": "^6.9.15",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-copy-to-clipboard": "^5.0.7",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@types/react-highlight-words": "^0.16.7",
|
||||
"@types/react-resizable": "^3.0.7",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/spinnies": "^0.5.3",
|
||||
"@types/tar": "^6.1.12",
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@types/warning": "^3.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
||||
"@typescript-eslint/parser": "^7.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"adm-zip": "^0.5.12",
|
||||
"ali-oss": "^6.20.0",
|
||||
"antd-img-crop": "^4.21.0",
|
||||
@ -266,7 +267,7 @@
|
||||
"husky": "^9.0.11",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"immer": "^10.0.4",
|
||||
"inquirer": "^9.2.18",
|
||||
"inquirer": "^9.2.19",
|
||||
"is-ci": "^3.0.1",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jest": "^29.7.0",
|
||||
@ -319,15 +320,16 @@
|
||||
"remark": "^15.0.1",
|
||||
"remark-cli": "^12.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-lint": "^9.1.2",
|
||||
"remark-lint-no-undefined-references": "^4.2.1",
|
||||
"remark-preset-lint-recommended": "^6.1.3",
|
||||
"remark-lint": "^10.0.0",
|
||||
"remark-lint-no-undefined-references": "^5.0.0",
|
||||
"remark-preset-lint-recommended": "^7.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"runes2": "^1.1.4",
|
||||
"semver": "^7.6.0",
|
||||
"sharp": "^0.33.3",
|
||||
"simple-git": "^3.24.0",
|
||||
"size-limit": "^11.1.2",
|
||||
"spinnies": "^0.5.1",
|
||||
"stylelint": "^16.3.1",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
|
@ -1,18 +1,65 @@
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable camelcase, no-async-promise-executor */
|
||||
import fs from 'node:fs';
|
||||
import runScript from '@npmcli/run-script';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import AdmZip from 'adm-zip';
|
||||
import axios from 'axios';
|
||||
import chalk from 'chalk';
|
||||
import cliProgress from 'cli-progress';
|
||||
import ora from 'ora';
|
||||
import Spinnies from 'spinnies';
|
||||
|
||||
import checkRepo from './check-repo';
|
||||
|
||||
const { Notification: Notifier } = require('node-notifier');
|
||||
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.exit(1);
|
||||
});
|
||||
@ -38,136 +85,193 @@ const emojify = (status: string = '') => {
|
||||
return `${emoji || ''} ${(status || '').padEnd(15)}`;
|
||||
};
|
||||
|
||||
async function downloadArtifact(url: string, filepath: string) {
|
||||
const bar = new cliProgress.SingleBar(
|
||||
{
|
||||
format: ` 下载中 [${chalk.cyan(
|
||||
'{bar}',
|
||||
)}] {percentage}% | 预计还剩: {eta}s | {value}/{total}`,
|
||||
},
|
||||
cliProgress.Presets.rect,
|
||||
);
|
||||
bar.start(1, 0);
|
||||
const toMB = (bytes: number) => (bytes / 1024 / 1024).toFixed(2);
|
||||
|
||||
async function downloadArtifact(msgKey: string, url: string, filepath: string, token?: string) {
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) {
|
||||
headers.Authorization = `token ${token}`;
|
||||
}
|
||||
|
||||
const response = await axios.get(url, {
|
||||
headers: {
|
||||
Authorization: `token ${process.env.GITHUB_ACCESS_TOKEN}`,
|
||||
},
|
||||
headers,
|
||||
responseType: 'arraybuffer',
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
bar.setTotal(progressEvent.total || 0);
|
||||
bar.update(progressEvent.loaded);
|
||||
const { loaded, total = 0 } = progressEvent;
|
||||
|
||||
showMessage(
|
||||
`下载进度 ${toMB(loaded)}MB/${toMB(total)}MB (${((loaded / total) * 100).toFixed(2)}%)`,
|
||||
true,
|
||||
msgKey,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
fs.writeFileSync(filepath, Buffer.from(response.data));
|
||||
|
||||
return filepath;
|
||||
}
|
||||
|
||||
const runPrePublish = async () => {
|
||||
await checkRepo();
|
||||
const spinner = ora();
|
||||
spinner.info(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查,远程 CI 通过后方可发布'));
|
||||
showMessage(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查,远程 CI 通过后方可发布'));
|
||||
const git = simpleGit();
|
||||
const octokit = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
|
||||
const { current: currentBranch } = await git.branch();
|
||||
|
||||
spinner.start(`正在拉取远程分支 ${currentBranch}`);
|
||||
showMessage(`正在拉取远程分支 ${currentBranch}`, true);
|
||||
await git.pull('origin', currentBranch);
|
||||
spinner.succeed(`成功拉取远程分支 ${currentBranch}`);
|
||||
spinner.start(`正在推送本地分支 ${currentBranch}`);
|
||||
showMessage(`成功拉取远程分支 ${currentBranch}`, 'succeed');
|
||||
showMessage(`正在推送本地分支 ${currentBranch}`, true);
|
||||
await git.push('origin', currentBranch);
|
||||
spinner.succeed(`成功推送远程分支 ${currentBranch}`);
|
||||
spinner.succeed(`已经和远程分支保持同步 ${currentBranch}`);
|
||||
showMessage(`成功推送远程分支 ${currentBranch}`, 'succeed');
|
||||
showMessage(`已经和远程分支保持同步 ${currentBranch}`, 'succeed');
|
||||
|
||||
const { latest } = await git.log();
|
||||
spinner.succeed(`找到本地最新 commit:`);
|
||||
spinner.info(chalk.cyan(` hash: ${latest.hash}`));
|
||||
spinner.info(chalk.cyan(` date: ${latest.date}`));
|
||||
spinner.info(chalk.cyan(` message: ${latest.message}`));
|
||||
spinner.info(chalk.cyan(` author_name: ${latest.author_name}`));
|
||||
const sha = process.env.TARGET_SHA || latest.hash;
|
||||
|
||||
showMessage(`找到本地最新 commit:`, 'succeed');
|
||||
showMessage(chalk.cyan(` hash: ${sha}`));
|
||||
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 repo = 'ant-design';
|
||||
spinner.start(`开始检查远程分支 ${currentBranch} 的 CI 状态`);
|
||||
showMessage(`开始检查远程分支 ${currentBranch} 的 CI 状态`, true);
|
||||
|
||||
const failureUrlList: string[] = [];
|
||||
const {
|
||||
data: { check_runs },
|
||||
} = await octokit.checks.listForRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: latest.hash,
|
||||
ref: sha,
|
||||
});
|
||||
spinner.succeed(`远程分支 CI 状态:`);
|
||||
showMessage(`远程分支 CI 状态(${check_runs.length}):`, 'succeed');
|
||||
check_runs.forEach((run) => {
|
||||
spinner.info(
|
||||
` ${run.name.padEnd(36)} ${emojify(run.status)} ${emojify(run.conclusion || '')}`,
|
||||
);
|
||||
showMessage(` ${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);
|
||||
if (
|
||||
conclusions.includes('failure') ||
|
||||
conclusions.includes('cancelled') ||
|
||||
conclusions.includes('timed_out')
|
||||
) {
|
||||
spinner.fail(chalk.bgRedBright('远程分支 CI 执行异常,无法继续发布,请尝试修复或重试'));
|
||||
spinner.info(` 点此查看状态:https://github.com/${owner}/${repo}/commit/${latest.hash}`);
|
||||
if (blockStatus.some((status) => conclusions.includes(status))) {
|
||||
showMessage(chalk.bgRedBright('远程分支 CI 执行异常,无法继续发布,请尝试修复或重试'), 'fail');
|
||||
showMessage(` 点此查看状态:https://github.com/${owner}/${repo}/commit/${sha}`);
|
||||
|
||||
failureUrlList.forEach((url) => {
|
||||
showMessage(` - ${url}`);
|
||||
});
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const statuses = check_runs.map((run) => run.status);
|
||||
if (check_runs.length < 1 || statuses.includes('queued') || statuses.includes('in_progress')) {
|
||||
spinner.fail(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试'));
|
||||
spinner.info(` 点此查看状态:https://github.com/${owner}/${repo}/commit/${latest.hash}`);
|
||||
showMessage(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试'), 'fail');
|
||||
showMessage(` 点此查看状态:https://github.com/${owner}/${repo}/commit/${sha}`);
|
||||
process.exit(1);
|
||||
}
|
||||
spinner.succeed(`远程分支 CI 已通过`);
|
||||
showMessage(`远程分支 CI 已通过`, 'succeed');
|
||||
// clean up
|
||||
await runScript({ event: 'clean', path: '.', stdio: 'inherit' });
|
||||
spinner.succeed(`成功清理构建产物目录`);
|
||||
spinner.start(`开始查找远程分支构建产物`);
|
||||
const {
|
||||
data: { workflow_runs },
|
||||
} = await octokit.rest.actions.listWorkflowRunsForRepo({
|
||||
owner,
|
||||
repo,
|
||||
head_sha: latest.hash,
|
||||
per_page: 100,
|
||||
exclude_pull_requests: true,
|
||||
event: 'push',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
head_branch: currentBranch,
|
||||
showMessage(`成功清理构建产物目录`, 'succeed');
|
||||
|
||||
// 从 github artifact 中下载产物
|
||||
const downloadArtifactPromise = Promise.resolve().then(async () => {
|
||||
showMessage('开始查找远程分支构建产物', true, '[Github]');
|
||||
|
||||
const {
|
||||
data: { workflow_runs },
|
||||
} = await octokit.rest.actions.listWorkflowRunsForRepo({
|
||||
owner,
|
||||
repo,
|
||||
head_sha: sha,
|
||||
per_page: 100,
|
||||
exclude_pull_requests: true,
|
||||
event: 'push',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
head_branch: currentBranch,
|
||||
});
|
||||
const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test');
|
||||
if (!testWorkflowRun) {
|
||||
throw new Error('找不到远程构建工作流');
|
||||
}
|
||||
|
||||
const {
|
||||
data: { artifacts },
|
||||
} = await octokit.actions.listWorkflowRunArtifacts({
|
||||
owner,
|
||||
repo,
|
||||
run_id: testWorkflowRun?.id || 0,
|
||||
});
|
||||
const artifact = artifacts.find((item) => item.name === 'build artifacts');
|
||||
if (!artifact) {
|
||||
throw new Error('找不到远程构建产物');
|
||||
}
|
||||
|
||||
showMessage(`准备从远程分支下载构建产物`, true, '[Github]');
|
||||
const { url } = await octokit.rest.actions.downloadArtifact.endpoint({
|
||||
owner,
|
||||
repo,
|
||||
artifact_id: artifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
|
||||
// 返回下载后的文件路径
|
||||
return downloadArtifact('[Github]', url, 'artifacts.zip', process.env.GITHUB_ACCESS_TOKEN);
|
||||
});
|
||||
const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test');
|
||||
if (!testWorkflowRun) {
|
||||
spinner.fail(chalk.bgRedBright('找不到远程构建工作流'));
|
||||
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);
|
||||
}
|
||||
const {
|
||||
data: { artifacts },
|
||||
} = await octokit.actions.listWorkflowRunArtifacts({
|
||||
owner,
|
||||
repo,
|
||||
run_id: testWorkflowRun?.id || 0,
|
||||
});
|
||||
const artifact = artifacts.find((item) => item.name === 'build artifacts');
|
||||
if (!artifact) {
|
||||
spinner.fail(chalk.bgRedBright('找不到远程构建产物'));
|
||||
process.exit(1);
|
||||
}
|
||||
spinner.info(`准备从远程分支下载构建产物`);
|
||||
const { url } = await octokit.rest.actions.downloadArtifact.endpoint({
|
||||
owner,
|
||||
repo,
|
||||
artifact_id: artifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
await downloadArtifact(url, 'artifacts.zip');
|
||||
spinner.info();
|
||||
spinner.succeed(`成功从远程分支下载构建产物`);
|
||||
|
||||
showMessage(`成功从远程分支下载构建产物`, 'succeed');
|
||||
|
||||
// unzip
|
||||
spinner.start(`正在解压构建产物`);
|
||||
const zip = new AdmZip('artifacts.zip');
|
||||
showMessage(`正在解压构建产物`, true);
|
||||
const zip = new AdmZip(firstArtifactFile);
|
||||
zip.extractAllTo('./', true);
|
||||
spinner.succeed(`成功解压构建产物`);
|
||||
showMessage(`成功解压构建产物`, 'succeed');
|
||||
await runScript({ event: 'test:dekko', path: '.', stdio: 'inherit' });
|
||||
await runScript({ event: 'test:package-diff', path: '.', stdio: 'inherit' });
|
||||
spinner.succeed(`文件检查通过,准备发布!`);
|
||||
showMessage(`文件检查通过,准备发布!`, 'succeed');
|
||||
|
||||
new Notifier().notify({
|
||||
title: '✅ 准备发布到 npm',
|
||||
|
@ -4,8 +4,8 @@ import React from 'react';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
|
||||
import dayjs from 'dayjs';
|
||||
import fse from 'fs-extra';
|
||||
import { globSync } from 'glob';
|
||||
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import MockDate from 'mockdate';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
@ -17,12 +17,8 @@ import { TriggerMockContext } from './demoTestContext';
|
||||
|
||||
jest.mock('../../components/grid/hooks/useBreakpoint', () => () => ({}));
|
||||
|
||||
const toMatchImageSnapshot = configureToMatchImageSnapshot({
|
||||
customSnapshotsDir: `${process.cwd()}/imageSnapshots`,
|
||||
customDiffDir: `${process.cwd()}/imageDiffSnapshots`,
|
||||
});
|
||||
|
||||
expect.extend({ toMatchImageSnapshot });
|
||||
const snapshotPath = path.join(process.cwd(), 'imageSnapshots');
|
||||
fse.ensureDirSync(snapshotPath);
|
||||
|
||||
const themes = {
|
||||
default: theme.defaultAlgorithm,
|
||||
@ -45,7 +41,7 @@ export default function imageTest(
|
||||
let doc: Document;
|
||||
let container: HTMLDivElement;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
const dom = new JSDOM('<!DOCTYPE html><body></body></p>', {
|
||||
url: 'http://localhost/',
|
||||
});
|
||||
@ -103,6 +99,8 @@ export default function imageTest(
|
||||
|
||||
// Fill window
|
||||
fillWindowEnv(win);
|
||||
|
||||
await page.setRequestInterception(true);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@ -112,8 +110,8 @@ export default function imageTest(
|
||||
|
||||
function test(name: string, suffix: string, themedComponent: React.ReactElement) {
|
||||
it(name, async () => {
|
||||
await jestPuppeteer.resetPage();
|
||||
await page.setRequestInterception(true);
|
||||
await page.setViewport({ width: 800, height: 600 });
|
||||
|
||||
const onRequestHandle = (request: any) => {
|
||||
if (['image'].includes(request.resourceType())) {
|
||||
request.abort();
|
||||
@ -212,9 +210,7 @@ export default function imageTest(
|
||||
fullPage: !options.onlyViewport,
|
||||
});
|
||||
|
||||
expect(image).toMatchImageSnapshot({
|
||||
customSnapshotIdentifier: `${identifier}${suffix}`,
|
||||
});
|
||||
await fse.writeFile(path.join(snapshotPath, `${identifier}${suffix}.png`), image);
|
||||
|
||||
MockDate.reset();
|
||||
page.off('request', onRequestHandle);
|
||||
|
Loading…
Reference in New Issue
Block a user