chore: feature merge master

This commit is contained in:
二货机器人 2023-08-29 19:51:31 +08:00
commit 620e168cfc
29 changed files with 847 additions and 593 deletions

View File

@ -25,7 +25,7 @@ import { isTwoCNChar, isUnBorderedButtonType, spaceChildren } from './buttonHelp
import IconWrapper from './IconWrapper';
import LoadingIcon from './LoadingIcon';
import useStyle from './style';
import CompactStyle from './style/compactCmp';
import CompactCmp from './style/compactCmp';
export type LegacyButtonType = ButtonType | 'danger';
@ -289,7 +289,7 @@ const InternalButton: React.ForwardRefRenderFunction<
{kids}
{/* Styles: compact */}
{compactItemClassnames && <CompactStyle prefixCls={prefixCls} />}
{compactItemClassnames && <CompactCmp key="compact" prefixCls={prefixCls} />}
</button>
);

View File

@ -2434,6 +2434,142 @@ 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"
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"
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
@ -6236,258 +6372,153 @@ exports[`renders components/form/demo/dynamic-form-items-complex.tsx extend cont
style="max-width: 600px;"
>
<div
class="ant-form-item"
style="display: flex; row-gap: 16px; flex-direction: column;"
>
<div
class="ant-row ant-form-item-row"
class="ant-card ant-card-bordered ant-card-small"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dynamic_form_complex_area"
title="Area"
>
Area
</label>
</div>
<div
class="ant-col ant-form-item-control"
class="ant-card-head"
>
<div
class="ant-form-item-control-input"
class="ant-card-head-wrapper"
>
<div
class="ant-form-item-control-input-content"
class="ant-card-head-title"
>
Item 1
</div>
<div
class="ant-card-extra"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
/>
</svg>
</span>
</div>
</div>
</div>
<div
class="ant-card-body"
>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
class="ant-col ant-col-6 ant-form-item-label"
>
<label
class=""
for="dynamic_form_complex_items_0_name"
title="Name"
>
Name
</label>
</div>
<div
class="ant-col ant-col-18 ant-form-item-control"
>
<div
class="ant-select-selector"
class="ant-form-item-control-input"
>
<span
class="ant-select-selection-search"
<div
class="ant-form-item-control-input-content"
>
<input
aria-autocomplete="list"
aria-controls="dynamic_form_complex_area_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="dynamic_form_complex_area_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="dynamic_form_complex_area"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
class="ant-input"
id="dynamic_form_complex_items_0_name"
type="text"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
</div>
<div
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-col-6 ant-form-item-label"
>
<label
class=""
title="List"
>
<div>
List
</label>
</div>
<div
class="ant-col ant-col-18 ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<div
id="dynamic_form_complex_area_list"
role="listbox"
style="height: 0px; width: 0px; overflow: hidden;"
style="display: flex; flex-direction: column; row-gap: 16px;"
>
<div
aria-label="Beijing"
aria-selected="false"
id="dynamic_form_complex_area_list_0"
role="option"
<button
class="ant-btn ant-btn-dashed ant-btn-block"
type="button"
>
Beijing
</div>
<div
aria-label="Shanghai"
aria-selected="false"
id="dynamic_form_complex_area_list_1"
role="option"
>
Shanghai
</div>
</div>
<div
class="rc-virtual-list"
style="position: relative;"
>
<div
class="rc-virtual-list-holder"
style="max-height: 256px; overflow-y: auto;"
>
<div>
<div
class="rc-virtual-list-holder-inner"
style="display: flex; flex-direction: column;"
>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option ant-select-item-option-active"
title="Beijing"
>
<div
class="ant-select-item-option-content"
>
Beijing
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="Shanghai"
>
<div
class="ant-select-item-option-content"
>
Shanghai
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
</div>
</div>
</div>
<span>
+ Add Sub Item
</span>
</button>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
<button
class="ant-btn ant-btn-dashed ant-btn-block"
type="button"
>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<button
class="ant-btn ant-btn-dashed ant-btn-block"
type="button"
>
<span
class="ant-btn-icon"
>
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</span>
</span>
<span>
Add sights
</span>
</button>
</div>
</div>
</div>
</div>
<span>
+ Add Item
</span>
</button>
</div>
<div
class="ant-form-item"
<article
class="ant-typography"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<button
class="ant-btn ant-btn-primary"
type="submit"
>
<span>
Submit
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<pre>
{
"items": [
{}
]
}
</pre>
</article>
</form>
`;

View File

@ -1777,6 +1777,140 @@ 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"
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"
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
@ -3514,178 +3648,149 @@ exports[`renders components/form/demo/dynamic-form-items-complex.tsx correctly 1
style="max-width:600px"
>
<div
class="ant-form-item"
style="display:flex;row-gap:16px;flex-direction:column"
>
<div
class="ant-row ant-form-item-row"
class="ant-card ant-card-bordered ant-card-small"
>
<div
class="ant-col ant-form-item-label"
>
<label
class="ant-form-item-required"
for="dynamic_form_complex_area"
title="Area"
>
Area
</label>
</div>
<div
class="ant-col ant-form-item-control"
class="ant-card-head"
>
<div
class="ant-form-item-control-input"
class="ant-card-head-wrapper"
>
<div
class="ant-form-item-control-input-content"
class="ant-card-head-title"
>
Item 1
</div>
<div
class="ant-card-extra"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
/>
</svg>
</span>
</div>
</div>
</div>
<div
class="ant-card-body"
>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
class="ant-col ant-col-6 ant-form-item-label"
>
<label
class=""
for="dynamic_form_complex_items_0_name"
title="Name"
>
Name
</label>
</div>
<div
class="ant-col ant-col-18 ant-form-item-control"
>
<div
class="ant-select-selector"
class="ant-form-item-control-input"
>
<span
class="ant-select-selection-search"
<div
class="ant-form-item-control-input-content"
>
<input
aria-autocomplete="list"
aria-controls="dynamic_form_complex_area_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="dynamic_form_complex_area_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="dynamic_form_complex_area"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
class="ant-input"
id="dynamic_form_complex_items_0_name"
type="text"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-col-6 ant-form-item-label"
>
<label
class=""
title="List"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
List
</label>
</div>
<div
class="ant-col ant-col-18 ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<div
style="display:flex;flex-direction:column;row-gap:16px"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
<button
class="ant-btn ant-btn-dashed ant-btn-block"
type="button"
>
<span>
+ Add Sub Item
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
<button
class="ant-btn ant-btn-dashed ant-btn-block"
type="button"
>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<button
class="ant-btn ant-btn-dashed ant-btn-block"
type="button"
>
<span
class="ant-btn-icon"
>
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</span>
</span>
<span>
Add sights
</span>
</button>
</div>
</div>
</div>
</div>
<span>
+ Add Item
</span>
</button>
</div>
<div
class="ant-form-item"
<article
class="ant-typography"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<button
class="ant-btn ant-btn-primary"
type="submit"
>
<span>
Submit
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<pre>
{}
</pre>
</article>
</form>
`;

View File

@ -1,7 +0,0 @@
## zh-CN
Buggy!
## en-US
Buggy!

View File

@ -1,31 +0,0 @@
import React from 'react';
import { Form, Input } from 'antd';
let acc = 0;
const App: React.FC = () => {
const [form] = Form.useForm();
return (
<Form
form={form}
name="debug"
initialValues={{ debug1: 'debug1', debug2: 'debug2' }}
style={{ maxWidth: 600 }}
>
<Form.Item noStyle dependencies={['debug1']}>
{
() => acc++
// return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>;
}
</Form.Item>
<Form.Item label="debug1" name="debug1">
<Input />
</Form.Item>
<Form.Item label="debug2" name="debug2">
<Input />
</Form.Item>
</Form>
);
};
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
Form.Item 可以通过 `dependencies` 属性,设置关联字段。当关联字段的值发生变化时,会触发校验与更新。
## en-US
Form.Item can set the associated field through the `dependencies` property. When the value of the associated field changes, the validation and update will be triggered.

View File

@ -0,0 +1,57 @@
import React from 'react';
import { Alert, Form, Input, Typography } from 'antd';
const App: React.FC = () => {
const [form] = Form.useForm();
return (
<Form
form={form}
name="dependencies"
autoComplete="off"
style={{ maxWidth: 600 }}
layout="vertical"
>
<Alert message=" Try modify `Password2` and then modify `Password`" type="info" showIcon />
<Form.Item label="Password" name="password" rules={[{ required: true }]}>
<Input />
</Form.Item>
{/* Field */}
<Form.Item
label="Confirm Password"
name="password2"
dependencies={['password']}
rules={[
{
required: true,
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('The new password that you entered do not match!'));
},
}),
]}
>
<Input />
</Form.Item>
{/* Render Props */}
<Form.Item noStyle dependencies={['password2']}>
{() => (
<Typography>
<p>
Only Update when <code>password2</code> updated:
</p>
<pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
</Typography>
)}
</Form.Item>
</Form>
);
};
export default App;

