react-artasys-ui 0.0.1
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/LICENSE +21 -0
- package/README.md +1 -0
- package/__tests__/static.test.js +0 -0
- package/babel.config.json +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.LICENSE.txt +9 -0
- package/dist/index.js.map +1 -0
- package/lib/File/File.d.ts +17 -0
- package/lib/File/index.d.ts +3 -0
- package/lib/Form/Element/Element.d.ts +13 -0
- package/lib/Form/Element/index.d.ts +3 -0
- package/lib/Form/Form.d.ts +7 -0
- package/lib/Form/index.d.ts +5 -0
- package/lib/Form/types.d.ts +7 -0
- package/lib/Form/useForm.d.ts +17 -0
- package/lib/Input/Input.d.ts +7 -0
- package/lib/Input/index.d.ts +2 -0
- package/lib/Spinner/Spinner.d.ts +6 -0
- package/lib/Spinner/index.d.ts +2 -0
- package/lib/TextArea/TextArea.d.ts +7 -0
- package/lib/TextArea/index.d.ts +2 -0
- package/lib/index.d.ts +9 -0
- package/package.json +52 -0
- package/src/File/File.tsx +84 -0
- package/src/File/index.tsx +9 -0
- package/src/Form/Element/Element.tsx +42 -0
- package/src/Form/Element/index.tsx +7 -0
- package/src/Form/Element/style.module.css +91 -0
- package/src/Form/Form.tsx +28 -0
- package/src/Form/index.tsx +8 -0
- package/src/Form/style.module.css +30 -0
- package/src/Form/types.ts +10 -0
- package/src/Form/useForm.tsx +68 -0
- package/src/Input/Input.tsx +45 -0
- package/src/Input/index.tsx +3 -0
- package/src/Input/style.module.css +16 -0
- package/src/Spinner/Spinner.tsx +13 -0
- package/src/Spinner/index.tsx +3 -0
- package/src/Spinner/style.module.css +74 -0
- package/src/TextArea/TextArea.tsx +57 -0
- package/src/TextArea/index.tsx +3 -0
- package/src/index.ts +28 -0
- package/tsconfig.json +118 -0
- package/types.d.ts +1 -0
- package/webpack.config.js +60 -0
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-artasys-ui",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "jest",
|
|
9
|
+
"build": "webpack",
|
|
10
|
+
"copy-css": "copyfiles -u 1 src/**/*.module.css lib/"
|
|
11
|
+
},
|
|
12
|
+
"jest": {
|
|
13
|
+
"transform": {
|
|
14
|
+
"^.+\\.[t|j]sx?$": "babel-jest"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/SergoMorello/react.artasys.ui.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [],
|
|
22
|
+
"author": "Sergey Serov",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/SergoMorello/react.artasys.ui/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/SergoMorello/react.artasys.ui#readme",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@babel/preset-env": "^7.22.20",
|
|
30
|
+
"@types/jest": "^29.5.0",
|
|
31
|
+
"@types/react": "^18.2.0",
|
|
32
|
+
"@types/react-dom": "^18.2.0",
|
|
33
|
+
"autoprefixer": "^10.4.16",
|
|
34
|
+
"babel-jest": "^29.5.0",
|
|
35
|
+
"copyfiles": "^2.4.1",
|
|
36
|
+
"css-loader": "^6.8.1",
|
|
37
|
+
"jest": "^29.5.0",
|
|
38
|
+
"postcss-loader": "^7.3.3",
|
|
39
|
+
"style-loader": "^3.3.3",
|
|
40
|
+
"ts-loader": "^9.5.0",
|
|
41
|
+
"typescript": "^5.2.2",
|
|
42
|
+
"typescript-plugin-css-modules": "^5.0.1",
|
|
43
|
+
"webpack-cli": "^5.1.4"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"react-hook-form": "^7.47.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": "^18.2.0",
|
|
50
|
+
"react-dom": "^18.2.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useRef,
|
|
4
|
+
MouseEvent,
|
|
5
|
+
AllHTMLAttributes
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
export type TFileMime = 'image/png' | 'image/jpg' | 'image/jpeg' | 'image/gif';
|
|
9
|
+
|
|
10
|
+
export type TFileData = {
|
|
11
|
+
name: string;
|
|
12
|
+
type: TFileMime;
|
|
13
|
+
size: number;
|
|
14
|
+
mime: TFileMime;
|
|
15
|
+
data: string;
|
|
16
|
+
base64?: string | null | ArrayBuffer;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface TFile<T = any> extends Omit<AllHTMLAttributes<T>, 'onChange' | 'accept'> {
|
|
20
|
+
onChange?: (data: TFileData) => void;
|
|
21
|
+
multiple?: boolean;
|
|
22
|
+
accept?: TFileMime[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const File = ({onChange, children, multiple, accept, ...props}: TFile) => {
|
|
26
|
+
const selector = useRef<HTMLInputElement>();
|
|
27
|
+
|
|
28
|
+
const handleChange = (e: Event) => {
|
|
29
|
+
const target = e.target as HTMLInputElement
|
|
30
|
+
if (target.files) {
|
|
31
|
+
for(const file of target.files) {
|
|
32
|
+
const reader = new FileReader();
|
|
33
|
+
reader.onload = (evt) => {
|
|
34
|
+
const data = evt.target?.result?.toString().split(';base64,');
|
|
35
|
+
const mime = (data?.[0].replace('data:', '') ?? '') as TFileMime;
|
|
36
|
+
const contentData = data?.[1] ?? '';
|
|
37
|
+
if (typeof onChange === 'function') {
|
|
38
|
+
selector.current!.value = '';
|
|
39
|
+
onChange({
|
|
40
|
+
name: file.name,
|
|
41
|
+
type: file.type as TFileMime,
|
|
42
|
+
size: file.size,
|
|
43
|
+
mime: mime,
|
|
44
|
+
data: contentData,
|
|
45
|
+
base64: evt.target?.result
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
reader.readAsDataURL(file);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleDialog = (e: MouseEvent<HTMLDivElement>) => {
|
|
55
|
+
selector.current!.click();
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const mount = () => {
|
|
60
|
+
const fileSelector = document.createElement('input');
|
|
61
|
+
fileSelector.setAttribute('type', 'file');
|
|
62
|
+
fileSelector.onchange = handleChange;
|
|
63
|
+
if (accept && accept.length > 0) {
|
|
64
|
+
fileSelector.setAttribute('accept', accept.join(','));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (multiple) {
|
|
68
|
+
fileSelector.setAttribute('multiple', 'multiple');
|
|
69
|
+
}
|
|
70
|
+
return fileSelector;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
selector.current = mount();
|
|
75
|
+
return () => {
|
|
76
|
+
selector.current?.remove();
|
|
77
|
+
}
|
|
78
|
+
}, []);
|
|
79
|
+
return(<div {...props} onClick={handleDialog}>
|
|
80
|
+
{children}
|
|
81
|
+
</div>)
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default File;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
AllHTMLAttributes,
|
|
5
|
+
ReactElement
|
|
6
|
+
} from "react";
|
|
7
|
+
import styles from "./style.module.css";
|
|
8
|
+
|
|
9
|
+
export interface IElement<T = any> extends Omit<AllHTMLAttributes<T>, 'children'> {
|
|
10
|
+
children?: ((props: AllHTMLAttributes<T>) => ReactElement) | AllHTMLAttributes<T>["children"];
|
|
11
|
+
error?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
styleContainer?: React.HTMLAttributes<T>["style"];
|
|
15
|
+
classNameContainer?: string;
|
|
16
|
+
beforeElement?: React.ReactElement;
|
|
17
|
+
afterElement?: React.ReactElement;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const Element = ({children, beforeElement, afterElement, error, placeholder, disabled, styleContainer, classNameContainer, ...props}: IElement) => {
|
|
21
|
+
const [currentError, setCurrentError] = useState('');
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setCurrentError(error ?? '');
|
|
25
|
+
},[error]);
|
|
26
|
+
|
|
27
|
+
return(<>
|
|
28
|
+
<label
|
|
29
|
+
className={
|
|
30
|
+
styles['container'] + (currentError ? ' ' + styles['error'] : '') + (disabled ? ' ' + styles['disabled'] : '') + (classNameContainer ? ' ' + classNameContainer : '')}
|
|
31
|
+
style={styleContainer}
|
|
32
|
+
>
|
|
33
|
+
{beforeElement}
|
|
34
|
+
{typeof children === 'function' ? children(props) : null}
|
|
35
|
+
{afterElement}
|
|
36
|
+
{placeholder && <span className={styles['placeholder']}>{placeholder}</span>}
|
|
37
|
+
</label>
|
|
38
|
+
{currentError && <div className={styles['error']}>{currentError}</div>}
|
|
39
|
+
</>);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default Element;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Input */
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
width: 100%;
|
|
7
|
+
min-height: 50px;
|
|
8
|
+
position: relative;
|
|
9
|
+
border: 1px solid #CCCCCC;
|
|
10
|
+
border-radius: 3px;
|
|
11
|
+
z-index: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.container > input, .container > textarea {
|
|
15
|
+
width: 100% !important;
|
|
16
|
+
min-height: 100% !important;
|
|
17
|
+
max-height: 500px !important;
|
|
18
|
+
max-height: 50dvh !important;
|
|
19
|
+
padding: 10px 5px !important;
|
|
20
|
+
margin: 0 !important;
|
|
21
|
+
font-size: 1.3em !important;
|
|
22
|
+
border: 0 !important;
|
|
23
|
+
box-sizing: border-box !important;
|
|
24
|
+
background-color: transparent !important;
|
|
25
|
+
outline: none;
|
|
26
|
+
resize: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.container.disabled {
|
|
30
|
+
background-color: #EFEFEF;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.container > input:-webkit-autofill,
|
|
34
|
+
.container > input:-webkit-autofill:hover,
|
|
35
|
+
.container > input:-webkit-autofill:focus,
|
|
36
|
+
.container > input:-webkit-autofill:active{
|
|
37
|
+
-webkit-background-clip: text;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.container > input:focus {
|
|
41
|
+
outline: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.container > .placeholder {
|
|
45
|
+
position: absolute;
|
|
46
|
+
display: flex;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
align-items: center;
|
|
49
|
+
top: 0;
|
|
50
|
+
left: 5px;
|
|
51
|
+
color: #7A7A73;
|
|
52
|
+
font-size: 1.2em;
|
|
53
|
+
z-index: -1;
|
|
54
|
+
user-select: none;
|
|
55
|
+
transition: transform 0.3s;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.container > .placeholder::before {
|
|
59
|
+
content: "";
|
|
60
|
+
position: absolute;
|
|
61
|
+
display: block;
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 5px;
|
|
64
|
+
background-color: #FFFFFF;
|
|
65
|
+
z-index: -1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.container > input:required ~ .placeholder::after {
|
|
69
|
+
content: "*";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.container > input:focus ~ .placeholder,
|
|
73
|
+
.container > textarea:focus ~ .placeholder,
|
|
74
|
+
.container > input:not([value=""]) ~ .placeholder,
|
|
75
|
+
.container > textarea:not(:empty) ~ .placeholder,
|
|
76
|
+
.container > input:not(:empty) ~ .placeholder {
|
|
77
|
+
transform: translate(-10%, -55%) scale(0.8);
|
|
78
|
+
z-index: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.container.error {
|
|
82
|
+
border-color: #FF6D6D;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.container.error > .placeholder {
|
|
86
|
+
color: #FF6D6D;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.error {
|
|
90
|
+
color: #FF6D6D;
|
|
91
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FormHTMLAttributes, ReactNode, FormEvent } from 'react';
|
|
2
|
+
import styles from './style.module.css';
|
|
3
|
+
|
|
4
|
+
import Spinner from '../Spinner';
|
|
5
|
+
|
|
6
|
+
interface IForm extends FormHTMLAttributes<HTMLFormElement> {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
wait?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Form = ({children, wait, className, onSubmit, ...props}: IForm) => {
|
|
12
|
+
|
|
13
|
+
const submit = (e: FormEvent<HTMLFormElement>) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
if (typeof onSubmit === 'function') {
|
|
16
|
+
onSubmit(e);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return(<form {...props} onSubmit={submit} className={styles['container'] + (wait ? ' ' + styles['wait'] : '') + (className ? ' ' + className : '')}>
|
|
21
|
+
{children}
|
|
22
|
+
<div className={styles['wait-indicator']}>
|
|
23
|
+
<Spinner size="large" color="contrast"/>
|
|
24
|
+
</div>
|
|
25
|
+
</form>);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default Form;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* Form */
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
flex: 1;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: 10px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.container > .wait-indicator {
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
align-items: center;
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: 0;
|
|
17
|
+
left: 0;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
background-color: #FFFFFF9F;
|
|
21
|
+
backdrop-filter: blur(2px);
|
|
22
|
+
z-index: -1;
|
|
23
|
+
opacity: 0;
|
|
24
|
+
transition: opacity 0.3s;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.container.wait > .wait-indicator {
|
|
28
|
+
z-index: 1;
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useState, FunctionComponent } from "react";
|
|
2
|
+
import type { TError } from "./types";
|
|
3
|
+
import styles from "./style.module.css";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
useForm as useHookForm,
|
|
7
|
+
UseFormReturn,
|
|
8
|
+
RegisterOptions,
|
|
9
|
+
UseFormRegisterReturn,
|
|
10
|
+
FieldValues,
|
|
11
|
+
UseFormRegister,
|
|
12
|
+
FieldPath
|
|
13
|
+
} from "react-hook-form";
|
|
14
|
+
|
|
15
|
+
type TuseErrors<TFieldValues extends FieldValues = FieldValues> = UseFormReturn<TFieldValues> & {
|
|
16
|
+
GlobalError: FunctionComponent;
|
|
17
|
+
setErrors: (e: TError<TFieldValues>) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const useForm = <TFieldValues extends FieldValues = FieldValues>(): TuseErrors<TFieldValues> => {
|
|
21
|
+
const [globalError, setGlobalError] = useState<TError<TFieldValues> | null>();
|
|
22
|
+
|
|
23
|
+
const form = useHookForm<TFieldValues>({
|
|
24
|
+
reValidateMode: 'onChange'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const register = <TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>(name: TFieldName, options?: RegisterOptions<TFieldValues, TFieldName>): UseFormRegisterReturn<TFieldName> => {
|
|
28
|
+
const inputRegister = form.register<TFieldName>(name, options);
|
|
29
|
+
const onChange = (e: {
|
|
30
|
+
target: any;
|
|
31
|
+
type?: any;
|
|
32
|
+
}) => {
|
|
33
|
+
setGlobalError(null);
|
|
34
|
+
form.clearErrors(name);
|
|
35
|
+
return inputRegister.onChange(e);
|
|
36
|
+
};
|
|
37
|
+
const errors = form?.formState.errors;
|
|
38
|
+
return {...inputRegister, onChange, error: errors?.[name]?.message?.toString()} as UseFormRegisterReturn<TFieldName>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const setErrors = (e: TError<TFieldValues>) => {
|
|
42
|
+
if (e.errors) {
|
|
43
|
+
Object.keys(e.errors).forEach((field) => form.setError(field as FieldPath<TFieldValues>, {message: e.errors?.[field]?.[0]}));
|
|
44
|
+
}else{
|
|
45
|
+
setGlobalError(e);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const GlobalError = () => (<>{(globalError && !globalError.errors) && <div className={styles['global-error']}>{globalError.message}</div>}</>);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...form,
|
|
53
|
+
register,
|
|
54
|
+
GlobalError,
|
|
55
|
+
setErrors
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const validate = {
|
|
60
|
+
email: {
|
|
61
|
+
pattern: {
|
|
62
|
+
value: /\S+@\S+\.\S+/,
|
|
63
|
+
message: "Неверный адрес электронной почты",
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default useForm;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useState,
|
|
4
|
+
ChangeEvent,
|
|
5
|
+
useEffect
|
|
6
|
+
} from "react";
|
|
7
|
+
import Element,{
|
|
8
|
+
IElement
|
|
9
|
+
} from "../Form/Element";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
interface IInput extends IElement<HTMLInputElement> {
|
|
13
|
+
onChangeText?: (text: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Input = forwardRef<HTMLInputElement, IInput>(({onChange, onInput, onChangeText, ...props}, ref) => {
|
|
17
|
+
const [currentValue, setCurrentValue] = useState(props.value);
|
|
18
|
+
|
|
19
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
20
|
+
if (typeof onChange === 'function') {
|
|
21
|
+
onChange(e);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (typeof onChangeText === 'function') {
|
|
25
|
+
onChangeText(e.target.value);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleInput = (e: ChangeEvent<HTMLInputElement>) => {
|
|
30
|
+
if (typeof onInput === 'function') {
|
|
31
|
+
onInput(e);
|
|
32
|
+
}
|
|
33
|
+
setCurrentValue(e.target.value);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setCurrentValue(props.value);
|
|
38
|
+
}, [props.value]);
|
|
39
|
+
|
|
40
|
+
return(<Element {...props}>
|
|
41
|
+
{ (props) => <input {...props} placeholder="" onChange={handleChange} value={currentValue} onInput={handleInput} ref={ref}/> }
|
|
42
|
+
</Element>);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export default Input;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.container > input {
|
|
2
|
+
width: calc(100% - 10px);
|
|
3
|
+
margin: 15px 0 15px 0;
|
|
4
|
+
font-size: 18px;
|
|
5
|
+
padding: 10px 0 10px 15px;
|
|
6
|
+
border: 2px solid #E2E8F2;
|
|
7
|
+
resize: none;
|
|
8
|
+
outline: none;
|
|
9
|
+
transition: 0.2s;
|
|
10
|
+
-webkit-transition: 0.2s;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.container > input[type="text"]:focus {
|
|
14
|
+
border: 2px solid #FBA31D;
|
|
15
|
+
border-bottom: 2px solid #000;
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styles from "./style.module.css";
|
|
3
|
+
|
|
4
|
+
type TSpinner = {
|
|
5
|
+
size?: 'small' | 'middle' | 'large';
|
|
6
|
+
color?: 'default' | 'contrast';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Spinner = ({size, color = 'default'}: TSpinner) => {
|
|
10
|
+
return(<div className={styles['spinner'] + (size ? ' ' + styles[size] : '') + (color ? ' ' + styles[color] : '') + ' ' + styles['animate']}></div>);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default Spinner;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
.spinner {
|
|
2
|
+
padding-left: calc(-0.5rem / 2);
|
|
3
|
+
padding-right: calc(-0.5rem / 2);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.spinner::before {
|
|
7
|
+
content: '';
|
|
8
|
+
display: inline-block;
|
|
9
|
+
width: 1rem;
|
|
10
|
+
height: 1rem;
|
|
11
|
+
--border-width: clamp(0.2em, 10%, 0.5em);
|
|
12
|
+
border-radius: 50%;
|
|
13
|
+
aspect-ratio: 1/1;
|
|
14
|
+
--mask: radial-gradient(
|
|
15
|
+
farthest-side,
|
|
16
|
+
transparent calc(100% - var(--border-width) - 0.5px),
|
|
17
|
+
#000 calc(100% - var(--border-width) + 0.5px)
|
|
18
|
+
);
|
|
19
|
+
mask: var(--mask);
|
|
20
|
+
background: linear-gradient(to top, rgba(251, 163, 29, 1), rgba(251, 163, 29, 0.5)) 100% 0/50% 100% no-repeat,
|
|
21
|
+
linear-gradient(rgba(251, 163, 29, 0.5) 50%, transparent 95%) 0 0/50% 100% no-repeat;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.spinner.default::before {
|
|
25
|
+
border-color: #FFFFFF;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.spinner.contrast::before {
|
|
29
|
+
background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.5)) 100% 0/50% 100% no-repeat,
|
|
30
|
+
linear-gradient(rgba(255, 255, 255, 0.5) 50%, transparent 95%) 0 0/50% 100% no-repeat;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.spinner.small::before {
|
|
34
|
+
width: 1rem;
|
|
35
|
+
height: 1rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.spinner.middle::before {
|
|
39
|
+
width: 4rem;
|
|
40
|
+
height: 4rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.spinner.large::before {
|
|
44
|
+
width: 8rem;
|
|
45
|
+
height: 8rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.spinner.big::before {
|
|
49
|
+
width: 2rem;
|
|
50
|
+
height: 2rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.spinner.full {
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
align-items: center;
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.spinner.full::before {
|
|
62
|
+
width: 80%;
|
|
63
|
+
height: 80%;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.spinner.animate::before {
|
|
67
|
+
animation: button-spinner-border .85s linear infinite;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@keyframes button-spinner-border {
|
|
71
|
+
to{
|
|
72
|
+
transform:rotate(360deg)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
ChangeEvent,
|
|
7
|
+
useImperativeHandle
|
|
8
|
+
} from "react";
|
|
9
|
+
import Element,{
|
|
10
|
+
IElement
|
|
11
|
+
} from "../Form/Element";
|
|
12
|
+
|
|
13
|
+
interface ITextArea extends IElement<HTMLTextAreaElement> {
|
|
14
|
+
onChangeText?: (text: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TextArea = forwardRef<HTMLTextAreaElement, ITextArea>(({onChange, onInput, onChangeText, ...props}, ref) => {
|
|
18
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
19
|
+
const [currentValue, setCurrentValue] = useState(props.value);
|
|
20
|
+
|
|
21
|
+
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
22
|
+
if (typeof onChange === 'function') {
|
|
23
|
+
onChange(e);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof onChangeText === 'function') {
|
|
27
|
+
onChangeText(e.target.value);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleInput = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
32
|
+
if (typeof onInput === 'function') {
|
|
33
|
+
onInput(e);
|
|
34
|
+
}
|
|
35
|
+
setCurrentValue(e.target.value);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!textareaRef.current) return;
|
|
40
|
+
|
|
41
|
+
textareaRef.current.style.height = '0';
|
|
42
|
+
const height = textareaRef.current.scrollHeight;
|
|
43
|
+
textareaRef.current.style.height = height + 'px';
|
|
44
|
+
},[currentValue]);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setCurrentValue(props.value);
|
|
48
|
+
}, [props.value]);
|
|
49
|
+
|
|
50
|
+
useImperativeHandle(ref, () => textareaRef.current as HTMLTextAreaElement);
|
|
51
|
+
|
|
52
|
+
return(<Element {...props}>
|
|
53
|
+
{ (props) => <textarea {...props} placeholder="" value={currentValue} onChange={handleChange} onInput={handleInput} ref={textareaRef}/> }
|
|
54
|
+
</Element>);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export default TextArea;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Form,{
|
|
2
|
+
useForm
|
|
3
|
+
} from "./Form";
|
|
4
|
+
import Input from "./Input";
|
|
5
|
+
import TextArea from "./TextArea";
|
|
6
|
+
import Spinner from "./Spinner";
|
|
7
|
+
import File,{
|
|
8
|
+
TFileData,
|
|
9
|
+
TFileMime
|
|
10
|
+
} from "./File";
|
|
11
|
+
|
|
12
|
+
const UI = {
|
|
13
|
+
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
Form,
|
|
18
|
+
useForm,
|
|
19
|
+
Input,
|
|
20
|
+
TextArea,
|
|
21
|
+
Spinner,
|
|
22
|
+
File
|
|
23
|
+
};
|
|
24
|
+
export type {
|
|
25
|
+
TFileData,
|
|
26
|
+
TFileMime
|
|
27
|
+
};
|
|
28
|
+
export default UI;
|