@uniai-fe/uds-templates 0.0.8 → 0.0.9
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/dist/styles.css +280 -327
- package/package.json +10 -7
- package/src/components/auth/container/AuthContainer.tsx +24 -0
- package/src/components/auth/container/index.scss +52 -0
- package/src/components/auth/container/index.tsx +4 -0
- package/src/components/auth/container/types.ts +8 -0
- package/src/components/auth/index.tsx +9 -0
- package/src/components/auth/login/data/valid-options.ts +8 -0
- package/src/components/auth/login/hooks/index.ts +6 -0
- package/src/components/auth/login/hooks/useAuthLoginForm.ts +80 -0
- package/src/components/auth/login/index.scss +1 -0
- package/src/components/auth/login/index.tsx +12 -0
- package/src/components/auth/login/markup/Container.tsx +34 -0
- package/src/components/auth/login/markup/FormField.tsx +115 -0
- package/src/components/auth/login/markup/LinkButtons.tsx +40 -0
- package/src/components/auth/login/styles/login.scss +52 -0
- package/src/components/auth/login/types/form.ts +0 -0
- package/src/components/auth/login/types/hooks.ts +82 -0
- package/src/components/auth/login/types/props.ts +95 -0
- package/src/components/auth/login/types.ts +2 -0
- package/src/index.tsx +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-templates",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "UNIAI Design System; UI Templates Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
|
-
"packageManager": "pnpm@10.
|
|
15
|
+
"packageManager": "pnpm@10.26.1",
|
|
16
16
|
"engines": {
|
|
17
17
|
"node": ">=24",
|
|
18
18
|
"pnpm": ">=10"
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
"@uniai-fe/uds-foundation": "^0.0.1",
|
|
52
52
|
"@uniai-fe/uds-primitives": "^0.0.2",
|
|
53
53
|
"react": ">= 19",
|
|
54
|
-
"react-dom": ">= 19"
|
|
54
|
+
"react-dom": ">= 19",
|
|
55
|
+
"react-hook-form": ">= 7",
|
|
56
|
+
"next": "^15"
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
59
|
"clsx": "^2.1.1",
|
|
58
60
|
"dayjs": "^1.11.19"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|
|
61
|
-
"@uniai-fe/uds-foundation": "workspace:*",
|
|
62
|
-
"@uniai-fe/uds-primitives": "workspace:*",
|
|
63
63
|
"@svgr/webpack": "^8.1.0",
|
|
64
64
|
"@types/node": "^24.10.2",
|
|
65
65
|
"@types/react": "^19.2.7",
|
|
@@ -67,10 +67,13 @@
|
|
|
67
67
|
"@uniai-fe/eslint-config": "workspace:*",
|
|
68
68
|
"@uniai-fe/next-devkit": "workspace:*",
|
|
69
69
|
"@uniai-fe/tsconfig": "workspace:*",
|
|
70
|
-
"
|
|
70
|
+
"@uniai-fe/uds-foundation": "workspace:*",
|
|
71
|
+
"@uniai-fe/uds-primitives": "workspace:*",
|
|
72
|
+
"eslint": "^9.39.2",
|
|
73
|
+
"next": "^15.5.9",
|
|
71
74
|
"prettier": "^3.7.4",
|
|
72
|
-
"sass": "^1.96.0",
|
|
73
75
|
"react-hook-form": "^7.68.0",
|
|
76
|
+
"sass": "^1.97.0",
|
|
74
77
|
"typescript": "~5.9.3"
|
|
75
78
|
}
|
|
76
79
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import type { AuthContainerProps } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 로그인/회원가입 화면을 감싸는 공용 컨테이너
|
|
6
|
+
* @component
|
|
7
|
+
*/
|
|
8
|
+
export function AuthContainer({
|
|
9
|
+
className,
|
|
10
|
+
header,
|
|
11
|
+
children,
|
|
12
|
+
footer,
|
|
13
|
+
}: AuthContainerProps) {
|
|
14
|
+
// 단일 열 레이아웃을 강제해 모바일·PC 환경에서 동일한 시각적 구조를 보장한다.
|
|
15
|
+
return (
|
|
16
|
+
<section className={clsx("auth-container", className)}>
|
|
17
|
+
<div className="auth-container__inner">
|
|
18
|
+
{header && <header className="auth-container__header">{header}</header>}
|
|
19
|
+
<div className="auth-container__body">{children}</div>
|
|
20
|
+
{footer && <footer className="auth-container__footer">{footer}</footer>}
|
|
21
|
+
</div>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--auth-container-max-width: 335px;
|
|
3
|
+
--auth-container-gap: var(--spacing-padding-7, 28px);
|
|
4
|
+
--auth-container-padding-inline: var(--spacing-padding-6, 24px);
|
|
5
|
+
--auth-container-padding-top: calc(
|
|
6
|
+
var(--spacing-padding-9, 32px) + env(safe-area-inset-top, 0px)
|
|
7
|
+
);
|
|
8
|
+
--auth-container-padding-bottom: var(--spacing-padding-10, 40px);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.auth-container {
|
|
12
|
+
min-height: min(100svh, 100dvh);
|
|
13
|
+
padding: var(--auth-container-padding-top)
|
|
14
|
+
var(--auth-container-padding-inline)
|
|
15
|
+
calc(
|
|
16
|
+
var(--auth-container-padding-bottom) + env(safe-area-inset-bottom, 0px)
|
|
17
|
+
);
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
background-color: var(--color-common-100, #ffffff);
|
|
23
|
+
color: inherit;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.auth-container__inner {
|
|
27
|
+
width: min(100%, var(--auth-container-max-width, 335px));
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
gap: var(--auth-container-gap);
|
|
31
|
+
margin-block: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.auth-container__header {
|
|
35
|
+
width: 100%;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.auth-container__body {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: var(--spacing-padding-6, 24px);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.auth-container__footer {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
gap: var(--spacing-padding-4, 16px);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.auth-container__footer {
|
|
51
|
+
margin-top: auto;
|
|
52
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useWatch } from "react-hook-form";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type {
|
|
5
|
+
AuthLoginFields,
|
|
6
|
+
AuthLoginFormValues,
|
|
7
|
+
UseAuthLoginFormOptions,
|
|
8
|
+
UseAuthLoginFormReturn,
|
|
9
|
+
} from "../types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 로그인 폼 훅
|
|
13
|
+
* @hook
|
|
14
|
+
* @template TFields
|
|
15
|
+
* @param {UseAuthLoginFormOptions<TFields>} options 훅 옵션
|
|
16
|
+
* @desc
|
|
17
|
+
* - 1) form init → 2) register merge → 3) helper/state → 4) submit 순서
|
|
18
|
+
*/
|
|
19
|
+
export function useAuthLoginForm<
|
|
20
|
+
TFields extends AuthLoginFields = AuthLoginFields,
|
|
21
|
+
>({
|
|
22
|
+
fields,
|
|
23
|
+
form,
|
|
24
|
+
onLogin,
|
|
25
|
+
}: UseAuthLoginFormOptions<TFields>): UseAuthLoginFormReturn<TFields> {
|
|
26
|
+
/** 1) form init — useForm은 FormField에서 생성 후 주입된다. */
|
|
27
|
+
const values = useWatch({
|
|
28
|
+
control: form.control,
|
|
29
|
+
}) as AuthLoginFormValues | undefined;
|
|
30
|
+
|
|
31
|
+
/** 2) register merge — 필드별 register 함수를 생성한다. */
|
|
32
|
+
const register = useMemo(() => {
|
|
33
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
34
|
+
(acc, fieldKey) => {
|
|
35
|
+
const config = fields[fieldKey];
|
|
36
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
37
|
+
acc[fieldKey] = form.register(fieldName);
|
|
38
|
+
return acc;
|
|
39
|
+
},
|
|
40
|
+
{} as UseAuthLoginFormReturn<TFields>["register"],
|
|
41
|
+
);
|
|
42
|
+
}, [fields, form]);
|
|
43
|
+
|
|
44
|
+
/** 3) helper/state — helper 메시지와 버튼 상태를 계산한다. */
|
|
45
|
+
const helpers = useMemo(() => {
|
|
46
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
47
|
+
(acc, fieldKey) => {
|
|
48
|
+
const config = fields[fieldKey];
|
|
49
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
50
|
+
const state = form.getFieldState(fieldName);
|
|
51
|
+
const helperText =
|
|
52
|
+
(state.error?.message as ReactNode | undefined) ?? config.helper;
|
|
53
|
+
acc[fieldKey] = {
|
|
54
|
+
text: helperText,
|
|
55
|
+
state: state.invalid ? "error" : undefined,
|
|
56
|
+
};
|
|
57
|
+
return acc;
|
|
58
|
+
},
|
|
59
|
+
{} as UseAuthLoginFormReturn<TFields>["helpers"],
|
|
60
|
+
);
|
|
61
|
+
}, [fields, form]);
|
|
62
|
+
|
|
63
|
+
const trimmedFilled =
|
|
64
|
+
values &&
|
|
65
|
+
Object.values(values).every(value =>
|
|
66
|
+
typeof value === "string" ? value.trim().length > 0 : false,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const disabled = form.formState.isSubmitting || !trimmedFilled;
|
|
70
|
+
|
|
71
|
+
/** 4) submit — onLogin을 handleSubmit과 결합한다. */
|
|
72
|
+
const onSubmit = form.handleSubmit(onLogin);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
register,
|
|
76
|
+
onSubmit,
|
|
77
|
+
disabled,
|
|
78
|
+
helpers,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use "./styles/login.scss";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "./index.scss";
|
|
2
|
+
|
|
3
|
+
export type { AuthLoginProps as AuthLoginTemplateProps } from "./types";
|
|
4
|
+
export * from "./hooks";
|
|
5
|
+
|
|
6
|
+
import AuthLoginContainer from "./markup/Container";
|
|
7
|
+
import AuthLoginFormField from "./markup/FormField";
|
|
8
|
+
|
|
9
|
+
export const AuthLogin = {
|
|
10
|
+
Container: AuthLoginContainer,
|
|
11
|
+
FormField: AuthLoginFormField,
|
|
12
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { AuthContainer } from "../../container";
|
|
3
|
+
import AuthLoginFormField from "./FormField";
|
|
4
|
+
import AuthLoginLinkButtons from "./LinkButtons";
|
|
5
|
+
import type { AuthLoginProps } from "../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 로그인 화면 템플릿 — AuthContainer 위에 아이디/비밀번호 입력과 CTA를 배치한다.
|
|
9
|
+
* @component
|
|
10
|
+
*/
|
|
11
|
+
export default function AuthLoginContainer({
|
|
12
|
+
className,
|
|
13
|
+
header,
|
|
14
|
+
footer,
|
|
15
|
+
linkOptions,
|
|
16
|
+
fieldOptions,
|
|
17
|
+
}: AuthLoginProps) {
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<AuthContainer
|
|
21
|
+
className={clsx("auth-login-container", className)}
|
|
22
|
+
header={header}
|
|
23
|
+
footer={footer}
|
|
24
|
+
>
|
|
25
|
+
<AuthLoginFormField {...fieldOptions} />
|
|
26
|
+
<AuthLoginLinkButtons
|
|
27
|
+
hrefFindId={linkOptions.find.id}
|
|
28
|
+
hrefFindPassword={linkOptions.find.password}
|
|
29
|
+
hrefSignup={linkOptions.signup}
|
|
30
|
+
/>
|
|
31
|
+
</AuthContainer>
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Input,
|
|
6
|
+
PasswordInput,
|
|
7
|
+
type InputPasswordProps,
|
|
8
|
+
type InputProps,
|
|
9
|
+
type InputState,
|
|
10
|
+
} from "@uniai-fe/uds-primitives";
|
|
11
|
+
import type {
|
|
12
|
+
AuthLoginFieldConfig,
|
|
13
|
+
AuthLoginFieldOptions,
|
|
14
|
+
AuthLoginFields,
|
|
15
|
+
AuthLoginFormValues,
|
|
16
|
+
} from "../types";
|
|
17
|
+
import { useAuthLoginForm } from "../hooks";
|
|
18
|
+
import { useForm } from "react-hook-form";
|
|
19
|
+
|
|
20
|
+
type HelperState = {
|
|
21
|
+
/**
|
|
22
|
+
* helper 텍스트
|
|
23
|
+
*/
|
|
24
|
+
text?: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* helper 상태
|
|
27
|
+
*/
|
|
28
|
+
state?: InputState;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const composeFieldProps = <TProps extends InputProps>(
|
|
32
|
+
config: AuthLoginFieldConfig<TProps>,
|
|
33
|
+
helper?: HelperState,
|
|
34
|
+
): TProps => {
|
|
35
|
+
const baseProps = {
|
|
36
|
+
...(config.attr ?? {}),
|
|
37
|
+
...(config.props ?? ({} as TProps)),
|
|
38
|
+
} as TProps;
|
|
39
|
+
const mergedLabelProps = config.labelClassName
|
|
40
|
+
? {
|
|
41
|
+
...baseProps.labelProps,
|
|
42
|
+
className: config.labelClassName,
|
|
43
|
+
}
|
|
44
|
+
: baseProps.labelProps;
|
|
45
|
+
const mergedHelperProps = config.helperClassName
|
|
46
|
+
? {
|
|
47
|
+
...baseProps.helperProps,
|
|
48
|
+
className: config.helperClassName,
|
|
49
|
+
}
|
|
50
|
+
: baseProps.helperProps;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...baseProps,
|
|
54
|
+
label: config.label ?? baseProps.label,
|
|
55
|
+
helper: helper?.text ?? config.helper ?? baseProps.helper,
|
|
56
|
+
state: helper?.state ?? baseProps.state,
|
|
57
|
+
block: config.block ?? baseProps.block ?? true,
|
|
58
|
+
className: config.className ?? baseProps.className,
|
|
59
|
+
labelProps: mergedLabelProps,
|
|
60
|
+
helperProps: mergedHelperProps,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default function AuthLoginFormField<
|
|
65
|
+
TFields extends AuthLoginFields = AuthLoginFields,
|
|
66
|
+
>({ fields, formAttr, onLogin }: AuthLoginFieldOptions<TFields>) {
|
|
67
|
+
const defaultValues = useMemo(
|
|
68
|
+
() =>
|
|
69
|
+
(Object.keys(fields) as Array<keyof TFields>).reduce((acc, fieldKey) => {
|
|
70
|
+
const config = fields[fieldKey];
|
|
71
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
72
|
+
acc[fieldName] = "";
|
|
73
|
+
return acc;
|
|
74
|
+
}, {} as AuthLoginFormValues),
|
|
75
|
+
[fields],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const form = useForm<AuthLoginFormValues>({
|
|
79
|
+
mode: "onChange",
|
|
80
|
+
defaultValues,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const { register, helpers, onSubmit, disabled } = useAuthLoginForm<TFields>({
|
|
84
|
+
fields,
|
|
85
|
+
form,
|
|
86
|
+
onLogin,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<form className="auth-login-form" {...formAttr} onSubmit={onSubmit}>
|
|
91
|
+
<div className="auth-login-fields">
|
|
92
|
+
<Input
|
|
93
|
+
{...composeFieldProps<InputProps>(fields.id, helpers.id)}
|
|
94
|
+
register={register.id}
|
|
95
|
+
/>
|
|
96
|
+
<PasswordInput
|
|
97
|
+
{...composeFieldProps<InputPasswordProps>(
|
|
98
|
+
fields.password,
|
|
99
|
+
helpers.password,
|
|
100
|
+
)}
|
|
101
|
+
register={register.password}
|
|
102
|
+
/>
|
|
103
|
+
<Button.Default
|
|
104
|
+
type="submit"
|
|
105
|
+
scale="solid-xlarge"
|
|
106
|
+
priority="primary"
|
|
107
|
+
block
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
>
|
|
110
|
+
로그인
|
|
111
|
+
</Button.Default>
|
|
112
|
+
</div>
|
|
113
|
+
</form>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Button, Divider } from "@uniai-fe/uds-primitives";
|
|
3
|
+
|
|
4
|
+
export default function AuthLoginLinkButtons({
|
|
5
|
+
hrefFindId,
|
|
6
|
+
hrefFindPassword,
|
|
7
|
+
hrefSignup,
|
|
8
|
+
}: {
|
|
9
|
+
hrefFindId: string;
|
|
10
|
+
hrefFindPassword: string;
|
|
11
|
+
hrefSignup: string;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="auth-login-util-container">
|
|
15
|
+
<div className="auth-login-find-account">
|
|
16
|
+
<Link href={hrefFindId} className="auth-login-find-account-button">
|
|
17
|
+
<span>아이디 찾기</span>
|
|
18
|
+
</Link>
|
|
19
|
+
<Divider />
|
|
20
|
+
<Link
|
|
21
|
+
href={hrefFindPassword}
|
|
22
|
+
className="auth-login-find-account-button"
|
|
23
|
+
>
|
|
24
|
+
<span>비밀번호 찾기</span>
|
|
25
|
+
</Link>
|
|
26
|
+
</div>
|
|
27
|
+
<div className="auth-login-signup">
|
|
28
|
+
<Button.Rounded
|
|
29
|
+
as={Link}
|
|
30
|
+
href={hrefSignup}
|
|
31
|
+
className="auth-login-signup-button"
|
|
32
|
+
priority="tertiary"
|
|
33
|
+
size="small"
|
|
34
|
+
>
|
|
35
|
+
회원가입
|
|
36
|
+
</Button.Rounded>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.auth-login-form {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--spacing-padding-7, 28px);
|
|
5
|
+
margin-top: var(--spacing-padding-10, 40px);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.auth-login-fields {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: var(--spacing-padding-5, 20px);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.auth-login-util-container {
|
|
15
|
+
margin-top: 80px;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--spacing-padding-6, 24px);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.auth-login-find-account {
|
|
22
|
+
display: flex;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
align-items: center;
|
|
25
|
+
|
|
26
|
+
--divider-height: 13px;
|
|
27
|
+
--divider-color: var(--color-label-neutral);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.auth-login-find-account-button {
|
|
31
|
+
color: var(--color-label-standard);
|
|
32
|
+
font-size: 13px;
|
|
33
|
+
line-height: 1em;
|
|
34
|
+
font-weight: 400;
|
|
35
|
+
padding-inline: var(--spacing-padding-1, 4px);
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
color: var(--color-primary-50, #2563eb);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.auth-login-signup {
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.auth-login-signup-button {
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
min-width: 160px;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
--theme-button-font-label-medium-size: var(--font-caption-medium-size);
|
|
52
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SubmitHandler,
|
|
3
|
+
UseFormRegisterReturn,
|
|
4
|
+
UseFormReturn,
|
|
5
|
+
} from "react-hook-form";
|
|
6
|
+
import type { ReactNode } from "react";
|
|
7
|
+
import type { AuthLoginFields } from "./props";
|
|
8
|
+
import type { InputState } from "@uniai-fe/uds-primitives";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 로그인 폼 값 타입
|
|
12
|
+
* @typedef {AuthLoginFormValues}
|
|
13
|
+
* @desc
|
|
14
|
+
* - 모든 로그인 필드 입력값을 문자열 레코드로 취급한다.
|
|
15
|
+
*/
|
|
16
|
+
export type AuthLoginFormValues = Record<string, string>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 로그인 훅 옵션; useAuthLoginForm 설정
|
|
20
|
+
* @typedef {UseAuthLoginFormOptions}
|
|
21
|
+
* @desc
|
|
22
|
+
* - 필드 정의, useForm 반환 객체, 로그인 핸들러를 전달한다.
|
|
23
|
+
* - 필드 타입은 제네릭으로 확장 가능하다.
|
|
24
|
+
* @property {AuthLoginFields<TFields>} fields 로그인 입력 필드 설정
|
|
25
|
+
* @property {UseFormReturn<Record<string, string>>} form RHF useForm 반환 객체
|
|
26
|
+
* @property {SubmitHandler<Record<string, string>>} onLogin 제출 콜백
|
|
27
|
+
*/
|
|
28
|
+
export type UseAuthLoginFormOptions<
|
|
29
|
+
TFields extends AuthLoginFields = AuthLoginFields,
|
|
30
|
+
> = {
|
|
31
|
+
/**
|
|
32
|
+
* 로그인 필드 설정
|
|
33
|
+
* - 필수 id/password + 확장 필드를 포함한다.
|
|
34
|
+
*/
|
|
35
|
+
fields: TFields;
|
|
36
|
+
/**
|
|
37
|
+
* RHF useForm 반환 객체
|
|
38
|
+
* - register/onSubmit 등을 전달한다.
|
|
39
|
+
*/
|
|
40
|
+
form: UseFormReturn<AuthLoginFormValues>;
|
|
41
|
+
/**
|
|
42
|
+
* 로그인 제출 핸들러
|
|
43
|
+
* - form.handleSubmit으로 감싼 뒤 동작한다.
|
|
44
|
+
*/
|
|
45
|
+
onLogin: SubmitHandler<AuthLoginFormValues>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 로그인 훅 반환타입; useAuthLoginForm 결과
|
|
50
|
+
* @typedef {UseAuthLoginFormReturn}
|
|
51
|
+
* @desc
|
|
52
|
+
* - register/onSubmit/disabled/helpers 네 축만 노출한다.
|
|
53
|
+
* - 필드는 제네릭 기반으로 key를 유지한다.
|
|
54
|
+
* @property {Record<keyof TFields, UseFormRegisterReturn>} register 필드별 register 함수
|
|
55
|
+
* @property {ReturnType<UseFormReturn["handleSubmit"]>} onSubmit 로그인 제출 핸들러
|
|
56
|
+
* @property {boolean} disabled 버튼 disabled 여부
|
|
57
|
+
* @property {Record<keyof TFields, { text?: string; state?: string }>} helpers 필드별 helper 상태
|
|
58
|
+
*/
|
|
59
|
+
export type UseAuthLoginFormReturn<
|
|
60
|
+
TFields extends AuthLoginFields = AuthLoginFields,
|
|
61
|
+
> = {
|
|
62
|
+
/**
|
|
63
|
+
* 필드별 register 함수
|
|
64
|
+
* - id/password 및 확장 필드를 동일 구조로 제공한다.
|
|
65
|
+
*/
|
|
66
|
+
register: Record<keyof TFields, UseFormRegisterReturn>;
|
|
67
|
+
/**
|
|
68
|
+
* 로그인 제출 핸들러
|
|
69
|
+
* - form.handleSubmit 결과를 그대로 노출한다.
|
|
70
|
+
*/
|
|
71
|
+
onSubmit: ReturnType<UseFormReturn["handleSubmit"]>;
|
|
72
|
+
/**
|
|
73
|
+
* 버튼 disabled 여부
|
|
74
|
+
* - trim 여부, isSubmitting 등 내부 로직으로 계산한다.
|
|
75
|
+
*/
|
|
76
|
+
disabled: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* 필드별 helper 상태
|
|
79
|
+
* - text/state 조합으로 error/info 메시지를 표현한다.
|
|
80
|
+
*/
|
|
81
|
+
helpers: Record<keyof TFields, { text?: ReactNode; state?: InputState }>;
|
|
82
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SubmitHandler } from "react-hook-form";
|
|
3
|
+
import type { AuthContainerProps } from "../../container";
|
|
4
|
+
import type {
|
|
5
|
+
InputFieldConfig,
|
|
6
|
+
InputPasswordProps,
|
|
7
|
+
InputProps,
|
|
8
|
+
} from "@uniai-fe/uds-primitives";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 로그인 입력 필드 설정
|
|
12
|
+
* @typedef {AuthLoginFieldConfig}
|
|
13
|
+
* @template TProps
|
|
14
|
+
* @desc
|
|
15
|
+
* - InputFieldConfig 제네릭을 래핑해 로그인 요구사항과 연결한다.
|
|
16
|
+
* - props 제네릭으로 Input/PasswordInput 등 세부 props를 주입한다.
|
|
17
|
+
*/
|
|
18
|
+
export type AuthLoginFieldConfig<TProps extends InputProps = InputProps> =
|
|
19
|
+
InputFieldConfig<TProps>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 로그인 필드 묶음
|
|
23
|
+
* @template TAdditional
|
|
24
|
+
* @desc
|
|
25
|
+
* - 필수 id/password 필드와 확장 필드를 포함한다.
|
|
26
|
+
* @property {AuthLoginFieldConfig} id 아이디 필드 설정
|
|
27
|
+
* @property {AuthLoginFieldConfig} password 비밀번호 필드 설정
|
|
28
|
+
*/
|
|
29
|
+
export type AuthLoginFields<
|
|
30
|
+
TAdditional extends Record<string, AuthLoginFieldConfig> = Record<
|
|
31
|
+
string,
|
|
32
|
+
AuthLoginFieldConfig
|
|
33
|
+
>,
|
|
34
|
+
> = TAdditional & {
|
|
35
|
+
/**
|
|
36
|
+
* 아이디 필드 설정
|
|
37
|
+
*/
|
|
38
|
+
id: AuthLoginFieldConfig<InputProps>;
|
|
39
|
+
/**
|
|
40
|
+
* 비밀번호 필드 설정
|
|
41
|
+
*/
|
|
42
|
+
password: AuthLoginFieldConfig<InputPasswordProps>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 로그인 필드 옵션
|
|
47
|
+
* @template TFields
|
|
48
|
+
* @desc
|
|
49
|
+
* - fields, formAttr, onLogin을 묶어 전달한다.
|
|
50
|
+
* @property {TFields} fields 로그인 입력 필드 설정
|
|
51
|
+
* @property {React.FormHTMLAttributes<HTMLFormElement>} [formAttr] form 요소 attr
|
|
52
|
+
* @property {SubmitHandler<Record<string, string>>} onLogin 로그인 제출 핸들러
|
|
53
|
+
*/
|
|
54
|
+
export type AuthLoginFieldOptions<
|
|
55
|
+
TFields extends AuthLoginFields = AuthLoginFields,
|
|
56
|
+
> = {
|
|
57
|
+
/** 로그인 필드 설정 */
|
|
58
|
+
fields: TFields;
|
|
59
|
+
/** form 요소에 전달할 attr */
|
|
60
|
+
formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
|
|
61
|
+
/** 로그인 제출 핸들러 */
|
|
62
|
+
onLogin: SubmitHandler<Record<string, string>>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 로그인 화면 링크 옵션
|
|
67
|
+
* @typedef {Object} AuthLoginLinkOptions
|
|
68
|
+
* @desc
|
|
69
|
+
* - 아이디/비밀번호 찾기, 회원가입 링크를 전달한다.
|
|
70
|
+
* @property {Object} find 아이디/비밀번호 찾기 링크
|
|
71
|
+
* @property {string} find.id 아이디 찾기 링크
|
|
72
|
+
* @property {string} find.password 비밀번호 찾기 링크
|
|
73
|
+
* @property {string} signup 회원가입 링크
|
|
74
|
+
*/
|
|
75
|
+
export type AuthLoginLinkOptions = {
|
|
76
|
+
find: {
|
|
77
|
+
id: string;
|
|
78
|
+
password: string;
|
|
79
|
+
};
|
|
80
|
+
signup: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 로그인 템플릿 props
|
|
85
|
+
* @template TFields
|
|
86
|
+
* @desc
|
|
87
|
+
* - AuthContainer props와 fieldOptions/linkOptions를 조합한다.
|
|
88
|
+
* @property {AuthLoginFieldOptions<TFields>} fieldOptions 입력 필드/폼 옵션
|
|
89
|
+
* @property {AuthLoginLinkOptions} linkOptions 링크 옵션
|
|
90
|
+
*/
|
|
91
|
+
export type AuthLoginProps<TFields extends AuthLoginFields = AuthLoginFields> =
|
|
92
|
+
Omit<AuthContainerProps, "children"> & {
|
|
93
|
+
fieldOptions: AuthLoginFieldOptions<TFields>;
|
|
94
|
+
linkOptions: AuthLoginLinkOptions;
|
|
95
|
+
};
|