@uniai-fe/uds-templates 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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.26.2",
15
+ "packageManager": "pnpm@10.27.0",
16
16
  "engines": {
17
17
  "node": ">=24",
18
18
  "pnpm": ">=10"
@@ -73,9 +73,9 @@
73
73
  "eslint": "^9.39.2",
74
74
  "next": "^15.5.9",
75
75
  "prettier": "^3.7.4",
76
- "react-hook-form": "^7.69.0",
76
+ "react-hook-form": "^7.70.0",
77
77
  "jotai": "^2.16.1",
78
- "sass": "^1.97.1",
78
+ "sass": "^1.97.2",
79
79
  "typescript": "~5.9.3"
80
80
  }
81
81
  }
@@ -28,9 +28,38 @@ import { getSignupFieldDefaultValue } from "../utils/getSignupFieldDefaultValue"
28
28
  import CheckAgreeIcon from "../img/check-agree.svg";
29
29
  import ChevronOpenDetailIcon from "../img/chevron-open-detail.svg";
30
30
 
31
- const DEFAULT_SUBMIT_LABEL = "다음";
31
+ const INITIAL_AGREEMENT_LABEL = "동의하고 진행하기";
32
32
  const REQUEST_CODE_LABEL = "인증코드 요청";
33
33
  const RESEND_CODE_LABEL = "인증번호 재요청";
34
+ const COMPLETE_LABEL = "완료";
35
+
36
+ const clampCodeLength = (length: number) =>
37
+ Math.max(4, Math.min(8, length ?? 6));
38
+
39
+ const resolveCodeLength = (preferred?: number, fallback?: number): number => {
40
+ if (typeof preferred === "number" && Number.isFinite(preferred)) {
41
+ return clampCodeLength(preferred);
42
+ }
43
+ if (typeof fallback === "number" && Number.isFinite(fallback)) {
44
+ return clampCodeLength(fallback);
45
+ }
46
+ return clampCodeLength(6);
47
+ };
48
+
49
+ const resolveCodeValue = (
50
+ inputProps?: EmailInputProps["codeInputProps"],
51
+ ): string => {
52
+ if (!inputProps) {
53
+ return "";
54
+ }
55
+ if (typeof inputProps.value === "string") {
56
+ return inputProps.value;
57
+ }
58
+ if (typeof inputProps.defaultValue === "string") {
59
+ return inputProps.defaultValue;
60
+ }
61
+ return "";
62
+ };
34
63
 
35
64
  /**
36
65
  * 회원가입 Step2; 약관 동의 + 이메일 인증
@@ -42,6 +71,8 @@ const RESEND_CODE_LABEL = "인증번호 재요청";
42
71
  * @param {(options?: AuthSignupAgreementToggleAllOptions) => void} props.onToggleAll 전체 동의(필수 only) 토글
43
72
  * @param {(agreementId: string) => void} [props.onOpenAgreementDetail] 약관 상세 보기
44
73
  * @param {React.FormHTMLAttributes<HTMLFormElement>} [props.formAttr] form attr
74
+ * @param {AuthSignupVerificationProps["verificationState"]} [props.verificationState] 수동 verify state
75
+ * @param {boolean} [props.verificationReady] CTA 활성화 여부 override
45
76
  * @param {import("react").ReactNode} [props.submitLabel] CTA 라벨
46
77
  */
