mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 03:59:01 +08:00
Form.Item support layout (#49119)
* feat: FormItem support layout * feat: doc * feat: test * feat: test * feat: test * feat: test * feat: itemPrefixCls
This commit is contained in:
parent
e244a0b816
commit
5932005ce4
@ -29,6 +29,7 @@ export type RequiredMark =
|
||||
| 'optional'
|
||||
| ((labelNode: React.ReactNode, info: { required: boolean }) => React.ReactNode);
|
||||
export type FormLayout = 'horizontal' | 'inline' | 'vertical';
|
||||
export type FormItemLayout = 'horizontal' | 'vertical';
|
||||
|
||||
export interface FormProps<Values = any> extends Omit<RcFormProps<Values>, 'form'> {
|
||||
prefixCls?: string;
|
||||
|
@ -47,11 +47,13 @@ export default function ItemHolder(props: ItemHolderProps) {
|
||||
required,
|
||||
isRequired,
|
||||
onSubItemMetaChange,
|
||||
layout,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const itemPrefixCls = `${prefixCls}-item`;
|
||||
const { requiredMark } = React.useContext(FormContext);
|
||||
const { requiredMark, vertical: formVertical } = React.useContext(FormContext);
|
||||
const vertical = formVertical || layout === 'vertical';
|
||||
|
||||
// ======================== Margin ========================
|
||||
const itemRef = React.useRef<HTMLDivElement>(null);
|
||||
@ -99,6 +101,9 @@ export default function ItemHolder(props: ItemHolderProps) {
|
||||
[`${itemPrefixCls}-has-error`]: mergedValidateStatus === 'error',
|
||||
[`${itemPrefixCls}-is-validating`]: mergedValidateStatus === 'validating',
|
||||
[`${itemPrefixCls}-hidden`]: hidden,
|
||||
|
||||
// Layout
|
||||
[`${itemPrefixCls}-${layout}`]: layout,
|
||||
});
|
||||
|
||||
return (
|
||||
@ -145,6 +150,7 @@ export default function ItemHolder(props: ItemHolderProps) {
|
||||
requiredMark={requiredMark}
|
||||
required={required ?? isRequired}
|
||||
prefixCls={prefixCls}
|
||||
vertical={vertical}
|
||||
/>
|
||||
{/* Input Group */}
|
||||
<FormItemInput
|
||||
|
@ -11,7 +11,7 @@ import { devUseWarning } from '../../_util/warning';
|
||||
import { ConfigContext } from '../../config-provider';
|
||||
import useCSSVarCls from '../../config-provider/hooks/useCSSVarCls';
|
||||
import { FormContext, NoStyleItemContext } from '../context';
|
||||
import type { FormInstance } from '../Form';
|
||||
import type { FormInstance, FormItemLayout } from '../Form';
|
||||
import type { FormItemInputProps } from '../FormItemInput';
|
||||
import type { FormItemLabelProps, LabelTooltipType } from '../FormItemLabel';
|
||||
import useChildren from '../hooks/useChildren';
|
||||
@ -102,6 +102,7 @@ export interface FormItemProps<Values = any>
|
||||
tooltip?: LabelTooltipType;
|
||||
/** @deprecated No need anymore */
|
||||
fieldKey?: React.Key | React.Key[];
|
||||
layout?: FormItemLayout;
|
||||
}
|
||||
|
||||
function genEmptyMeta(): Meta {
|
||||
@ -132,6 +133,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
||||
validateTrigger,
|
||||
hidden,
|
||||
help,
|
||||
layout,
|
||||
} = props;
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
const { name: formName } = React.useContext(FormContext);
|
||||
@ -273,6 +275,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
||||
warnings={mergedWarnings}
|
||||
meta={meta}
|
||||
onSubItemMetaChange={onSubItemMetaChange}
|
||||
layout={layout}
|
||||
>
|
||||
{baseChildren}
|
||||
</ItemHolder>
|
||||
|
@ -44,6 +44,7 @@ export interface FormItemLabelProps {
|
||||
*/
|
||||
requiredMark?: RequiredMark;
|
||||
tooltip?: LabelTooltipType;
|
||||
vertical?: boolean;
|
||||
}
|
||||
|
||||
const FormItemLabel: React.FC<FormItemLabelProps & { required?: boolean; prefixCls: string }> = ({
|
||||
@ -56,11 +57,11 @@ const FormItemLabel: React.FC<FormItemLabelProps & { required?: boolean; prefixC
|
||||
required,
|
||||
requiredMark,
|
||||
tooltip,
|
||||
vertical,
|
||||
}) => {
|
||||
const [formLocale] = useLocale('Form');
|
||||
|
||||
const {
|
||||
vertical,
|
||||
labelAlign: contextLabelAlign,
|
||||
labelCol: contextLabelCol,
|
||||
labelWrap,
|
||||
|
@ -8790,6 +8790,92 @@ exports[`renders components/form/demo/layout-can-wrap.tsx extend context correct
|
||||
|
||||
exports[`renders components/form/demo/layout-can-wrap.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/form/demo/layout-multiple.tsx extend context correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
id="layout-multiple"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-4 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="layout-multiple_name"
|
||||
title="name"
|
||||
>
|
||||
name
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-20 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="layout-multiple_name"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item ant-form-item-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-24 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="layout-multiple_age"
|
||||
title="loooooooooooooooooooooooooooooooong"
|
||||
>
|
||||
loooooooooooooooooooooooooooooooong
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-24 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="layout-multiple_age"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`renders components/form/demo/layout-multiple.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/form/demo/nest-messages.tsx extend context correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
|
@ -5138,6 +5138,90 @@ exports[`renders components/form/demo/layout-can-wrap.tsx correctly 1`] = `
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`renders components/form/demo/layout-multiple.tsx correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
id="layout-multiple"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-4 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="layout-multiple_name"
|
||||
title="name"
|
||||
>
|
||||
name
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-20 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="layout-multiple_name"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item ant-form-item-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-24 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="layout-multiple_age"
|
||||
title="loooooooooooooooooooooooooooooooong"
|
||||
>
|
||||
loooooooooooooooooooooooooooooooong
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-24 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="layout-multiple_age"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`renders components/form/demo/nest-messages.tsx correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
|
@ -1085,6 +1085,124 @@ exports[`Form form should support disabled 1`] = `
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`Form form.item should support layout 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-4 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="name"
|
||||
title="name"
|
||||
>
|
||||
name
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined"
|
||||
id="name"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item ant-form-item-horizontal"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-4 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="horizontal"
|
||||
title="horizontal"
|
||||
>
|
||||
horizontal
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined"
|
||||
id="horizontal"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item ant-form-item-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-4 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="vertical"
|
||||
title="vertical"
|
||||
>
|
||||
vertical
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined"
|
||||
id="vertical"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`Form rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal ant-form-rtl"
|
||||
|
@ -1320,6 +1320,24 @@ describe('Form', () => {
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('form.item should support layout', () => {
|
||||
const App: React.FC = () => (
|
||||
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 14 }} layout="horizontal">
|
||||
<Form.Item label="name" name="name">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="horizontal" name="horizontal" layout="horizontal">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="vertical" name="vertical" layout="vertical">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
const { container } = render(<App />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('_internalItemRender api test', () => {
|
||||
const { container } = render(
|
||||
<Form>
|
||||
|
7
components/form/demo/layout-multiple.md
Normal file
7
components/form/demo/layout-multiple.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
在 `Form.Item` 上单独定义 `layout`,可以做到一个表单多种布局。
|
||||
|
||||
## en-US
|
||||
|
||||
Defining a separate `layout` on `Form.Item` can achieve multiple layouts for a single form.
|
22
components/form/demo/layout-multiple.tsx
Normal file
22
components/form/demo/layout-multiple.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Form name="layout-multiple" layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
|
||||
<Form.Item label="name" name="name" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
layout="vertical"
|
||||
label="loooooooooooooooooooooooooooooooong"
|
||||
name="age"
|
||||
rules={[{ required: true }]}
|
||||
labelCol={{ span: 24 }}
|
||||
wrapperCol={{ span: 24 }}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
|
||||
export default App;
|
@ -18,6 +18,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
|
||||
<code src="./demo/basic.tsx">Basic Usage</code>
|
||||
<code src="./demo/control-hooks.tsx">Form methods</code>
|
||||
<code src="./demo/layout.tsx">Form Layout</code>
|
||||
<code src="./demo/layout-multiple.tsx">Form mix layout</code>
|
||||
<code src="./demo/disabled.tsx">Form disabled</code>
|
||||
<code src="./demo/variant.tsx" version="5.13.0">Form variants</code>
|
||||
<code src="./demo/required-mark.tsx">Required style</code>
|
||||
@ -90,6 +91,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| onFinishFailed | Trigger after submitting the form and verifying data failed | function({ values, errorFields, outOfDate }) | - | |
|
||||
| onValuesChange | Trigger when value updated | function(changedValues, allValues) | - | |
|
||||
| clearOnDestroy | Clear form values when the form is uninstalled | boolean | false | 5.18.0 |
|
||||
| layout | Form item layout | `horizontal` \| `vertical` | - | 5.20.0 |
|
||||
|
||||
### validateMessages
|
||||
|
||||
|
@ -19,6 +19,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
|
||||
<code src="./demo/basic.tsx">基本使用</code>
|
||||
<code src="./demo/control-hooks.tsx">表单方法调用</code>
|
||||
<code src="./demo/layout.tsx">表单布局</code>
|
||||
<code src="./demo/layout-multiple.tsx">表单混合布局</code>
|
||||
<code src="./demo/disabled.tsx">表单禁用</code>
|
||||
<code src="./demo/variant.tsx" version="5.13.0">表单变体</code>
|
||||
<code src="./demo/required-mark.tsx">必选样式</code>
|
||||
@ -91,6 +92,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
|
||||
| onFinishFailed | 提交表单且数据验证失败后回调事件 | function({ values, errorFields, outOfDate }) | - | |
|
||||
| onValuesChange | 字段值更新时触发回调事件 | function(changedValues, allValues) | - | |
|
||||
| clearOnDestroy | 当表单被卸载时清空表单值 | boolean | false | 5.18.0 |
|
||||
| layout | 表单项布局 | `horizontal` \| `vertical` \| | - | 5.20.0 |
|
||||
|
||||
### validateMessages
|
||||
|
||||
|
@ -499,15 +499,15 @@ const genVerticalStyle: GenerateStyle<FormToken> = (token) => {
|
||||
return {
|
||||
[`${componentCls}-vertical`]: {
|
||||
[formItemCls]: {
|
||||
'&-row': {
|
||||
[`${formItemCls}-row`]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
|
||||
'&-label > label': {
|
||||
[`${formItemCls}-label > label`]: {
|
||||
height: 'auto',
|
||||
},
|
||||
|
||||
[`${componentCls}-item-control`]: {
|
||||
[`${formItemCls}-control`]: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
@ -546,6 +546,56 @@ const genVerticalStyle: GenerateStyle<FormToken> = (token) => {
|
||||
};
|
||||
};
|
||||
|
||||
const genItemVerticalStyle: GenerateStyle<FormToken> = (token) => {
|
||||
const { formItemCls, rootPrefixCls } = token;
|
||||
return {
|
||||
[`${formItemCls}-vertical`]: {
|
||||
[`${formItemCls}-row`]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
|
||||
[`${formItemCls}-label > label`]: {
|
||||
height: 'auto',
|
||||
},
|
||||
|
||||
[`${formItemCls}-control`]: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
|
||||
[`${formItemCls}-vertical ${formItemCls}-label,
|
||||
.${rootPrefixCls}-col-24${formItemCls}-label,
|
||||
.${rootPrefixCls}-col-xl-24${formItemCls}-label`]: makeVerticalLayoutLabel(token),
|
||||
|
||||
[`@media (max-width: ${unit(token.screenXSMax)})`]: [
|
||||
makeVerticalLayout(token),
|
||||
{
|
||||
[formItemCls]: {
|
||||
[`.${rootPrefixCls}-col-xs-24${formItemCls}-label`]: makeVerticalLayoutLabel(token),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
[`@media (max-width: ${unit(token.screenSMMax)})`]: {
|
||||
[formItemCls]: {
|
||||
[`.${rootPrefixCls}-col-sm-24${formItemCls}-label`]: makeVerticalLayoutLabel(token),
|
||||
},
|
||||
},
|
||||
|
||||
[`@media (max-width: ${unit(token.screenMDMax)})`]: {
|
||||
[formItemCls]: {
|
||||
[`.${rootPrefixCls}-col-md-24${formItemCls}-label`]: makeVerticalLayoutLabel(token),
|
||||
},
|
||||
},
|
||||
|
||||
[`@media (max-width: ${unit(token.screenLGMax)})`]: {
|
||||
[formItemCls]: {
|
||||
[`.${rootPrefixCls}-col-lg-24${formItemCls}-label`]: makeVerticalLayoutLabel(token),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export const prepareComponentToken: GetDefaultToken<'Form'> = (token) => ({
|
||||
labelRequiredMarkColor: token.colorError,
|
||||
@ -584,6 +634,7 @@ export default genStyleHooks(
|
||||
genHorizontalStyle(formToken),
|
||||
genInlineStyle(formToken),
|
||||
genVerticalStyle(formToken),
|
||||
genItemVerticalStyle(formToken),
|
||||
genCollapseMotion(formToken),
|
||||
zoomIn,
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user