diff --git a/components/form/Form.tsx b/components/form/Form.tsx index c853d9c30c55..aa7334ab9da0 100644 --- a/components/form/Form.tsx +++ b/components/form/Form.tsx @@ -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 extends Omit, 'form'> { diff --git a/components/form/FormItemLabel.tsx b/components/form/FormItemLabel.tsx index 69fed60960d8..8d53fff2f317 100644 --- a/components/form/FormItemLabel.tsx +++ b/components/form/FormItemLabel.tsx @@ -114,7 +114,13 @@ const FormItemLabel: React.FC {labelChildren} @@ -127,7 +133,7 @@ const FormItemLabel: React.FC + diff --git a/components/form/__tests__/__snapshots__/demo.test.tsx.snap b/components/form/__tests__/__snapshots__/demo.test.tsx.snap index 6d2a2cdabc01..2ed8bfc4e07c 100644 --- a/components/form/__tests__/__snapshots__/demo.test.tsx.snap +++ b/components/form/__tests__/__snapshots__/demo.test.tsx.snap @@ -6557,6 +6557,25 @@ exports[`renders components/form/demo/required-mark.tsx correctly 1`] = ` class="ant-radio-group ant-radio-group-outline" id="requiredMarkValue" > + diff --git a/components/form/__tests__/index.test.tsx b/components/form/__tests__/index.test.tsx index 4f434f47edd5..81169a08f1b4 100644 --- a/components/form/__tests__/index.test.tsx +++ b/components/form/__tests__/index.test.tsx @@ -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( -
- - - - - - -
, - ); + const { container } = render( +
+ + + + + + +
, + ); + + 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( +
`${label}: ${info.required}`}> + + + + + + +
, + ); - 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', () => { diff --git a/components/form/demo/required-mark.tsx b/components/form/demo/required-mark.tsx index 876f94c6692c..0fe8a9ebe338 100644 --- a/components/form/demo/required-mark.tsx +++ b/components/form/demo/required-mark.tsx @@ -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 ? Required : optional} + {label} + +); const App: React.FC = () => { const [form] = Form.useForm(); @@ -18,13 +25,14 @@ const App: React.FC = () => { layout="vertical" initialValues={{ requiredMarkValue: requiredMark }} onValuesChange={onRequiredTypeChange} - requiredMark={requiredMark} + requiredMark={requiredMark === 'customize' ? customizeRequiredMark : requiredMark} > + Default Optional - Required Hidden + Customize diff --git a/components/form/index.en-US.md b/components/form/index.en-US.md index 8faff8b8168a..90cca7376ccd 100644 --- a/components/form/index.en-US.md +++ b/components/form/index.en-US.md @@ -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) | - | | diff --git a/components/form/index.zh-CN.md b/components/form/index.zh-CN.md index d4dbe11b8389..ce78f81c8df9 100644 --- a/components/form/index.zh-CN.md +++ b/components/form/index.zh-CN.md @@ -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) | - | |