47
78
  export function AuthSignupVerificationForm({
@@ -53,6 +84,9 @@ export function AuthSignupVerificationForm({
53
84
  onOpenAgreementDetail,
54
85
  formAttr,
55
86
  submitLabel,
87
+ submitDisabled,
88
+ verificationState,
89
+ verificationReady,
56
90
  onSubmit,
57
91
  }: AuthSignupVerificationProps) {
58
92
  const [openedAgreementId, setOpenedAgreementId] = useState<string | null>(
@@ -139,12 +173,66 @@ export function AuthSignupVerificationForm({
139
173
  requestButtonDisabled: undefined,
140
174
  };
141
175
 
142
- const shouldRequestCode =
143
- allRequiredChecked && !codeRequested && Boolean(emailRequestHandler);
144
- const ctaLabel = shouldRequestCode
145
- ? REQUEST_CODE_LABEL
146
- : (submitLabel ?? DEFAULT_SUBMIT_LABEL);
147
- const ctaDisabled = disabled || !allRequiredChecked;
176
+ const codeVerificationMeta = useMemo(() => {
177
+ const value = resolveCodeValue(normalizedEmailFieldProps.codeInputProps);
178
+ const length = resolveCodeLength(
179
+ normalizedEmailFieldProps.codeLength,
180
+ normalizedEmailFieldProps.codeInputProps?.length,
181
+ );
182
+ const ready =
183
+ Boolean(normalizedEmailFieldProps.codeVisible) && value.length === length;
184
+
185
+ return {
186
+ ready,
187
+ };
188
+ }, [
189
+ normalizedEmailFieldProps.codeInputProps,
190
+ normalizedEmailFieldProps.codeLength,
191
+ normalizedEmailFieldProps.codeVisible,
192
+ ]);
193
+
194
+ const isRequestStage = Boolean(emailRequestHandler) && !codeRequested;
195
+ const hasVerificationFlow = isRequestStage || codeRequested;
196
+ const requestReady = allRequiredChecked && !disabled;
197
+ const derivedVerificationReady = hasVerificationFlow
198
+ ? codeRequested
199
+ ? codeVerificationMeta.ready
200
+ : requestReady
201
+ : !disabled;
202
+ const resolvedVerificationReady =
203
+ typeof verificationReady === "boolean"
204
+ ? verificationReady
205
+ : derivedVerificationReady;
206
+ const resolvedVerificationState =
207
+ verificationState ??
208
+ (() => {
209
+ if (!hasVerificationFlow) {
210
+ return "idle";
211
+ }
212
+ if (!codeRequested) {
213
+ if (!allRequiredChecked) {
214
+ return "agreements-pending";
215
+ }
216
+ return resolvedVerificationReady ? "request-ready" : "idle";
217
+ }
218
+ return resolvedVerificationReady ? "code-ready" : "code-pending";
219
+ })();
220
+
221
+ const derivedCtaLabel = (() => {
222
+ if (!hasVerificationFlow) {
223
+ return INITIAL_AGREEMENT_LABEL;
224
+ }
225
+ if (codeRequested) {
226
+ return COMPLETE_LABEL;
227
+ }
228
+ return resolvedVerificationReady
229
+ ? REQUEST_CODE_LABEL
230
+ : INITIAL_AGREEMENT_LABEL;
231
+ })();
232
+
233
+ const ctaLabel = submitLabel ?? derivedCtaLabel;
234
+ const ctaDisabled =
235
+ disabled || Boolean(submitDisabled) || !resolvedVerificationReady;
148
236
 
149
237
  const handleFormSubmit = (event: FormEvent<HTMLFormElement>) => {
150
238
  formAttr?.onSubmit?.(event);
@@ -152,10 +240,18 @@ export function AuthSignupVerificationForm({
152
240
  return;
153
241
  }
154
242
 
155
- if (shouldRequestCode && emailRequestHandler) {
243
+ if (isRequestStage && emailRequestHandler) {
244
+ event.preventDefault();
245
+ event.stopPropagation();
246
+ if (resolvedVerificationReady) {
247
+ emailRequestHandler();
248
+ }
249
+ return;
250
+ }
251
+
252
+ if (codeRequested && !resolvedVerificationReady) {
156
253
  event.preventDefault();
157
254
  event.stopPropagation();
158
- emailRequestHandler();
159
255
  return;
160
256
  }
161
257
 
@@ -178,6 +274,8 @@ export function AuthSignupVerificationForm({
178
274
  <form
179
275
  className="auth-signup-form auth-signup-form--verification"
180
276
  {...formAttr}
277
+ data-verify-state={resolvedVerificationState}
278
+ data-verify-ready={resolvedVerificationReady ? "true" : "false"}
181
279
  onSubmit={handleFormSubmit}
182
280
  >
183
281
  <div className="auth-signup-fields">
@@ -51,6 +51,13 @@ export type AuthSignupVerificationFields = {
51
51
 
52
52
  export type AuthSignupVerificationValues = Record<string, string>;
53
53
 
54
+ export type AuthSignupVerificationState =
55
+ | "idle"
56
+ | "agreements-pending"
57
+ | "request-ready"
58
+ | "code-pending"
59
+ | "code-ready";
60
+
54
61
  export interface AuthSignupVerificationProps {
55
62
  fields: AuthSignupVerificationFields;
56
63
  agreements: AuthSignupAgreementOption[];
@@ -59,6 +66,9 @@ export interface AuthSignupVerificationProps {
59
66
  onToggleAll: (options?: AuthSignupAgreementToggleAllOptions) => void;
60
67
  onOpenAgreementDetail?: (agreementId: string) => void;
61
68
  submitLabel?: ReactNode;
69
+ submitDisabled?: boolean;
70
+ verificationState?: AuthSignupVerificationState;
71
+ verificationReady?: boolean;
62
72
  formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
63
73
  onSubmit: SubmitHandler<AuthSignupVerificationValues>;
64
74
  }