@carefrees/form-utils-react-native 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # React Native
2
+
3
+ ## 安装
4
+
5
+ ```bash
6
+ npm install @carefrees/form-utils-react-native # yarn add @carefrees/form-utils-react-native # pnpm add @carefrees/form-utils-react-native
7
+ ```
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@carefrees/form-utils-react-native",
3
+ "author": "SunLxy <1011771396@qq.com>",
4
+ "description": "react-native 表单组件",
5
+ "homepage": "https://github.com/SunLxy/carefrees-form-utils",
6
+ "version": "0.0.7",
7
+ "main": "esm/index.js",
8
+ "types": "esm/index.d.ts",
9
+ "module": "esm/index.js",
10
+ "license": "ISC",
11
+ "scripts": {
12
+ "build": "carefrees-rslib build",
13
+ "watch": "carefrees-rslib build --watch"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "src",
20
+ "esm"
21
+ ],
22
+ "dependencies": {
23
+ "@carefrees/form-utils": "^0.0.7",
24
+ "@carefrees/form-utils-react-hooks": "^0.0.7"
25
+ },
26
+ "devDependencies": {
27
+ "react-native": "0.76.9",
28
+ "@types/react": "18.2.21",
29
+ "react": "^18.2.0"
30
+ }
31
+ }
@@ -0,0 +1,102 @@
1
+ import { LayoutFormItem, LayoutFormItemProps } from '../layout/layout.formItem';
2
+ import { useFormItemAttr, FormItemAttrOptions } from '../hooks/attr/attr.FormItem';
3
+ import { Fragment, memo } from 'react';
4
+ import { useRegisterFormHideItem, FormItemParentNameProvider } from '@carefrees/form-utils-react-hooks';
5
+
6
+ export interface FormItemProps extends FormItemAttrOptions, LayoutFormItemProps {
7
+ /**不进行样式渲染*/
8
+ noStyle?: boolean;
9
+ }
10
+
11
+ /**表单项基础实例*/
12
+ const FormItemInstance = memo((props: FormItemProps) => {
13
+ const {
14
+ labelMode,
15
+ noStyle,
16
+ onlyRuleStyle,
17
+ label,
18
+ helpText,
19
+ extra,
20
+ errorLayout,
21
+ required,
22
+ showColon,
23
+ colSpan,
24
+ rowSpan,
25
+ ...rest
26
+ } = props;
27
+ const { children, ruleInstance, formItemInstance, htmlFor, validateResult } = useFormItemAttr({ ...rest });
28
+ if (noStyle) {
29
+ return (
30
+ <FormItemParentNameProvider name={formItemInstance.name} sort={formItemInstance.sort}>
31
+ {children}
32
+ </FormItemParentNameProvider>
33
+ );
34
+ }
35
+ return (
36
+ <FormItemParentNameProvider name={formItemInstance.name} sort={formItemInstance.sort}>
37
+ <LayoutFormItem
38
+ labelMode={labelMode}
39
+ onlyRuleStyle={onlyRuleStyle}
40
+ required={required || ruleInstance?.isRequired?.()}
41
+ label={label}
42
+ helpText={helpText}
43
+ extra={extra}
44
+ errorLayout={errorLayout}
45
+ showColon={showColon}
46
+ colSpan={colSpan}
47
+ rowSpan={rowSpan}
48
+ htmlFor={htmlFor}
49
+ validateResult={validateResult}
50
+ >
51
+ {children}
52
+ </LayoutFormItem>
53
+ </FormItemParentNameProvider>
54
+ );
55
+ });
56
+
57
+ /**表单项*/
58
+ export const FormItem = memo((props: Partial<FormItemProps>) => {
59
+ const { name } = props;
60
+ if (name) {
61
+ return <FormItemInstance {...props} name={name} />;
62
+ }
63
+ const {
64
+ labelMode,
65
+ onlyRuleStyle,
66
+ label,
67
+ helpText,
68
+ extra,
69
+ errorLayout,
70
+ required,
71
+ showColon,
72
+ colSpan,
73
+ rowSpan,
74
+ children,
75
+ } = props;
76
+ return (
77
+ <LayoutFormItem
78
+ labelMode={labelMode}
79
+ onlyRuleStyle={onlyRuleStyle}
80
+ required={required}
81
+ label={label}
82
+ helpText={helpText}
83
+ extra={extra}
84
+ errorLayout={errorLayout}
85
+ showColon={showColon}
86
+ colSpan={colSpan}
87
+ rowSpan={rowSpan}
88
+ >
89
+ {children}
90
+ </LayoutFormItem>
91
+ );
92
+ });
93
+
94
+ /**隐藏表单项*/
95
+ export const FormHideItem = memo((props: FormItemProps) => {
96
+ const { name, sort, isJoinParentField } = props;
97
+ const { isHide } = useRegisterFormHideItem({ name, sort: sort, isJoinParentField });
98
+ if (isHide) {
99
+ return <Fragment />;
100
+ }
101
+ return <FormItemInstance {...props} />;
102
+ });
@@ -0,0 +1,62 @@
1
+ import { RuleInstanceBase, FormItemInstanceBase, FormListInstanceBase } from '@carefrees/form-utils';
2
+ import React, { Fragment, memo } from 'react';
3
+ import {
4
+ useRegisterFormHideItem,
5
+ FormItemParentNameProvider,
6
+ FormListInstanceContext,
7
+ useRegisterFormList,
8
+ RegisterFormListOptions,
9
+ } from '@carefrees/form-utils-react-hooks';
10
+
11
+ export interface FormListChildrenProps {
12
+ /**数据集合*/
13
+ fields: { name: number; key: number }[];
14
+ /**添加*/
15
+ onAdd: (initialValue?: Object) => void;
16
+ /**删除*/
17
+ onDelete: (index: number | number[]) => void;
18
+ /**移动*/
19
+ onMove: (from: number, to: number) => void;
20
+ }
21
+
22
+ export interface FormListProps extends RegisterFormListOptions {
23
+ children: (
24
+ options: FormListChildrenProps,
25
+ instances: {
26
+ ruleInstance: RuleInstanceBase;
27
+ formItemInstance: FormItemInstanceBase;
28
+ formListInstance: FormListInstanceBase;
29
+ },
30
+ ) => React.ReactNode;
31
+ }
32
+
33
+ /**form list 组件*/
34
+ export const FormList = memo((props: FormListProps) => {
35
+ const { children, ...rest } = props;
36
+ const { formListInstance, ruleInstance, formItemInstance } = useRegisterFormList(rest);
37
+ return (
38
+ <FormListInstanceContext.Provider value={formListInstance}>
39
+ <FormItemParentNameProvider name={formListInstance.name} sort={formListInstance.sort}>
40
+ {children(
41
+ {
42
+ fields: formListInstance.getFields(),
43
+ onAdd: formListInstance.onAdd,
44
+ onDelete: formListInstance.onDelete,
45
+ onMove: formListInstance.onMove,
46
+ },
47
+ { ruleInstance, formItemInstance, formListInstance },
48
+ )}
49
+ </FormItemParentNameProvider>
50
+ </FormListInstanceContext.Provider>
51
+ );
52
+ });
53
+
54
+ /**隐藏 form list item 组件*/
55
+ export const FormHideList = memo((props: FormListProps) => {
56
+ const { name, sort, isJoinParentField } = props;
57
+ const { isHide } = useRegisterFormHideItem({ name, sort: sort, isJoinParentField });
58
+ if (isHide) {
59
+ return <Fragment />;
60
+ }
61
+ return <FormList {...props} />;
62
+ });
@@ -0,0 +1,131 @@
1
+ import { RuleInstanceBase, FormInstanceBase, FormItemInstanceBase, get } from '@carefrees/form-utils';
2
+ import { useRegisterFormItem, RegisterFormItemOptions, useHtmlFor } from '@carefrees/form-utils-react-hooks';
3
+ import React, { cloneElement, isValidElement, useMemo } from 'react';
4
+
5
+ export interface FormItemAttrOptions extends RegisterFormItemOptions {
6
+ /**依赖更新项*/
7
+ dependencies?: string[];
8
+ /**通知 只用于校验规则提示 字段 */
9
+ noticeOnlyRuleDataField?: string[];
10
+ /**通知父级字段监听方法更新*/
11
+ isNoticeParentField?: boolean;
12
+ /**通知watch监听方法更新*/
13
+ noticeWatchField?: string[];
14
+ /**是否保护值(不进行表单项组件卸载重置初始值)*/
15
+ preserve?: boolean;
16
+ /**重写规则*/
17
+ useRules?: (ruleInstance: RuleInstanceBase, form: FormInstanceBase, formItemInstance: FormItemInstanceBase) => void;
18
+ /**输入框属性重写*/
19
+ useAttrs?: (attrs: any, form: FormInstanceBase, formItemInstance: FormItemInstanceBase) => any;
20
+ /**输入框属性*/
21
+ attrs?: any;
22
+ /**传递组件字段*/
23
+ valuePropName?: string;
24
+ /**取值字段(默认text)*/
25
+ getValuePath?: string;
26
+ /**自定义获取值*/
27
+ getValueFromEvent?: (event: any, form: FormInstanceBase, formItemInstance: FormItemInstanceBase) => any;
28
+ /**值格式化*/
29
+ formatValue?: (value: any, form: FormInstanceBase, formItemInstance: FormItemInstanceBase, event: any) => any;
30
+ /**触发数据更新之后触发(用于数据联动之类的)*/
31
+ onAfterUpdate?: (value: any, form: FormInstanceBase, formItemInstance: FormItemInstanceBase, event: any) => void;
32
+ /**事件名称*/
33
+ trigger?: string;
34
+ /**子元素*/
35
+ children?: React.ReactNode;
36
+ }
37
+
38
+ /**表单项参数*/
39
+ export const useFormItemAttr = (options: FormItemAttrOptions) => {
40
+ const {
41
+ trigger = 'onChange',
42
+ dependencies,
43
+ noticeOnlyRuleDataField,
44
+ isNoticeParentField,
45
+ noticeWatchField,
46
+ preserve,
47
+ valuePropName = 'value',
48
+ getValuePath = 'text',
49
+ getValueFromEvent,
50
+ formatValue,
51
+ onAfterUpdate,
52
+ useAttrs,
53
+ useRules,
54
+ attrs,
55
+ children,
56
+ ...rest
57
+ } = options;
58
+ const { formItemInstance, form, ruleInstance, newName } = useRegisterFormItem({ ...rest });
59
+ formItemInstance.dependencies = dependencies;
60
+ formItemInstance.noticeOnlyRuleDataField = noticeOnlyRuleDataField;
61
+ formItemInstance.isNoticeParentField = isNoticeParentField;
62
+ formItemInstance.onAfterUpdate = onAfterUpdate;
63
+ formItemInstance.noticeWatchField = noticeWatchField;
64
+ formItemInstance.preserve = preserve;
65
+
66
+ /**获取值*/
67
+ const oldValue = form.getFieldValue(newName);
68
+
69
+ const onValueChange = (event: any) => {
70
+ try {
71
+ let value = event;
72
+ const target = event?.nativeEvent;
73
+ // event.nativeEvent.text
74
+ if (typeof getValueFromEvent === 'function') {
75
+ value = getValueFromEvent(event, form, formItemInstance);
76
+ } else if (event && target && typeof target === 'object' && getValuePath in target) {
77
+ value = get(target, getValuePath);
78
+ }
79
+ if (typeof formatValue === 'function') {
80
+ value = formatValue(value, form, formItemInstance, event);
81
+ }
82
+ if (oldValue !== value) {
83
+ form.updatedFieldValue(newName, value, 'validate');
84
+ formItemInstance.onAfterUpdate?.(value, form, formItemInstance, event);
85
+ if (Array.isArray(formItemInstance.noticeWatchField) && formItemInstance.noticeWatchField.length) {
86
+ form.noticeWatch(formItemInstance.noticeWatchField);
87
+ }
88
+ if (
89
+ Array.isArray(formItemInstance.noticeOnlyRuleDataField) &&
90
+ formItemInstance.noticeOnlyRuleDataField.length
91
+ ) {
92
+ form.onlyValidate(formItemInstance.noticeOnlyRuleDataField);
93
+ }
94
+ if (formItemInstance.isNoticeParentField && formItemInstance.parentDataField) {
95
+ form.notice(formItemInstance.parentDataField);
96
+ }
97
+ }
98
+ } catch (error) {
99
+ console.log(error);
100
+ }
101
+ };
102
+ formItemInstance.onChange = onValueChange;
103
+
104
+ // useHtmlFor
105
+ const htmlFor = useHtmlFor(newName);
106
+ formItemInstance.htmlFor = htmlFor;
107
+
108
+ const control: any = {
109
+ [trigger]: onValueChange,
110
+ ...attrs,
111
+ name: newName,
112
+ id: htmlFor,
113
+ [valuePropName]: oldValue,
114
+ };
115
+
116
+ /**触发数据调整*/
117
+ const newControl = useAttrs?.(control, form, formItemInstance) || control;
118
+ formItemInstance.control = newControl;
119
+ useRules?.(ruleInstance, form, formItemInstance);
120
+ const validateResult = useMemo(() => ruleInstance.getValidateResult(), [ruleInstance.messages]);
121
+
122
+ return {
123
+ children: isValidElement(children) ? cloneElement(children, newControl) : children,
124
+ form,
125
+ formItemInstance,
126
+ ruleInstance,
127
+ onChange: onValueChange,
128
+ htmlFor,
129
+ validateResult,
130
+ };
131
+ };
@@ -0,0 +1,42 @@
1
+ /**公共属性*/
2
+ import { createContext, useContext } from 'react';
3
+ import { ViewProps } from 'react-native';
4
+
5
+ export interface AttrsOptions {
6
+ /**列数据*/
7
+ colCount?: number;
8
+ /**规则校验失败错误提示位置*/
9
+ errorLayout?: 'left-bottom' | 'right-bottom' | 'top-right' | 'top-left';
10
+ /**
11
+ * label显示模式
12
+ * @platform taro 支持 between
13
+ */
14
+ labelMode?: 'left' | 'top' | 'between' | 'hide';
15
+ /**是否显示label后的冒号*/
16
+ showColon?: boolean;
17
+ /**表单项 className*/
18
+ formItemClassName?: string;
19
+ /**表单项 style*/
20
+ formItemStyle?: ViewProps['style'];
21
+ /**表单项 label className*/
22
+ formItemLabelClassName?: string;
23
+ /**表单项 label style*/
24
+ formItemLabelStyle?: ViewProps['style'];
25
+ /**
26
+ * 输入框底部边框
27
+ * @platform taro
28
+ */
29
+ inputBordered?: boolean;
30
+ }
31
+
32
+ /**公共属性 Context */
33
+ export const AttrsContext = createContext<AttrsOptions>({
34
+ colCount: 1,
35
+ errorLayout: 'left-bottom',
36
+ labelMode: 'top',
37
+ showColon: true,
38
+ inputBordered: true,
39
+ });
40
+
41
+ /**子项中获取公共属性*/
42
+ export const useAttrs = () => useContext(AttrsContext);
package/src/index.tsx ADDED
@@ -0,0 +1,80 @@
1
+ import React, { useMemo, useEffect } from 'react';
2
+ import { FormInstanceBase, ValidateErrorEntity } from '@carefrees/form-utils';
3
+ import { FormLayout, FormLayoutProps } from './layout';
4
+ import { ViewProps, View } from 'react-native';
5
+ import { useRegisterForm, useForm, FormInstanceContext } from '@carefrees/form-utils-react-hooks';
6
+ export * from './formItem';
7
+ export * from './formList';
8
+ export * from './layout';
9
+ export * from './layout/layout.formItem';
10
+ export * from './hooks/attr/attr.FormItem';
11
+ export * from '@carefrees/form-utils-react-hooks';
12
+ export { useAttrs, AttrsOptions, AttrsContext } from './hooks/useAttrs';
13
+
14
+ export interface FormProps<T = any> extends FormLayoutProps {
15
+ children?: React.ReactNode;
16
+ form?: FormInstanceBase;
17
+ style?: ViewProps['style'];
18
+ className?: string;
19
+ layoutClassName?: string;
20
+ layoutStyle?: ViewProps['style'];
21
+ /**表单数据*/
22
+ formData?: any;
23
+ /**值更新触发*/
24
+ onValuesChange?: (changedValues: Partial<T>, values: T) => void;
25
+ /**提交保存 验证成功*/
26
+ onFinish?: (values: T) => void;
27
+ /**提交保存 验证失败*/
28
+ onFinishFailed?: (errorInfo: ValidateErrorEntity<T>) => void;
29
+ /**隐藏表单项初始值*/
30
+ hideData?: Record<string, boolean>;
31
+ /**表单名称*/
32
+ name?: string;
33
+ /**隐藏规则校验*/
34
+ hideRuleData?: Record<string, boolean>;
35
+ /**自动重置更新formData数据*/
36
+ isAutoUpdatedFormData?: boolean;
37
+ }
38
+
39
+ export function Form<T = any>(props: FormProps<T>) {
40
+ const {
41
+ children,
42
+ form,
43
+ style,
44
+ className,
45
+ formData,
46
+ hideData,
47
+ hideRuleData,
48
+ isAutoUpdatedFormData = false,
49
+ name,
50
+ onFinish,
51
+ onFinishFailed,
52
+ onValuesChange,
53
+ layoutStyle,
54
+ layoutClassName,
55
+ ...rest
56
+ } = props;
57
+ const formInstance = useForm(form);
58
+ useRegisterForm(formInstance, name);
59
+ useMemo(() => formInstance.ctor(formData, hideData, hideRuleData), []);
60
+
61
+ formInstance.onFinish = onFinish;
62
+ formInstance.onValuesChange = onValuesChange;
63
+ formInstance.onFinishFailed = onFinishFailed;
64
+
65
+ useEffect(() => {
66
+ if (isAutoUpdatedFormData) {
67
+ formInstance.resetFormValues(formData);
68
+ }
69
+ }, [isAutoUpdatedFormData, formData]);
70
+
71
+ return (
72
+ <FormInstanceContext.Provider value={formInstance}>
73
+ <View style={style}>
74
+ <FormLayout {...rest} className={layoutClassName} style={layoutStyle}>
75
+ {children}
76
+ </FormLayout>
77
+ </View>
78
+ </FormInstanceContext.Provider>
79
+ );
80
+ }
@@ -0,0 +1,146 @@
1
+ import React, { Fragment, useMemo, useRef, memo } from 'react';
2
+ import { AttrsOptions, useAttrs, AttrsContext } from './../hooks/useAttrs';
3
+ import { ViewProps, View, Text } from 'react-native';
4
+ import { StylesBase } from '../styles';
5
+
6
+ export interface FormLayoutProps extends AttrsOptions {
7
+ /**标题*/
8
+ title?: React.ReactNode;
9
+ /**额外内容*/
10
+ extra?: React.ReactNode;
11
+ /**内容*/
12
+ children?: React.ReactNode;
13
+ /**是否占据整行*/
14
+ isAllColSpan?: boolean;
15
+ className?: string;
16
+ /**头部ClassName*/
17
+ headerClassName?: string;
18
+ /**内容ClassName*/
19
+ bodyClassName?: string;
20
+ style?: ViewProps['style'];
21
+ /**头部样式*/
22
+ headerStyle?: ViewProps['style'];
23
+ /**内容样式*/
24
+ bodyStyle?: ViewProps['style'];
25
+ /**是否添加边框*/
26
+ bordered?: boolean;
27
+ /**列数据*/
28
+ colCount?: number;
29
+ /**
30
+ * @description gap 属性是用来设置网格行与列之间的间隙,该属性是row-gap and column-gap的简写形式。
31
+ */
32
+ gap?: string | number;
33
+ }
34
+
35
+ /**布局组件*/
36
+ export const FormLayout = memo((props: FormLayoutProps) => {
37
+ const {
38
+ colCount: p_colCount = 4,
39
+ errorLayout: p_errorLayout = 'left-bottom',
40
+ labelMode: p_labelMode = 'left',
41
+ showColon: p_showColon = true,
42
+ formItemClassName: p_formItemClassName,
43
+ formItemStyle: p_formItemStyle,
44
+ formItemLabelClassName: p_formItemLabelClassName,
45
+ formItemLabelStyle: p_formItemLabelStyle,
46
+ } = useAttrs();
47
+ const {
48
+ colCount = p_colCount,
49
+ title,
50
+ extra,
51
+ children,
52
+ isAllColSpan,
53
+ className,
54
+ headerClassName,
55
+ bodyClassName,
56
+ style,
57
+ headerStyle,
58
+ bodyStyle,
59
+ errorLayout = p_errorLayout,
60
+ labelMode = p_labelMode,
61
+ showColon = p_showColon,
62
+ formItemClassName = p_formItemClassName,
63
+ formItemStyle = p_formItemStyle,
64
+ formItemLabelClassName = p_formItemLabelClassName,
65
+ formItemLabelStyle = p_formItemLabelStyle,
66
+ bordered = false,
67
+ gap,
68
+ } = props;
69
+ const propsRef = useRef(props);
70
+ propsRef.current = props;
71
+
72
+ const value = useMemo(() => {
73
+ return {
74
+ colCount,
75
+ errorLayout,
76
+ labelMode,
77
+ showColon,
78
+ formItemClassName,
79
+ formItemStyle,
80
+ formItemLabelClassName,
81
+ formItemLabelStyle,
82
+ };
83
+ }, [
84
+ colCount,
85
+ errorLayout,
86
+ labelMode,
87
+ showColon,
88
+ formItemClassName,
89
+ formItemStyle,
90
+ formItemLabelClassName,
91
+ formItemLabelStyle,
92
+ ]);
93
+
94
+ const styleBase = useMemo(() => {
95
+ const css: ViewProps['style'] = {};
96
+ if (typeof gap === 'string') {
97
+ css.gap = Number(gap);
98
+ }
99
+ if (typeof gap === 'number') {
100
+ css.gap = gap;
101
+ }
102
+ return css;
103
+ }, [colCount, gap]);
104
+
105
+ const layoutStyle = useMemo(() => {
106
+ return [
107
+ StylesBase['carefrees-form-layout'],
108
+ bordered && StylesBase['carefrees-form-layout.bordered'],
109
+ isAllColSpan && StylesBase.isAllColSpan,
110
+ style,
111
+ ].filter(Boolean);
112
+ }, [bordered, isAllColSpan, style]);
113
+
114
+ const headStyle = useMemo(() => {
115
+ return [
116
+ StylesBase['carefrees-form-layout-header'],
117
+ bordered && StylesBase['carefrees-form-layout-header.bordered'],
118
+ headerStyle,
119
+ ].filter(Boolean);
120
+ }, [bordered, headerStyle]);
121
+
122
+ return (
123
+ <AttrsContext.Provider value={value}>
124
+ <View style={layoutStyle}>
125
+ {title || extra ? (
126
+ <View style={headStyle}>
127
+ <View>
128
+ <Text style={[StylesBase['carefrees-form-layout-header-title']]}>{title}</Text>
129
+ </View>
130
+ <View>
131
+ <Text style={[StylesBase['carefrees-form-layout-header-extra']]}>{extra}</Text>
132
+ </View>
133
+ </View>
134
+ ) : (
135
+ <Fragment />
136
+ )}
137
+ <View style={[StylesBase['carefrees-form-layout-body'], styleBase, bodyStyle]}>{children}</View>
138
+ </View>
139
+ </AttrsContext.Provider>
140
+ );
141
+ });
142
+
143
+ /**布局组件 占据一整行*/
144
+ export const FormLayoutRows = (props: ViewProps) => {
145
+ return <View {...props} style={[props.style, StylesBase.isAllColSpan]} />;
146
+ };
@@ -0,0 +1,205 @@
1
+ import { View, ViewProps, Text } from 'react-native';
2
+ import React, { Fragment, useMemo, memo } from 'react';
3
+ import { useAttrs } from '../hooks/useAttrs';
4
+ import { StylesBase } from '../styles';
5
+
6
+ export interface LayoutFormItemProps {
7
+ /**规则校验失败错误提示位置*/
8
+ errorLayout?: 'left-bottom' | 'right-bottom' | 'top-right' | 'top-left';
9
+ /**必填样式*/
10
+ required?: boolean;
11
+ /**label显示模式*/
12
+ labelMode?: 'left' | 'top' | 'between' | 'hide';
13
+ /**内容*/
14
+ children?: React.ReactNode;
15
+ /**只进行规则样式*/
16
+ onlyRuleStyle?: boolean;
17
+ label?: React.ReactNode;
18
+ /**底部提示内容*/
19
+ helpText?: React.ReactNode;
20
+ /**额外内容*/
21
+ extra?: React.ReactNode;
22
+ /**是否显示label后的冒号*/
23
+ showColon?: boolean;
24
+ /**
25
+ * 表单项占据列数
26
+ * @default 1
27
+ */
28
+ colSpan?: number;
29
+ /**
30
+ * 表单项占据行数
31
+ * @default 1
32
+ */
33
+ rowSpan?: number;
34
+
35
+ htmlFor?: string;
36
+ /**规则验证结果*/
37
+ validateResult?: {
38
+ tip: string | (string | undefined)[];
39
+ isInvalid: boolean;
40
+ };
41
+ // 样式部分
42
+ style?: ViewProps['style'];
43
+ className?: string;
44
+ labelStyle?: ViewProps['style'];
45
+ labelClassName?: string;
46
+ /**底部边框*/
47
+ inputBordered?: boolean;
48
+ }
49
+
50
+ /**布局组件 表单项*/
51
+ export const LayoutFormItem = memo((props: LayoutFormItemProps) => {
52
+ const {
53
+ formItemClassName,
54
+ formItemLabelClassName,
55
+ formItemLabelStyle,
56
+ formItemStyle,
57
+ labelMode: p_labelMode = 'top',
58
+ errorLayout: p_errorLayout = 'left-bottom',
59
+ showColon: p_showColon = true,
60
+ colCount = 1,
61
+ inputBordered: p_inputBordered = true,
62
+ } = useAttrs();
63
+
64
+ const {
65
+ children,
66
+ labelMode = p_labelMode,
67
+ onlyRuleStyle,
68
+ label,
69
+ helpText,
70
+ extra,
71
+ showColon = p_showColon,
72
+ colSpan = 1,
73
+ validateResult,
74
+ htmlFor,
75
+ required,
76
+ errorLayout = p_errorLayout,
77
+ style,
78
+ className,
79
+ labelClassName,
80
+ labelStyle,
81
+ inputBordered = p_inputBordered,
82
+ } = props;
83
+
84
+ const tip = validateResult?.tip;
85
+ const isInvalid = !!validateResult?.isInvalid;
86
+ const _errorLayout = labelMode === 'between' ? 'right-bottom' : errorLayout;
87
+ const _isLabel = useMemo(() => label && labelMode !== 'hide', [label, labelMode]);
88
+
89
+ const _isShowColon = useMemo(() => {
90
+ return showColon && (labelMode === 'left' || labelMode === 'between');
91
+ }, [showColon, labelMode]);
92
+
93
+ const widthStyles: ViewProps['style'] = useMemo(() => {
94
+ if (colCount >= colSpan) {
95
+ return {
96
+ width: `${(100 / colCount) * colSpan}%`,
97
+ };
98
+ }
99
+ return { width: `100%` };
100
+ }, [colSpan, colCount]);
101
+
102
+ const warpStyles = useMemo(() => {
103
+ return [
104
+ StylesBase['carefrees-form-item'],
105
+ onlyRuleStyle && StylesBase['carefrees-form-item.only-rule-style'],
106
+ widthStyles,
107
+ formItemStyle,
108
+ style,
109
+ ].filter(Boolean);
110
+ }, [style, widthStyles, onlyRuleStyle, formItemStyle]);
111
+
112
+ const containerStyles = useMemo(() => {
113
+ return [
114
+ StylesBase['carefrees-form-item-container'],
115
+ labelMode === 'left' && StylesBase['carefrees-form-item-container.left'],
116
+ labelMode === 'between' && StylesBase['carefrees-form-item-container.between'],
117
+ ].filter(Boolean);
118
+ }, [labelMode]);
119
+
120
+ const labelWarpStyles = useMemo(() => {
121
+ return [
122
+ StylesBase['carefrees-form-item-label-warp'],
123
+ labelMode === 'left' && StylesBase['carefrees-form-item-label-warp.left'],
124
+ (labelMode === 'left' || labelMode == 'between') && StylesBase['carefrees-form-item-label-warp.minHeight'],
125
+ formItemLabelStyle,
126
+ labelStyle,
127
+ ].filter(Boolean);
128
+ }, [labelMode, labelStyle, formItemLabelStyle]);
129
+
130
+ const bodyStyles = useMemo(() => {
131
+ return [
132
+ StylesBase['carefrees-form-item-body'],
133
+ labelMode === 'between' && StylesBase['carefrees-form-item-body-com.between'],
134
+ ].filter(Boolean);
135
+ }, [labelMode]);
136
+
137
+ const bodyInputStyles = useMemo(() => {
138
+ return [
139
+ StylesBase['carefrees-form-item-body-input'],
140
+ inputBordered && StylesBase['carefrees-form-item-body-input.input-bordered'],
141
+ labelMode === 'between' && StylesBase['carefrees-form-item-body-com.between'],
142
+ ].filter(Boolean);
143
+ }, [labelMode, inputBordered]);
144
+
145
+ const errorStyles = useMemo(() => {
146
+ return [
147
+ StylesBase['carefrees-form-item-body-error'],
148
+ _errorLayout && StylesBase[`carefrees-form-item-body-error.${_errorLayout}`],
149
+ ].filter(Boolean);
150
+ }, [_errorLayout]);
151
+
152
+ return (
153
+ <View style={warpStyles}>
154
+ <View style={containerStyles}>
155
+ {_isLabel ? (
156
+ <View style={labelWarpStyles}>
157
+ {required ? (
158
+ <View>
159
+ <Text style={[StylesBase.fontSize14, StylesBase['carefrees-form-item-label.required']]}>*</Text>
160
+ </View>
161
+ ) : (
162
+ <Fragment />
163
+ )}
164
+ <View style={StylesBase['carefrees-form-item-label']}>
165
+ {typeof label === 'string' ? <Text style={StylesBase.fontSize14}>{label}</Text> : label}
166
+ </View>
167
+ {_isShowColon ? (
168
+ <View style={[StylesBase['carefrees-form-item-label.show-colon']]}>
169
+ <Text style={StylesBase.fontSize14}>:</Text>
170
+ </View>
171
+ ) : (
172
+ <Fragment />
173
+ )}
174
+ </View>
175
+ ) : (
176
+ <Fragment />
177
+ )}
178
+ <View style={bodyStyles}>
179
+ <View style={bodyInputStyles}>{children}</View>
180
+ {helpText ? (
181
+ <View style={StylesBase['carefrees-form-item-body-help']}>
182
+ {typeof helpText === 'string' ? <Text style={StylesBase.fontSize12}>{helpText}</Text> : helpText}
183
+ </View>
184
+ ) : (
185
+ <Fragment />
186
+ )}
187
+ {isInvalid ? (
188
+ <View style={errorStyles}>
189
+ <Text style={StylesBase['error-text']}>{tip}</Text>
190
+ </View>
191
+ ) : (
192
+ <Fragment />
193
+ )}
194
+ </View>
195
+ </View>
196
+ {extra ? (
197
+ <View style={StylesBase['carefrees-form-item-extra']}>
198
+ {typeof extra === 'string' ? <Text style={StylesBase.fontSize14}>{extra}</Text> : extra}
199
+ </View>
200
+ ) : (
201
+ <Fragment />
202
+ )}
203
+ </View>
204
+ );
205
+ });
@@ -0,0 +1,203 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export const StylesBase = StyleSheet.create({
4
+ 'carefrees-form': {
5
+ fontSize: 14,
6
+ },
7
+ 'carefrees-form-layout': {
8
+ width: '100%',
9
+ borderRadius: 4,
10
+ paddingBottom: 8,
11
+ position: 'relative',
12
+ },
13
+ 'carefrees-form-layout.bordered': {
14
+ borderColor: '#e0e0e0',
15
+ borderWidth: 1,
16
+ borderStyle: 'solid',
17
+ },
18
+ 'carefrees-form-layout-header': {
19
+ display: 'flex',
20
+ justifyContent: 'space-between',
21
+ alignItems: 'center',
22
+ flexDirection: 'row',
23
+ borderBottomColor: '#e0e0e0',
24
+ borderBottomWidth: 1,
25
+ paddingTop: 5,
26
+ paddingBottom: 5,
27
+ marginBlockEnd: 4,
28
+ paddingInlineStart: 2,
29
+ paddingInlineEnd: 2,
30
+ },
31
+ 'carefrees-form-layout-header.bordered': {
32
+ paddingInlineStart: 8,
33
+ paddingInlineEnd: 8,
34
+ },
35
+ isAllColSpan: {
36
+ width: '100%',
37
+ },
38
+ title: {
39
+ fontSize: 16,
40
+ fontWeight: 600,
41
+ color: '#1d2129',
42
+ },
43
+ 'carefrees-form-layout-header-title': {
44
+ fontSize: 16,
45
+ fontWeight: 600,
46
+ color: '#1d2129',
47
+ },
48
+ extra: {
49
+ fontSize: 14,
50
+ fontWeight: 500,
51
+ color: '#1d2129',
52
+ },
53
+ 'carefrees-form-layout-header-extra': {
54
+ fontSize: 14,
55
+ fontWeight: 500,
56
+ color: '#1d2129',
57
+ },
58
+ 'carefrees-form-layout-body': {
59
+ width: '100%',
60
+ display: 'flex',
61
+ flexDirection: 'row',
62
+ flexWrap: 'wrap',
63
+ paddingInlineStart: 2,
64
+ paddingInlineEnd: 2,
65
+ gap: 2,
66
+ },
67
+ 'carefrees-form-item': {
68
+ display: 'flex',
69
+ flexDirection: 'row',
70
+ alignItems: 'flex-start',
71
+ padding: 8,
72
+ color: 'rgba(0, 0, 0, 0.88)',
73
+ },
74
+ 'carefrees-form-item.only-rule-style': {
75
+ padding: 0,
76
+ },
77
+ 'carefrees-form-item-container': {
78
+ display: 'flex',
79
+ flexDirection: 'column',
80
+ gap: 4,
81
+ height: '100%',
82
+ flex: 1,
83
+ },
84
+ 'carefrees-form-item-container.left': {
85
+ flexDirection: 'row',
86
+ gap: 8,
87
+ textAlign: 'left',
88
+ },
89
+ 'carefrees-form-item-label-warp.left': {
90
+ justifyContent: 'flex-end',
91
+ },
92
+ 'carefrees-form-item-container.between': {
93
+ gap: 8,
94
+ textAlign: 'left',
95
+ flexDirection: 'row',
96
+ justifyContent: 'space-between',
97
+ },
98
+ 'carefrees-form-item-body-com.between': {
99
+ justifyContent: 'flex-end',
100
+ textAlign: 'right',
101
+ },
102
+ 'carefrees-form-item-label-warp': {
103
+ display: 'flex',
104
+ flexDirection: 'row',
105
+ justifyContent: 'flex-start',
106
+ },
107
+ 'carefrees-form-item-label-warp.minHeight': {
108
+ minHeight: 32,
109
+ alignItems: 'center',
110
+ },
111
+ 'carefrees-form-item-label': {
112
+ display: 'flex',
113
+ alignItems: 'center',
114
+ justifyContent: 'flex-start',
115
+ position: 'relative',
116
+ fontSize: 14,
117
+ color: 'rgba(0, 0, 0, 0.88)',
118
+ },
119
+ 'carefrees-form-item-label.show-colon': {
120
+ textAlign: 'center',
121
+ margin: 0,
122
+ marginInlineEnd: 2,
123
+ marginInlineStart: 2,
124
+ },
125
+ 'carefrees-form-item-label.required': {
126
+ color: 'red',
127
+ margin: 0,
128
+ marginInlineEnd: 2,
129
+ },
130
+ 'carefrees-form-item-body': {
131
+ position: 'relative',
132
+ display: 'flex',
133
+ flexDirection: 'column',
134
+ alignItems: 'flex-start',
135
+ justifyContent: 'flex-start',
136
+ gap: 4,
137
+ flex: 1,
138
+ },
139
+ 'carefrees-form-item-body-input': {
140
+ width: '100%',
141
+ flex: 1,
142
+ display: 'flex',
143
+ justifyContent: 'flex-start',
144
+ alignItems: 'center',
145
+ flexDirection: 'row',
146
+ },
147
+ 'carefrees-form-item-body-input.input-bordered': {
148
+ borderWidth: 0,
149
+ borderBottomColor: '#e0e0e0',
150
+ borderBottomWidth: 1,
151
+ borderStyle: 'solid',
152
+ },
153
+ 'carefrees-form-item-body-help': {
154
+ width: '100%',
155
+ },
156
+ 'carefrees-form-item-body-error': {
157
+ position: 'absolute',
158
+ width: '100%',
159
+ color: 'red',
160
+ top: 'auto',
161
+ left: 0,
162
+ right: 0,
163
+ bottom: -16,
164
+ paddingTop: 2,
165
+ zIndex: 10,
166
+ fontSize: 12,
167
+ display: 'flex',
168
+ flexDirection: 'row',
169
+ justifyContent: 'flex-start',
170
+ },
171
+ 'error-text': {
172
+ fontSize: 12,
173
+ color: 'red',
174
+ },
175
+ 'carefrees-form-item-body-error.right-bottom': {
176
+ top: 'auto',
177
+ left: 0,
178
+ right: 0,
179
+ bottom: -16,
180
+ justifyContent: 'flex-end',
181
+ },
182
+ 'carefrees-form-item-body-error.top-left': {
183
+ top: -16,
184
+ left: 0,
185
+ right: 0,
186
+ bottom: 'auto',
187
+ justifyContent: 'flex-start',
188
+ },
189
+ 'carefrees-form-item-body-error.top-right': {
190
+ top: -16,
191
+ left: 0,
192
+ right: 0,
193
+ bottom: 'auto',
194
+ justifyContent: 'flex-end',
195
+ },
196
+ 'carefrees-form-item-extra': {},
197
+ fontSize12: {
198
+ fontSize: 12,
199
+ },
200
+ fontSize14: {
201
+ fontSize: 14,
202
+ },
203
+ });