@uniai-fe/uds-templates 0.0.9 → 0.0.11
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 +77 -1
- package/dist/styles.css +212 -267
- package/package.json +6 -4
- package/src/components/auth/index.tsx +11 -0
- package/src/components/auth/login/index.tsx +1 -1
- package/src/components/auth/login/markup/FormField.tsx +2 -2
- package/src/components/auth/login/types/props.ts +12 -12
- package/src/components/auth/login/types.ts +2 -2
- package/src/components/auth/signup/hooks/index.ts +3 -0
- package/src/components/auth/signup/hooks/useSignupAccountForm.ts +77 -0
- package/src/components/auth/signup/hooks/useSignupUserInfoForm.ts +81 -0
- package/src/components/auth/signup/hooks/useSignupVerificationForm.ts +77 -0
- package/src/components/auth/signup/index.ts +24 -0
- package/src/components/auth/signup/markup/AccountForm.tsx +124 -0
- package/src/components/auth/signup/markup/Complete.tsx +61 -0
- package/src/components/auth/signup/markup/UserInfoForm.tsx +97 -0
- package/src/components/auth/signup/markup/VerificationForm.tsx +155 -0
- package/src/components/auth/signup/markup/index.ts +4 -0
- package/src/components/auth/signup/styles/signup.scss +135 -0
- package/src/components/auth/signup/types/hooks.ts +85 -0
- package/src/components/auth/signup/types/index.ts +2 -0
- package/src/components/auth/signup/types/props.ts +105 -0
- package/src/components/auth/signup/utils/composeFieldProps.ts +50 -0
- package/src/components/modal/core/components/Container.tsx +41 -0
- package/src/components/modal/core/components/FooterButtons.tsx +132 -0
- package/src/components/modal/core/components/Provider.tsx +28 -0
- package/src/components/modal/core/components/Root.tsx +93 -0
- package/src/components/modal/core/hooks/useModal.ts +136 -0
- package/src/components/modal/core/jotai/atoms.ts +10 -0
- package/src/components/modal/index.scss +4 -0
- package/src/components/modal/index.tsx +16 -0
- package/src/components/modal/styles/animations.scss +24 -0
- package/src/components/modal/styles/base.scss +45 -0
- package/src/components/modal/styles/container.scss +138 -0
- package/src/components/modal/styles/dimmer.scss +23 -0
- package/src/components/modal/templates/Alert.tsx +104 -0
- package/src/components/modal/templates/Dialog.tsx +112 -0
- package/src/components/modal/types/footer.ts +36 -0
- package/src/components/modal/types/index.ts +21 -0
- package/src/components/modal/types/options.ts +6 -0
- package/src/components/modal/types/state.ts +31 -0
- package/src/components/modal/types/templates.ts +32 -0
- package/src/index.scss +1 -0
- package/src/index.tsx +1 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useForm } from "react-hook-form";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
CheckboxField,
|
|
6
|
+
EmailVerificationInput,
|
|
7
|
+
type EmailVerificationInputProps,
|
|
8
|
+
} from "@uniai-fe/uds-primitives";
|
|
9
|
+
import type {
|
|
10
|
+
AuthSignupAgreementOption,
|
|
11
|
+
AuthSignupVerificationProps,
|
|
12
|
+
AuthSignupVerificationValues,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import { useSignupVerificationForm } from "../hooks";
|
|
15
|
+
import { composeSignupFieldProps } from "../utils/composeFieldProps";
|
|
16
|
+
|
|
17
|
+
const DEFAULT_SUBMIT_LABEL = "다음";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 회원가입 Step2; 약관 동의 + 이메일 인증
|
|
21
|
+
* @component
|
|
22
|
+
* @param {AuthSignupVerificationProps} props verification props
|
|
23
|
+
* @param {AuthSignupAgreementOption[]} props.agreements 약관 목록
|
|
24
|
+
* @param {Record<string, boolean>} props.agreementState 약관 체크 상태
|
|
25
|
+
* @param {(agreementId: string) => void} props.onToggleAgreement 단일 약관 토글
|
|
26
|
+
* @param {() => void} props.onToggleAll 전체 동의 토글
|
|
27
|
+
* @param {(agreementId: string) => void} [props.onOpenAgreementDetail] 약관 상세 보기
|
|
28
|
+
* @param {React.FormHTMLAttributes<HTMLFormElement>} [props.formAttr] form attr
|
|
29
|
+
* @param {import("react").ReactNode} [props.submitLabel] CTA 라벨
|
|
30
|
+
*/
|
|
31
|
+
export function AuthSignupVerificationForm({
|
|
32
|
+
fields,
|
|
33
|
+
agreements,
|
|
34
|
+
agreementState,
|
|
35
|
+
onToggleAgreement,
|
|
36
|
+
onToggleAll,
|
|
37
|
+
onOpenAgreementDetail,
|
|
38
|
+
formAttr,
|
|
39
|
+
submitLabel,
|
|
40
|
+
onSubmit,
|
|
41
|
+
}: AuthSignupVerificationProps) {
|
|
42
|
+
// 이메일 필드 name attr 기반으로 RHF defaultValues를 설정한다.
|
|
43
|
+
const defaultValues = useMemo(
|
|
44
|
+
() =>
|
|
45
|
+
(Object.keys(fields) as Array<keyof typeof fields>).reduce(
|
|
46
|
+
(acc, fieldKey) => {
|
|
47
|
+
const config = fields[fieldKey];
|
|
48
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
49
|
+
acc[fieldName] = "";
|
|
50
|
+
return acc;
|
|
51
|
+
},
|
|
52
|
+
{} as AuthSignupVerificationValues,
|
|
53
|
+
),
|
|
54
|
+
[fields],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const form = useForm<AuthSignupVerificationValues>({
|
|
58
|
+
mode: "onChange",
|
|
59
|
+
defaultValues,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const {
|
|
63
|
+
register,
|
|
64
|
+
helpers,
|
|
65
|
+
disabled,
|
|
66
|
+
onSubmit: handleSubmit,
|
|
67
|
+
} = useSignupVerificationForm({
|
|
68
|
+
fields,
|
|
69
|
+
form,
|
|
70
|
+
onSubmit,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 전체 동의 상태를 agreementState 집계로 계산한다.
|
|
74
|
+
const allChecked = agreements.every(
|
|
75
|
+
agreement => agreementState[agreement.id],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const renderAgreementLabel = (option: AuthSignupAgreementOption) => (
|
|
79
|
+
<span className="auth-signup-agreement-label">
|
|
80
|
+
<span className="auth-signup-agreement-title">{option.label}</span>
|
|
81
|
+
<span className="auth-signup-agreement-required">
|
|
82
|
+
{option.required ? "필수" : "선택"}
|
|
83
|
+
</span>
|
|
84
|
+
</span>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="auth-signup-verification">
|
|
89
|
+
<section className="auth-signup-agreements" aria-label="약관 동의">
|
|
90
|
+
<div className="auth-signup-agreement">
|
|
91
|
+
<CheckboxField
|
|
92
|
+
size="large"
|
|
93
|
+
label={
|
|
94
|
+
<span className="auth-signup-agreement-title">전체 동의</span>
|
|
95
|
+
}
|
|
96
|
+
helperText="필수 및 선택 약관 전체 동의"
|
|
97
|
+
checked={allChecked}
|
|
98
|
+
onCheckedChange={() => onToggleAll()}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
<div className="auth-signup-agreement-divider" aria-hidden />
|
|
102
|
+
{agreements.map(option => (
|
|
103
|
+
<div
|
|
104
|
+
className="auth-signup-agreement"
|
|
105
|
+
key={option.id}
|
|
106
|
+
data-required={option.required ? "true" : undefined}
|
|
107
|
+
>
|
|
108
|
+
<CheckboxField
|
|
109
|
+
size="large"
|
|
110
|
+
label={renderAgreementLabel(option)}
|
|
111
|
+
helperText={option.description}
|
|
112
|
+
checked={Boolean(agreementState[option.id])}
|
|
113
|
+
onCheckedChange={() => onToggleAgreement(option.id)}
|
|
114
|
+
/>
|
|
115
|
+
{onOpenAgreementDetail ? (
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
className="auth-signup-agreement-detail-button"
|
|
119
|
+
onClick={() => onOpenAgreementDetail(option.id)}
|
|
120
|
+
>
|
|
121
|
+
자세히 보기
|
|
122
|
+
</button>
|
|
123
|
+
) : null}
|
|
124
|
+
</div>
|
|
125
|
+
))}
|
|
126
|
+
</section>
|
|
127
|
+
<form
|
|
128
|
+
className="auth-signup-form auth-signup-form--verification"
|
|
129
|
+
{...formAttr}
|
|
130
|
+
onSubmit={handleSubmit}
|
|
131
|
+
>
|
|
132
|
+
<div className="auth-signup-fields">
|
|
133
|
+
<EmailVerificationInput
|
|
134
|
+
{...composeSignupFieldProps<EmailVerificationInputProps>(
|
|
135
|
+
fields.email,
|
|
136
|
+
helpers.email,
|
|
137
|
+
)}
|
|
138
|
+
register={register.email}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
<Button.Default
|
|
142
|
+
type="submit"
|
|
143
|
+
scale="solid-xlarge"
|
|
144
|
+
priority="primary"
|
|
145
|
+
block
|
|
146
|
+
disabled={disabled}
|
|
147
|
+
>
|
|
148
|
+
{submitLabel ?? DEFAULT_SUBMIT_LABEL}
|
|
149
|
+
</Button.Default>
|
|
150
|
+
</form>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default AuthSignupVerificationForm;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
.auth-signup-form {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--spacing-padding-8, 32px);
|
|
5
|
+
margin-top: var(--spacing-padding-9, 36px);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.auth-signup-fields {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: var(--spacing-padding-5, 20px);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.auth-signup-verification {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
gap: var(--spacing-padding-9, 36px);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.auth-signup-agreements {
|
|
21
|
+
padding: var(--spacing-padding-7, 28px);
|
|
22
|
+
border-radius: var(--shape-rounded-3, 16px);
|
|
23
|
+
background: var(--color-background-subtle);
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: var(--spacing-padding-4, 16px);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.auth-signup-agreement {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: flex-start;
|
|
32
|
+
justify-content: space-between;
|
|
33
|
+
gap: var(--spacing-padding-3, 12px);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.auth-signup-agreement-divider {
|
|
37
|
+
height: 1px;
|
|
38
|
+
width: 100%;
|
|
39
|
+
background: var(--color-border-default);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.auth-signup-agreement-label {
|
|
43
|
+
display: flex;
|
|
44
|
+
gap: var(--spacing-padding-2, 8px);
|
|
45
|
+
align-items: center;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.auth-signup-agreement-title {
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
color: var(--color-label-standard);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.auth-signup-agreement-required {
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
color: var(--color-label-assistive);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.auth-signup-agreement-detail-button {
|
|
59
|
+
border: none;
|
|
60
|
+
background: none;
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
color: var(--color-primary-60, #1d4ed8);
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
|
|
65
|
+
&:hover {
|
|
66
|
+
color: var(--color-primary-70, #1e40af);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.auth-signup-password-rules {
|
|
71
|
+
list-style: none;
|
|
72
|
+
padding: 0;
|
|
73
|
+
margin: 0;
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
gap: var(--spacing-padding-2, 8px);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.auth-signup-password-rule {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: var(--spacing-padding-3, 12px);
|
|
83
|
+
color: var(--color-label-assistive);
|
|
84
|
+
|
|
85
|
+
&[data-fulfilled="true"] {
|
|
86
|
+
color: var(--color-label-positive);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.auth-signup-password-rule-indicator {
|
|
91
|
+
width: 8px;
|
|
92
|
+
height: 8px;
|
|
93
|
+
border-radius: 50%;
|
|
94
|
+
background: var(--color-border-default);
|
|
95
|
+
flex-shrink: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.auth-signup-password-rule[data-fulfilled="true"]
|
|
99
|
+
.auth-signup-password-rule-indicator {
|
|
100
|
+
background: var(--color-label-positive);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.auth-signup-password-rule-text {
|
|
104
|
+
font-size: 14px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.auth-signup-complete {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: var(--spacing-padding-7, 28px);
|
|
111
|
+
text-align: center;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.auth-signup-complete-illustration {
|
|
115
|
+
display: flex;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.auth-signup-complete-title {
|
|
120
|
+
font-size: 24px;
|
|
121
|
+
font-weight: 700;
|
|
122
|
+
color: var(--color-label-standard);
|
|
123
|
+
margin: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.auth-signup-complete-description {
|
|
127
|
+
margin: var(--spacing-padding-2, 8px) 0 0;
|
|
128
|
+
color: var(--color-label-assistive);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.auth-signup-complete-actions {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
gap: var(--spacing-padding-4, 16px);
|
|
135
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { InputState } from "@uniai-fe/uds-primitives";
|
|
3
|
+
import type {
|
|
4
|
+
AuthSignupFieldProps,
|
|
5
|
+
AuthSignupUserInfoFields,
|
|
6
|
+
AuthSignupUserInfoValues,
|
|
7
|
+
AuthSignupVerificationFields,
|
|
8
|
+
AuthSignupVerificationValues,
|
|
9
|
+
AuthSignupAccountFields,
|
|
10
|
+
AuthSignupAccountValues,
|
|
11
|
+
} from "./props";
|
|
12
|
+
import type {
|
|
13
|
+
SubmitHandler,
|
|
14
|
+
UseFormRegisterReturn,
|
|
15
|
+
UseFormReturn,
|
|
16
|
+
} from "react-hook-form";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 공용 Helper 타입
|
|
20
|
+
*/
|
|
21
|
+
export type AuthSignupHelperMap<
|
|
22
|
+
TFields extends Record<string, AuthSignupFieldProps>,
|
|
23
|
+
> = Record<keyof TFields, { text?: ReactNode; state?: InputState }>;
|
|
24
|
+
|
|
25
|
+
/** Step1 옵션 */
|
|
26
|
+
export interface UseSignupUserInfoFormOptions<
|
|
27
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
28
|
+
AuthSignupUserInfoFields,
|
|
29
|
+
> {
|
|
30
|
+
fields: TFields;
|
|
31
|
+
form: UseFormReturn<AuthSignupUserInfoValues>;
|
|
32
|
+
onSubmit: SubmitHandler<AuthSignupUserInfoValues>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UseSignupUserInfoFormReturn<
|
|
36
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
37
|
+
AuthSignupUserInfoFields,
|
|
38
|
+
> {
|
|
39
|
+
register: Record<keyof TFields, UseFormRegisterReturn>;
|
|
40
|
+
helpers: AuthSignupHelperMap<TFields>;
|
|
41
|
+
disabled: boolean;
|
|
42
|
+
onSubmit: ReturnType<UseFormReturn<AuthSignupUserInfoValues>["handleSubmit"]>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Step2 옵션 */
|
|
46
|
+
export interface UseSignupVerificationFormOptions<
|
|
47
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
48
|
+
AuthSignupVerificationFields,
|
|
49
|
+
> {
|
|
50
|
+
fields: TFields;
|
|
51
|
+
form: UseFormReturn<AuthSignupVerificationValues>;
|
|
52
|
+
onSubmit: SubmitHandler<AuthSignupVerificationValues>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UseSignupVerificationFormReturn<
|
|
56
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
57
|
+
AuthSignupVerificationFields,
|
|
58
|
+
> {
|
|
59
|
+
register: Record<keyof TFields, UseFormRegisterReturn>;
|
|
60
|
+
helpers: AuthSignupHelperMap<TFields>;
|
|
61
|
+
disabled: boolean;
|
|
62
|
+
onSubmit: ReturnType<
|
|
63
|
+
UseFormReturn<AuthSignupVerificationValues>["handleSubmit"]
|
|
64
|
+
>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Step3 옵션 */
|
|
68
|
+
export interface UseSignupAccountFormOptions<
|
|
69
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
70
|
+
AuthSignupAccountFields,
|
|
71
|
+
> {
|
|
72
|
+
fields: TFields;
|
|
73
|
+
form: UseFormReturn<AuthSignupAccountValues>;
|
|
74
|
+
onSubmit: SubmitHandler<AuthSignupAccountValues>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface UseSignupAccountFormReturn<
|
|
78
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
79
|
+
AuthSignupAccountFields,
|
|
80
|
+
> {
|
|
81
|
+
register: Record<keyof TFields, UseFormRegisterReturn>;
|
|
82
|
+
helpers: AuthSignupHelperMap<TFields>;
|
|
83
|
+
disabled: boolean;
|
|
84
|
+
onSubmit: ReturnType<UseFormReturn<AuthSignupAccountValues>["handleSubmit"]>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import type { SubmitHandler } from "react-hook-form";
|
|
4
|
+
import type {
|
|
5
|
+
EmailVerificationInputProps,
|
|
6
|
+
InputFieldProps,
|
|
7
|
+
InputPasswordProps,
|
|
8
|
+
InputProps,
|
|
9
|
+
PhoneInputProps,
|
|
10
|
+
} from "@uniai-fe/uds-primitives";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 회원가입 공통 필드 props; primitives InputFieldProps를 그대로 사용한다.
|
|
14
|
+
*/
|
|
15
|
+
export type AuthSignupFieldProps<TProps extends InputProps = InputProps> =
|
|
16
|
+
InputFieldProps<TProps>;
|
|
17
|
+
|
|
18
|
+
/** 사용자 기본 정보(이름/휴대폰) */
|
|
19
|
+
export type AuthSignupUserInfoFields = {
|
|
20
|
+
name: AuthSignupFieldProps<InputProps>;
|
|
21
|
+
phone: AuthSignupFieldProps<PhoneInputProps>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AuthSignupUserInfoValues = Record<string, string>; // name/phone 값
|
|
25
|
+
|
|
26
|
+
export interface AuthSignupUserInfoProps {
|
|
27
|
+
fields: AuthSignupUserInfoFields;
|
|
28
|
+
submitLabel?: ReactNode;
|
|
29
|
+
formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
|
|
30
|
+
onSubmit: SubmitHandler<AuthSignupUserInfoValues>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 약관 + 이메일 인증 */
|
|
34
|
+
export interface AuthSignupAgreementOption {
|
|
35
|
+
id: string;
|
|
36
|
+
label: ReactNode;
|
|
37
|
+
description?: ReactNode;
|
|
38
|
+
required?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type AuthSignupAgreementState = Record<string, boolean>;
|
|
42
|
+
|
|
43
|
+
export type AuthSignupVerificationFields = {
|
|
44
|
+
email: AuthSignupFieldProps<EmailVerificationInputProps>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type AuthSignupVerificationValues = Record<string, string>;
|
|
48
|
+
|
|
49
|
+
export interface AuthSignupVerificationProps {
|
|
50
|
+
fields: AuthSignupVerificationFields;
|
|
51
|
+
agreements: AuthSignupAgreementOption[];
|
|
52
|
+
agreementState: AuthSignupAgreementState;
|
|
53
|
+
onToggleAgreement: (agreementId: string) => void;
|
|
54
|
+
onToggleAll: () => void;
|
|
55
|
+
onOpenAgreementDetail?: (agreementId: string) => void;
|
|
56
|
+
submitLabel?: ReactNode;
|
|
57
|
+
formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
|
|
58
|
+
onSubmit: SubmitHandler<AuthSignupVerificationValues>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 계정 정보(아이디/비밀번호) */
|
|
62
|
+
export type AuthSignupAccountFields = {
|
|
63
|
+
accountId: AuthSignupFieldProps<InputProps>;
|
|
64
|
+
password: AuthSignupFieldProps<InputPasswordProps>;
|
|
65
|
+
confirmPassword: AuthSignupFieldProps<InputPasswordProps>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type AuthSignupAccountValues = Record<string, string>;
|
|
69
|
+
|
|
70
|
+
export interface AuthSignupPasswordRule {
|
|
71
|
+
id: string;
|
|
72
|
+
label: ReactNode;
|
|
73
|
+
fulfilled: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AuthSignupAccountProps {
|
|
77
|
+
fields: AuthSignupAccountFields;
|
|
78
|
+
passwordRules?: AuthSignupPasswordRule[];
|
|
79
|
+
submitLabel?: ReactNode;
|
|
80
|
+
formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
|
|
81
|
+
onSubmit: SubmitHandler<AuthSignupAccountValues>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 완료 CTA */
|
|
85
|
+
export interface AuthSignupCompleteAction {
|
|
86
|
+
label: ReactNode;
|
|
87
|
+
onClick: () => void;
|
|
88
|
+
disabled?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface AuthSignupCompleteProps {
|
|
92
|
+
title: ReactNode;
|
|
93
|
+
description?: ReactNode;
|
|
94
|
+
illustration?: ReactNode;
|
|
95
|
+
primaryAction: AuthSignupCompleteAction;
|
|
96
|
+
secondaryAction?: AuthSignupCompleteAction;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 전체 플로우 옵션 */
|
|
100
|
+
export interface AuthSignupFlowProps {
|
|
101
|
+
userInfo: AuthSignupUserInfoProps;
|
|
102
|
+
verification: AuthSignupVerificationProps;
|
|
103
|
+
account: AuthSignupAccountProps;
|
|
104
|
+
complete: AuthSignupCompleteProps;
|
|
105
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { InputProps, InputState } from "@uniai-fe/uds-primitives";
|
|
3
|
+
import type { AuthSignupFieldProps } from "../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 필드 helper 상태; helper 텍스트 및 상태를 전달한다.
|
|
7
|
+
*/
|
|
8
|
+
export interface AuthSignupHelperState {
|
|
9
|
+
text?: ReactNode;
|
|
10
|
+
state?: InputState;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 입력 필드 구성 요소; attr/props를 병합해 label/helper/state를 일관되게 만든다.
|
|
15
|
+
* @param {AuthSignupFieldProps<TProps>} config 필드 설정
|
|
16
|
+
* @param {AuthSignupHelperState} [helper] 훅에서 계산한 helper 정보
|
|
17
|
+
*/
|
|
18
|
+
export const composeSignupFieldProps = <TProps extends InputProps>(
|
|
19
|
+
config: AuthSignupFieldProps<TProps>,
|
|
20
|
+
helper?: AuthSignupHelperState,
|
|
21
|
+
): TProps => {
|
|
22
|
+
// attr + props를 합쳐 RHF register/label/helper className을 동기화한다.
|
|
23
|
+
const baseProps = {
|
|
24
|
+
...(config.attr ?? {}),
|
|
25
|
+
...(config.props ?? ({} as TProps)),
|
|
26
|
+
} as TProps;
|
|
27
|
+
const mergedLabelProps = config.labelClassName
|
|
28
|
+
? {
|
|
29
|
+
...baseProps.labelProps,
|
|
30
|
+
className: config.labelClassName,
|
|
31
|
+
}
|
|
32
|
+
: baseProps.labelProps;
|
|
33
|
+
const mergedHelperProps = config.helperClassName
|
|
34
|
+
? {
|
|
35
|
+
...baseProps.helperProps,
|
|
36
|
+
className: config.helperClassName,
|
|
37
|
+
}
|
|
38
|
+
: baseProps.helperProps;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
...baseProps,
|
|
42
|
+
label: config.label ?? baseProps.label,
|
|
43
|
+
helper: helper?.text ?? config.helper ?? baseProps.helper,
|
|
44
|
+
state: helper?.state ?? baseProps.state,
|
|
45
|
+
block: config.block ?? baseProps.block ?? true,
|
|
46
|
+
className: config.className ?? baseProps.className,
|
|
47
|
+
labelProps: mergedLabelProps,
|
|
48
|
+
helperProps: mergedHelperProps,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ModalFooterButton,
|
|
5
|
+
ModalSections,
|
|
6
|
+
ModalStackKey,
|
|
7
|
+
} from "../../types";
|
|
8
|
+
import { ModalFooterButtons } from "./FooterButtons";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 모달 컨테이너; header/body/footer 슬롯만 담당한다.
|
|
12
|
+
* @component
|
|
13
|
+
* @param {ModalSections & {stackKey: ModalStackKey; footerButtons?: ModalFooterButton[]}} props
|
|
14
|
+
* @param {React.ReactNode} [props.header] 헤더 슬롯
|
|
15
|
+
* @param {React.ReactNode} props.body 본문 슬롯
|
|
16
|
+
* @param {React.ReactNode} [props.footer] footer 슬롯
|
|
17
|
+
* @param {ModalFooterButton[]} [props.footerButtons] 버튼 스펙 배열
|
|
18
|
+
*/
|
|
19
|
+
export function ModalContainer({
|
|
20
|
+
stackKey,
|
|
21
|
+
header,
|
|
22
|
+
body,
|
|
23
|
+
footer,
|
|
24
|
+
footerButtons,
|
|
25
|
+
}: ModalSections & {
|
|
26
|
+
stackKey: ModalStackKey;
|
|
27
|
+
footerButtons?: ModalFooterButton[];
|
|
28
|
+
}) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="uds-modal-container">
|
|
31
|
+
{header ? <div className="uds-modal-header">{header}</div> : null}
|
|
32
|
+
<div className="uds-modal-body">{body}</div>
|
|
33
|
+
{footer ? <div className="uds-modal-footer">{footer}</div> : null}
|
|
34
|
+
{!footer && footerButtons && footerButtons.length ? (
|
|
35
|
+
<div className="uds-modal-footer">
|
|
36
|
+
<ModalFooterButtons stackKey={stackKey} buttons={footerButtons} />
|
|
37
|
+
</div>
|
|
38
|
+
) : null}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|