Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Form requiredMark support renderProps #44073

Merged
merged 3 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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