x-ui-design 0.1.0
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 +36 -0
- package/eslint.config.mjs +16 -0
- package/next.config.ts +7 -0
- package/package.json +27 -0
- package/src/app/components/Checkbox/index.tsx +90 -0
- package/src/app/components/Checkbox/style.css +88 -0
- package/src/app/components/Empty/index.tsx +31 -0
- package/src/app/components/Empty/style.css +12 -0
- package/src/app/components/Form/Item/index.tsx +63 -0
- package/src/app/components/Form/Item/style.css +46 -0
- package/src/app/components/Form/index.tsx +74 -0
- package/src/app/components/Input/index.tsx +99 -0
- package/src/app/components/Input/style.css +80 -0
- package/src/app/components/Radio/Button/index.tsx +43 -0
- package/src/app/components/Radio/Button/style.css +43 -0
- package/src/app/components/Radio/Group/index.tsx +88 -0
- package/src/app/components/Radio/Group/style.css +53 -0
- package/src/app/components/Radio/index.tsx +65 -0
- package/src/app/components/Radio/style.css +55 -0
- package/src/app/components/Select/Option/index.tsx +32 -0
- package/src/app/components/Select/Option/style.css +42 -0
- package/src/app/components/Select/Tag/index.tsx +40 -0
- package/src/app/components/Select/Tag/style.css +76 -0
- package/src/app/components/Select/index.tsx +411 -0
- package/src/app/components/Select/style.css +172 -0
- package/src/app/components/icons/index.tsx +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +27 -0
- package/src/app/helpers/index.ts +19 -0
- package/src/app/hooks/useForm.ts +204 -0
- package/src/app/hooks/useWatch.ts +35 -0
- package/src/app/layout.tsx +16 -0
- package/src/app/page.tsx +79 -0
- package/src/app/types/checkbox.ts +28 -0
- package/src/app/types/empty.ts +8 -0
- package/src/app/types/form.ts +70 -0
- package/src/app/types/index.ts +25 -0
- package/src/app/types/input.ts +20 -0
- package/src/app/types/radio.ts +57 -0
- package/src/app/types/select.ts +105 -0
- package/src/app/utils/index.ts +7 -0
- package/tsconfig.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
First, run the development server:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
# or
|
|
10
|
+
yarn dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm dev
|
|
13
|
+
# or
|
|
14
|
+
bun dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
+
|
|
19
|
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
+
|
|
21
|
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
+
|
|
23
|
+
## Learn More
|
|
24
|
+
|
|
25
|
+
To learn more about Next.js, take a look at the following resources:
|
|
26
|
+
|
|
27
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
+
|
|
30
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
+
|
|
32
|
+
## Deploy on Vercel
|
|
33
|
+
|
|
34
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
+
|
|
36
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { dirname } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const compat = new FlatCompat({
|
|
9
|
+
baseDirectory: __dirname,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const eslintConfig = [
|
|
13
|
+
...compat.extends("next/core-web-vitals", "next/typescript"),
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export default eslintConfig;
|
package/next.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "x-ui-design",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": "Gabriel Boyajyan",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev --turbopack",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"antd": "^5.24.3",
|
|
13
|
+
"classcat": "^5.0.5",
|
|
14
|
+
"next": "15.2.0",
|
|
15
|
+
"react": "^19.0.0",
|
|
16
|
+
"react-dom": "^19.0.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@eslint/eslintrc": "^3",
|
|
20
|
+
"@types/node": "^20",
|
|
21
|
+
"@types/react": "^19",
|
|
22
|
+
"@types/react-dom": "^19",
|
|
23
|
+
"eslint": "^9",
|
|
24
|
+
"eslint-config-next": "15.2.0",
|
|
25
|
+
"typescript": "^5"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React, { useState, useEffect, FC, MouseEvent } from "react";
|
|
2
|
+
import { CheckboxProps } from "@/app/types/checkbox";
|
|
3
|
+
import { prefixClsCheckbox } from "@/app/utils";
|
|
4
|
+
import { SyntheticBaseEvent } from "@/app/types";
|
|
5
|
+
import cc from "classcat";
|
|
6
|
+
import "./style.css";
|
|
7
|
+
|
|
8
|
+
const Checkbox: FC<CheckboxProps> = ({
|
|
9
|
+
prefixCls = prefixClsCheckbox,
|
|
10
|
+
className = "",
|
|
11
|
+
defaultChecked = false,
|
|
12
|
+
checked,
|
|
13
|
+
style,
|
|
14
|
+
disabled = false,
|
|
15
|
+
onChange,
|
|
16
|
+
onClick,
|
|
17
|
+
onMouseEnter,
|
|
18
|
+
onMouseLeave,
|
|
19
|
+
onKeyPress,
|
|
20
|
+
onKeyDown,
|
|
21
|
+
tabIndex,
|
|
22
|
+
name,
|
|
23
|
+
children,
|
|
24
|
+
id,
|
|
25
|
+
autoFocus,
|
|
26
|
+
type = "checkbox",
|
|
27
|
+
value = false,
|
|
28
|
+
required = false
|
|
29
|
+
}) => {
|
|
30
|
+
const isChecked = checked !== undefined ? checked : defaultChecked || value;
|
|
31
|
+
const [internalChecked, setInternalChecked] = useState(isChecked);
|
|
32
|
+
|
|
33
|
+
const handleClick = (e: MouseEvent<HTMLInputElement> & SyntheticBaseEvent) => {
|
|
34
|
+
e.stopPropagation()
|
|
35
|
+
|
|
36
|
+
if (disabled) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setInternalChecked(!internalChecked);
|
|
41
|
+
e.target.value = !internalChecked;
|
|
42
|
+
|
|
43
|
+
onClick?.(e);
|
|
44
|
+
onChange?.(e);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (checked !== undefined) {
|
|
49
|
+
setInternalChecked(checked);
|
|
50
|
+
}
|
|
51
|
+
}, [checked]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
style={style}
|
|
56
|
+
onClick={handleClick}
|
|
57
|
+
className={cc([
|
|
58
|
+
prefixCls,
|
|
59
|
+
className,
|
|
60
|
+
{
|
|
61
|
+
[`${prefixCls}-disabled`]: disabled,
|
|
62
|
+
[`${prefixCls}-checked`]: internalChecked
|
|
63
|
+
}])}
|
|
64
|
+
>
|
|
65
|
+
<input
|
|
66
|
+
id={id}
|
|
67
|
+
type={type}
|
|
68
|
+
name={name}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
tabIndex={tabIndex}
|
|
71
|
+
required={required}
|
|
72
|
+
autoFocus={autoFocus}
|
|
73
|
+
onKeyDown={onKeyDown}
|
|
74
|
+
onKeyPress={onKeyPress}
|
|
75
|
+
onMouseEnter={onMouseEnter}
|
|
76
|
+
onMouseLeave={onMouseLeave}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<span className={`${prefixCls}-box`}>
|
|
80
|
+
<span
|
|
81
|
+
className={`${prefixCls}-check`}
|
|
82
|
+
style={{ opacity: Number(internalChecked) }}/>
|
|
83
|
+
</span>
|
|
84
|
+
|
|
85
|
+
{children && <span className={`${prefixCls}-label`}>{children}</span>}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export { Checkbox };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.xUi-checkbox-wrapper {
|
|
2
|
+
cursor: pointer;
|
|
3
|
+
font-size: var(--font-size-md);
|
|
4
|
+
align-items: center;
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.xUi-checkbox {
|
|
9
|
+
width: 14px;
|
|
10
|
+
height: 14px;
|
|
11
|
+
position: relative;
|
|
12
|
+
border-radius: var(--border-radius-sm);
|
|
13
|
+
transition: all 0.3s;
|
|
14
|
+
display: inline-block;
|
|
15
|
+
background-color: var(--background-color);
|
|
16
|
+
border: 1px solid var(--border-color);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.xUi-checkbox.xUi-checkbox-checked {
|
|
20
|
+
border-color: var(--main-color);
|
|
21
|
+
background-color: #f0f5ff;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.xUi-checkbox input {
|
|
25
|
+
inset: 0;
|
|
26
|
+
opacity: 0;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
position: absolute;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.xUi-checkbox-inner {
|
|
32
|
+
top: 50%;
|
|
33
|
+
left: 50%;
|
|
34
|
+
width: 10px;
|
|
35
|
+
height: 6px;
|
|
36
|
+
border-top: 0;
|
|
37
|
+
border-left: 0;
|
|
38
|
+
position: absolute;
|
|
39
|
+
border: 2px solid var(--background-color);
|
|
40
|
+
transform: rotate(45deg) scale(0);
|
|
41
|
+
transition: transform 0.2s ease-in-out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.xUi-checkbox-check {
|
|
45
|
+
width: 100%;
|
|
46
|
+
height: 100%;
|
|
47
|
+
display: block;
|
|
48
|
+
position: relative;
|
|
49
|
+
transition: 0.1s ease;
|
|
50
|
+
border-color: var(--main-color);
|
|
51
|
+
background-color: var(--main-color);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.xUi-checkbox-check::after {
|
|
55
|
+
top: 1px;
|
|
56
|
+
left: 4px;
|
|
57
|
+
width: 3px;
|
|
58
|
+
height: 7px;
|
|
59
|
+
content: "";
|
|
60
|
+
position: absolute;
|
|
61
|
+
border: solid white;
|
|
62
|
+
transform: rotate(45deg);
|
|
63
|
+
border-width: 0 2px 2px 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.xUi-checkbox-disabled,
|
|
67
|
+
.xUi-checkbox-disabled .xUi-checkbox-check {
|
|
68
|
+
opacity: 0.5;
|
|
69
|
+
cursor: not-allowed;
|
|
70
|
+
background-color: var(--color-disabled);
|
|
71
|
+
border-color: var(--border-color) !important;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.xUi-checkbox-label {
|
|
75
|
+
margin-left: 8px;
|
|
76
|
+
user-select: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.xUi-checkbox:hover:not(.disabled),
|
|
80
|
+
.xUi-checkbox:focus:not(.disabled) {
|
|
81
|
+
border-color: var(--main-color);
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.xUi-checkbox.disabled {
|
|
86
|
+
opacity: 0.5;
|
|
87
|
+
cursor: not-allowed;
|
|
88
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { prefixClsEmpty } from "@/app/utils"
|
|
2
|
+
import { EmptyContentProps } from "@/app/types/empty"
|
|
3
|
+
import './style.css'
|
|
4
|
+
|
|
5
|
+
const EmptyContent = ({
|
|
6
|
+
icon,
|
|
7
|
+
style = {},
|
|
8
|
+
className = '',
|
|
9
|
+
title = 'No Data',
|
|
10
|
+
description = 'No data',
|
|
11
|
+
prefixCls = prefixClsEmpty,
|
|
12
|
+
}: EmptyContentProps) => (
|
|
13
|
+
<div style={style} className={`${prefixCls} ${prefixCls}-normal ${prefixCls}-small ${className}`}>
|
|
14
|
+
<div className={`${prefixCls}-image`}>
|
|
15
|
+
{icon || <svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg">
|
|
16
|
+
<title>{title}</title>
|
|
17
|
+
<g transform="translate(0 1)" fill="none">
|
|
18
|
+
<ellipse fill="#f5f5f5" cx="32" cy="33" rx="32" ry="7"></ellipse>
|
|
19
|
+
<g stroke="#d9d9d9">
|
|
20
|
+
<path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"></path>
|
|
21
|
+
<path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="#fafafa"></path>
|
|
22
|
+
</g>
|
|
23
|
+
</g>
|
|
24
|
+
</svg>}
|
|
25
|
+
</div>
|
|
26
|
+
<div className={`${prefixCls}-description`}>{description}</div>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export { EmptyContent }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { cloneElement, FC, useContext, useEffect, useMemo } from 'react';
|
|
2
|
+
import { FormContext } from '..';
|
|
3
|
+
import { prefixClsFormItem } from '@/app/utils';
|
|
4
|
+
import { OptionProps } from '@/app/types/select';
|
|
5
|
+
import { SyntheticBaseEvent, RuleType } from '@/app/types';
|
|
6
|
+
import { FormItemProps } from '@/app/types/form';
|
|
7
|
+
import './style.css';
|
|
8
|
+
|
|
9
|
+
export const FormItem: FC<FormItemProps> = ({
|
|
10
|
+
prefixCls = prefixClsFormItem,
|
|
11
|
+
name,
|
|
12
|
+
label,
|
|
13
|
+
rules = [],
|
|
14
|
+
children,
|
|
15
|
+
className = '',
|
|
16
|
+
layout = 'horizontal',
|
|
17
|
+
style = {},
|
|
18
|
+
...props
|
|
19
|
+
}) => {
|
|
20
|
+
const formContext = useContext(FormContext);
|
|
21
|
+
|
|
22
|
+
if (!formContext) {
|
|
23
|
+
throw new Error('FormItem must be used within a Form');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { registerField, getFieldValue, setFieldValue, getFieldError } =
|
|
27
|
+
formContext;
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
registerField(name, rules);
|
|
31
|
+
}, [name, registerField, rules]);
|
|
32
|
+
|
|
33
|
+
const isRequired = useMemo(() => rules.some(rule => rule.required), [rules]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div style={style} className={`${prefixCls} ${className} ${layout}`}>
|
|
37
|
+
<label className={`${prefixCls}-label`} htmlFor={name}>
|
|
38
|
+
{layout === 'horizontal' ? `${label}: ` : label} {isRequired && <span className={`${prefixCls}-required`}>*</span>}
|
|
39
|
+
</label>
|
|
40
|
+
|
|
41
|
+
{cloneElement(children, {
|
|
42
|
+
...props,
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
44
|
+
// @ts-expect-error
|
|
45
|
+
size: children.props.size || props.size,
|
|
46
|
+
error: !!getFieldError(name).length,
|
|
47
|
+
value: getFieldValue(name) ?? children.props.value,
|
|
48
|
+
onChange: (e: SyntheticBaseEvent, option?: OptionProps) => {
|
|
49
|
+
const value: RuleType | SyntheticBaseEvent = e.target ? e.target.value : e;
|
|
50
|
+
|
|
51
|
+
setFieldValue(name, value as RuleType)
|
|
52
|
+
|
|
53
|
+
const childOnChange = (children.props as { onChange?: (e: SyntheticBaseEvent, option?: OptionProps) => void }).onChange;
|
|
54
|
+
childOnChange?.(e, option);
|
|
55
|
+
}
|
|
56
|
+
})}
|
|
57
|
+
|
|
58
|
+
{getFieldError(name).length ? (
|
|
59
|
+
<span className={`${prefixCls}-error`}>{getFieldError(name)[0]}</span>
|
|
60
|
+
) : null}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.xUi-form-item {
|
|
2
|
+
display: flex;
|
|
3
|
+
margin-bottom: 22px;
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.xUi-form-item-label {
|
|
8
|
+
font-weight: 500;
|
|
9
|
+
margin-bottom: 4px;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
font-size: var(--font-size-md);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.xUi-form-item-error {
|
|
16
|
+
right: 0;
|
|
17
|
+
bottom: -18px;
|
|
18
|
+
font-size: var(--font-size-xs);
|
|
19
|
+
margin-top: 4px;
|
|
20
|
+
position: absolute;
|
|
21
|
+
color: var(--error-text-color);
|
|
22
|
+
user-select: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.xUi-form-item-required {
|
|
26
|
+
line-height: 1;
|
|
27
|
+
font-size: var(--font-size-md);
|
|
28
|
+
margin-left: 4px;
|
|
29
|
+
margin-right: 4px;
|
|
30
|
+
display: inline-block;
|
|
31
|
+
color: var(--error-text-color);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.xUi-form-item.horizontal {
|
|
35
|
+
gap: 4px;
|
|
36
|
+
flex-direction: row;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.xUi-form-item.vertical {
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.xUi-form-item .xUi-input-container,
|
|
44
|
+
.xUi-form-item .xUi-select {
|
|
45
|
+
width: -webkit-fill-available;
|
|
46
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Children, cloneElement, createContext, FC, isValidElement, SyntheticEvent, useEffect, useRef } from 'react';
|
|
2
|
+
import { FormInstance, FormItemProps, FormProps } from '@/app/types/form';
|
|
3
|
+
import { useForm } from '@/app/hooks/useForm';
|
|
4
|
+
import { FormItem } from './Item';
|
|
5
|
+
import { prefixClsForm } from '@/app/utils';
|
|
6
|
+
|
|
7
|
+
export const FormContext = createContext<FormInstance | null>(null);
|
|
8
|
+
|
|
9
|
+
const Form: FC<FormProps> & { Item: FC<FormItemProps> } = ({
|
|
10
|
+
children,
|
|
11
|
+
form,
|
|
12
|
+
style = {},
|
|
13
|
+
prefixCls = prefixClsForm,
|
|
14
|
+
className = '',
|
|
15
|
+
onFinish,
|
|
16
|
+
onFinishFailed,
|
|
17
|
+
initialValues = {},
|
|
18
|
+
onValuesChange,
|
|
19
|
+
onFieldsChange,
|
|
20
|
+
layout = 'horizontal',
|
|
21
|
+
...rest
|
|
22
|
+
}) => {
|
|
23
|
+
const internalForm = useForm(initialValues, onFieldsChange, onValuesChange);
|
|
24
|
+
const formInstance = form || internalForm;
|
|
25
|
+
const formRef = useRef<HTMLFormElement>(null);
|
|
26
|
+
|
|
27
|
+
const handleSubmit = async (e: SyntheticEvent) => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
|
|
30
|
+
if (await formInstance.validateFields()) {
|
|
31
|
+
onFinish?.(formInstance.getFieldsValue());
|
|
32
|
+
} else if (onFinishFailed) {
|
|
33
|
+
const errorFields = formInstance.getFieldsError();
|
|
34
|
+
onFinishFailed({ values: formInstance.getFieldsValue(), errorFields });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (onFieldsChange) {
|
|
40
|
+
formInstance.onFieldsChange = onFieldsChange;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (onValuesChange) {
|
|
44
|
+
formInstance.onValuesChange = onValuesChange;
|
|
45
|
+
}
|
|
46
|
+
}, [formInstance, onFieldsChange, onValuesChange]);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<FormContext.Provider value={formInstance}>
|
|
54
|
+
<form style={style} className={`${prefixCls} ${className}`} ref={formRef} onSubmit={handleSubmit} {...rest}>
|
|
55
|
+
{Children.map(children, (child) => {
|
|
56
|
+
if (isValidElement(child)) {
|
|
57
|
+
return cloneElement(child, {
|
|
58
|
+
...rest,
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
60
|
+
// @ts-expect-error
|
|
61
|
+
layout: child.props.layout || layout
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return child;
|
|
66
|
+
})}
|
|
67
|
+
</form>
|
|
68
|
+
</FormContext.Provider>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
Form.Item = FormItem
|
|
73
|
+
|
|
74
|
+
export { Form };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ForwardedRef, forwardRef, KeyboardEvent, MouseEvent, useState } from "react";
|
|
4
|
+
import { prefixClsInput } from "@/app/utils";
|
|
5
|
+
import { SyntheticBaseEvent, TargetProps } from "@/app/types";
|
|
6
|
+
import { InputProps } from "@/app/types/input";
|
|
7
|
+
import cc from "classcat";
|
|
8
|
+
import "./style.css";
|
|
9
|
+
|
|
10
|
+
const Input = forwardRef(({
|
|
11
|
+
size = "middle",
|
|
12
|
+
error,
|
|
13
|
+
suffix,
|
|
14
|
+
prefix,
|
|
15
|
+
addonAfter,
|
|
16
|
+
addonBefore,
|
|
17
|
+
onPressEnter,
|
|
18
|
+
disabled = false,
|
|
19
|
+
allowClear = false,
|
|
20
|
+
prefixCls = prefixClsInput,
|
|
21
|
+
className = '',
|
|
22
|
+
value = undefined,
|
|
23
|
+
...props
|
|
24
|
+
}: InputProps,
|
|
25
|
+
ref: ForwardedRef<HTMLInputElement>
|
|
26
|
+
) => {
|
|
27
|
+
const [internalValue, setInternalValue] = useState(value ?? '');
|
|
28
|
+
|
|
29
|
+
const handleChange = (e: SyntheticBaseEvent) => {
|
|
30
|
+
setInternalValue(e.target.value as string);
|
|
31
|
+
|
|
32
|
+
props.onChange?.({
|
|
33
|
+
target: e?.target,
|
|
34
|
+
currentTarget: e.currentTarget
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleClear = (e: MouseEvent<HTMLSpanElement> & TargetProps) => {
|
|
39
|
+
setInternalValue("");
|
|
40
|
+
|
|
41
|
+
e.target.value = ''
|
|
42
|
+
|
|
43
|
+
props.onChange?.({
|
|
44
|
+
target: e?.target,
|
|
45
|
+
currentTarget: e.currentTarget
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleOnKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
50
|
+
if (e.key === "Enter" && onPressEnter) {
|
|
51
|
+
onPressEnter(e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
className={cc([
|
|
58
|
+
`${prefixCls}-container`,
|
|
59
|
+
{
|
|
60
|
+
[`${prefixCls}-error`]: error,
|
|
61
|
+
[`${prefixCls}-disabled`]: disabled,
|
|
62
|
+
[`${prefixCls}-${size}`]: size,
|
|
63
|
+
},
|
|
64
|
+
className
|
|
65
|
+
])}
|
|
66
|
+
style={props.style}
|
|
67
|
+
>
|
|
68
|
+
{addonBefore && <span className={`${prefixCls}-addon ${prefixCls}-before`}>{addonBefore}</span>}
|
|
69
|
+
|
|
70
|
+
<div className={`${prefixCls}-wrapper`}>
|
|
71
|
+
{prefix && <span className={`${prefixCls}-prefix`}>{prefix}</span>}
|
|
72
|
+
|
|
73
|
+
<input
|
|
74
|
+
ref={ref}
|
|
75
|
+
{...props}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
value={internalValue}
|
|
78
|
+
onChange={handleChange}
|
|
79
|
+
onKeyDown={handleOnKeyDown}
|
|
80
|
+
className={cc([prefixCls, className])}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
{allowClear && (internalValue) && (
|
|
84
|
+
<span className={`${prefixCls}-clear`} onClick={handleClear}>
|
|
85
|
+
✕
|
|
86
|
+
</span>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{suffix && <span className={`${prefixCls}-suffix`}>{suffix}</span>}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{addonAfter && <span className={`${prefixCls}-addon ${prefixCls}-after`}>{addonAfter}</span>}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
Input.displayName = "Input";
|
|
98
|
+
|
|
99
|
+
export { Input };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.xUi-input-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
border: 1px solid #ccc;
|
|
5
|
+
border-radius: var(--border-radius-sm);
|
|
6
|
+
transition: border 0.3s;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.xUi-input-container.xUi-input-error {
|
|
10
|
+
border-color: red;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.xUi-input-wrapper {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: 6px;
|
|
16
|
+
height: inherit;
|
|
17
|
+
padding: 0 2px;
|
|
18
|
+
align-items: center;
|
|
19
|
+
flex-grow: 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.xUi-input {
|
|
23
|
+
flex: 1;
|
|
24
|
+
padding: 0;
|
|
25
|
+
border: none;
|
|
26
|
+
outline: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.xUi-input-prefix,
|
|
30
|
+
.xUi-input-suffix {
|
|
31
|
+
margin: 0 6px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.xUi-input-clear {
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
margin: 0 2px;
|
|
37
|
+
color: #ccc;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.xUi-input-disabled {
|
|
41
|
+
background-color: var(--color-disabled);
|
|
42
|
+
cursor: not-allowed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.xUi-input-small {
|
|
46
|
+
height: 22px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.xUi-input-large .xUi-input-clear,
|
|
50
|
+
.xUi-input-small .xUi-input {
|
|
51
|
+
font-size: var(--font-size-md);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.xUi-input-middle {
|
|
55
|
+
height: 30px;
|
|
56
|
+
border-radius: var(--border-radius-md);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.xUi-input-large .xUi-input-clear,
|
|
60
|
+
.xUi-input-middle .xUi-input {
|
|
61
|
+
font-size: var(--font-size-md);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.xUi-input-large {
|
|
65
|
+
height: 40px;
|
|
66
|
+
border-radius: var(--border-radius-lg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.xUi-input-large .xUi-input-clear,
|
|
70
|
+
.xUi-input-large .xUi-input {
|
|
71
|
+
font-size: var(--font-size-lg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.xUi-input-middle {
|
|
75
|
+
padding: 0 12px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.xUi-input-large {
|
|
79
|
+
padding: 0 10px;
|
|
80
|
+
}
|