Skip to content

Commit

Permalink
feat: add component: FormHelper
Browse files Browse the repository at this point in the history
  • Loading branch information
zggmd committed Apr 10, 2024
1 parent efe364c commit b28cae4
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 1 deletion.
40 changes: 40 additions & 0 deletions src/FormHelper/autoFocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { MutableRefObject, useState } from 'react';

type RefOrDomOrId<T> = HTMLElement | string | React.Ref<T>;

type F = any;

const getDom = (refOrDomOrId: RefOrDomOrId<F>) =>
typeof refOrDomOrId === 'string'
? document.querySelector(`#${refOrDomOrId}`)
: (refOrDomOrId as MutableRefObject<F>)?.current || refOrDomOrId;

// https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#input_%E7%B1%BB%E5%9E%8B
const disabled = ':not([disabled]):not([readonly])';
const queryInputTypes =
['text', 'password', 'search', 'tel', 'url', 'number', 'email', '']
.map(item => `input[type="${item}"]${disabled}`)
.join(', ') + `, input:not([type])${disabled}, textarea${disabled}`;
const setAutoFocus = (refOrDomOrId: RefOrDomOrId<F>) => {
const dom = getDom(refOrDomOrId);
if (!dom) return;
const input = dom.querySelector?.(queryInputTypes);
if (!input?.focus) return;
input.focus();
return true;
};

/**
* Passing a ref, id, or DOM element to obtain and set the focus state of the first non-disabled and non-readonly input or textarea.
* @param {RefOrDomOrId} refOrDomOrId - 支持类型 HTMLElement | string | React.Ref<T>
* @returns void
*/
export const useAutoFocus = (refOrDomOrId?: RefOrDomOrId<F>) => {
const [focused, setFocused] = useState<boolean>(false);
React.useEffect(() => {
if (focused || !refOrDomOrId) return;
const setRes = setAutoFocus(refOrDomOrId);
if (!setRes) return;
setFocused(true);
}, [refOrDomOrId, focused, setFocused]);
};
26 changes: 26 additions & 0 deletions src/FormHelper/demos/DomUseAutoFocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useAutoFocus } from '@yuntijs/ui';
import React, { useEffect, useState } from 'react';

import FormContent from './components/FormContent';
import RenderContainer from './components/RenderContainer';

type IDomUseAutoFocus = Record<string, never>;

const FormContentWrapper = () => {
const [fomDom, setFormDom] = useState<HTMLElement | null>(null);
useEffect(() => {
// <Form id="form123"> in FormContent
setFormDom(document.querySelector('#form123') as HTMLElement);
}, []);
useAutoFocus(fomDom);
return <FormContent />;
};

const DomUseAutoFocus: React.FC<IDomUseAutoFocus> = () => {
return (
<RenderContainer>
<FormContentWrapper />
</RenderContainer>
);
};
export default DomUseAutoFocus;
22 changes: 22 additions & 0 deletions src/FormHelper/demos/FormHelperDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FormHelper } from '@yuntijs/ui';
import { Button, Form, Input } from 'antd';
import React, { useState } from 'react';

const Test = () => {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={setOpen.bind('', !open)}>Click me!</Button>
{open && (
<FormHelper>
<Form>
<Form.Item>
<Input />
</Form.Item>
</Form>
</FormHelper>
)}
</>
);
};
export default Test;
15 changes: 15 additions & 0 deletions src/FormHelper/demos/HocFormHelper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { withFormHelper } from '@yuntijs/ui';
import React from 'react';

import FormContent from './components/FormContent';
import RenderContainer from './components/RenderContainer';

const HocFormHelper: React.FC<Record<string, never>> = () => {
const FormContentWrapper = withFormHelper()(FormContent);
return (
<RenderContainer>
<FormContentWrapper />
</RenderContainer>
);
};
export default HocFormHelper;
20 changes: 20 additions & 0 deletions src/FormHelper/demos/IdUseAutoFocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useAutoFocus } from '@yuntijs/ui';
import React from 'react';

import FormContent from './components/FormContent';
import RenderContainer from './components/RenderContainer';

const FormContentWrapper = () => {
// <Form id="form123"> in FormContent
useAutoFocus('form123');
return <FormContent />;
};

const IdUseAutoFocus: React.FC<Record<string, never>> = () => {
return (
<RenderContainer>
<FormContentWrapper />
</RenderContainer>
);
};
export default IdUseAutoFocus;
25 changes: 25 additions & 0 deletions src/FormHelper/demos/RefUseAutoFocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useAutoFocus } from '@yuntijs/ui';
import React, { useRef } from 'react';

import FormContent from './components/FormContent';
import RenderContainer from './components/RenderContainer';

const FormContentWrapper = () => {
const divRef = useRef(null);
useAutoFocus(divRef);
// ⚠️warning The form instance obtained through the ref of the Ant Design's Form component does not take effect. You need to pass the ref of native HTML tags like div or span.
return (
<div ref={divRef}>
<FormContent />
</div>
);
};

