mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
Form.Item support noLabel (#51524)
* feat: Form.Item support noLabel * feat: doc * feat: test * feat: test * feat: test * feat: review * feat: review * feat: 仅支持 span * feat: review * feat: review * feat: review * feat: review * feat: review * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: add test * feat: demo * feat: test * feat: test * feat: test * feat: 代码优化 * feat: add labelCol * feat: 代码优化 * feat: 代码优化 * feat: reset * feat: test * feat: test * feat: review * feat: doc
This commit is contained in:
parent
868d344d90
commit
54fb6bd831
@ -1,5 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { get, set } from 'rc-util';
|
||||||
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
|
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
|
||||||
|
|
||||||
import type { ColProps } from '../grid/col';
|
import type { ColProps } from '../grid/col';
|
||||||
@ -31,17 +32,21 @@ interface FormItemInputMiscProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FormItemInputProps {
|
export interface FormItemInputProps {
|
||||||
|
labelCol?: ColProps;
|
||||||
wrapperCol?: ColProps;
|
wrapperCol?: ColProps;
|
||||||
extra?: React.ReactNode;
|
extra?: React.ReactNode;
|
||||||
status?: ValidateStatus;
|
status?: ValidateStatus;
|
||||||
help?: React.ReactNode;
|
help?: React.ReactNode;
|
||||||
fieldId?: string;
|
fieldId?: string;
|
||||||
|
label?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
const GRID_MAX = 24;
|
||||||
|
|
||||||
const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = (props) => {
|
const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
prefixCls,
|
prefixCls,
|
||||||
status,
|
status,
|
||||||
|
labelCol,
|
||||||
wrapperCol,
|
wrapperCol,
|
||||||
children,
|
children,
|
||||||
errors,
|
errors,
|
||||||
@ -52,19 +57,41 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = (pr
|
|||||||
fieldId,
|
fieldId,
|
||||||
marginBottom,
|
marginBottom,
|
||||||
onErrorVisibleChanged,
|
onErrorVisibleChanged,
|
||||||
|
label,
|
||||||
} = props;
|
} = props;
|
||||||
const baseClassName = `${prefixCls}-item`;
|
const baseClassName = `${prefixCls}-item`;
|
||||||
|
|
||||||
const formContext = React.useContext(FormContext);
|
const formContext = React.useContext(FormContext);
|
||||||
|
|
||||||
const mergedWrapperCol: ColProps = wrapperCol || formContext.wrapperCol || {};
|
const mergedWrapperCol = React.useMemo(() => {
|
||||||
|
let mergedWrapper: ColProps = { ...(wrapperCol || formContext.wrapperCol || {}) };
|
||||||
|
if (label === null && !labelCol && !wrapperCol && formContext.labelCol) {
|
||||||
|
const list = [undefined, 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'] as const;
|
||||||
|
|
||||||
|
list.forEach((size) => {
|
||||||
|
const _size = size ? [size] : [];
|
||||||
|
|
||||||
|
const formLabel = get(formContext.labelCol, _size);
|
||||||
|
const formLabelObj = typeof formLabel === 'object' ? formLabel : {};
|
||||||
|
|
||||||
|
const wrapper = get(mergedWrapper, _size);
|
||||||
|
const wrapperObj = typeof wrapper === 'object' ? wrapper : {};
|
||||||
|
|
||||||
|
if ('span' in formLabelObj && !('offset' in wrapperObj) && formLabelObj.span < GRID_MAX) {
|
||||||
|
mergedWrapper = set(mergedWrapper, [..._size, 'offset'], formLabelObj.span);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return mergedWrapper;
|
||||||
|
}, [wrapperCol, formContext]);
|
||||||
|
|
||||||
const className = classNames(`${baseClassName}-control`, mergedWrapperCol.className);
|
const className = classNames(`${baseClassName}-control`, mergedWrapperCol.className);
|
||||||
|
|
||||||
// Pass to sub FormItem should not with col info
|
// Pass to sub FormItem should not with col info
|
||||||
const subFormContext = React.useMemo(() => ({ ...formContext }), [formContext]);
|
const subFormContext = React.useMemo(() => {
|
||||||
delete subFormContext.labelCol;
|
const { labelCol, wrapperCol, ...rest } = formContext;
|
||||||
delete subFormContext.wrapperCol;
|
return rest;
|
||||||
|
}, [formContext]);
|
||||||
|
|
||||||
const extraRef = React.useRef<HTMLDivElement>(null);
|
const extraRef = React.useRef<HTMLDivElement>(null);
|
||||||
const [extraHeight, setExtraHeight] = React.useState<number>(0);
|
const [extraHeight, setExtraHeight] = React.useState<number>(0);
|
||||||
|
@ -21143,7 +21143,7 @@ exports[`renders components/form/demo/time-related-controls.tsx extend context c
|
|||||||
class="ant-row ant-form-item-row"
|
class="ant-row ant-form-item-row"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-col ant-form-item-control ant-col-xs-24 ant-col-xs-offset-0 ant-col-sm-16 ant-col-sm-offset-8"
|
class="ant-col ant-form-item-control ant-col-xs-24 ant-col-sm-16 ant-col-sm-offset-8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-control-input"
|
class="ant-form-item-control-input"
|
||||||
|
@ -8747,7 +8747,7 @@ exports[`renders components/form/demo/time-related-controls.tsx correctly 1`] =
|
|||||||
class="ant-row ant-form-item-row"
|
class="ant-row ant-form-item-row"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-col ant-form-item-control ant-col-xs-24 ant-col-xs-offset-0 ant-col-sm-16 ant-col-sm-offset-8"
|
class="ant-col ant-form-item-control ant-col-xs-24 ant-col-sm-16 ant-col-sm-offset-8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-control-input"
|
class="ant-form-item-control-input"
|
||||||
|
@ -1367,6 +1367,104 @@ describe('Form', () => {
|
|||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('form.item should support label = null', () => {
|
||||||
|
// base size
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 14 }}>
|
||||||
|
<Form.Item label="name" name="name">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={null}>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
const { container } = render(<App />);
|
||||||
|
|
||||||
|
const items = container.querySelectorAll('.ant-form-item');
|
||||||
|
const oneItems = items[0].querySelector('.ant-row')?.querySelectorAll('.ant-col');
|
||||||
|
expect(oneItems?.[0]).toHaveClass('ant-col-4');
|
||||||
|
expect(oneItems?.[0].className.includes('offset')).toBeFalsy();
|
||||||
|
expect(oneItems?.[1]).toHaveClass('ant-col-14');
|
||||||
|
expect(oneItems?.[1].className.includes('offset')).toBeFalsy();
|
||||||
|
const twoItem = items[1].querySelector('.ant-row')?.querySelector('.ant-col');
|
||||||
|
expect(twoItem).toHaveClass('ant-col-14 ant-col-offset-4');
|
||||||
|
|
||||||
|
// more size
|
||||||
|
const list = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'] as const;
|
||||||
|
list.forEach((size) => {
|
||||||
|
const { container } = render(
|
||||||
|
<Form labelCol={{ [size]: { span: 4 } }} wrapperCol={{ span: 14 }}>
|
||||||
|
<Form.Item label="name" name="name">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={null}>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = container.querySelectorAll('.ant-form-item');
|
||||||
|
const oneItems = items[0].querySelector('.ant-row')?.querySelectorAll('.ant-col');
|
||||||
|
expect(oneItems?.[0]).toHaveClass(`ant-col-${size}-4`);
|
||||||
|
expect(oneItems?.[0].className.includes('offset')).toBeFalsy();
|
||||||
|
expect(oneItems?.[1]).toHaveClass('ant-col-14');
|
||||||
|
expect(oneItems?.[1].className.includes('offset')).toBeFalsy();
|
||||||
|
const twoItem = items[1].querySelector('.ant-row')?.querySelector('.ant-col');
|
||||||
|
expect(twoItem).toHaveClass(`ant-col-14 ant-col-${size}-offset-4`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('form.item should support label = null and labelCol.span = 24', () => {
|
||||||
|
// base size
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<Form labelCol={{ span: 24 }} wrapperCol={{ span: 24 }}>
|
||||||
|
<Form.Item label="name" name="name">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={null}>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
const { container } = render(<App />);
|
||||||
|
|
||||||
|
const items = container.querySelectorAll('.ant-form-item');
|
||||||
|
const oneItems = items[0].querySelector('.ant-row')?.querySelectorAll('.ant-col');
|
||||||
|
expect(oneItems?.[0]).toHaveClass('ant-col-24');
|
||||||
|
expect(oneItems?.[0].className.includes('offset')).toBeFalsy();
|
||||||
|
expect(oneItems?.[1]).toHaveClass('ant-col-24');
|
||||||
|
expect(oneItems?.[1].className.includes('offset')).toBeFalsy();
|
||||||
|
const twoItem = items[1].querySelector('.ant-row')?.querySelector('.ant-col');
|
||||||
|
expect(twoItem).toHaveClass('ant-col-24');
|
||||||
|
expect(twoItem?.className.includes('offset')).toBeFalsy();
|
||||||
|
|
||||||
|
// more size
|
||||||
|
const list = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'] as const;
|
||||||
|
list.forEach((size) => {
|
||||||
|
const { container } = render(
|
||||||
|
<Form labelCol={{ [size]: { span: 24 } }} wrapperCol={{ span: 24 }}>
|
||||||
|
<Form.Item label="name" name="name">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={null}>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = container.querySelectorAll('.ant-form-item');
|
||||||
|
const oneItems = items[0].querySelector('.ant-row')?.querySelectorAll('.ant-col');
|
||||||
|
expect(oneItems?.[0]).toHaveClass(`ant-col-${size}-24`);
|
||||||
|
expect(oneItems?.[0].className.includes('offset')).toBeFalsy();
|
||||||
|
expect(oneItems?.[1]).toHaveClass('ant-col-24');
|
||||||
|
expect(oneItems?.[1].className.includes('offset')).toBeFalsy();
|
||||||
|
const twoItem = items[1].querySelector('.ant-row')?.querySelector('.ant-col');
|
||||||
|
expect(twoItem).toHaveClass(`ant-col-24`);
|
||||||
|
expect(twoItem?.className.includes('offset')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('_internalItemRender api test', () => {
|
it('_internalItemRender api test', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<Form>
|
<Form>
|
||||||
|
@ -43,15 +43,11 @@ const App: React.FC = () => (
|
|||||||
<Input.Password />
|
<Input.Password />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item<FieldType>
|
<Form.Item<FieldType> name="remember" valuePropName="checked" label={null}>
|
||||||
name="remember"
|
|
||||||
valuePropName="checked"
|
|
||||||
wrapperCol={{ offset: 8, span: 16 }}
|
|
||||||
>
|
|
||||||
<Checkbox>Remember me</Checkbox>
|
<Checkbox>Remember me</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
<Form.Item label={null}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -66,7 +66,7 @@ const App: React.FC = () => (
|
|||||||
<Input placeholder="Input birth month" />
|
<Input placeholder="Input birth month" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label=" " colon={false}>
|
<Form.Item label={null}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -33,7 +33,7 @@ const App: React.FC = () => (
|
|||||||
<DatePicker />
|
<DatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
<Form.Item label={null}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -44,7 +44,7 @@ const App: React.FC = () => (
|
|||||||
<Form.Item name={['user', 'introduction']} label="Introduction">
|
<Form.Item name={['user', 'introduction']} label="Introduction">
|
||||||
<Input.TextArea />
|
<Input.TextArea />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item wrapperCol={{ ...layout.wrapperCol, offset: 8 }}>
|
<Form.Item label={null}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -66,7 +66,7 @@ const App: React.FC = () => (
|
|||||||
<Form.Item name="time-picker" label="TimePicker" {...config}>
|
<Form.Item name="time-picker" label="TimePicker" {...config}>
|
||||||
<TimePicker />
|
<TimePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item wrapperCol={{ xs: { span: 24, offset: 0 }, sm: { span: 16, offset: 8 } }}>
|
<Form.Item label={null}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -136,8 +136,8 @@ Form field component for data bidirectional binding, validation, layout, and so
|
|||||||
| hidden | Whether to hide Form.Item (still collect and validate value) | boolean | false | 4.4.0 |
|
| hidden | Whether to hide Form.Item (still collect and validate value) | boolean | false | 4.4.0 |
|
||||||
| htmlFor | Set sub label `htmlFor` | string | - | |
|
| htmlFor | Set sub label `htmlFor` | string | - | |
|
||||||
| initialValue | Config sub default value. Form `initialValues` get higher priority when conflict | string | - | 4.2.0 |
|
| initialValue | Config sub default value. Form `initialValues` get higher priority when conflict | string | - | 4.2.0 |
|
||||||
| label | Label text | ReactNode | - | |
|
| label | Label text. When there is no need for a label but it needs to be aligned with a colon, it can be set to null | ReactNode | - | null: 5.22.0 |
|
||||||
| labelAlign | The text align of label | `left` \| `right` | `right` | |
|
| labelAlign | The text align of label, | `left` \| `right` | `right` | |
|
||||||
| labelCol | The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>`. You can set `labelCol` on Form which will not affect nest Item. If both exists, use Item first | [object](/components/grid/#col) | - | |
|
| labelCol | The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>`. You can set `labelCol` on Form which will not affect nest Item. If both exists, use Item first | [object](/components/grid/#col) | - | |
|
||||||
| messageVariables | The default validate field info, description [see below](#messagevariables) | Record<string, string> | - | 4.7.0 |
|
| messageVariables | The default validate field info, description [see below](#messagevariables) | Record<string, string> | - | 4.7.0 |
|
||||||
| name | Field name, support array | [NamePath](#namepath) | - | |
|
| name | Field name, support array | [NamePath](#namepath) | - | |
|
||||||
|
@ -137,7 +137,7 @@ const validateMessages = {
|
|||||||
| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | 4.4.0 |
|
| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | 4.4.0 |
|
||||||
| htmlFor | 设置子元素 label `htmlFor` 属性 | string | - | |
|
| htmlFor | 设置子元素 label `htmlFor` 属性 | string | - | |
|
||||||
| initialValue | 设置子元素默认值,如果与 Form 的 `initialValues` 冲突则以 Form 为准 | string | - | 4.2.0 |
|
| initialValue | 设置子元素默认值,如果与 Form 的 `initialValues` 冲突则以 Form 为准 | string | - | 4.2.0 |
|
||||||
| label | `label` 标签的文本 | ReactNode | - | |
|
| label | `label` 标签的文本,当不需要 label 又需要与冒号对齐,可以设为 null | ReactNode | - | null: 5.22.0 |
|
||||||
| labelAlign | 标签文本对齐方式 | `left` \| `right` | `right` | |
|
| labelAlign | 标签文本对齐方式 | `left` \| `right` | `right` | |
|
||||||
| labelCol | `label` 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}`。你可以通过 Form 的 `labelCol` 进行统一设置,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准 | [object](/components/grid-cn#col) | - | |
|
| labelCol | `label` 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}`。你可以通过 Form 的 `labelCol` 进行统一设置,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准 | [object](/components/grid-cn#col) | - | |
|
||||||
| messageVariables | 默认验证字段的信息,查看[详情](#messagevariables) | Record<string, string> | - | 4.7.0 |
|
| messageVariables | 默认验证字段的信息,查看[详情](#messagevariables) | Record<string, string> | - | 4.7.0 |
|
||||||
|
@ -214,14 +214,14 @@ describe('Grid', () => {
|
|||||||
// https://github.com/ant-design/ant-design/issues/39690
|
// https://github.com/ant-design/ant-design/issues/39690
|
||||||
it('Justify and align properties should reactive for Row', () => {
|
it('Justify and align properties should reactive for Row', () => {
|
||||||
const ReactiveTest = () => {
|
const ReactiveTest = () => {
|
||||||
const [justify, setjustify] = useState<any>('start');
|
const [justify, setJustify] = useState<any>('start');
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row justify={justify} align="bottom">
|
<Row justify={justify} align="bottom">
|
||||||
<div>button1</div>
|
<div>button1</div>
|
||||||
<div>button</div>
|
<div>button</div>
|
||||||
</Row>
|
</Row>
|
||||||
<span onClick={() => setjustify('end')} />
|
<span onClick={() => setJustify('end')} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user