Skip to content

Commit

Permalink
feat: 1. 增强 FormformRef,增加 scrollToField 方法,支持根据 name 滚动至指定表…
Browse files Browse the repository at this point in the history
…单项; 2. 增强 `Form` 的 `validate` 方法,校验成功或失败后返回更多的校验信息;
  • Loading branch information
saint3347 committed Nov 25, 2024
1 parent 8b8bf66 commit c46e242
Show file tree
Hide file tree
Showing 32 changed files with 703 additions and 448 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "sheinx",
"private": true,
"version": "3.5.2-beta.6",
"version": "3.5.3-beta.1",
"description": "A react library developed with sheinx",
"module": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/button/button-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const Group = (props: ButtonGroupProps) => {
const shapeSetted = shape === 'round' ? 'round' : undefined;

return (
<div className={groupClass} style={style} dir={config.direction}>
<div className={groupClass} style={style} dir={config.direction} id={props.id}>
{Children.toArray(children).map((child) => {
const Child = child as React.ReactElement<ButtonProps>;
return cloneElement<ButtonProps>(Child, {
Expand Down
6 changes: 6 additions & 0 deletions packages/base/src/button/button.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ export interface ButtonGroupProps extends Pick<CommonType, 'style' | 'className'
*/
children: React.ReactNode;

/**
* @en The id of the button group
* @cn 按钮组id
*/
id?: string;

jssStyle?: ButtonJssStyle;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/base/src/cascader/cascader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect, useMemo } from 'react';
import React, { useState, useRef, useEffect, useMemo, useContext } from 'react';
import classNames from 'classnames';
import {
util,
Expand All @@ -23,13 +23,15 @@ import Result from '../select/result';
import Icons from '../icons';
import useWithFormConfig from '../common/use-with-form-config';
import useTip from '../common/use-tip';
import { FormFieldContext } from '../form/form-field-context';

import { useConfig, getLocale } from '../config';

const Cascader = <DataItem, Value extends KeygenResult[]>(
props0: CascaderProps<DataItem, Value>,
) => {
const props = useWithFormConfig(props0);
const { fieldId } = useContext(FormFieldContext);
const defaultHeight = 250;
const {
jssStyle,
Expand Down Expand Up @@ -711,6 +713,7 @@ const Cascader = <DataItem, Value extends KeygenResult[]>(

return (
<div
id={fieldId}
tabIndex={disabled === true || showInput ? undefined : 0}
{...util.getDataAttribute({ ['input-border']: 'true' })}
className={rootClass}
Expand Down
8 changes: 5 additions & 3 deletions packages/base/src/checkbox/checkbox-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { CheckboxGroupProps } from './checkbox-group.type';
import { useInputAble, useListSelectMultiple, usePersistFn, util } from '@sheinx/hooks';
import GroupContext from './group-context';
import Checkbox from './checkbox';
import React from 'react';
import React, { useContext } from 'react';
import classNames from 'classnames';
import useWithFormConfig from '../common/use-with-form-config';
import { FormFieldContext } from '../form/form-field-context';

const defaultFormat = (d: any) => d;
const defaultRenderItem = (d: any) => d;
Expand All @@ -13,6 +14,7 @@ const defaultRenderItem = (d: any) => d;
const Group = <DataItem, Value extends any[]>(props0: CheckboxGroupProps<DataItem, Value>) => {
const {format = defaultFormat, renderItem = defaultRenderItem} = props0
const props = useWithFormConfig(props0);
const { fieldId } = useContext(FormFieldContext);
const { children, className, block, keygen, jssStyle, size, style, disabled } = props;
const checkboxStyle = jssStyle?.checkbox?.();

Expand Down Expand Up @@ -89,13 +91,13 @@ const Group = <DataItem, Value extends any[]>(props0: CheckboxGroupProps<DataIte
);
if (props.data === undefined) {
return (
<div className={groupClass} style={style}>
<div className={groupClass} style={style} id={fieldId}>
<GroupContext.Provider value={providerValue}>{children}</GroupContext.Provider>
</div>
);
} else {
return (
<div className={groupClass} style={style}>
<div className={groupClass} style={style} id={fieldId}>
{props.data.map((d, i) => (
<Checkbox
jssStyle={jssStyle}
Expand Down
5 changes: 4 additions & 1 deletion packages/base/src/checkbox/simple-checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useCheck, util } from '@sheinx/hooks';
import classNames from 'classnames';
import React from 'react';
import React, { useContext } from 'react';
import { SimpleCheckboxProps } from './checkbox.type';
import { FormFieldContext } from '../form/form-field-context';

const { getDataAttribute } = util;

const Checkbox = (props: SimpleCheckboxProps) => {
const { jssStyle, className, style, children, renderFooter, size, theme, ...rest } = props;
const { fieldId } = useContext(FormFieldContext);
const checkboxStyle = jssStyle?.checkbox?.();
const { getRootProps, getIndicatorProps, getInputProps, disabled, checked } = useCheck({
...rest,
Expand All @@ -33,6 +35,7 @@ const Checkbox = (props: SimpleCheckboxProps) => {

return (
<div
id={fieldId}
{...getRootProps({
className: rootClass,
style,
Expand Down
9 changes: 8 additions & 1 deletion packages/base/src/date-picker/result.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { getLocale, useConfig } from '../config';
import { DatePickerProps } from './date-picker.type';
import React, { useEffect, useRef, useState } from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import { FormFieldContext } from '../form/form-field-context';

export const Input = (props: {
id?: string;
className?: string;
value: string;
placeholder?: string;
Expand Down Expand Up @@ -43,6 +45,7 @@ export const Input = (props: {
}, [props.value, props.open]);
return (
<input
id={props.id}
ref={props.onRef}
style={props.style}
className={props.className}
Expand Down Expand Up @@ -107,6 +110,8 @@ const Result = (props: ResultProps) => {
} = props;
const { locale } = useConfig();

const { fieldId } = useContext(FormFieldContext);

const styles = jssStyle?.datePicker?.();
const { current: context } = useRef<{ inputRefs: Array<HTMLDivElement>; clickIndex: number }>({
inputRefs: [],
Expand Down Expand Up @@ -162,6 +167,7 @@ const Result = (props: ResultProps) => {

const placeholderArr = getPlaceHolderArr();

const formFieldId = fieldId.split('__separator__');
const renderItem = (info: {
inputable?: boolean;
target: string | undefined;
Expand Down Expand Up @@ -211,6 +217,7 @@ const Result = (props: ResultProps) => {
props.onFocus?.(e);
}}
onClick={onClickProps}
id={formFieldId[info.index]}
/>
</span>
<div className={styles?.resultTextBg}></div>
Expand Down
6 changes: 5 additions & 1 deletion packages/base/src/editable-area/editable-area.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { Textarea } from '../textarea';
import { EditableAreaProps } from './editable-area.type';
import AbsoluteList from '../absolute-list';
import { useInputAble, usePersistFn } from '@sheinx/hooks';
import classNames from 'classnames';
import Icons from '../icons';
import useInnerTitle from '../common/use-inner-title';
import { FormFieldContext } from '../form/form-field-context';

function formatShowValue(value: unknown) {
if (!value && value !== 0) return '';
Expand Down Expand Up @@ -37,6 +38,8 @@ const EditableArea = (props: EditableAreaProps) => {
bordered = false,
} = props;

const { fieldId } = useContext(FormFieldContext);

const editableAreaStyle = jssStyle?.editableArea?.();

const status = error ? 'error' : props.status;
Expand Down Expand Up @@ -166,6 +169,7 @@ const EditableArea = (props: EditableAreaProps) => {

return (
<div
id={fieldId}
className={classNames(
className,
editableAreaStyle?.wrapper,
Expand Down
12 changes: 12 additions & 0 deletions packages/base/src/form/form-field-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 把id放到context中
import React from 'react';

interface FormFieldContextProps {
fieldId: string;
separator: string;
}

export const FormFieldContext = React.createContext<FormFieldContextProps>({
fieldId: '',
separator: '__separator__',
});
30 changes: 25 additions & 5 deletions packages/base/src/form/form-field.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import React, { useContext, useMemo } from 'react';
import { useFormControl, usePersistFn, util } from '@sheinx/hooks';
import { FieldControlProps, FormFieldProps } from './form-field.type';
import { FormFieldContext } from './form-field-context';

const FormField = <T extends any = any>(props: FormFieldProps<T>) => {
const { children } = props;
Expand Down Expand Up @@ -37,11 +38,21 @@ const FormField = <T extends any = any>(props: FormFieldProps<T>) => {

const status = childrenProps.status ?? (formControl.error ? 'error' : undefined);

const { separator } = useContext(FormFieldContext);
const formFieldId = useMemo(() => {
if(childrenProps.id) return childrenProps.id

if(Array.isArray(formControl.name)) {
return formControl.name.map(name => util.getFieldId(name, props.formName)).join(separator)
}

return util.getFieldId(formControl.name, props.formName)
}, [formControl.name, props.formName, childrenProps.id])

const cloneProps: FieldControlProps<T> = {
onChange: handleChange,
status,
error,
id: childrenProps.id || util.getFieldId(formControl.name, props.formName),
};

if (formControl.inForm) {
Expand All @@ -51,12 +62,21 @@ const FormField = <T extends any = any>(props: FormFieldProps<T>) => {
if (formControl.disabled) {
cloneProps.disabled = true;
}

let finalChildren
if (util.isFunc(children)) {
return children(cloneProps);
finalChildren = children(cloneProps)
} else if (React.isValidElement(children)) {
finalChildren = React.cloneElement(children, cloneProps)
} else {
finalChildren = children
}
if (React.isValidElement(children)) return React.cloneElement(children, cloneProps);

return children;
return (
<FormFieldContext.Provider value={{ fieldId: formFieldId, separator }}>
{finalChildren}
</FormFieldContext.Provider>
);
};

export default FormField;
1 change: 0 additions & 1 deletion packages/base/src/form/form-field.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export interface FieldControlProps<T> {
status?: 'error';
disabled?: boolean;
error?: { message?: string } | string;
id?: string;
}

export type FormFieldChildrenFunc<T> = (props: FieldControlProps<T>) => React.ReactElement;
Expand Down
8 changes: 4 additions & 4 deletions packages/base/src/form/form.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export interface FormClasses {

export interface FormValidateFn<
FormValue,
FieldKey extends KeyType = keyof FormValue,
FieldsType = FieldKey | FieldKey[]
// FieldKey extends KeyType = keyof FormValue,
// FieldsType = FieldKey | FieldKey[]
> {
/**
* 验证所有表单的值,并且返回报错和表单数据
* @param fields 需要校验的表单字段
*/
(fields?: FieldsType): Promise<Partial<FormValue>>;
(fields?: string | string[]): Promise<FormValue>;
}


Expand Down Expand Up @@ -67,7 +67,7 @@ export interface FormRef<FormValue> {
* @en Scroll to the position of the specified field
* @cn 滚动到指定字段的位置
*/
scrollToField: (name: string) => void;
scrollToField: (name: string, scrollIntoViewOptions?: ScrollIntoViewOptions) => void;
}
export interface FormProps<V extends ObjectType>
extends Partial<BaseFormProps<V>>,
Expand Down
6 changes: 4 additions & 2 deletions packages/base/src/input/simple-input.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useInput, useKeyEvent, usePersistFn, util } from '@sheinx/hooks';
import classNames from 'classnames';
import React, { KeyboardEvent } from 'react';
import React, { KeyboardEvent, useContext } from 'react';
import { SimpleInputProps } from './input.type';
import Icons from '../icons';
import { useConfig } from '../config';

import { FormFieldContext } from '../form/form-field-context';
const Input = (props: SimpleInputProps) => {
const {
jssStyle,
Expand All @@ -27,6 +27,7 @@ const Input = (props: SimpleInputProps) => {

const inputStyle = jssStyle?.input?.();
const config = useConfig();
const { fieldId } = useContext(FormFieldContext);
const { getRootProps, getClearProps, getInputProps, showClear, focused, disabled } = useInput({
...rest,
onFocusedChange,
Expand Down Expand Up @@ -69,6 +70,7 @@ const Input = (props: SimpleInputProps) => {

return (
<div
id={fieldId}
{...util.getDataAttribute({ ['input-border']: 'true' })}
{...getRootProps({
className: rootClass,
Expand Down
8 changes: 5 additions & 3 deletions packages/base/src/radio/radio-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import { RadioGroupProps } from './radio-group.type';
import { useInputAble, useListSelectSingle, usePersistFn, util } from '@sheinx/hooks';
import groupContext from './group-context';
import Radio from './radio';
import React from 'react';
import React, { useContext } from 'react';
import classNames from 'classnames';
import useWithFormConfig from '../common/use-with-form-config';
import Button from '../button/button';
import { FormFieldContext } from '../form/form-field-context';

const defaultFormat = (d: any) => d;
const defaultRenderItem = (d: any) => d;

const Group = <DataItem, Value>(props0: RadioGroupProps<DataItem, Value>) => {
const {format = defaultFormat, renderItem = defaultRenderItem} = props0
const props = useWithFormConfig(props0);
const { fieldId } = useContext(FormFieldContext);

const { children, className, button, block, keygen, jssStyle, style, size, disabled } = props;
const radioClasses = jssStyle?.radio?.();
Expand Down Expand Up @@ -125,13 +127,13 @@ const Group = <DataItem, Value>(props0: RadioGroupProps<DataItem, Value>) => {
);
if (button)
return (
<Button.Group jssStyle={jssStyle} className={groupClass} style={style}>
<Button.Group jssStyle={jssStyle} className={groupClass} style={style} id={fieldId}>
{Radios}
</Button.Group>
);

return (
<div className={groupClass} style={style}>
<div className={groupClass} style={style} id={fieldId}>
{Radios}
</div>
);
Expand Down
6 changes: 4 additions & 2 deletions packages/base/src/radio/simple-radio.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useCheck } from '@sheinx/hooks';
import classNames from 'classnames';
import React from 'react';
import React, { useContext } from 'react';
import { SimpleRadioProps } from './radio.type';
import { FormFieldContext } from '../form/form-field-context';

const Radio = (props: SimpleRadioProps) => {
const { jssStyle, className, style, children, renderRadio, size, theme, ...rest } = props;
const { fieldId } = useContext(FormFieldContext);
const radioClasses = jssStyle?.radio?.();
const { getRootProps, getIndicatorProps, getInputProps, disabled, checked } = useCheck({
...rest,
Expand All @@ -31,7 +33,7 @@ const Radio = (props: SimpleRadioProps) => {
const indicatorProps = getIndicatorProps();

const simpleRadio = (
<div {...rootProps}>
<div id={fieldId} {...rootProps}>
<input {...inputProps} type='radio' />
<span className={indicatorClass}>
<span {...indicatorProps} className={radioClasses?.indicator} />
Expand Down
Loading

0 comments on commit c46e242

Please sign in to comment.