Merge branch 'master' into feature-merge-master

This commit is contained in:
tanghui 2024-04-26 17:20:46 +08:00
commit d70de6c6e6
50 changed files with 1232 additions and 1039 deletions

View File

@ -43,7 +43,7 @@ const useStyle = createStyles(({ token }) => ({
align-items: center;
column-gap: ${token.paddingXXS}px;
border-radius: ${token.borderRadiusSM}px;
padding-inline: ${token.paddingXS}px;
padding-inline: ${token.paddingXXS}px;
transition: all ${token.motionDurationSlow} !important;
font-family: ${token.codeFamily};
color: ${token.colorTextSecondary} !important;
@ -62,6 +62,7 @@ const useStyle = createStyles(({ token }) => ({
`,
from: css`
color: ${token.magenta8};
margin-inline-end: 0.5em;
`,
antd: css`
color: ${token.green8};
@ -144,19 +145,17 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
{
label: locale.import,
children: (
<CopyToClipboard text={`import { ${component} } from "antd";`} onCopy={onCopy}>
<Tooltip
placement="right"
title={copied ? locale.copied : locale.copy}
onOpenChange={onOpenChange}
>
<span>
<CopyToClipboard text={`import { ${component} } from "antd";`} onCopy={onCopy}>
<Typography.Text className={styles.code} onClick={onCopy}>
{importList}
</Typography.Text>
</CopyToClipboard>
</span>
</Tooltip>
</CopyToClipboard>
),
},
filledSource && {

View File

@ -7,14 +7,16 @@ import type { IApi, IRoute } from 'dumi';
import ReactTechStack from 'dumi/dist/techStacks/react';
import sylvanas from 'sylvanas';
import localPackage from '../../package.json';
import { dependencies, devDependencies } from '../../package.json';
function extractEmotionStyle(html: string) {
// copy from emotion ssr
// https://github.com/vercel/next.js/blob/deprecated-main/examples/with-emotion-vanilla/pages/_document.js
const styles = global.__ANTD_STYLE_CACHE_MANAGER_FOR_SSR__.getCacheList().map((cache) => {
const result = createEmotionServer(cache).extractCritical(html);
if (!result.css) return null;
if (!result.css) {
return null;
}
const { css, ids } = result;
@ -46,10 +48,7 @@ class AntdReactTechStack extends ReactTechStack {
const codePath = opts.fileAbsPath!.replace(/\.\w+$/, '.tsx');
const code = fs.existsSync(codePath) ? fs.readFileSync(codePath, 'utf-8') : '';
props.pkgDependencyList = {
...localPackage.devDependencies,
...localPackage.dependencies,
};
props.pkgDependencyList = { ...devDependencies, ...dependencies };
props.jsx = sylvanas.parseText(code);
if (md) {

View File

@ -46,7 +46,7 @@ const useStyle = createStyles(({ token }) => {
height: 100%;
font-size: ${fontSize}px;
font-family: Avenir, ${fontFamily}, sans-serif;
border: 0;
border: 0 !important;
&${antCls}-menu-horizontal {
border-bottom: none;
@ -93,25 +93,6 @@ const useStyle = createStyles(({ token }) => {
text-align: center;
}
`,
popoverMenuNav: css`
${antCls}-menu-item,
${antCls}-menu-submenu {
text-align: left;
}
${antCls}-menu-item-group-title {
padding-inline-start: ${token.paddingLG}px;
}
${antCls}-menu-item-group-list {
padding: 0 ${token.paddingLG}px;
}
${antCls}-menu-item,
a {
color: #333;
}
`,
};
});

View File

@ -51,6 +51,7 @@ const useStyle = createStyles(({ token, css }) => {
@media only screen and (max-width: ${token.mobileMaxWidth}px) {
text-align: center;
border: none;
}
.nav-search-wrapper {
@ -175,9 +176,6 @@ const Header: React.FC = () => {
const onWindowResize = useCallback(() => {
setHeaderState((prev) => ({ ...prev, windowWidth: window.innerWidth }));
}, []);
const handleShowMenu = useCallback(() => {
setHeaderState((prev) => ({ ...prev, menuVisible: true }));
}, []);
const onMenuVisibleChange = useCallback((visible: boolean) => {
setHeaderState((prev) => ({ ...prev, menuVisible: visible }));
}, []);
@ -362,7 +360,7 @@ const Header: React.FC = () => {
arrow={{ pointAtCenter: true }}
onOpenChange={onMenuVisibleChange}
>
<MenuOutlined className="nav-phone-icon" onClick={handleShowMenu} />
<MenuOutlined className="nav-phone-icon" />
</Popover>
)}
{isZhCN && bannerVisible && (

3
.github/FUNDING.yml vendored
View File

@ -1,5 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: ant-design
open_collective: ant-design
issuehunt: ant-design/ant-design
buy_me_a_coffee: antdesign

View File

@ -136,6 +136,8 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => {
lineHeight: 1,
[`${componentCls}-count`]: {
display: 'inline-flex',
justifyContent: 'center',
zIndex: token.indicatorZIndex,
minWidth: indicatorHeight,
height: indicatorHeight,

View File

@ -75,7 +75,6 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
...resetComponent(token),
backgroundColor: headerBg,
border: borderBase,
borderBottom: 0,
borderRadius: collapsePanelBorderRadius,
[`&-rtl`]: {
@ -199,6 +198,8 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
},
[`${componentCls}-item:last-child`]: {
borderBottom: 0,
[`> ${componentCls}-content`]: {
borderRadius: `0 0 ${unit(collapsePanelBorderRadius)} ${unit(collapsePanelBorderRadius)}`,
},

View File

@ -700,8 +700,8 @@ describe('ColorPicker', () => {
});
describe('default clearValue should be changed', () => {
const Demo = () => {
const [color, setColor] = useState<string>('');
const Demo = ({ defaultValue }: { defaultValue?: string }) => {
const [color, setColor] = useState<string | undefined>(defaultValue);
useEffect(() => {
setColor('#1677ff');
}, []);
@ -709,12 +709,28 @@ describe('ColorPicker', () => {
};
it('normal', () => {
const { container } = render(<Demo />);
const { container } = render(<Demo defaultValue="" />);
expect(container.querySelector('.ant-color-picker-clear')).toBeFalsy();
});
it('strict', () => {
const { container } = render(
<React.StrictMode>
<Demo defaultValue="" />
</React.StrictMode>,
);
expect(container.querySelector('.ant-color-picker-clear')).toBeFalsy();
});
it('default undefined, normal', () => {
const { container } = render(<Demo />);
expect(container.querySelector('.ant-color-picker-clear')).toBeFalsy();
});
it('default undefined, strict', () => {
const { container } = render(
<React.StrictMode>
<Demo />

View File

@ -43,13 +43,11 @@ const useColorState = (
return;
}
prevValue.current = value;
if (hasValue(value)) {
const newColor = generateColor(value || '');
const newColor = generateColor(hasValue(value) ? value || '' : prevColor.current);
if (prevColor.current.cleared === true) {
newColor.cleared = 'controlled';
}
setColorValue(newColor);
}
}, [value]);
return [colorValue, setColorValue, prevColor] as const;

View File

@ -77,7 +77,7 @@ export interface ThemeConfig {
* @descCN `hashed` antd `false`
* @descEN Whether to enable the `hashed` attribute. If there is only one version of antd in your application, you can set `false` to reduce the bundle size.
* @default true
* @since 5.12.0
* @since 5.0.0
*/
hashed?: boolean;
/**

View File

@ -8,6 +8,21 @@ const locale: PickerLocale = {
lang: {
placeholder: 'Datum auswählen',
rangePlaceholder: ['Startdatum', 'Enddatum'],
shortWeekDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
shortMonths: [
'Jan',
'Feb',
'Mär',
'Apr',
'Mai',
'Jun',
'Jul',
'Aug',
'Sep',
'Okt',
'Nov',
'Dez',
],
...CalendarLocale,
},
timePickerLocale: {

View File

@ -8,6 +8,21 @@ const locale: PickerLocale = {
lang: {
placeholder: 'Seleccionar fecha',
rangePlaceholder: ['Fecha inicial', 'Fecha final'],
shortWeekDays: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'],
shortMonths: [
'Ene',
'Feb',
'Mar',
'Abr',
'May',
'Jun',
'Jul',
'Ago',
'Sep',
'Oct',
'Nov',
'Dic',
],
...CalendarLocale,
},
timePickerLocale: {

View File

@ -2332,142 +2332,6 @@ exports[`renders components/form/demo/customized-form-controls.tsx extend contex
exports[`renders components/form/demo/customized-form-controls.tsx extend context correctly 2`] = `[]`;
exports[`renders components/form/demo/dependencies.tsx extend context correctly 1`] = `
<form
autocomplete="off"
class="ant-form ant-form-vertical"
id="dependencies"
style="max-width: 600px;"
>
<div
class="ant-alert ant-alert-info"
data-show="true"
role="alert"
>
<span
aria-label="info-circle"
class="anticon anticon-info-circle ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="info-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Try modify \`Password2\` and then modify \`Password\`
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password"
title="Password"
>
Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password2"
title="Confirm Password"
>
Confirm Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password2"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<article
class="ant-typography"
>
<p>
Only Update when
<code>
password2
</code>
updated:
</p>
<pre>
{}
</pre>
</article>
</form>
`;
exports[`renders components/form/demo/dependencies.tsx extend context correctly 2`] = `[]`;
exports[`renders components/form/demo/disabled.tsx extend context correctly 1`] = `
Array [
<label
@ -7291,6 +7155,142 @@ exports[`renders components/form/demo/form-context.tsx extend context correctly
]
`;
exports[`renders components/form/demo/form-dependencies.tsx extend context correctly 1`] = `
<form
autocomplete="off"
class="ant-form ant-form-vertical"
id="dependencies"
style="max-width: 600px;"
>
<div
class="ant-alert ant-alert-info"
data-show="true"
role="alert"
>
<span
aria-label="info-circle"
class="anticon anticon-info-circle ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="info-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Try modify \`Password2\` and then modify \`Password\`
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password"
title="Password"
>
Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password2"
title="Confirm Password"
>
Confirm Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password2"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<article
class="ant-typography"
>
<p>
Only Update when
<code>
password2
</code>
updated:
</p>
<pre>
{}
</pre>
</article>
</form>
`;
exports[`renders components/form/demo/form-dependencies.tsx extend context correctly 2`] = `[]`;
exports[`renders components/form/demo/form-in-modal.tsx extend context correctly 1`] = `
Array [
<button

View File

@ -1730,140 +1730,6 @@ exports[`renders components/form/demo/customized-form-controls.tsx correctly 1`]
</form>
`;
exports[`renders components/form/demo/dependencies.tsx correctly 1`] = `
<form
autocomplete="off"
class="ant-form ant-form-vertical"
id="dependencies"
style="max-width:600px"
>
<div
class="ant-alert ant-alert-info"
data-show="true"
role="alert"
>
<span
aria-label="info-circle"
class="anticon anticon-info-circle ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="info-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Try modify \`Password2\` and then modify \`Password\`
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password"
title="Password"
>
Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password2"
title="Confirm Password"
>
Confirm Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password2"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<article
class="ant-typography"
>
<p>
Only Update when
<code>
password2
</code>
updated:
</p>
<pre>
{}
</pre>
</article>
</form>
`;
exports[`renders components/form/demo/disabled.tsx correctly 1`] = `
Array [
<label
@ -4218,6 +4084,140 @@ exports[`renders components/form/demo/form-context.tsx correctly 1`] = `
</form>
`;
exports[`renders components/form/demo/form-dependencies.tsx correctly 1`] = `
<form
autocomplete="off"
class="ant-form ant-form-vertical"
id="dependencies"
style="max-width:600px"
>
<div
class="ant-alert ant-alert-info"
data-show="true"
role="alert"
>
<span
aria-label="info-circle"
class="anticon anticon-info-circle ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="info-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Try modify \`Password2\` and then modify \`Password\`
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password"
title="Password"
>
Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dependencies_password2"
title="Confirm Password"
>
Confirm Password
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input ant-input-outlined"
id="dependencies_password2"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</div>
<article
class="ant-typography"
>
<p>
Only Update when
<code>
password2
</code>
updated:
</p>
<pre>
{}
</pre>
</article>
</form>
`;
exports[`renders components/form/demo/form-in-modal.tsx correctly 1`] = `
Array [
<button

View File

@ -46,7 +46,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
<code src="./demo/without-form-create.tsx">Handle Form Data Manually</code>
<code src="./demo/validate-static.tsx">Customized Validation</code>
<code src="./demo/dynamic-rule.tsx">Dynamic Rules</code>
<code src="./demo/dependencies.tsx">Dependencies</code>
<code src="./demo/form-dependencies.tsx">Dependencies</code>
<code src="./demo/validate-scroll-to-field.tsx" iframe="360">Slide to error field</code>
<code src="./demo/validate-other.tsx">Other Form Controls</code>
<code src="./demo/disabled-input-debug.tsx" debug>Disabled Input Debug</code>

View File

@ -47,7 +47,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
<code src="./demo/without-form-create.tsx">自行处理表单数据</code>
<code src="./demo/validate-static.tsx">自定义校验</code>
<code src="./demo/dynamic-rule.tsx">动态校验规则</code>
<code src="./demo/dependencies.tsx">校验与更新依赖</code>
<code src="./demo/form-dependencies.tsx">校验与更新依赖</code>
<code src="./demo/validate-scroll-to-field.tsx" iframe="360">滑动到错误字段</code>
<code src="./demo/validate-other.tsx">校验其他组件</code>
<code src="./demo/disabled-input-debug.tsx" debug>Disabled Input Debug</code>

View File

@ -44449,7 +44449,7 @@ exports[`Locale Provider should display the text as de 1`] = `
tabindex="-1"
type="button"
>
Sept.
Sep
</button>
<button
class="ant-picker-year-btn"
@ -46668,7 +46668,7 @@ exports[`Locale Provider should display the text as de 1`] = `
tabindex="-1"
type="button"
>
Sept.
Sep
</button>
<button
class="ant-picker-year-btn"
@ -47206,7 +47206,7 @@ exports[`Locale Provider should display the text as de 1`] = `
tabindex="-1"
type="button"
>
Okt.
Okt
</button>
<button
class="ant-picker-year-btn"
@ -48301,9 +48301,9 @@ exports[`Locale Provider should display the text as de 1`] = `
</span>
<span
class="ant-select-selection-item"
title="Sept."
title="Sep"
>
Sept.
Sep
</span>
</div>
<span
@ -65553,7 +65553,7 @@ exports[`Locale Provider should display the text as es 1`] = `
tabindex="-1"
type="button"
>
sep
Sep
</button>
<button
class="ant-picker-year-btn"
@ -65591,25 +65591,25 @@ exports[`Locale Provider should display the text as es 1`] = `
<thead>
<tr>
<th>
lu
Lun
</th>
<th>
ma
Mar
</th>
<th>
mi
Mié
</th>
<th>
ju
Jue
</th>
<th>
vi
Vie
</th>
<th>
Sáb
</th>
<th>
do
Dom
</th>
</tr>
</thead>
@ -67772,7 +67772,7 @@ exports[`Locale Provider should display the text as es 1`] = `
tabindex="-1"
type="button"
>
sep
Sep
</button>
<button
class="ant-picker-year-btn"
@ -67812,25 +67812,25 @@ exports[`Locale Provider should display the text as es 1`] = `
<thead>
<tr>
<th>
lu
Lun
</th>
<th>
ma
Mar
</th>
<th>
mi
Mié
</th>
<th>
ju
Jue
</th>
<th>
vi
Vie
</th>
<th>
Sáb
</th>
<th>
do
Dom
</th>
</tr>
</thead>
@ -68310,7 +68310,7 @@ exports[`Locale Provider should display the text as es 1`] = `
tabindex="-1"
type="button"
>
oct
Oct
</button>
<button
class="ant-picker-year-btn"
@ -68348,25 +68348,25 @@ exports[`Locale Provider should display the text as es 1`] = `
<thead>
<tr>
<th>
lu
Lun
</th>
<th>
ma
Mar
</th>
<th>
mi
Mié
</th>
<th>
ju
Jue
</th>
<th>
vi
Vie
</th>
<th>
Sáb
</th>
<th>
do
Dom
</th>
</tr>
</thead>
@ -69405,9 +69405,9 @@ exports[`Locale Provider should display the text as es 1`] = `
</span>
<span
class="ant-select-selection-item"
title="sep"
title="Sep"
>
sep
Sep
</span>
</div>
<span
@ -69497,25 +69497,25 @@ exports[`Locale Provider should display the text as es 1`] = `
<thead>
<tr>
<th>
lu
Lun
</th>
<th>
ma
Mar
</th>
<th>
mi
Mié
</th>
<th>
ju
Jue
</th>
<th>
vi
Vie
</th>
<th>
Sáb
</th>
<th>
do
Dom
</th>
</tr>
</thead>

View File

@ -12,7 +12,7 @@ import { ConfigProvider, Menu, Space, theme } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuProps['items'] = [
const items: MenuItem[] = [
{
label: 'Navigation One',
key: 'mail',
@ -33,76 +33,74 @@ const items: MenuProps['items'] = [
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
{ label: 'Option 1', key: 'setting:1' },
{ label: 'Option 2', key: 'setting:2' },
],
},
{
type: 'group',
label: 'Item 2',
children: [
{
label: 'Option 3',
key: 'setting:3',
},
{
label: 'Option 4',
key: 'setting:4',
},
{ label: 'Option 3', key: 'setting:3' },
{ label: 'Option 4', key: 'setting:4' },
],
},
],
},
{
key: 'alipay',
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
),
key: 'alipay',
},
];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
} as MenuItem;
}
const items2: MenuProps['items'] = [
getItem('Option 1', '1', <PieChartOutlined />),
getItem('Option 2', '2', <DesktopOutlined />),
getItem('Option 3', '3', <ContainerOutlined />),
getItem('Navigation One', 'sub1', <MailOutlined />, [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Option 7', '7'),
getItem('Option 8', '8'),
]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Submenu', 'sub3', null, [getItem('Option 11', '11'), getItem('Option 12', '12')]),
]),
const items2: MenuItem[] = [
{
key: '1',
icon: <PieChartOutlined />,
label: 'Option 1',
},
{
key: '2',
icon: <DesktopOutlined />,
label: 'Option 2',
},
{
key: '3',
icon: <ContainerOutlined />,
label: 'Option 3',
},
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
],
},
];
const App: React.FC = () => {

View File

@ -3,7 +3,9 @@ import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/ico
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
const items: MenuProps['items'] = [
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{
label: 'Navigation One',
key: 'mail',
@ -24,39 +26,27 @@ const items: MenuProps['items'] = [
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
{ label: 'Option 1', key: 'setting:1' },
{ label: 'Option 2', key: 'setting:2' },
],
},
{
type: 'group',
label: 'Item 2',
children: [
{
label: 'Option 3',
key: 'setting:3',
},
{
label: 'Option 4',
key: 'setting:4',
},
{ label: 'Option 3', key: 'setting:3' },
{ label: 'Option 4', key: 'setting:4' },
],
},
],
},
{
key: 'alipay',
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
),
key: 'alipay',
},
];

View File

@ -3,7 +3,9 @@ import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/ico
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
const items: MenuProps['items'] = [
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{
label: 'Navigation One',
key: 'mail',
@ -24,39 +26,27 @@ const items: MenuProps['items'] = [
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
{ label: 'Option 1', key: 'setting:1' },
{ label: 'Option 2', key: 'setting:2' },
],
},
{
type: 'group',
label: 'Item 2',
children: [
{
label: 'Option 3',
key: 'setting:3',
},
{
label: 'Option 4',
key: 'setting:4',
},
{ label: 'Option 3', key: 'setting:3' },
{ label: 'Option 4', key: 'setting:4' },
],
},
],
},
{
key: 'alipay',
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
),
key: 'alipay',
},
];

View File

@ -13,40 +13,38 @@ import { Button, Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Option 1', '1', <PieChartOutlined />),
getItem('Option 2', '2', <DesktopOutlined />),
getItem('Option 3', '3', <ContainerOutlined />),
getItem('Navigation One', 'sub1', <MailOutlined />, [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Option 7', '7'),
getItem('Option 8', '8'),
]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Submenu', 'sub3', null, [getItem('Option 11', '11'), getItem('Option 12', '12')]),
]),
{ key: '1', icon: <PieChartOutlined />, label: 'Option 1' },
{ key: '2', icon: <DesktopOutlined />, label: 'Option 2' },
{ key: '3', icon: <ContainerOutlined />, label: 'Option 3' },
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
],
},
];
const App: React.FC = () => {

View File

@ -5,44 +5,72 @@ import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
} as MenuItem;
}
const items: MenuProps['items'] = [
getItem('Navigation One', 'sub1', <MailOutlined />, [
getItem('Item 1', 'g1', null, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'),
getItem('Item 2', 'g2', null, [getItem('Option 3', '3'), getItem('Option 4', '4')], 'group'),
]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
]),
{ type: 'divider' },
getItem('Navigation Three', 'sub4', <SettingOutlined />, [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Option 11', '11'),
getItem('Option 12', '12'),
]),
getItem('Group', 'grp', null, [getItem('Option 13', '13'), getItem('Option 14', '14')], 'group'),
const items: MenuItem[] = [
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
{
key: 'g2',
label: 'Item 2',
type: 'group',
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
type: 'divider',
},
{
key: 'sub4',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
{
key: 'grp',
label: 'Group',
type: 'group',
children: [
{ key: '13', label: 'Option 13' },
{ key: '14', label: 'Option 14' },
],
},
];
const App: React.FC = () => {

View File

@ -11,46 +11,64 @@ import type { MenuProps } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key?: React.Key | null,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Navigation One', '1', <MailOutlined />),
getItem('Navigation Two', '2', <CalendarOutlined />),
getItem('Navigation Two', 'sub1', <AppstoreOutlined />, [
getItem(
{
key: '1',
icon: <MailOutlined />,
label: 'Navigation One',
},
{
key: '2',
icon: <CalendarOutlined />,
label: 'Navigation Two',
},
{
key: 'sub1',
icon: <AppstoreOutlined />,
label: 'Navigation Two',
children: [
{
key: '3',
label: (
<Typography.Text ellipsis>
Ant Design, a design language for background applications, is refined by Ant UED Team
</Typography.Text>,
'3',
</Typography.Text>
),
getItem('Option 4', '4'),
getItem('Submenu', 'sub1-2', null, [getItem('Option 5', '5'), getItem('Option 6', '6')]),
]),
getItem('Navigation Three', 'sub2', <SettingOutlined />, [
getItem('Option 7', '7'),
getItem('Option 8', '8'),
getItem('Option 9', '9'),
getItem('Option 10', '10'),
]),
getItem(
},
{
key: '4',
label: 'Option 4',
},
{
key: 'sub1-2',
label: 'Submenu',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ label: 'Option 7', key: '7' },
{ label: 'Option 8', key: '8' },
{ label: 'Option 9', key: '9' },
{ label: 'Option 10', key: '10' },
],
},
{
key: 'link',
icon: <LinkOutlined />,
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Ant Design
</a>,
'link',
<LinkOutlined />,
</a>
),
},
];
const App: React.FC = () => {

View File

@ -5,55 +5,63 @@ import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Navigation One', '1', <MailOutlined />, [
getItem('Option 1', '11'),
getItem('Option 2', '12'),
getItem('Option 3', '13'),
getItem('Option 4', '14'),
]),
getItem('Navigation Two', '2', <AppstoreOutlined />, [
getItem('Option 1', '21'),
getItem('Option 2', '22'),
getItem('Submenu', '23', null, [
getItem('Option 1', '231'),
getItem('Option 2', '232'),
getItem('Option 3', '233'),
]),
getItem('Submenu 2', '24', null, [
getItem('Option 1', '241'),
getItem('Option 2', '242'),
getItem('Option 3', '243'),
]),
]),
getItem('Navigation Three', '3', <SettingOutlined />, [
getItem('Option 1', '31'),
getItem('Option 2', '32'),
getItem('Option 3', '33'),
getItem('Option 4', '34'),
]),
{
key: '1',
icon: <MailOutlined />,
label: 'Navigation One',
children: [
{ key: '11', label: 'Option 1' },
{ key: '12', label: 'Option 2' },
{ key: '13', label: 'Option 3' },
{ key: '14', label: 'Option 4' },
],
},
{
key: '2',
icon: <AppstoreOutlined />,
label: 'Navigation Two',
children: [
{ key: '21', label: 'Option 1' },
{ key: '22', label: 'Option 2' },
{
key: '23',
label: 'Submenu',
children: [
{ key: '231', label: 'Option 1' },
{ key: '232', label: 'Option 2' },
{ key: '233', label: 'Option 3' },
],
},
{
key: '24',
label: 'Submenu 2',
children: [
{ key: '241', label: 'Option 1' },
{ key: '242', label: 'Option 2' },
{ key: '243', label: 'Option 3' },
],
},
],
},
{
key: '3',
icon: <SettingOutlined />,
label: 'Navigation Three',
children: [
{ key: '31', label: 'Option 1' },
{ key: '32', label: 'Option 2' },
{ key: '33', label: 'Option 3' },
{ key: '34', label: 'Option 4' },
],
},
];
interface LevelKeysProps {
key?: string;
children?: LevelKeysProps[];
}
const getLevelKeys = (items1: LevelKeysProps[]) => {
const key: Record<string, number> = {};
const func = (items2: LevelKeysProps[], level = 1) => {
@ -62,13 +70,14 @@ const getLevelKeys = (items1: LevelKeysProps[]) => {
key[item.key] = level;
}
if (item.children) {
return func(item.children, level + 1);
func(item.children, level + 1);
}
});
};
func(items1);
return key;
};
const levelKeys = getLevelKeys(items as LevelKeysProps[]);
const App: React.FC = () => {

View File

@ -5,42 +5,45 @@ import { Menu, Switch } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key?: React.Key | null,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Navigation One Long Long Long Long', 'sub1', <MailOutlined />, [
getItem('Option 1', '1'),
getItem('Option 2', '2'),
getItem('Option 3', '3'),
getItem('Option 4', '4'),
]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
]),
getItem('Option 11', '11'),
getItem('Option 12', '12'),
{
key: 'sub1',
label: 'Navigation One Long Long Long Long',
icon: <MailOutlined />,
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
];
const App: React.FC = () => {
const [theme, setTheme] = useState<MenuTheme>('dark');
const [menuTheme, setMenuTheme] = useState<MenuTheme>('dark');
const [current, setCurrent] = useState('1');
const changeTheme = (value: boolean) => {
setTheme(value ? 'dark' : 'light');
setMenuTheme(value ? 'dark' : 'light');
};
const onClick: MenuProps['onClick'] = (e) => {
@ -51,7 +54,7 @@ const App: React.FC = () => {
return (
<>
<Switch
checked={theme === 'dark'}
checked={menuTheme === 'dark'}
onChange={changeTheme}
checkedChildren="Dark"
unCheckedChildren="Light"
@ -59,7 +62,7 @@ const App: React.FC = () => {
<br />
<br />
<Menu
theme={theme}
theme={menuTheme}
onClick={onClick}
selectedKeys={[current]}
mode="inline"

View File

@ -5,28 +5,12 @@ import { Menu, Switch } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key?: React.Key | null,
icon?: React.ReactNode,
children?: MenuItem[],
theme?: 'light' | 'dark',
): MenuItem {
return {
key,
icon,
children,
label,
theme,
} as MenuItem;
}
const App: React.FC = () => {
const [theme, setTheme] = useState<MenuTheme>('light');
const [menuTheme, setMenuTheme] = useState<MenuTheme>('light');
const [current, setCurrent] = useState('1');
const changeTheme = (value: boolean) => {
setTheme(value ? 'dark' : 'light');
setMenuTheme(value ? 'dark' : 'light');
};
const onClick: MenuProps['onClick'] = (e) => {
@ -34,21 +18,25 @@ const App: React.FC = () => {
};
const items: MenuItem[] = [
getItem(
'Navigation One',
'sub1',
<MailOutlined />,
[getItem('Option 1', '1'), getItem('Option 2', '2'), getItem('Option 3', '3')],
theme,
),
getItem('Option 5', '5'),
getItem('Option 6', '6'),
{
key: 'sub1',
icon: <MailOutlined />,
label: 'Navigation One',
theme: menuTheme,
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
],
},
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
];
return (
<>
<Switch
checked={theme === 'dark'}
checked={menuTheme === 'dark'}
onChange={changeTheme}
checkedChildren="Dark"
unCheckedChildren="Light"
@ -63,9 +51,7 @@ const App: React.FC = () => {
mode="vertical"
theme="dark"
items={items}
getPopupContainer={function test(node) {
return node.parentNode as HTMLElement;
}}
getPopupContainer={(node) => node.parentNode as HTMLElement}
/>
</>
);

View File

@ -10,43 +10,57 @@ import { Divider, Menu, Switch } from 'antd';
import type { GetProp, MenuProps } from 'antd';
type MenuTheme = GetProp<MenuProps, 'theme'>;
type MenuItem = GetProp<MenuProps, 'items'>[number];
function getItem(
label: React.ReactNode,
key?: React.Key | null,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Navigation One', '1', <MailOutlined />),
getItem('Navigation Two', '2', <CalendarOutlined />),
getItem('Navigation Two', 'sub1', <AppstoreOutlined />, [
getItem('Option 3', '3'),
getItem('Option 4', '4'),
getItem('Submenu', 'sub1-2', null, [getItem('Option 5', '5'), getItem('Option 6', '6')]),
]),
getItem('Navigation Three', 'sub2', <SettingOutlined />, [
getItem('Option 7', '7'),
getItem('Option 8', '8'),
getItem('Option 9', '9'),
getItem('Option 10', '10'),
]),
getItem(
{
key: '1',
icon: <MailOutlined />,
label: 'Navigation One',
},
{
key: '2',
icon: <CalendarOutlined />,
label: 'Navigation Two',
},
{
key: 'sub1',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
{
key: 'sub1-2',
label: 'Submenu',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
],
},
{
key: 'link',
icon: <LinkOutlined />,
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Ant Design
</a>,
'link',
<LinkOutlined />,
</a>
),
},
];
const App: React.FC = () => {

View File

@ -5,42 +5,46 @@ import { Menu, Switch } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key?: React.Key | null,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Navigation One', 'sub1', <MailOutlined />, [
getItem('Option 1', '1'),
getItem('Option 2', '2'),
getItem('Option 3', '3'),
getItem('Option 4', '4'),
]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
]),
getItem('Navigation Three', 'sub4', <SettingOutlined />, [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Option 11', '11'),
getItem('Option 12', '12'),
]),
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
key: 'sub4',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
];
const App: React.FC = () => {

View File

@ -5,40 +5,60 @@ import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key?: React.Key | null,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Navigation One', 'sub1', <MailOutlined />, [
getItem('Item 1', null, null, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'),
getItem('Item 2', null, null, [getItem('Option 3', '3'), getItem('Option 4', '4')], 'group'),
]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
]),
getItem('Navigation Three', 'sub4', <SettingOutlined />, [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Option 11', '11'),
getItem('Option 12', '12'),
]),
{
key: 'sub1',
icon: <MailOutlined />,
label: 'Navigation One',
children: [
{
key: '1-1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
{
key: '1-2',
label: 'Item 2',
type: 'group',
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
],
},
{
key: 'sub2',
icon: <AppstoreOutlined />,
label: 'Navigation Two',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
key: 'sub4',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
];
const onClick: MenuProps['onClick'] = (e) => {

View File

@ -90,6 +90,8 @@ const genBaseStyle: GenerateStyle<PopoverToken> = (token) => {
userSelect: 'text',
transformOrigin: `var(--arrow-x, 50%) var(--arrow-y, 50%)`,
'--antd-arrow-background-color': colorBgElevated,
width: 'max-content',
maxWidth: '100vw',
'&-rtl': {
direction: 'rtl',

View File

@ -4363,13 +4363,18 @@ Array [
<table
style="table-layout: auto;"
>
<colgroup />
<colgroup>
<col
style="width: 80px;"
/>
</colgroup>
<thead
class="ant-table-thead"
>
<tr>
<th
class="ant-table-cell"
style="text-align: center;"
/>
<th
class="ant-table-cell"
@ -4405,16 +4410,24 @@ Array [
>
<td
class="ant-table-cell"
style="text-align: center;"
>
<button
class="ant-btn ant-btn-text ant-btn-sm ant-btn-icon-only"
style="cursor: move;"
type="button"
>
<span
aria-label="menu"
class="anticon anticon-menu"
class="ant-btn-icon"
>
<span
aria-label="holder"
class="anticon anticon-holder"
role="img"
style="cursor: move;"
>
<svg
aria-hidden="true"
data-icon="menu"
data-icon="holder"
fill="currentColor"
focusable="false"
height="1em"
@ -4422,10 +4435,12 @@ Array [
width="1em"
>
<path
d="M904 160H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0 624H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0-312H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z"
d="M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"
/>
</svg>
</span>
</span>
</button>
</td>
<td
class="ant-table-cell"
@ -4440,7 +4455,7 @@ Array [
<td
class="ant-table-cell"
>
Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text
Long text Long
</td>
</tr>
<tr
@ -4454,16 +4469,24 @@ Array [
>
<td
class="ant-table-cell"
style="text-align: center;"
>
<button
class="ant-btn ant-btn-text ant-btn-sm ant-btn-icon-only"
style="cursor: move;"
type="button"
>
<span
aria-label="menu"
class="anticon anticon-menu"
class="ant-btn-icon"
>
<span
aria-label="holder"
class="anticon anticon-holder"
role="img"
style="cursor: move;"
>
<svg
aria-hidden="true"
data-icon="menu"
data-icon="holder"
fill="currentColor"
focusable="false"
height="1em"
@ -4471,10 +4494,12 @@ Array [
width="1em"
>
<path
d="M904 160H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0 624H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0-312H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z"
d="M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"
/>
</svg>
</span>
</span>
</button>
</td>
<td
class="ant-table-cell"
@ -4503,16 +4528,24 @@ Array [
>
<td
class="ant-table-cell"
style="text-align: center;"
>
<button
class="ant-btn ant-btn-text ant-btn-sm ant-btn-icon-only"
style="cursor: move;"
type="button"
>
<span
aria-label="menu"
class="anticon anticon-menu"
class="ant-btn-icon"
>
<span
aria-label="holder"
class="anticon anticon-holder"
role="img"
style="cursor: move;"
>
<svg
aria-hidden="true"
data-icon="menu"
data-icon="holder"
fill="currentColor"
focusable="false"
height="1em"
@ -4520,10 +4553,12 @@ Array [
width="1em"
>
<path
d="M904 160H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0 624H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0-312H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z"
d="M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"
/>
</svg>
</span>
</span>
</button>
</td>
<td
class="ant-table-cell"

View File

@ -3821,13 +3821,18 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
style="width:80px"
/>
</colgroup>
<thead
class="ant-table-thead"
>
<tr>
<th
class="ant-table-cell"
style="text-align:center"
/>
<th
class="ant-table-cell"
@ -3863,16 +3868,24 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
>
<td
class="ant-table-cell"
style="text-align:center"
>
<button
class="ant-btn ant-btn-text ant-btn-sm ant-btn-icon-only"
style="cursor:move"
type="button"
>
<span
aria-label="menu"
class="anticon anticon-menu"
class="ant-btn-icon"
>
<span
aria-label="holder"
class="anticon anticon-holder"
role="img"
style="touch-action:none;cursor:move"
>
<svg
aria-hidden="true"
data-icon="menu"
data-icon="holder"
fill="currentColor"
focusable="false"
height="1em"
@ -3880,10 +3893,12 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
width="1em"
>
<path
d="M904 160H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0 624H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0-312H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z"
d="M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"
/>
</svg>
</span>
</span>
</button>
</td>
<td
class="ant-table-cell"
@ -3898,7 +3913,7 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
<td
class="ant-table-cell"
>
Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text
Long text Long
</td>
</tr>
<tr
@ -3912,16 +3927,24 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
>
<td
class="ant-table-cell"
style="text-align:center"
>
<button
class="ant-btn ant-btn-text ant-btn-sm ant-btn-icon-only"
style="cursor:move"
type="button"
>
<span
aria-label="menu"
class="anticon anticon-menu"
class="ant-btn-icon"
>
<span
aria-label="holder"
class="anticon anticon-holder"
role="img"
style="touch-action:none;cursor:move"
>
<svg
aria-hidden="true"
data-icon="menu"
data-icon="holder"
fill="currentColor"
focusable="false"
height="1em"
@ -3929,10 +3952,12 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
width="1em"
>
<path
d="M904 160H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0 624H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0-312H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z"
d="M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"
/>
</svg>
</span>
</span>
</button>
</td>
<td
class="ant-table-cell"
@ -3961,16 +3986,24 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
>
<td
class="ant-table-cell"
style="text-align:center"
>
<button
class="ant-btn ant-btn-text ant-btn-sm ant-btn-icon-only"
style="cursor:move"
type="button"
>
<span
aria-label="menu"
class="anticon anticon-menu"
class="ant-btn-icon"
>
<span
aria-label="holder"
class="anticon anticon-holder"
role="img"
style="touch-action:none;cursor:move"
>
<svg
aria-hidden="true"
data-icon="menu"
data-icon="holder"
fill="currentColor"
focusable="false"
height="1em"
@ -3978,10 +4011,12 @@ exports[`renders components/table/demo/drag-sorting-handler.tsx correctly 1`] =
width="1em"
>
<path
d="M904 160H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0 624H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0-312H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z"
d="M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"
/>
</svg>
</span>
</span>
</button>
</td>
<td
class="ant-table-cell"

View File

@ -1,7 +1,8 @@
import React, { useState } from 'react';
import { MenuOutlined } from '@ant-design/icons';
import React, { useContext, useMemo } from 'react';
import { HolderOutlined } from '@ant-design/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
@ -10,7 +11,7 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Table } from 'antd';
import { Button, Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
interface DataType {
@ -20,29 +21,45 @@ interface DataType {
address: string;
}
interface RowContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
}
const RowContext = React.createContext<RowContextProps>({});
const DragHandle: React.FC = () => {
const { setActivatorNodeRef, listeners } = useContext(RowContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
ref={setActivatorNodeRef}
{...listeners}
/>
);
};
const columns: ColumnsType<DataType> = [
{
key: 'sort',
},
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
{ key: 'sort', align: 'center', width: 80, render: () => <DragHandle /> },
{ title: 'Name', dataIndex: 'name' },
{ title: 'Age', dataIndex: 'age' },
{ title: 'Address', dataIndex: 'address' },
];
const initialData: DataType[] = [
{ key: '1', name: 'John Brown', age: 32, address: 'Long text Long' },
{ key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park' },
{ key: '3', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park' },
];
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
'data-row-key': string;
}
const Row = ({ children, ...props }: RowProps) => {
const Row: React.FC<RowProps> = (props) => {
const {
attributes,
listeners,
@ -51,84 +68,46 @@ const Row = ({ children, ...props }: RowProps) => {
transform,
transition,
isDragging,
} = useSortable({
id: props['data-row-key'],
});
} = useSortable({ id: props['data-row-key'] });
const style: React.CSSProperties = {
...props.style,
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
transform: CSS.Translate.toString(transform),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
const contextValue = useMemo<RowContextProps>(
() => ({ setActivatorNodeRef, listeners }),
[setActivatorNodeRef, listeners],
);
return (
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
{React.Children.map(children, (child) => {
if ((child as React.ReactElement).key === 'sort') {
return React.cloneElement(child as React.ReactElement, {
children: (
<MenuOutlined
ref={setActivatorNodeRef}
style={{ touchAction: 'none', cursor: 'move' }}
{...listeners}
/>
),
});
}
return child;
})}
</tr>
<RowContext.Provider value={contextValue}>
<tr {...props} ref={setNodeRef} style={style} {...attributes} />
</RowContext.Provider>
);
};
const App: React.FC = () => {
const [dataSource, setDataSource] = useState([
{
key: '1',
name: 'John Brown',
age: 32,
address:
'Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
]);
const [dataSource, setDataSource] = React.useState<DataType[]>(initialData);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
setDataSource((previous) => {
const activeIndex = previous.findIndex((i) => i.key === active.id);
const overIndex = previous.findIndex((i) => i.key === over?.id);
return arrayMove(previous, activeIndex, overIndex);
setDataSource((prevState) => {
const activeIndex = prevState.findIndex((record) => record.key === active?.id);
const overIndex = prevState.findIndex((record) => record.key === over?.id);
return arrayMove(prevState, activeIndex, overIndex);
});
}
};
return (
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext
// rowKey array
items={dataSource.map((i) => i.key)}
strategy={verticalListSortingStrategy}
>
<SortableContext items={dataSource.map((i) => i.key)} strategy={verticalListSortingStrategy}>
<Table
components={{
body: {
row: Row,
},
}}
rowKey="key"
components={{ body: { row: Row } }}
columns={columns}
dataSource={dataSource}
/>

View File

@ -45,7 +45,7 @@ const Row = (props: RowProps) => {
const style: React.CSSProperties = {
...props.style,
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
transform: CSS.Translate.toString(transform),
transition,
cursor: 'move',
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),

View File

@ -21,7 +21,7 @@ const DraggableTabNode = ({ className, ...props }: DraggableTabPaneProps) => {
const style: React.CSSProperties = {
...props.style,
transform: CSS.Transform.toString(transform && { ...transform, scaleX: 1 }),
transform: CSS.Translate.toString(transform),
transition,
cursor: 'move',
};

View File

@ -18,7 +18,7 @@ const App: React.FC = () => {
justifyContent: 'center',
}}
>
<Tooltip title="Thanks for using antd. Have a nice day!" trigger="click" defaultOpen>
<Tooltip title="Thanks for using antd. Have a nice day!" trigger="click" open>
<Button>Scroll The Window</Button>
</Tooltip>
</div>

View File

@ -1,7 +1,8 @@
import React from 'react';
import type { UploadProps } from '..';
import type { UploadListProps, UploadProps } from '..';
import Upload from '..';
import UploadList from '../UploadList';
describe('Upload.typescript', () => {
it('Upload', () => {
@ -210,4 +211,24 @@ describe('Upload.typescript', () => {
expect(upload2).toBeTruthy();
expect(upload3).toBeTruthy();
});
it('UploadProps type', () => {
const uploadProps: UploadProps<number | string> = {
customRequest({ onSuccess }) {
onSuccess?.(1234);
onSuccess?.('test');
},
};
expect(<Upload {...uploadProps} />).toBeTruthy();
});
it('UploadListProps type', () => {
const uploadListProps: UploadListProps<number | string> = {
locale: {},
removeIcon: (file) => <div>{JSON.stringify(file.response)}</div>,
downloadIcon: (file) => <div>{JSON.stringify(file.response)}</div>,
previewIcon: (file) => <div>{JSON.stringify(file.response)}</div>,
};
expect(<UploadList {...uploadListProps} />).toBeTruthy();
});
});

View File

@ -23,7 +23,7 @@ const DraggableUploadListItem = ({ originNode, file }: DraggableUploadListItemPr
});
const style: React.CSSProperties = {
transform: CSS.Transform.toString(transform),
transform: CSS.Translate.toString(transform),
transition,
cursor: 'move',
};

View File

@ -118,7 +118,7 @@ export interface UploadProps<T = any> extends Pick<RcUploadProps, 'capture' | 'h
style?: React.CSSProperties;
disabled?: boolean;
prefixCls?: string;
customRequest?: (options: RcCustomRequestOptions) => void;
customRequest?: (options: RcCustomRequestOptions<T>) => void;
withCredentials?: boolean;
openFileDialogOnClick?: boolean;
locale?: UploadLocale;
@ -152,9 +152,9 @@ export interface UploadListProps<T = any> {
showRemoveIcon?: boolean;
showDownloadIcon?: boolean;
showPreviewIcon?: boolean;
removeIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
downloadIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
previewIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
removeIcon?: React.ReactNode | ((file: UploadFile<T>) => React.ReactNode);
downloadIcon?: React.ReactNode | ((file: UploadFile<T>) => React.ReactNode);
previewIcon?: React.ReactNode | ((file: UploadFile<T>) => React.ReactNode);
locale: UploadLocale;
previewFile?: PreviewFileHandler;
iconRender?: (file: UploadFile<T>, listType?: UploadListType) => React.ReactNode;

View File

@ -133,12 +133,18 @@ Create a new `src/components/ProductList.tsx` file with the following code.
```tsx
import React from 'react';
import { Button, Popconfirm, Table } from 'antd';
import type { TableProps } from 'antd';
const ProductList: React.FC<{ products: { name: string }[]; onDelete: (id: string) => void }> = ({
interface DataType {
id: string;
name: string;
}
const ProductList: React.FC<{ products: DataType[]; onDelete: (id: string) => void }> = ({
onDelete,
products,
}) => {
const columns = [
const columns: TableProps<DataType>['columns'] = [
{
title: 'Name',
dataIndex: 'name',
@ -164,7 +170,7 @@ export default ProductList;
Assuming we have agreed on an API interface with the backend developers, we can now use Mock data to locally mock up the data that the API should return, so that front-end and back-end development can proceed simultaneously without the front-end work being blocked because the back-end API is still being developed. Umi provides an out-of-the-box [Mock function](https://umijs.org/docs/guides/mock) that allows you to set up Mock data in a convenient and easy way.
Create a `mock` directory and add a new `products.ts` file to this directory with the following code.
Create a new `mock/products.ts` file in the root directory with the following code.
```ts
import { defineMock } from 'umi';

View File

@ -133,12 +133,18 @@ export default defineConfig({
```tsx
import React from 'react';
import { Button, Popconfirm, Table } from 'antd';
import type { TableProps } from 'antd';
const ProductList: React.FC<{ products: { name: string }[]; onDelete: (id: string) => void }> = ({
interface DataType {
id: string;
name: string;
}
const ProductList: React.FC<{ products: DataType[]; onDelete: (id: string) => void }> = ({
onDelete,
products,
}) => {
const columns = [
const columns: TableProps<DataType>['columns'] = [
{
title: 'Name',
dataIndex: 'name',
@ -164,7 +170,7 @@ export default ProductList;
假设我们已经和后端约定好了 API 接口,那现在就可以使用 Mock 数据来在本地模拟出 API 应该返回的数据,这样一来前后端开发就可以同时进行,不会因为后端 API 还在开发而导致前端的工作被阻塞。Umi 提供了开箱即用的 [Mock 功能](https://umijs.org/docs/guides/mock),能够用方便简单的方式来完成 Mock 数据的设置。
创建 `mock` 目录,并在此目录下新增 `products.ts` 文件,内容如下。
在根目录下新建 `mock/products.ts` 文件,内容如下。
```ts
import { defineMock } from 'umi';

View File

@ -63,7 +63,7 @@
"format": "biome format --write .",
"install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16 @testing-library/react@12",
"install-react-17": "npm i --no-save --legacy-peer-deps react@17 react-dom@17 @testing-library/react@12",
"install-react-18": "npm i --no-save --legacy-peer-deps react@18 react-dom@18",
"install-react-18": "npm i --no-save --legacy-peer-deps react@18.2 react-dom@18.2",
"prelint": "dumi setup",
"lint": "npm run version && npm run tsc && npm run lint:script && npm run lint:demo && npm run lint:md && npm run lint:style && npm run lint:changelog",
"lint-fix": "npm run lint-fix:script && npm run lint-fix:demo",
@ -151,7 +151,7 @@
"rc-resize-observer": "^1.4.0",
"rc-segmented": "~2.3.0",
"rc-select": "~14.13.1",
"rc-slider": "~10.6.1",
"rc-slider": "~10.6.2",
"rc-steps": "~6.0.1",
"rc-switch": "~4.1.0",
"rc-table": "~7.45.4",
@ -171,7 +171,7 @@
"@ant-design/tools": "^18.0.2",
"@antv/g6": "^4.8.24",
"@babel/eslint-plugin": "^7.23.5",
"@biomejs/biome": "^1.7.0",
"@biomejs/biome": "^1.7.1",
"@codesandbox/sandpack-react": "^2.13.8",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
@ -189,7 +189,7 @@
"@stackblitz/sdk": "^1.9.0",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^15.0.2",
"@testing-library/react": "^15.0.4",
"@testing-library/user-event": "^14.5.2",
"@types/adm-zip": "^0.5.5",
"@types/ali-oss": "^6.16.11",
@ -225,8 +225,8 @@
"@types/tar": "^6.1.13",
"@types/throttle-debounce": "^5.0.2",
"@types/warning": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"adm-zip": "^0.5.12",
"ali-oss": "^6.20.0",
"antd-img-crop": "^4.21.0",
@ -298,22 +298,22 @@
"prettier-plugin-jsdoc": "^1.3.0",
"pretty-format": "^29.7.0",
"prismjs": "^1.29.0",
"puppeteer": "^22.6.5",
"puppeteer": "^22.7.1",
"qs": "^6.12.1",
"rc-footer": "^0.6.8",
"rc-tween-one": "^3.0.6",
"rc-virtual-list": "^3.11.5",
"react": "^18.2.0",
"react": "18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-countup": "^6.5.3",
"react-dom": "^18.2.0",
"react-dom": "18.2.0",
"react-draggable": "^4.4.6",
"react-fast-marquee": "^1.6.4",
"react-highlight-words": "^0.20.0",
"react-infinite-scroll-component": "^6.1.0",
"react-intersection-observer": "^9.8.2",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.22.3",
"react-router-dom": "^6.23.0",
"react-sticky-box": "^2.0.5",
"regenerator-runtime": "^0.14.1",
"rehype-stringify": "^10.0.0",
@ -330,15 +330,15 @@
"simple-git": "^3.24.0",
"size-limit": "^11.1.2",
"spinnies": "^0.5.1",
"stylelint": "^16.3.1",
"stylelint": "^16.4.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^36.0.0",
"stylelint-prettier": "^5.0.0",
"sylvanas": "^0.6.1",
"tar": "^7.0.1",
"tar-fs": "^3.0.5",
"terser": "^5.30.3",
"tsx": "^4.7.2",
"terser": "^5.30.4",
"tsx": "^4.7.3",
"typedoc": "^0.25.13",
"typescript": "~5.4.5",
"vanilla-jsoneditor": "^0.23.2",

View File

@ -5,9 +5,7 @@ import ora from 'ora';
import simpleGit from 'simple-git';
import type { StatusResult } from 'simple-git';
import localPackage from '../package.json';
const { version } = localPackage;
import { version } from '../package.json';
const cwd = process.cwd();
const git = simpleGit(cwd);

View File

@ -5,9 +5,7 @@ import chalk from 'chalk';
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween';
import localPackage from '../package.json';
const { version } = localPackage;
import { version } from '../package.json';
dayjs.extend(isBetween);

View File

@ -1,9 +1,7 @@
import fs from 'fs';
import path from 'path';
import localPackage from '../package.json';
const { version } = localPackage;
import { version } from '../package.json';
fs.writeFileSync(
path.join(__dirname, '..', 'components', 'version', 'version.ts'),

View File

@ -135,7 +135,7 @@ function getMdImageTag(desc: IImageDesc, extraCaption?: boolean) {
}
interface IBadCase {
type: 'removed' | 'changed';
type: 'removed' | 'changed' | 'added';
filename: string;
/**
* compare target file
@ -179,31 +179,57 @@ function generateLineReport(
lineHTMLReport += '| ';
lineHTMLReport += [
// add ref as query to avoid github cache image object
getMdImageTag({
getMdImageTag(
{
src: `${publicPath}/images/base/${filename}?ref=${currentRef}`,
alt: targetFilename || '',
}, extraCaption),
getMdImageTag({
},
extraCaption,
),
getMdImageTag(
{
src: `${publicPath}/images/current/${filename}?ref=${currentRef}`,
alt: filename,
}, extraCaption),
getMdImageTag({
},
extraCaption,
),
getMdImageTag(
{
src: `${publicPath}/images/diff/${filename}?ref=${currentRef}`,
alt: '',
}, extraCaption),
},
extraCaption,
),
].join(' | ');
lineHTMLReport += ' |\n';
} else if (type === 'removed') {
lineHTMLReport += '| ';
lineHTMLReport += [
getMdImageTag({
getMdImageTag(
{
src: `${publicPath}/images/base/${filename}?ref=${currentRef}`,
alt: targetFilename || '',
}, extraCaption),
alt: filename || '',
},
extraCaption,
),
`⛔️⛔️⛔️ Missing ⛔️⛔️⛔️`,
`🚨🚨🚨 Removed 🚨🚨🚨`,
].join(' | ');
lineHTMLReport += ' |\n';
} else if (type === 'added') {
lineHTMLReport += '| ';
lineHTMLReport += [
'',
getMdImageTag(
{
src: `${publicPath}/images/current/${filename}?ref=${currentRef}`,
alt: filename,
},
extraCaption,
),
`🆕🆕🆕 Added 🆕🆕🆕`,
].join(' | ');
lineHTMLReport += ' |\n';
}
return lineHTMLReport;
}
@ -259,20 +285,10 @@ ${fullReport}
diffCount += 1;
if (diffCount <= 10) {
// 将图片下方增加文件名
reportMdStr += generateLineReport(
badCase,
publicPath,
currentRef,
true,
);
reportMdStr += generateLineReport(badCase, publicPath, currentRef, true);
}
fullVersionMd += generateLineReport(
badCase,
publicPath,
currentRef,
false,
);
fullVersionMd += generateLineReport(badCase, publicPath, currentRef, false);
}
reportMdStr += addonFullReportDesc;
@ -335,6 +351,7 @@ async function boot() {
.filter((i) => !i.endsWith('.css-var.png'))
.map((n) => path.basename(n, path.extname(n)));
// compare to target branch
for (const basename of cssInJsImgNames) {
for (const extname of ['.png', '.css-var.png']) {
// baseImg always use cssinjs png
@ -386,6 +403,33 @@ async function boot() {
}
}
// collect all new added cases
const currentImgFileList = readPngs(currentImgSourceDir);
/* --- text report stage --- */
console.log(
chalk.blue(`📊 Text report from pr #${prId} comparing to ${targetBranch}@${targetCommitSha}\n`),
);
// new images
const newImgs = difference(currentImgFileList, baseImgFileList);
if (newImgs.length) {
console.log(chalk.green(`🆕 ${newImgs.length} images added from this pr`));
console.log(chalk.green('🆕 Added images list:\n'));
console.log(prettyList(newImgs));
console.log('\n');
}
for (const newImg of newImgs) {
badCases.push({
type: 'added',
filename: newImg,
weight: 0,
});
await fse.copy(
path.join(currentImgSourceDir, newImg),
path.resolve(currentImgReportDir, newImg),
);
}
/* --- generate report stage --- */
const jsonl = badCases.map((i) => JSON.stringify(i)).join('\n');
// write jsonl and markdown report to diffImgDir
@ -416,21 +460,9 @@ async function boot() {
await fse.readdir(REPORT_DIR),
);
const currentImgFileList = readPngs(currentImgSourceDir);
/* --- text report stage --- */
console.log(
chalk.blue(`📊 Text report from pr #${prId} comparing to ${targetBranch}@${targetCommitSha}\n`),
);
// new images
const newImgs = difference(currentImgFileList, baseImgFileList);
if (newImgs.length) {
console.log(chalk.green(`🆕 ${newImgs.length} images added from this pr`));
console.log(chalk.green('🆕 Added images list:\n'));
console.log(prettyList(newImgs));
console.log('\n');
}
const validBadCases = badCases.filter((i) => ['removed', 'changed'].includes(i.type));
if (!badCases.length) {
if (!validBadCases.length) {
console.log(chalk.green('🎉 All passed!'));
console.log('\n');
return;

View File

@ -1,5 +1,5 @@
/* eslint-disable global-require */
import pkg from '../package.json';
import { version as packageVersion } from '../package.json';
const testDist = process.env.LIB_DIR === 'dist';
const testDistMin = process.env.LIB_DIR === 'dist-min';
@ -28,14 +28,14 @@ describe('antd dist files', () => {
// eslint-disable-next-line global-require,import/no-unresolved
const antd = require('../dist/antd');
expect(antd).toBeTruthy();
expect(antd.version).toBe(pkg.version);
expect(antd.version).toBe(packageVersion);
});
it('antd.min.js should export version', () => {
// eslint-disable-next-line global-require,import/no-unresolved
const antd = require('../dist/antd.min');
expect(antd).toBeTruthy();
expect(antd.version).toBe(pkg.version);
expect(antd.version).toBe(packageVersion);
});
}
});