const RefUseAutoFocus: React.FC<Record<string, never>> = () => {
return (
<RenderContainer>
<FormContentWrapper />
</RenderContainer>
);
};
export default RefUseAutoFocus;
34 changes: 34 additions & 0 deletions src/FormHelper/demos/components/FormContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Form, Input, InputNumber, Radio, Select, Switch } from 'antd';
import React from 'react';

const FormContent: React.FC<Record<string, never>> = () => {
return (
<Form id="form123" initialValues={{ select: '' }}>
<div>
<input id="originRadio" type="radio" value="111" />
<label htmlFor="originRadio">HTML native radio</label>
</div>
<Form.Item label="select" name={'select'}>
<Select disabled placeholder="hello select" showSearch>
<Select.Option>123</Select.Option>
</Select>
</Form.Item>
<Form.Item label="switch" name="inputNumber">
<InputNumber />
</Form.Item>
<Form.Item label="Radio">
<Radio>123</Radio>
</Form.Item>
<Form.Item label="switch" name="enabled">
<Switch />
</Form.Item>
<Form.Item label="password">
<Input.Password placeholder="hello" />
</Form.Item>
<Form.Item label="username">
<Input placeholder="hello" />
</Form.Item>
</Form>
);
};
export default FormContent;
19 changes: 19 additions & 0 deletions src/FormHelper/demos/components/RenderContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Button } from 'antd';
import React, { useState } from 'react';

interface IRenderContainer {
children: React.ReactNode;
}

const RenderContainer: React.FC<IRenderContainer> = props => {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={setOpen.bind('', !open)}>Click me!</Button>
<br />
<br />
{open && props.children}
</>
);
};
export default RenderContainer;
53 changes: 53 additions & 0 deletions src/FormHelper/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
nav: Components
group: Utils
title: FormHelper
description: Enhanced component for Form
---

# FormHelper

Form enhancement component, currently supporting:

- autoFocus: automatically selecting the first non-disabled and non-readonly input or textarea of the form when focused.

## Usage

### React component FormHelper

Wrap the Form component with FormHelper. Supported attributes include:

| props | description | default | required |
| --------- | -------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| autoFocus | Automatically select the focus state of the first non-disabled and non-readonly input or textarea in the form. | true | false |
| id | The id of the outer wrapping div. | - | false |
| className | The className of the outer wrapping div. | - | false |
| style | The style of the outer wrapping div. | - | false |

#### usage

<code src="./demos/FormHelperDemo"></code>

### withFormHelper - Higher-Order Component (HOC)

As a higher-order component, withFormHelper supports the same parameters as the React component FormHelper.

#### usage

<code src="./demos/HocFormHelper"></code>

### useAutoFocus - hooks

Supports passing a ref, id, or DOM element as a parameter.

#### Example of a ref parameter:

<code src="./demos/RefUseAutoFocus"></code>

#### Example of a id parameter:

<code src="./demos/IdUseAutoFocus"></code>

#### Example of a dom parameter:

<code src="./demos/DomUseAutoFocus"></code>
50 changes: 50 additions & 0 deletions src/FormHelper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { ComponentType, FC, useRef } from 'react';

import { useAutoFocus } from './autoFocus';

export { useAutoFocus } from './autoFocus';
// Component
export type FormHelperProps = {
children: React.ReactNode;
autoFocus?: boolean;
id?: string;
className?: string;
style?: React.CSSProperties;
};

const FormHelper: React.FC<FormHelperProps> = props => {
const { autoFocus = true } = props;

const ref = useRef(null);
useAutoFocus(autoFocus ? ref : undefined);
return (
<div
className={props.className}
id={props.id}
ref={ref}
style={props.style ?? { display: props.className ? undefined : 'contents' }}
>
{props.children}
</div>
);
};

// HOC
type FormHelperConfig = Omit<FormHelperProps, 'children'>;

export const withFormHelper =
(formHelperConfig?: FormHelperConfig) =>
<P extends Record<string, never>>(WrappedComponent: ComponentType<P>): FC<P> => {
const HocComponent: FC<P> = props => {
return (
<FormHelper {...(formHelperConfig || {})}>
<WrappedComponent {...props} />
</FormHelper>
);
};
const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
HocComponent.displayName = `withFormHelper(${displayName})`;
return HocComponent;
};

export { FormHelper };
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './Card';
export * from './Descriptions';
export * from './Divider';
export * from './Drawer';
export * from './FormHelper';
export * from './Modal';

// ~ antd
Expand Down Expand Up @@ -64,7 +65,7 @@ export {
type FormRule,
Grid,
Image,
ImageProps,
type ImageProps,
Input,
InputNumber,
type InputNumberProps,
Expand Down

0 comments on commit b28cae4

Please sign in to comment.