mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-23 18:50:06 +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 classNames from 'classnames';
|
||||
import { get, set } from 'rc-util';
|
||||
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
|
||||
|
||||
import type { ColProps } from '../grid/col';
|
||||
@ -31,17 +32,21 @@ interface FormItemInputMiscProps {
|
||||
}
|
||||
|
||||
export interface FormItemInputProps {
|
||||
labelCol?: ColProps;
|
||||
wrapperCol?: ColProps;
|
||||
extra?: React.ReactNode;
|
||||
status?: ValidateStatus;
|
||||
help?: React.ReactNode;
|
||||
fieldId?: string;
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
const GRID_MAX = 24;
|
||||
|
||||
const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = (props) => {
|
||||
const {
|
||||
prefixCls,
|
||||
status,
|
||||
labelCol,
|
||||
wrapperCol,
|
||||
children,
|
||||
errors,
|
||||
@ -52,19 +57,41 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = (pr
|
||||
fieldId,
|
||||
marginBottom,
|
||||
onErrorVisibleChanged,
|
||||
label,
|
||||
} = props;
|
||||
const baseClassName = `${prefixCls}-item`;
|
||||
|
||||
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);
|
||||
|
||||
// Pass to sub FormItem should not with col info
|
||||
const subFormContext = React.useMemo(() => ({ ...formContext }), [formContext]);
|
||||
delete subFormContext.labelCol;
|
||||
delete subFormContext.wrapperCol;
|
||||
const subFormContext = React.useMemo(() => {
|
||||
const { labelCol, wrapperCol, ...rest } = formContext;
|
||||
return rest;
|
||||
}, [formContext]);
|
||||
|
||||
const extraRef = React.useRef<HTMLDivElement>(null);
|
||||
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"
|
||||
>
|
||||
<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
|
||||
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"
|
||||
>
|
||||
<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
|
||||
class="ant-form-item-control-input"
|
||||
|
@ -1367,6 +1367,104 @@ describe('Form', () => {
|
||||
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', () => {
|
||||
const { container } = render(
|
||||
<Form>
|
||||
|
@ -43,15 +43,11 @@ const App: React.FC = () => (
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FieldType>
|
||||
name="remember"
|
||||
valuePropName="checked"
|
||||
wrapperCol={{ offset: 8, span: 16 }}
|
||||
>
|
||||
<Form.Item<FieldType> name="remember" valuePropName="checked" label={null}>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Form.Item label={null}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
|
@ -66,7 +66,7 @@ const App: React.FC = () => (
|
||||
<Input placeholder="Input birth month" />
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
<Form.Item label=" " colon={false}>
|
||||
<Form.Item label={null}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
|
@ -33,7 +33,7 @@ const App: React.FC = () => (
|
||||
<DatePicker />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Form.Item label={null}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
|
@ -44,7 +44,7 @@ const App: React.FC = () => (
|
||||
<Form.Item name={['user', 'introduction']} label="Introduction">
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ ...layout.wrapperCol, offset: 8 }}>
|
||||
<Form.Item label={null}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
|
@ -66,7 +66,7 @@ const App: React.FC = () => (
|
||||
<Form.Item name="time-picker" label="TimePicker" {...config}>
|
||||
<TimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ xs: { span: 24, offset: 0 }, sm: { span: 16, offset: 8 } }}>
|
||||
<Form.Item label={null}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</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 |
|
||||
| htmlFor | Set sub label `htmlFor` | string | - | |
|
||||
| initialValue | Config sub default value. Form `initialValues` get higher priority when conflict | string | - | 4.2.0 |
|
||||
| label | Label text | ReactNode | - | |
|
||||
| labelAlign | The text align of label | `left` \| `right` | `right` | |
|
||||
| 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` | |
|
||||
| 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 |
|
||||
| name | Field name, support array | [NamePath](#namepath) | - | |
|
||||
|
@ -137,7 +137,7 @@ const validateMessages = {
|
||||
| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | 4.4.0 |
|
||||
| htmlFor | 设置子元素 label `htmlFor` 属性 | string | - | |
|
||||
| initialValue | 设置子元素默认值,如果与 Form 的 `initialValues` 冲突则以 Form 为准 | string | - | 4.2.0 |
|
||||
| label | `label` 标签的文本 | ReactNode | - | |
|
||||
| label | `label` 标签的文本,当不需要 label 又需要与冒号对齐,可以设为 null | ReactNode | - | null: 5.22.0 |
|
||||
| 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) | - | |
|
||||
| messageVariables | 默认验证字段的信息,查看[详情](#messagevariables) | Record<string, string> | - | 4.7.0 |
|
||||
|
@ -214,14 +214,14 @@ describe('Grid', () => {
|
||||
// https://github.com/ant-design/ant-design/issues/39690
|
||||
it('Justify and align properties should reactive for Row', () => {
|
||||
const ReactiveTest = () => {
|
||||
const [justify, setjustify] = useState<any>('start');
|
||||
const [justify, setJustify] = useState<any>('start');
|
||||
return (
|
||||
<>
|
||||
<Row justify={justify} align="bottom">
|
||||
<div>button1</div>
|
||||
<div>button</div>
|
||||
</Row>
|
||||
<span onClick={() => setjustify('end')} />
|
||||
<span onClick={() => setJustify('end')} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user