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: 添加FormItemRender组件 #8012

Merged
merged 1 commit into from
Jan 7, 2024
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
219 changes: 219 additions & 0 deletions packages/form/src/components/FormItemRender/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { Form } from 'antd';
import React from 'react';
import ProFormItem from '../FormItem';

interface ControlPropsType {
id: string;
value: any;
onChange: (value: any, ...args: any[]) => void;
}

export type WithControlPropsType<T = object> = T & Partial<ControlPropsType>;

interface ControlModelType {
value: any;
onChange: (value: any) => void;
}
interface FormControlProps {
valuePropName?: string;
trigger?: string;
}
interface FormControlMultiProps extends FormControlProps {
name: string;
}
type GetArrayFieldType<T extends readonly { name: string }[]> =
T[number]['name'];

function getControlConfigProps(props = {} as any): {
valuePropName: string;
trigger: string;
name: string;
} {
const valuePropName = props.valuePropName || 'value';
const trigger = props.trigger || 'onChange';
const name = props.name;
return {
valuePropName,
trigger,
name,
};
}

export function useControlModel<T extends FormControlProps>(
{ value, onChange, id }: WithControlPropsType,
model?: T,
): ControlModelType;
export function useControlModel<const T extends readonly string[]>(
{ value, onChange, id }: WithControlPropsType,
model?: T,
): { [P in T[number]]: ControlModelType };
export function useControlModel<
const T extends readonly FormControlMultiProps[],
>(
{ value, onChange, id }: WithControlPropsType,
model?: T,
): { [P in GetArrayFieldType<T>]: ControlModelType };
export function useControlModel<
T extends
| FormControlProps
| (string | FormControlMultiProps)[] = FormControlProps,
>({ value, onChange }: WithControlPropsType, model?: T): unknown {
if (!Array.isArray(model)) {
const p = getControlConfigProps(model);
return {
[p.valuePropName]: value,
[p.trigger]: (e: any) => {
onChange?.(e?.target ? e.target[p.valuePropName] : e);
},
};
}

return model.reduce((acc, k) => {
const p = getControlConfigProps(k);
const name = p.name || (k as string);
acc[name] = {
[p.valuePropName]: value?.[name],
[p.trigger]: (v: any) => {
onChange?.({
...value,
[name]: v?.target ? v.target[p.valuePropName] : v,
});
},
};
return acc;
}, {} as Record<string, unknown>) as unknown;
}

export type FormControlFC<P> = (
props: WithControlPropsType<P>,
) => React.ReactNode;

type FormControlInjectProps = ReturnType<typeof Form.Item.useStatus> & {
id: string;
value: any;
onChange: (value: any) => void;
[x: string]: any;
};

/**
* 用在 ProForm.Item 或 Form.Item 于 表单项之间的,用于劫持渲染的函数
* ``` tsx
* <Form.Item name='name' label='名称'>
* <FormControlRender>
* {
* formItemProps => (
* <div>
* <span>prefix</span>
* <Input {...formItemProps} />
* </div>
* )
* }
* </FormControlRender>
* </Form.Item>
* ```
*/
export function FormControlRender(
props: WithControlPropsType<{
children: (props: FormControlInjectProps) => React.ReactElement;
}>,
) {
const { children, ...restProps } = props;
const { status, errors, warnings } = Form.Item.useStatus();
return children({
status,
errors,
warnings,
...(restProps as ControlPropsType),
});
}

/**
* 提取props中的 value 和 onChange 属性
*/
export function pickControlProps(props: FormControlInjectProps) {
return {
value: props.value,
onChange: (value: any) =>
props.onChange(value?.target ? value.target.value : value),
};
}

/**
* 提取props中的 value、onChange 和 id 属性
*/
export function pickControlPropsWithId(props: FormControlInjectProps) {
return {
...pickControlProps(props),
id: props.id,
};
}

/**
* 用于包裹ProForm.Item Form.Item,使其可以使用render props的形式
* @description 用法
* const FormItem = withFormItemRender(用于包裹ProForm.Item)
* ``` tsx
* <FormItem name='name' label='名称'>
* {
* formItemProps => (
* <div>
* <span>prefix</span>
* <Input {...formItemProps} />
* </div>
* )
* }
* </FormItem>
* ```
*/
export function withFormItemRender<T extends React.FC<any>>(
Comp: T,
): React.FC<
Omit<React.ComponentProps<T>, 'children'> & {
children: (formItemProps: FormControlInjectProps) => React.ReactNode;
}
> {
return function (props: React.PropsWithChildren<any>) {
const { children, ...restProps } = props;

return (
<Comp {...restProps}>
<FormControlRender>{children}</FormControlRender>
</Comp>
);
};
}

/**
* 用 withFormItemRender 加工Form.Item,使其拥有render props能力,同时暴露出 status 属性方便自定义组件校验使用
*
* ``` tsx
* <FormItemRender name='name' label='名称'>
* {
* formItemProps => (
* <div>
* <h3>prefix</h3>
* <textarea {...formItemProps} style={{ borderColor: formItemProps.status === 'error' ? 'red' : undefined }} />
* </div>
* )
* }
* </FormItemRender>
* ```
*/
export const FormItemRender = withFormItemRender(Form.Item);

/**
* 用 withFormItemRender 加工 ProForm.Item,使其拥有render props能力,同时暴露出 status 属性方便自定义组件校验使用
* ``` tsx
* <ProFormItemRender name='name' label='名称'>
* {
* formItemProps => (
* <div>
* <h3>prefix</h3>
* <textarea {...formItemProps} style={{ borderColor: formItemProps.status === 'error' ? 'red' : undefined }} />
* </div>
* )
* }
* </ProFormItemRender>
* ```
*/
export const ProFormItemRender = withFormItemRender(ProFormItem);
Loading
Loading