Skip to content

Commit

Permalink
feat: Form requiredMark support renderProps (ant-design#44073)
Browse files Browse the repository at this point in the history
* feat: Form requiredMark support renderProps

* test: add test case

* test: update snapshot
  • Loading branch information
zombieJ authored Aug 7, 2023
1 parent 1f03d37 commit 5e9ef68
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 38 deletions.
5 changes: 4 additions & 1 deletion components/form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import type { FormLabelAlign } from './interface';
import useStyle from './style';
import ValidateMessagesContext from './validateMessagesContext';

export type RequiredMark = boolean | 'optional';
export type RequiredMark =
| boolean
| 'optional'
| ((labelNode: React.ReactNode, info: { required: boolean }) => React.ReactNode);
export type FormLayout = 'horizontal' | 'inline' | 'vertical';

export interface FormProps<Values = any> extends Omit<RcFormProps<Values>, 'form'> {
Expand Down
10 changes: 8 additions & 2 deletions components/form/FormItemLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@ const FormItemLabel: React.FC<FormItemLabelProps & { required?: boolean; prefixC
);
}

if (requiredMark === 'optional' && !required) {
// Required Mark
const isOptionalMark = requiredMark === 'optional';
const isRenderMark = typeof requiredMark === 'function';

if (isRenderMark) {
labelChildren = requiredMark(labelChildren, { required: !!required });
} else if (isOptionalMark && !required) {
labelChildren = (
<>
{labelChildren}
Expand All @@ -127,7 +133,7 @@ const FormItemLabel: React.FC<FormItemLabelProps & { required?: boolean; prefixC

const labelClassName = classNames({
[`${prefixCls}-item-required`]: required,
[`${prefixCls}-item-required-mark-optional`]: requiredMark === 'optional',
[`${prefixCls}-item-required-mark-optional`]: isOptionalMark || isRenderMark,
[`${prefixCls}-item-no-colon`]: !computedColon,
});

Expand Down
27 changes: 23 additions & 4 deletions components/form/__tests__/__snapshots__/demo-extend.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9730,6 +9730,25 @@ exports[`renders components/form/demo/required-mark.tsx extend context correctly
class="ant-radio-group ant-radio-group-outline"
id="requiredMarkValue"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-in-form-item"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="true"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Default
</span>
</label>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked ant-radio-button-wrapper-in-form-item"
>
Expand Down Expand Up @@ -9759,14 +9778,14 @@ exports[`renders components/form/demo/required-mark.tsx extend context correctly
<input
class="ant-radio-button-input"
type="radio"
value="true"
value="false"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Required
Hidden
</span>
</label>
<label
Expand All @@ -9778,14 +9797,14 @@ exports[`renders components/form/demo/required-mark.tsx extend context correctly
<input
class="ant-radio-button-input"
type="radio"
value="false"
value="customize"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Hidden
Customize
</span>
</label>
</div>
Expand Down
27 changes: 23 additions & 4 deletions components/form/__tests__/__snapshots__/demo.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6557,6 +6557,25 @@ exports[`renders components/form/demo/required-mark.tsx correctly 1`] = `
class="ant-radio-group ant-radio-group-outline"
id="requiredMarkValue"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-in-form-item"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="true"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Default
</span>
</label>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked ant-radio-button-wrapper-in-form-item"
>
Expand Down Expand Up @@ -6586,14 +6605,14 @@ exports[`renders components/form/demo/required-mark.tsx correctly 1`] = `
<input
class="ant-radio-button-input"
type="radio"
value="true"
value="false"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Required
Hidden
</span>
</label>
<label
Expand All @@ -6605,14 +6624,14 @@ exports[`renders components/form/demo/required-mark.tsx correctly 1`] = `
<input
class="ant-radio-button-input"
type="radio"
value="false"
value="customize"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Hidden
Customize
</span>
</label>
</div>
Expand Down
69 changes: 48 additions & 21 deletions components/form/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1840,29 +1840,56 @@ describe('Form', () => {
expect(onChange).toHaveBeenNthCalledWith(idx++, 'success');
});

// https://user-images.githubusercontent.com/32004925/230819163-464fe90d-422d-4a6d-9e35-44a25d4c64f1.png
it('should not render `requiredMark` when Form.Item has no required prop', () => {
// Escaping TypeScript error
const genProps = (value: any) => ({ ...value });
describe('requiredMark', () => {
// https://user-images.githubusercontent.com/32004925/230819163-464fe90d-422d-4a6d-9e35-44a25d4c64f1.png
it('should not render `requiredMark` when Form.Item has no required prop', () => {
// Escaping TypeScript error
const genProps = (value: any) => ({ ...value });

const { container } = render(
<Form name="basic" requiredMark="optional">
<Form.Item
label="First Name"
name="firstName"
required
{...genProps({ requiredMark: false })}
>
<Input />
</Form.Item>
<Form.Item label="Last Name" name="lastName" required {...genProps({ requiredMark: true })}>
<Input />
</Form.Item>
</Form>,
);
const { container } = render(
<Form name="basic" requiredMark="optional">
<Form.Item
label="First Name"
name="firstName"
required
{...genProps({ requiredMark: false })}
>
<Input />
</Form.Item>
<Form.Item
label="Last Name"
name="lastName"
required
{...genProps({ requiredMark: true })}
>
<Input />
</Form.Item>
</Form>,
);

expect(container.querySelectorAll('.ant-form-item-required')).toHaveLength(2);
expect(container.querySelectorAll('.ant-form-item-required-mark-optional')).toHaveLength(2);
});

it('customize logic', () => {
const { container } = render(
<Form name="basic" requiredMark={(label, info) => `${label}: ${info.required}`}>
<Form.Item label="Required" required>
<Input />
</Form.Item>
<Form.Item label="Optional">
<Input />
</Form.Item>
</Form>,
);

expect(container.querySelectorAll('.ant-form-item-required')).toHaveLength(2);
expect(container.querySelectorAll('.ant-form-item-required-mark-optional')).toHaveLength(2);
expect(container.querySelectorAll('.ant-form-item-label')[0].textContent).toEqual(
'Required: true',
);
expect(container.querySelectorAll('.ant-form-item-label')[1].textContent).toEqual(
'Optional: false',
);
});
});

it('children support comment', () => {
Expand Down
16 changes: 12 additions & 4 deletions components/form/demo/required-mark.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React, { useState } from 'react';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Form, Input, Radio } from 'antd';
import { Button, Form, Input, Radio, Tag } from 'antd';

type RequiredMark = boolean | 'optional';
type RequiredMark = boolean | 'optional' | 'customize';

const customizeRequiredMark = (label: React.ReactNode, { required }: { required: boolean }) => (
<>
{required ? <Tag color="error">Required</Tag> : <Tag color="warning">optional</Tag>}
{label}
</>
);

const App: React.FC = () => {
const [form] = Form.useForm();
Expand All @@ -18,13 +25,14 @@ const App: React.FC = () => {
layout="vertical"
initialValues={{ requiredMarkValue: requiredMark }}
onValuesChange={onRequiredTypeChange}
requiredMark={requiredMark}
requiredMark={requiredMark === 'customize' ? customizeRequiredMark : requiredMark}
>
<Form.Item label="Required Mark" name="requiredMarkValue">
<Radio.Group>
<Radio.Button value>Default</Radio.Button>
<Radio.Button value="optional">Optional</Radio.Button>
<Radio.Button value>Required</Radio.Button>
<Radio.Button value={false}>Hidden</Radio.Button>
<Radio.Button value="customize">Customize</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Field A" required tooltip="This is a required field">
Expand Down
2 changes: 1 addition & 1 deletion components/form/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ High performance Form component with data scope management. Including data colle
| layout | Form layout | `horizontal` \| `vertical` \| `inline` | `horizontal` | |
| name | Form name. Will be the prefix of Field `id` | string | - | |
| preserve | Keep field value even when field removed | boolean | true | 4.4.0 |
| requiredMark | Required mark style. Can use required mark or optional mark. You can not config to single Form.Item since this is a Form level config | boolean \| `optional` | true | 4.6.0 |
| requiredMark | Required mark style. Can use required mark or optional mark. You can not config to single Form.Item since this is a Form level config | boolean \| `optional` \| ((label: ReactNode, info: { required: boolean }) => ReactNode) | true | `renderProps`: 5.9.0 |
| scrollToFirstError | Auto scroll to first failed field when submit | boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) | false | |
| size | Set field component size (antd components only) | `small` \| `middle` \| `large` | - | |
| validateMessages | Validation prompt template, description [see below](#validatemessages) | [ValidateMessages](https://github.com/ant-design/ant-design/blob/6234509d18bac1ac60fbb3f92a5b2c6a6361295a/components/locale/en_US.ts#L88-L134) | - | |
Expand Down
2 changes: 1 addition & 1 deletion components/form/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
| layout | 表单布局 | `horizontal` \| `vertical` \| `inline` | `horizontal` | |
| name | 表单名称,会作为表单字段 `id` 前缀使用 | string | - | |
| preserve | 当字段被删除时保留字段值 | boolean | true | 4.4.0 |
| requiredMark | 必选样式,可以切换为必选或者可选展示样式。此为 Form 配置,Form.Item 无法单独配置 | boolean \| `optional` | true | 4.6.0 |
| requiredMark | 必选样式,可以切换为必选或者可选展示样式。此为 Form 配置,Form.Item 无法单独配置 | boolean \| `optional` \| ((label: ReactNode, info: { required: boolean }) => ReactNode) | true | `renderProps`: 5.9.0 |
| scrollToFirstError | 提交失败自动滚动到第一个错误字段 | boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) | false | |
| size | 设置字段组件的尺寸(仅限 antd 组件) | `small` \| `middle` \| `large` | - | |
| validateMessages | 验证提示模板,说明[见下](#validatemessages) | [ValidateMessages](https://github.com/ant-design/ant-design/blob/6234509d18bac1ac60fbb3f92a5b2c6a6361295a/components/locale/en_US.ts#L88-L134) | - | |
Expand Down

0 comments on commit 5e9ef68

Please sign in to comment.