View File

@ -1,7 +1,7 @@
## zh-CN
这个例子演示了一个表单中包含多个表单控件的情况
多个 Form.List 嵌套的使用场景
## en-US
This example demonstrates the case that a form contains multiple form controls.
Multiple Form.List nested usage scenarios.

View File

@ -1,96 +1,83 @@
import React from 'react';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Select, Space } from 'antd';
const { Option } = Select;
const areas = [
{ label: 'Beijing', value: 'Beijing' },
{ label: 'Shanghai', value: 'Shanghai' },
];
const sights = {
Beijing: ['Tiananmen', 'Great Wall'],
Shanghai: ['Oriental Pearl', 'The Bund'],
};
type SightsKeys = keyof typeof sights;
import { CloseOutlined } from '@ant-design/icons';
import { Button, Card, Form, Input, Space, Typography } from 'antd';
const App: React.FC = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log('Received values of form:', values);
};
const handleChange = () => {
form.setFieldsValue({ sights: [] });
};
return (
<Form
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
form={form}
name="dynamic_form_complex"
onFinish={onFinish}
style={{ maxWidth: 600 }}
autoComplete="off"
initialValues={{ items: [{}] }}
>
<Form.Item name="area" label="Area" rules={[{ required: true, message: 'Missing area' }]}>
<Select options={areas} onChange={handleChange} />
</Form.Item>
<Form.List name="sights">
<Form.List name="items">
{(fields, { add, remove }) => (
<>
<div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}>
{fields.map((field) => (
<Space key={field.key} align="baseline">
<Form.Item
noStyle
shouldUpdate={(prevValues, curValues) =>
prevValues.area !== curValues.area || prevValues.sights !== curValues.sights
}
>
{() => (
<Form.Item
{...field}
label="Sight"
name={[field.name, 'sight']}
rules={[{ required: true, message: 'Missing sight' }]}
>
<Select disabled={!form.getFieldValue('area')} style={{ width: 130 }}>
{(sights[form.getFieldValue('area') as SightsKeys] || []).map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</Form.Item>
)}
</Form.Item>
<Form.Item
{...field}
label="Price"
name={[field.name, 'price']}
rules={[{ required: true, message: 'Missing price' }]}
>
<Card
size="small"
title={`Item ${field.name + 1}`}
key={field.key}
extra={
<CloseOutlined
onClick={() => {
remove(field.name);
}}
/>
}
>
<Form.Item label="Name" name={[field.name, 'name']}>
<Input />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Space>
{/* Nest Form.List */}
<Form.Item label="List">
<Form.List name={[field.name, 'list']}>
{(subFields, subOpt) => (
<div style={{ display: 'flex', flexDirection: 'column', rowGap: 16 }}>
{subFields.map((subField) => (
<Space key={subField.key}>
<Form.Item noStyle name={[subField.name, 'first']}>
<Input placeholder="first" />
</Form.Item>
<Form.Item noStyle name={[subField.name, 'second']}>
<Input placeholder="second" />
</Form.Item>
<CloseOutlined
onClick={() => {
subOpt.remove(subField.name);
}}
/>
</Space>
))}
<Button type="dashed" onClick={() => subOpt.add()} block>
+ Add Sub Item
</Button>
</div>
)}
</Form.List>
</Form.Item>
</Card>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
Add sights
</Button>
</Form.Item>
</>
<Button type="dashed" onClick={() => add()} block>
+ Add Item
</Button>
</div>
)}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Form.Item noStyle shouldUpdate>
{() => (
<Typography>
<pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
</Typography>
)}
</Form.Item>
</Form>
);

View File

@ -46,9 +46,9 @@ High performance Form component with data scope management. Including data colle
<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/validate-other.tsx">Other Form Controls</code>
<code src="./demo/disabled-input-debug.tsx" debug>Disabled Input Debug</code>
<code src="./demo/dep-debug.tsx" debug>Dep Debug</code>
<code src="./demo/label-debug.tsx" debug>label ellipsis</code>
<code src="./demo/col-24-debug.tsx" debug>Test col 24 usage</code>
<code src="./demo/ref-item.tsx" debug>Ref item</code>
@ -154,12 +154,10 @@ After wrapped by `Form.Item` with `name` property, `value`(or other property def
### dependencies
Used when there are dependencies between fields. If a field has the `dependencies` prop, this field will automatically trigger updates and validations when upstream is updated. A common scenario is a user registration form with "password" and "confirm password" fields. The "Confirm Password" validation depends on the "Password" field. After setting `dependencies`, the "Password" field update will re-trigger the validation of "Check Password". You can refer [examples](#components-form-demo-register).
Used when there are dependencies between fields. If a field has the `dependencies` prop, this field will automatically trigger updates and validations when upstream is updated. A common scenario is a user registration form with "password" and "confirm password" fields. The "Confirm Password" validation depends on the "Password" field. After setting `dependencies`, the "Password" field update will re-trigger the validation of "Check Password". You can refer [examples](#components-form-demo-dependencies).
`dependencies` shouldn't be used together with `shouldUpdate`, since it may result in conflicting update logic.
`dependencies` supports `Form.Item` with render props children since `4.5.0`.
### shouldUpdate
Form updates only the modified field-related components for performance optimization purposes by incremental update. In most cases, you only need to write code or do validation with the [`dependencies`](#dependencies) property. In some specific cases, such as when a new field option appears with a field value changed, or you just want to keep some area updating by form update, you can modify the update logic of Form.Item via the `shouldUpdate`.

View File

@ -47,9 +47,9 @@ 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/validate-other.tsx">校验其他组件</code>
<code src="./demo/disabled-input-debug.tsx" debug>Disabled Input Debug</code>
<code src="./demo/dep-debug.tsx" debug>Dep Debug</code>
<code src="./demo/label-debug.tsx" debug>测试 label 省略</code>
<code src="./demo/col-24-debug.tsx" debug>测试特殊 col 24 用法</code>
<code src="./demo/ref-item.tsx" debug>引用字段</code>
@ -155,12 +155,10 @@ const validateMessages = {
### dependencies
当字段间存在依赖关系时使用。如果一个字段设置了 `dependencies` 属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 `dependencies` 后,“密码”字段更新会重新触发“校验密码”的校验逻辑。你可以参考[具体例子](#components-form-demo-register)。
当字段间存在依赖关系时使用。如果一个字段设置了 `dependencies` 属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 `dependencies` 后,“密码”字段更新会重新触发“校验密码”的校验逻辑。你可以参考[具体例子](#components-form-demo-dependencies)。
`dependencies` 不应和 `shouldUpdate` 一起使用,因为这可能带来更新逻辑的混乱。
`4.5.0` 版本开始,`dependencies` 支持使用 render props 类型 children 的 `Form.Item`
### shouldUpdate
Form 通过增量更新方式,只更新被修改的字段相关组件以达到性能优化目的。大部分场景下,你只需要编写代码或者与 [`dependencies`](#dependencies) 属性配合校验即可。而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 `shouldUpdate` 修改 Form.Item 的更新逻辑。

View File

@ -201,7 +201,4 @@ export default genComponentStyleHook(
token.paddingSM
}px`,
}),
{
clientOnly: true,
},
);

View File

@ -1,6 +1,6 @@
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import { useNotification as useRcNotification } from 'rc-notification';
import { NotificationProvider, useNotification as useRcNotification } from 'rc-notification';
import type { NotificationAPI } from 'rc-notification/lib';
import * as React from 'react';
import warning from '../_util/warning';
@ -17,6 +17,8 @@ import type {
} from './interface';
import useStyle from './style';
import { getMotion, wrapPromiseFn } from './util';
import type { FC, PropsWithChildren } from 'react';
import type { NotificationConfig as RcNotificationConfig } from 'rc-notification/lib/useNotification';
const DEFAULT_OFFSET = 8;
const DEFAULT_DURATION = 3;
@ -30,10 +32,27 @@ type HolderProps = ConfigOptions & {
interface HolderRef extends NotificationAPI {
prefixCls: string;
hashId: string;
message?: ComponentStyleConfig;
}
const Wrapper: FC<PropsWithChildren<{ prefixCls: string }>> = ({ children, prefixCls }) => {
const [, hashId] = useStyle(prefixCls);
return (
<NotificationProvider classNames={{ list: hashId, notice: hashId }}>
{children}
</NotificationProvider>
);
};
const renderNotifications: RcNotificationConfig['renderNotifications'] = (
node,
{ prefixCls, key },
) => (
<Wrapper prefixCls={prefixCls} key={key}>
{node}
</Wrapper>
);
const Holder = React.forwardRef<HolderRef, HolderProps>((props, ref) => {
const {
top,
@ -49,8 +68,6 @@ const Holder = React.forwardRef<HolderRef, HolderProps>((props, ref) => {
const prefixCls = staticPrefixCls || getPrefixCls('message');
const [, hashId] = useStyle(prefixCls);
// =============================== Style ===============================
const getStyle = (): React.CSSProperties => ({
left: '50%',
@ -58,7 +75,7 @@ const Holder = React.forwardRef<HolderRef, HolderProps>((props, ref) => {
top: top ?? DEFAULT_OFFSET,
});
const getClassName = () => classNames(hashId, { [`${prefixCls}-rtl`]: rtl });
const getClassName = () => classNames({ [`${prefixCls}-rtl`]: rtl });
// ============================== Motion ===============================
const getNotificationMotion = () => getMotion(prefixCls, transitionName);
@ -82,13 +99,13 @@ const Holder = React.forwardRef<HolderRef, HolderProps>((props, ref) => {
getContainer: () => staticGetContainer?.() || getPopupContainer?.() || document.body,
maxCount,
onAllRemoved,
renderNotifications,
});
// ================================ Ref ================================
React.useImperativeHandle(ref, () => ({
...api,
prefixCls,
hashId,
message,
}));
@ -128,7 +145,7 @@ export function useInternalMessage(
return fakeResult;
}
const { open: originOpen, prefixCls, hashId, message } = holderRef.current;
const { open: originOpen, prefixCls, message } = holderRef.current;
const noticePrefixCls = `${prefixCls}-notice`;
const { content, icon, type, key, className, style, onClose, ...restConfig } = config;
@ -151,7 +168,6 @@ export function useInternalMessage(
placement: 'top',
className: classNames(
type && `${noticePrefixCls}-${type}`,
hashId,
className,
message?.className,
),

View File

@ -300,7 +300,4 @@ export default genComponentStyleHook(
zIndexPopup: token.zIndexPopupBase + 50,
width: 384,
}),
{
clientOnly: true,
},
);

View File

@ -1,7 +1,10 @@
import * as React from 'react';
import classNames from 'classnames';
import { useNotification as useRcNotification } from 'rc-notification';
import type { NotificationAPI } from 'rc-notification/lib';
import { NotificationProvider, useNotification as useRcNotification } from 'rc-notification';
import type {
NotificationAPI,
NotificationConfig as RcNotificationConfig,
} from 'rc-notification/lib';
import warning from '../_util/warning';
import { ConfigContext } from '../config-provider';
import type { ComponentStyleConfig } from '../config-provider/context';
@ -14,6 +17,7 @@ import type {
import { getCloseIcon, PureContent } from './PurePanel';
import useStyle from './style';
import { getMotion, getPlacementStyle } from './util';
import type { FC, PropsWithChildren } from 'react';
const DEFAULT_OFFSET = 24;
const DEFAULT_DURATION = 4.5;
@ -28,10 +32,27 @@ type HolderProps = NotificationConfig & {
interface HolderRef extends NotificationAPI {
prefixCls: string;
hashId: string;
notification?: ComponentStyleConfig;
}
const Wrapper: FC<PropsWithChildren<{ prefixCls: string }>> = ({ children, prefixCls }) => {
const [, hashId] = useStyle(prefixCls);
return (
<NotificationProvider classNames={{ list: hashId, notice: hashId }}>
{children}
</NotificationProvider>
);
};
const renderNotifications: RcNotificationConfig['renderNotifications'] = (
node,
{ prefixCls, key },
) => (
<Wrapper prefixCls={prefixCls} key={key}>
{node}
</Wrapper>
);
const Holder = React.forwardRef<HolderRef, HolderProps>((props, ref) => {
const {
top,
@ -50,10 +71,7 @@ const Holder = React.forwardRef<HolderRef, HolderProps>((props, ref) => {
const getStyle = (placement: NotificationPlacement): React.CSSProperties =>
getPlacementStyle(placement, top ?? DEFAULT_OFFSET, bottom ?? DEFAULT_OFFSET);
// Style
const [, hashId] = useStyle(prefixCls);
const getClassName = () => classNames(hashId, { [`${prefixCls}-rtl`]: rtl });
const getClassName = () => classNames({ [`${prefixCls}-rtl`]: rtl });
// ============================== Motion ===============================
const getNotificationMotion = () => getMotion(prefixCls);
@ -70,13 +88,13 @@ const Holder = React.forwardRef<HolderRef, HolderProps>((props, ref) => {
getContainer: () => staticGetContainer?.() || getPopupContainer?.() || document.body,
maxCount,
onAllRemoved,
renderNotifications,
});
// ================================ Ref ================================
React.useImperativeHandle(ref, () => ({
...api,
prefixCls,
hashId,
notification,
}));
@ -106,7 +124,7 @@ export function useInternalNotification(
return;
}
const { open: originOpen, prefixCls, hashId, notification } = holderRef.current;
const { open: originOpen, prefixCls, notification } = holderRef.current;
const noticePrefixCls = `${prefixCls}-notice`;
@ -142,7 +160,6 @@ export function useInternalNotification(
),
className: classNames(
type && `${noticePrefixCls}-${type}`,
hashId,
className,
notification?.className,
),

View File

@ -9,6 +9,10 @@ const onSearch = (value: string) => {
console.log('search:', value);
};
// Filter `option.label` match the user type `input`
const filterOption = (input: string, option: { label: string; value: string }) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase());
const App: React.FC = () => (
<Select
showSearch
@ -16,9 +20,7 @@ const App: React.FC = () => (
optionFilterProp="children"
onChange={onChange}
onSearch={onSearch}
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
filterOption={filterOption}
options={[
{
value: 'jack',

View File

@ -70,7 +70,7 @@ Common props ref[Common props](/docs/react/common-props)
| dropdownRender | Customize dropdown content | (originNode: ReactNode) => ReactNode | - | |
| dropdownStyle | The style of dropdown menu | CSSProperties | - | |
| fieldNames | Customize node label, value, optionsgroupLabel field name | object | { label: `label`, value: `value`, options: `options`, groupLabel: `label` } | 4.17.0 (`groupLabel` added in 5.6.0) |
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded | boolean \| function(inputValue, option) | true | |
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded. [Example](#select-demo-search) | boolean \| function(inputValue, option) | true | |
| filterSort | Sort function for search options sorting, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction | (optionA: Option, optionB: Option) => number | - | 4.9.0 |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
| labelInValue | Whether to embed label in value, turn the format of value from `string` to { value: string, label: ReactNode } | boolean | false | |

View File

@ -71,7 +71,7 @@ demo:
| dropdownRender | 自定义下拉框内容 | (originNode: ReactNode) => ReactNode | - | |
| dropdownStyle | 下拉菜单的 style 属性 | CSSProperties | - | |
| fieldNames | 自定义节点 label、value、options、groupLabel 的字段 | object | { label: `label`, value: `value`, options: `options`, groupLabel: `label` } | 4.17.0`groupLabel` 在 5.6.0 新增) |
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true反之则返回 false | boolean \| function(inputValue, option) | true | |
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true反之则返回 false。[示例](#select-demo-search) | boolean \| function(inputValue, option) | true | |
| filterSort | 搜索时对筛选结果项的排序函数, 类似[Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)里的 compareFunction | (optionA: Option, optionB: Option) => number | - | 4.9.0 |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 Select 的 value 类型从 `string` 变为 { value: string, label: ReactNode } 的格式 | boolean | false | |

View File

@ -1,8 +1,9 @@
'use client';
import * as React from 'react';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import * as React from 'react';
import type { PresetColorType, PresetStatusColorType } from '../_util/colors';
import { isPresetColor, isPresetStatusColor } from '../_util/colors';
import useClosable from '../_util/hooks/useClosable';
@ -12,6 +13,8 @@ import Wave from '../_util/wave';
import { ConfigContext } from '../config-provider';
import CheckableTag from './CheckableTag';
import useStyle from './style';
import PresetCmp from './style/presetCmp';
import StatusCmp from './style/statusCmp';
export type { CheckableTagProps } from './CheckableTag';
@ -69,7 +72,9 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
}
}, [props.visible]);
const isInternalColor = isPresetColor(color) || isPresetStatusColor(color);
const isPreset = isPresetColor(color);
const isStatus = isPresetStatusColor(color);
const isInternalColor = isPreset || isStatus;
const tagStyle: React.CSSProperties = {
backgroundColor: color && !isInternalColor ? color : undefined,
@ -140,6 +145,8 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
<span {...props} ref={ref} className={tagClassName} style={tagStyle}>
{kids}
{mergedCloseIcon}
{isPreset && <PresetCmp key="preset" prefixCls={prefixCls} />}
{isStatus && <StatusCmp key="status" prefixCls={prefixCls} />}
</span>
);

View File

@ -1,9 +1,11 @@
import type { CSSInterpolation } from '@ant-design/cssinjs';
import type React from 'react';
import capitalize from '../../_util/capitalize';
import type { CSSInterpolation } from '@ant-design/cssinjs';
import { resetComponent } from '../../style';
import type { GlobalToken } from '../../theme';
import type { FullToken } from '../../theme/internal';
import { genComponentStyleHook, genPresetColor, mergeToken } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import type { GenStyleFn } from '../../theme/util/genComponentStyleHook';
export interface ComponentToken {
/**
@ -18,7 +20,7 @@ export interface ComponentToken {
defaultColor: string;
}
interface TagToken extends FullToken<'Tag'> {
export interface TagToken extends FullToken<'Tag'> {
tagFontSize: number;
tagLineHeight: React.CSSProperties['lineHeight'];
tagIconSize: number;
@ -28,44 +30,6 @@ interface TagToken extends FullToken<'Tag'> {
// ============================== Styles ==============================
type CssVariableType = 'Success' | 'Info' | 'Error' | 'Warning';
const genTagStatusStyle = (
token: TagToken,
status: 'success' | 'processing' | 'error' | 'warning',
cssVariableType: CssVariableType,
): CSSInterpolation => {
const capitalizedCssVariableType = capitalize<CssVariableType>(cssVariableType);
return {
[`${token.componentCls}-${status}`]: {
color: token[`color${cssVariableType}`],
background: token[`color${capitalizedCssVariableType}Bg`],
borderColor: token[`color${capitalizedCssVariableType}Border`],
[`&${token.componentCls}-borderless`]: {
borderColor: 'transparent',
},
},
};
};
const genPresetStyle = (token: TagToken) =>
genPresetColor(token, (colorKey, { textColor, lightBorderColor, lightColor, darkColor }) => ({
[`${token.componentCls}-${colorKey}`]: {
color: textColor,
background: lightColor,
borderColor: lightBorderColor,
// Inverse color
'&-inverse': {
color: token.colorTextLightSolid,
background: darkColor,
borderColor: darkColor,
},
[`&${token.componentCls}-borderless`]: {
borderColor: 'transparent',
},
},
}));
const genBaseStyle = (token: TagToken): CSSInterpolation => {
const { paddingXXS, lineWidth, tagPaddingHorizontal, componentCls } = token;
const paddingInline = tagPaddingHorizontal - lineWidth;
@ -162,33 +126,33 @@ const genBaseStyle = (token: TagToken): CSSInterpolation => {
};
// ============================== Export ==============================
export const prepareToken: (token: Parameters<GenStyleFn<'Tag'>>[0]) => TagToken = (token) => {
const { lineWidth, fontSizeIcon } = token;
const tagFontSize = token.fontSizeSM;
const tagLineHeight = `${token.lineHeightSM * tagFontSize}px`;
const tagToken = mergeToken<TagToken>(token, {
tagFontSize,
tagLineHeight,
tagIconSize: fontSizeIcon - 2 * lineWidth, // Tag icon is much smaller
tagPaddingHorizontal: 8, // Fixed padding.
tagBorderlessBg: token.colorFillTertiary,
});
return tagToken;
};
export const prepareCommonToken: (token: GlobalToken) => ComponentToken = (token) => ({
defaultBg: token.colorFillQuaternary,
defaultColor: token.colorText,
});
export default genComponentStyleHook(
'Tag',
(token) => {
const { lineWidth, fontSizeIcon } = token;
const tagToken = prepareToken(token);
const tagFontSize = token.fontSizeSM;
const tagLineHeight = `${token.lineHeightSM * tagFontSize}px`;
const tagToken = mergeToken<TagToken>(token, {
tagFontSize,
tagLineHeight,
tagIconSize: fontSizeIcon - 2 * lineWidth, // Tag icon is much smaller
tagPaddingHorizontal: 8, // Fixed padding.
tagBorderlessBg: token.colorFillTertiary,
});
return [
genBaseStyle(tagToken),
genPresetStyle(tagToken),
genTagStatusStyle(tagToken, 'success', 'Success'),
genTagStatusStyle(tagToken, 'processing', 'Info'),
genTagStatusStyle(tagToken, 'error', 'Error'),
genTagStatusStyle(tagToken, 'warning', 'Warning'),
];
return genBaseStyle(tagToken);
},
(token) => ({
defaultBg: token.colorFillQuaternary,
defaultColor: token.colorText,
}),
prepareCommonToken,
);

View File

@ -0,0 +1,33 @@
// Style as status component
import { prepareCommonToken, prepareToken, type TagToken } from '.';
import { genPresetColor, genSubStyleComponent } from '../../theme/internal';
// ============================== Preset ==============================
const genPresetStyle = (token: TagToken) =>
genPresetColor(token, (colorKey, { textColor, lightBorderColor, lightColor, darkColor }) => ({
[`${token.componentCls}-${colorKey}`]: {
color: textColor,
background: lightColor,
borderColor: lightBorderColor,
// Inverse color
'&-inverse': {
color: token.colorTextLightSolid,
background: darkColor,
borderColor: darkColor,
},
[`&${token.componentCls}-borderless`]: {
borderColor: 'transparent',
},
},
}));
// ============================== Export ==============================
export default genSubStyleComponent(
['Tag', 'preset'],
(token) => {
const tagToken = prepareToken(token);
return genPresetStyle(tagToken);
},
prepareCommonToken,
);

View File

@ -0,0 +1,43 @@
// Style as status component
import type { CSSInterpolation } from '@ant-design/cssinjs';
import { prepareCommonToken, prepareToken, type TagToken } from '.';
import capitalize from '../../_util/capitalize';
import { genSubStyleComponent } from '../../theme/internal';
// ============================== Status ==============================
type CssVariableType = 'Success' | 'Info' | 'Error' | 'Warning';
const genTagStatusStyle = (
token: TagToken,
status: 'success' | 'processing' | 'error' | 'warning',
cssVariableType: CssVariableType,
): CSSInterpolation => {
const capitalizedCssVariableType = capitalize<CssVariableType>(cssVariableType);
return {
[`${token.componentCls}-${status}`]: {
color: token[`color${cssVariableType}`],
background: token[`color${capitalizedCssVariableType}Bg`],
borderColor: token[`color${capitalizedCssVariableType}Border`],
[`&${token.componentCls}-borderless`]: {
borderColor: 'transparent',
},
},
};
};
// ============================== Export ==============================
export default genSubStyleComponent(
['Tag', 'status'],
(token) => {
const tagToken = prepareToken(token);
return [
genTagStatusStyle(tagToken, 'success', 'Success'),
genTagStatusStyle(tagToken, 'processing', 'Info'),
genTagStatusStyle(tagToken, 'error', 'Error'),
genTagStatusStyle(tagToken, 'warning', 'Warning'),
];
},
prepareCommonToken,
);

View File

@ -184,13 +184,19 @@ export interface SubStyleComponentProps {
prefixCls: string;
}
export function genSubStyleComponent<ComponentName extends OverrideComponent>(
export const genSubStyleComponent: <ComponentName extends OverrideComponent>(
...args: Parameters<typeof genComponentStyleHook<ComponentName>>
): ComponentType<SubStyleComponentProps> {
const useStyle = genComponentStyleHook(...args);
) => ComponentType<SubStyleComponentProps> = (componentName, styleFn, getDefaultToken, options) => {
const useStyle = genComponentStyleHook(componentName, styleFn, getDefaultToken, {
resetStyle: false,
// Sub Style should default after root one
order: -998,
...options,
});
return ({ prefixCls }: SubStyleComponentProps) => {
useStyle(prefixCls);
return null;
};
}
};

View File

@ -305,6 +305,11 @@ const genTransferListStyle: GenerateStyle<TransferToken> = (token: TransferToken
'&-footer': {
borderTop: `${lineWidth}px ${lineType} ${colorSplit}`,
},
// fix: https://github.com/ant-design/ant-design/issues/44489
'&-checkbox': {
lineHeight: 1,
},
};
};

View File

@ -72,7 +72,7 @@ Common props ref[Common props](/docs/react/common-props)
| progress | Custom progress bar | [ProgressProps](/components/progress/#api) (support `type="line"` only) | { strokeWidth: 2, showInfo: false } | 4.3.0 |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon` and `downloadIcon` individually | boolean \| { showPreviewIcon?: boolean, showDownloadIcon?: boolean, showRemoveIcon?: boolean, previewIcon?: ReactNode \| (file: UploadFile) => ReactNode, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| withCredentials | The ajax upload with cookie sent | boolean | false | |
| onChange | A callback function, can be executed when uploading state is changing, see [onChange](#onchange) | function | - | |
| onChange | A callback function, can be executed when uploading state is changing. It will trigger by every uploading phase. see [onChange](#onchange) | function | - | |
| onDrop | A callback function executed when files are dragged and dropped into the upload area | (event: React.DragEvent) => void | - | 4.16.0 |
| onDownload | Click the method to download the file, pass the method to perform the method logic, and do not pass the default jump to the new TAB | function(file): void | (Jump to new TAB) | |
| onPreview | A callback function, will be executed when the file link or preview icon is clicked | function(file) | - | |
@ -94,7 +94,7 @@ Extends File with additional props.
### onChange
> The function will be called when uploading is in progress, completed, or failed.
> 💡 The function will be called when uploading is in progress, completed, or failed.
When uploading state change, it returns:

View File

@ -73,7 +73,7 @@ demo:
| progress | 自定义进度条样式 | [ProgressProps](/components/progress-cn#api)(仅支持 `type="line"` | { strokeWidth: 2, showInfo: false } | 4.3.0 |
| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon``downloadIcon` | boolean \| { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, previewIcon?: ReactNode \| (file: UploadFile) => ReactNode, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| withCredentials | 上传请求时是否携带 cookie | boolean | false | |
| onChange | 上传文件改变时的回调,详见 [onChange](#onchange) | function | - | |
| onChange | 上传文件改变时的回调,上传每个阶段都会触发该事件。详见 [onChange](#onchange) | function | - | |
| onDrop | 当文件被拖入上传区域时执行的回调功能 | (event: React.DragEvent) => void | - | 4.16.0 |
| onDownload | 点击下载文件时的回调,如果没有指定,则默认跳转到文件 url 对应的标签页 | function(file): void | (跳转新标签页) | |
| onPreview | 点击文件链接或预览图标时的回调 | function(file) | - | |
@ -95,7 +95,7 @@ demo:
### onChange
> 上传中、完成、失败都会调用这个函数。
> 💡 上传中、完成、失败都会调用这个函数。
文件状态改变的回调,返回为:

View File

@ -1,10 +1,11 @@
import React from 'react';
import Watermark from '..';
import Modal from '../../modal';
import Drawer from '../../drawer';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render, waitFakeTimer, waitFor } from '../../../tests/utils';
import Drawer from '../../drawer';
import Modal from '../../modal';
describe('Watermark', () => {
mountTest(Watermark);
@ -18,10 +19,18 @@ describe('Watermark', () => {
});
});
beforeEach(() => {
jest.useFakeTimers();
});
afterAll(() => {
mockSrcSet.mockRestore();
});
afterEach(() => {
jest.useRealTimers();
});
it('The watermark should render successfully', () => {
const { container } = render(<Watermark className="watermark" content="Ant Design" />);
expect(container.querySelector('.watermark div')).toBeTruthy();
@ -126,4 +135,15 @@ describe('Watermark', () => {
() => document.body.querySelector('.ant-drawer-content')!.lastChild!,
);
});
it('should not crash if content is empty string', async () => {
const spy = jest.spyOn(CanvasRenderingContext2D.prototype, 'drawImage');
render(<Watermark content="" className="watermark" />);
await waitFakeTimer();
expect(spy).not.toHaveBeenCalledWith(expect.anything(), 0, 0);
expect(spy).not.toHaveBeenCalledWith(expect.anything(), -0, 0);
expect(spy).not.toHaveBeenCalledWith(expect.anything(), -0, -0);
expect(spy).not.toHaveBeenCalledWith(expect.anything(), 0, -0);
spy.mockRestore();
});
});

View File

@ -69,7 +69,9 @@ export default function useClips() {
// Copy from `ctx` and rotate
rCtx.translate(realMaxSize / 2, realMaxSize / 2);
rCtx.rotate(angle);
rCtx.drawImage(canvas, -contentWidth / 2, -contentHeight / 2);
if (contentWidth > 0 && contentHeight > 0) {
rCtx.drawImage(canvas, -contentWidth / 2, -contentHeight / 2);
}
// Get boundary of rotated text
function getRotatePos(x: number, y: number) {

View File

@ -136,7 +136,7 @@
"rc-mentions": "~2.6.0",
"rc-menu": "~9.11.0",
"rc-motion": "^2.7.3",
"rc-notification": "~5.0.4",
"rc-notification": "~5.1.1",
"rc-pagination": "~3.6.0",
"rc-picker": "~3.13.0",
"rc-progress": "~3.5.1",