@uniai-fe/uds-templates 0.0.11 → 0.0.13

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.
Files changed (122) hide show
  1. package/README.md +11 -0
  2. package/dist/styles.css +916 -1074
  3. package/package.json +3 -3
  4. package/src/auth/common/complete/Template.tsx +47 -0
  5. package/src/auth/common/complete/img/circle-check-complete.svg +4 -0
  6. package/src/auth/common/complete/index.scss +38 -0
  7. package/src/auth/common/complete/types.ts +15 -0
  8. package/src/auth/common/container/header/StageHeader.tsx +61 -0
  9. package/src/auth/common/container/header/index.tsx +5 -0
  10. package/src/auth/common/container/header/stage-header.scss +50 -0
  11. package/src/{components/auth → auth/common}/container/index.tsx +2 -0
  12. package/src/auth/common/find/hooks/useFindAccountForm.ts +79 -0
  13. package/src/auth/common/find/markup/CodeStep.tsx +166 -0
  14. package/src/auth/common/find/markup/Header.tsx +46 -0
  15. package/src/auth/common/find/markup/InfoStep.tsx +109 -0
  16. package/src/auth/common/find/styles/email.scss +55 -0
  17. package/src/auth/common/find/styles/find-account.scss +4 -0
  18. package/src/auth/common/find/styles/layout.scss +19 -0
  19. package/src/auth/common/find/styles/password.scss +39 -0
  20. package/src/auth/common/find/styles/result.scss +78 -0
  21. package/src/auth/common/find/types/forms.ts +30 -0
  22. package/src/auth/common/find/types/index.ts +121 -0
  23. package/src/auth/common/find/utils/composeFieldProps.ts +45 -0
  24. package/src/auth/common/password/constants.ts +19 -0
  25. package/src/auth/common/password/hooks/useCheckPassword.ts +133 -0
  26. package/src/auth/common/password/img/check-password.svg +3 -0
  27. package/src/auth/common/password/markup/PasswordSetField.tsx +250 -0
  28. package/src/auth/common/password/styles/password-set-field.scss +49 -0
  29. package/src/auth/common/password/types.ts +142 -0
  30. package/src/auth/common/password/utils/composePasswordFieldProps.ts +44 -0
  31. package/src/auth/find-account.ts +28 -0
  32. package/src/auth/find-id/hooks/index.ts +1 -0
  33. package/src/auth/find-id/index.scss +1 -0
  34. package/src/auth/find-id/index.ts +23 -0
  35. package/src/auth/find-id/markup/StepComplete.tsx +58 -0
  36. package/src/auth/find-id/markup/StepIdentify.tsx +46 -0
  37. package/src/auth/find-id/markup/StepVerifyCode.tsx +48 -0
  38. package/src/auth/find-id/types/index.ts +66 -0
  39. package/src/auth/find-password/index.scss +1 -0
  40. package/src/auth/find-password/index.ts +30 -0
  41. package/src/auth/find-password/markup/StepComplete.tsx +30 -0
  42. package/src/auth/find-password/markup/StepIdentify.tsx +45 -0
  43. package/src/auth/find-password/markup/StepResetPassword.tsx +150 -0
  44. package/src/auth/find-password/markup/StepVerifyCode.tsx +48 -0
  45. package/src/auth/index.tsx +41 -0
  46. package/src/{components/auth → auth}/login/markup/Container.tsx +1 -1
  47. package/src/{components/auth → auth}/login/types/props.ts +1 -1
  48. package/src/{components/auth → auth}/signup/hooks/useSignupAccountForm.ts +26 -2
  49. package/src/{components/auth → auth}/signup/hooks/useSignupUserInfoForm.ts +10 -3
  50. package/src/auth/signup/img/check-agree.svg +3 -0
  51. package/src/auth/signup/img/chevron-open-detail.svg +3 -0
  52. package/src/{components/auth → auth}/signup/index.ts +3 -0
  53. package/src/auth/signup/markup/AccountForm.tsx +113 -0
  54. package/src/auth/signup/markup/Complete.tsx +59 -0
  55. package/src/auth/signup/markup/Template.tsx +110 -0
  56. package/src/{components/auth → auth}/signup/markup/UserInfoForm.tsx +23 -13
  57. package/src/auth/signup/markup/VerificationForm.tsx +285 -0
  58. package/src/{components/auth → auth}/signup/markup/index.ts +1 -0
  59. package/src/auth/signup/styles/signup.scss +187 -0
  60. package/src/{components/auth → auth}/signup/types/hooks.ts +1 -0
  61. package/src/{components/auth → auth}/signup/types/props.ts +49 -9
  62. package/src/auth/signup/utils/getSignupFieldDefaultValue.ts +40 -0
  63. package/src/index.scss +5 -4
  64. package/src/index.tsx +3 -3
  65. package/src/page-frame/mobile/header/PageFrameMobileHeader.tsx +52 -0
  66. package/src/page-frame/mobile/header/index.ts +4 -0
  67. package/src/page-frame/mobile/header/page-frame-mobile-header.scss +48 -0
  68. package/src/page-frame/mobile/img/chevron-backward.svg +3 -0
  69. package/src/components/auth/index.tsx +0 -20
  70. package/src/components/auth/signup/markup/AccountForm.tsx +0 -124
  71. package/src/components/auth/signup/markup/Complete.tsx +0 -61
  72. package/src/components/auth/signup/markup/VerificationForm.tsx +0 -155
  73. package/src/components/auth/signup/styles/signup.scss +0 -135
  74. /package/src/{components/auth → auth/common}/container/AuthContainer.tsx +0 -0
  75. /package/src/{components/auth → auth/common}/container/index.scss +0 -0
  76. /package/src/{components/auth → auth/common}/container/types.ts +0 -0
  77. /package/src/{components/auth → auth}/login/data/valid-options.ts +0 -0
  78. /package/src/{components/auth → auth}/login/hooks/index.ts +0 -0
  79. /package/src/{components/auth → auth}/login/hooks/useAuthLoginForm.ts +0 -0
  80. /package/src/{components/auth → auth}/login/index.scss +0 -0
  81. /package/src/{components/auth → auth}/login/index.tsx +0 -0
  82. /package/src/{components/auth → auth}/login/markup/FormField.tsx +0 -0
  83. /package/src/{components/auth → auth}/login/markup/LinkButtons.tsx +0 -0
  84. /package/src/{components/auth → auth}/login/styles/login.scss +0 -0
  85. /package/src/{components/auth → auth}/login/types/form.ts +0 -0
  86. /package/src/{components/auth → auth}/login/types/hooks.ts +0 -0
  87. /package/src/{components/auth → auth}/login/types.ts +0 -0
  88. /package/src/{components/auth → auth}/signup/hooks/index.ts +0 -0
  89. /package/src/{components/auth → auth}/signup/hooks/useSignupVerificationForm.ts +0 -0
  90. /package/src/{components/auth → auth}/signup/types/index.ts +0 -0
  91. /package/src/{components/auth → auth}/signup/utils/composeFieldProps.ts +0 -0
  92. /package/src/{components/modal → modal}/core/components/Container.tsx +0 -0
  93. /package/src/{components/modal → modal}/core/components/FooterButtons.tsx +0 -0
  94. /package/src/{components/modal → modal}/core/components/Provider.tsx +0 -0
  95. /package/src/{components/modal → modal}/core/components/Root.tsx +0 -0
  96. /package/src/{components/modal → modal}/core/hooks/useModal.ts +0 -0
  97. /package/src/{components/modal → modal}/core/jotai/atoms.ts +0 -0
  98. /package/src/{components/modal → modal}/index.scss +0 -0
  99. /package/src/{components/modal → modal}/index.tsx +0 -0
  100. /package/src/{components/modal → modal}/styles/animations.scss +0 -0
  101. /package/src/{components/modal → modal}/styles/base.scss +0 -0
  102. /package/src/{components/modal → modal}/styles/container.scss +0 -0
  103. /package/src/{components/modal → modal}/styles/dimmer.scss +0 -0
  104. /package/src/{components/modal → modal}/templates/Alert.tsx +0 -0
  105. /package/src/{components/modal → modal}/templates/Dialog.tsx +0 -0
  106. /package/src/{components/modal → modal}/types/footer.ts +0 -0
  107. /package/src/{components/modal → modal}/types/index.ts +0 -0
  108. /package/src/{components/modal → modal}/types/options.ts +0 -0
  109. /package/src/{components/modal → modal}/types/state.ts +0 -0
  110. /package/src/{components/modal → modal}/types/templates.ts +0 -0
  111. /package/src/{components/page-frame → page-frame}/container/PageFrameContainer.tsx +0 -0
  112. /package/src/{components/page-frame → page-frame}/container/index.scss +0 -0
  113. /package/src/{components/page-frame → page-frame}/container/index.tsx +0 -0
  114. /package/src/{components/page-frame → page-frame}/container/types.ts +0 -0
  115. /package/src/{components/page-frame → page-frame}/index.tsx +0 -0
  116. /package/src/{components/page-frame → page-frame}/mobile/PageFrameMobile.tsx +0 -0
  117. /package/src/{components/page-frame → page-frame}/mobile/index.scss +0 -0
  118. /package/src/{components/page-frame → page-frame}/mobile/index.tsx +0 -0
  119. /package/src/{components/page-frame → page-frame}/mobile/types.ts +0 -0
  120. /package/src/{components/page-frame → page-frame}/navigation/PageFrameNavigation.tsx +0 -0
  121. /package/src/{components/page-frame → page-frame}/navigation/index.scss +0 -0
  122. /package/src/{components/page-frame → page-frame}/navigation/index.tsx +0 -0
@@ -0,0 +1,55 @@
1
+ .auth-find-account-email-block {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--spacing-padding-3, 12px);
5
+ margin-bottom: var(--spacing-padding-7, 28px);
6
+ }
7
+
8
+ .auth-find-account-email-label {
9
+ color: var(--color-label-standard, var(--primitive-coolgray-20, #3d3f43));
10
+ font-size: var(--font-label-small-size, 13px);
11
+ line-height: 1.5;
12
+ margin: 0;
13
+ }
14
+
15
+ .auth-find-account-email-field {
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: space-between;
19
+ border-bottom: 1px solid
20
+ var(--color-border-standard, var(--primitive-coolgray-90, #e4e5e7));
21
+ min-height: var(--spacing-padding-14, 56px);
22
+ padding-bottom: var(--spacing-padding-2, 8px);
23
+ }
24
+
25
+ .auth-find-account-email-value {
26
+ font-size: var(--font-body-large-size, 19px);
27
+ color: var(--color-label-strong, var(--primitive-coolgray-10, #18191b));
28
+ line-height: 1.5;
29
+ word-break: break-word;
30
+ }
31
+
32
+ .auth-find-account-email-helper {
33
+ font-size: var(--font-caption-medium-size, 12px);
34
+ color: var(--color-label-alternative, var(--primitive-coolgray-50, #afb1b6));
35
+ margin: 0;
36
+ }
37
+
38
+ .auth-find-account-meta {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ gap: var(--spacing-padding-4, 16px);
43
+ }
44
+
45
+ .auth-find-account-timer-text {
46
+ font-size: var(--font-caption-medium-size, 12px);
47
+ color: var(--color-primary-50, #2563eb);
48
+ }
49
+
50
+ .auth-find-account-timer-helper,
51
+ .auth-find-account-helper {
52
+ font-size: var(--font-caption-medium-size, 12px);
53
+ color: var(--color-label-neutral, var(--primitive-coolgray-40, #797e86));
54
+ margin: 0;
55
+ }
@@ -0,0 +1,4 @@
1
+ @use "./layout.scss";
2
+ @use "./email.scss";
3
+ @use "./result.scss";
4
+ @use "./password.scss";
@@ -0,0 +1,19 @@
1
+ .auth-find-account-container {
2
+ width: 100%;
3
+ }
4
+
5
+ .auth-find-account-form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: var(--spacing-padding-6, 24px);
9
+ }
10
+
11
+ .auth-find-account-fields {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: var(--spacing-padding-5, 20px);
15
+ }
16
+
17
+ .auth-find-account-form--code .auth-find-account-fields {
18
+ margin-bottom: var(--spacing-padding-4, 16px);
19
+ }
@@ -0,0 +1,39 @@
1
+ .auth-find-account-password-rules {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--spacing-padding-2, 8px);
5
+ padding: 0;
6
+ margin: 0;
7
+ list-style: none;
8
+ }
9
+
10
+ .auth-find-account-password-rule {
11
+ display: flex;
12
+ align-items: center;
13
+ gap: var(--spacing-padding-3, 12px);
14
+ font-size: var(--font-body-medium-size, 15px);
15
+ color: var(--color-label-assistive);
16
+ }
17
+
18
+ .auth-find-account-password-rule::before {
19
+ content: "";
20
+ width: 8px;
21
+ height: 8px;
22
+ border-radius: 50%;
23
+ background: var(--color-border-standard);
24
+ flex-shrink: 0;
25
+ }
26
+
27
+ .auth-find-account-password-rule[data-satisfied="true"] {
28
+ color: var(--color-label-positive);
29
+ }
30
+
31
+ .auth-find-account-password-rule[data-satisfied="true"]::before {
32
+ background: var(--color-label-positive);
33
+ }
34
+
35
+ .auth-find-account-password-helper {
36
+ font-size: var(--font-caption-medium-size, 12px);
37
+ color: var(--color-label-neutral);
38
+ margin: 0;
39
+ }
@@ -0,0 +1,78 @@
1
+ .auth-find-account-result-card {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--spacing-padding-2, 8px);
5
+ padding: var(--spacing-padding-7, 28px);
6
+ border-radius: var(--shape-rounded-3, 16px);
7
+ background: var(--color-background-subtle);
8
+ margin-bottom: var(--spacing-padding-6, 24px);
9
+ }
10
+
11
+ .auth-find-account-result-label {
12
+ font-size: var(--font-caption-medium-size, 12px);
13
+ color: var(--color-label-assistive);
14
+ margin: 0;
15
+ }
16
+
17
+ .auth-find-account-result-value {
18
+ font-size: var(--font-heading-medium-size, 21px);
19
+ font-weight: 700;
20
+ color: var(--color-label-strong);
21
+ margin: 0;
22
+ word-break: break-word;
23
+ }
24
+
25
+ .auth-find-account-result-sub {
26
+ font-size: var(--font-body-medium-size, 15px);
27
+ color: var(--color-label-standard);
28
+ margin: 0;
29
+ }
30
+
31
+ .auth-find-account-result-helper {
32
+ font-size: var(--font-caption-medium-size, 12px);
33
+ color: var(--color-label-assistive);
34
+ margin: 0;
35
+ }
36
+
37
+ .auth-find-account-id-summary {
38
+ display: flex;
39
+ flex-direction: column;
40
+ align-items: center;
41
+ gap: var(--spacing-padding-4, 16px);
42
+ width: 100%;
43
+ }
44
+
45
+ .auth-find-account-id-summary-card {
46
+ width: fit-content;
47
+ min-width: 230px;
48
+ padding: 0 var(--spacing-padding-6, 16px);
49
+ border-radius: var(--theme-radius-medium-3, 8px);
50
+ background: var(
51
+ --color-background-alternative-cool_gray,
52
+ var(--colot-cool-gray-95, #f2f2f3)
53
+ );
54
+ }
55
+
56
+ .auth-find-account-id-summary-row {
57
+ display: flex;
58
+ justify-content: space-between;
59
+ align-items: center;
60
+ gap: var(--spacing-padding-5, 20px);
61
+ margin: var(--spacing-gap-5, 12px) 0;
62
+
63
+ .auth-find-account-id-summary-text {
64
+ font-size: 0;
65
+ span {
66
+ font-size: var(--font-caption-large-size);
67
+ line-height: var(--font-caption-large-line-height);
68
+ }
69
+ }
70
+
71
+ dt {
72
+ color: var(--color-label-neutral);
73
+ }
74
+
75
+ dd {
76
+ color: var(--color-label-strong);
77
+ }
78
+ }
@@ -0,0 +1,30 @@
1
+ import type React from "react";
2
+ import type {
3
+ FieldValues,
4
+ SubmitHandler,
5
+ UseFormReturn,
6
+ } from "react-hook-form";
7
+ import type { InputFieldProps } from "@uniai-fe/uds-primitives";
8
+ import type { FindAccountFieldHelper } from "./index";
9
+
10
+ export type UseFindAccountFormOptions<
11
+ TFields extends Record<string, InputFieldProps>,
12
+ TValues extends FieldValues,
13
+ > = {
14
+ fields: TFields;
15
+ form: UseFormReturn<TValues>;
16
+ onSubmit: SubmitHandler<TValues>;
17
+ isSubmittable?: (values?: TValues) => boolean;
18
+ };
19
+
20
+ export type UseFindAccountFormReturn<
21
+ TFields extends Record<string, InputFieldProps>,
22
+ TValues extends FieldValues,
23
+ > = {
24
+ register: {
25
+ [K in keyof TFields]: ReturnType<UseFormReturn<TValues>["register"]>;
26
+ };
27
+ helpers: Record<keyof TFields, FindAccountFieldHelper>;
28
+ disabled: boolean;
29
+ onSubmit: React.FormEventHandler<HTMLFormElement>;
30
+ };
@@ -0,0 +1,121 @@
1
+ import type React from "react";
2
+ import type { SubmitHandler } from "react-hook-form";
3
+ import type { AuthContainerProps } from "../../container";
4
+ import type {
5
+ AuthCodeInputProps,
6
+ ButtonProps,
7
+ InputFieldProps,
8
+ InputProps,
9
+ } from "@uniai-fe/uds-primitives";
10
+
11
+ export type FindAccountNavigationProps = {
12
+ title?: React.ReactNode;
13
+ backLabel?: React.ReactNode;
14
+ backIcon?: React.ReactNode;
15
+ onBack?: () => void;
16
+ };
17
+
18
+ export type FindAccountProgressProps = {
19
+ total: number;
20
+ current: number;
21
+ };
22
+
23
+ export type FindAccountHeadlineProps = {
24
+ title?: React.ReactNode;
25
+ description?: React.ReactNode;
26
+ progress?: FindAccountProgressProps;
27
+ };
28
+
29
+ export type FindAccountCTAProps = {
30
+ label: React.ReactNode;
31
+ buttonProps?: Omit<ButtonProps, "children">;
32
+ };
33
+
34
+ export type FindAccountFieldHelper = {
35
+ text?: React.ReactNode;
36
+ state?: "error" | "success" | "warning";
37
+ };
38
+
39
+ export type FindAccountInfoValues = Record<string, string>;
40
+
41
+ export type FindAccountInfoFields = {
42
+ name: InputFieldProps<InputProps>;
43
+ email: InputFieldProps<InputProps>;
44
+ };
45
+
46
+ export type FindAccountInfoFieldOptions<
47
+ TFields extends FindAccountInfoFields = FindAccountInfoFields,
48
+ > = {
49
+ fields: TFields;
50
+ formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
51
+ onSubmit: SubmitHandler<FindAccountInfoValues>;
52
+ isSubmittable?: (values?: FindAccountInfoValues) => boolean;
53
+ };
54
+
55
+ export type FindAccountInfoStepProps<
56
+ TFields extends FindAccountInfoFields = FindAccountInfoFields,
57
+ > = Omit<AuthContainerProps, "children"> & {
58
+ fieldOptions: FindAccountInfoFieldOptions<TFields>;
59
+ cta?: FindAccountCTAProps;
60
+ navigation?: FindAccountNavigationProps;
61
+ headline?: FindAccountHeadlineProps;
62
+ };
63
+
64
+ export type FindAccountInfoScreenProps<
65
+ TFields extends FindAccountInfoFields = FindAccountInfoFields,
66
+ > = FindAccountInfoStepProps<TFields>;
67
+
68
+ export type FindAccountCodeValues = Record<string, string>;
69
+
70
+ export type FindAccountCodeField = {
71
+ attr?: {
72
+ name?: string;
73
+ };
74
+ label?: React.ReactNode;
75
+ helper?: React.ReactNode;
76
+ length?: number;
77
+ props?: Omit<AuthCodeInputProps, "label" | "helper" | "length">;
78
+ };
79
+
80
+ export type FindAccountCodeFields = {
81
+ code: FindAccountCodeField;
82
+ };
83
+
84
+ export type FindAccountCodeFieldOptions<
85
+ TFields extends FindAccountCodeFields = FindAccountCodeFields,
86
+ > = {
87
+ fields: TFields;
88
+ formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
89
+ onSubmit: SubmitHandler<FindAccountCodeValues>;
90
+ isSubmittable?: (values?: FindAccountCodeValues) => boolean;
91
+ };
92
+
93
+ export type FindAccountEmailDisplayProps = {
94
+ label?: React.ReactNode;
95
+ value: React.ReactNode;
96
+ helper?: React.ReactNode;
97
+ resend?: FindAccountCTAProps;
98
+ };
99
+
100
+ export type FindAccountTimerProps = {
101
+ text: React.ReactNode;
102
+ helper?: React.ReactNode;
103
+ extend?: FindAccountCTAProps;
104
+ };
105
+
106
+ export type FindAccountCodeStepProps<
107
+ TFields extends FindAccountCodeFields = FindAccountCodeFields,
108
+ > = Omit<AuthContainerProps, "children"> & {
109
+ emailDisplay: FindAccountEmailDisplayProps;
110
+ timer?: FindAccountTimerProps;
111
+ fieldOptions: FindAccountCodeFieldOptions<TFields>;
112
+ cta?: FindAccountCTAProps;
113
+ navigation?: FindAccountNavigationProps;
114
+ headline?: FindAccountHeadlineProps;
115
+ };
116
+
117
+ export type FindAccountCodeScreenProps<
118
+ TFields extends FindAccountCodeFields = FindAccountCodeFields,
119
+ > = FindAccountCodeStepProps<TFields>;
120
+
121
+ export type * from "./forms";
@@ -0,0 +1,45 @@
1
+ import type { InputFieldProps, InputProps } from "@uniai-fe/uds-primitives";
2
+ import type { FindAccountFieldHelper } from "../types";
3
+
4
+ export const composeFindAccountFieldProps = <
5
+ TProps extends InputProps = InputProps,
6
+ >(
7
+ config: InputFieldProps<TProps>,
8
+ helper?: FindAccountFieldHelper,
9
+ ): TProps => {
10
+ const baseProps = {
11
+ ...(config.attr ?? {}),
12
+ ...(config.props ?? ({} as TProps)),
13
+ } as TProps;
14
+
15
+ const mergedLabelProps = config.labelClassName
16
+ ? {
17
+ ...baseProps.labelProps,
18
+ className: config.labelClassName,
19
+ }
20
+ : baseProps.labelProps;
21
+
22
+ const mergedHelperProps = config.helperClassName
23
+ ? {
24
+ ...baseProps.helperProps,
25
+ className: config.helperClassName,
26
+ }
27
+ : baseProps.helperProps;
28
+
29
+ const resolvedPriority =
30
+ baseProps.priority ?? ("secondary" as TProps["priority"]);
31
+ const resolvedSize = baseProps.size ?? ("large" as TProps["size"]);
32
+
33
+ return {
34
+ ...baseProps,
35
+ label: config.label ?? baseProps.label,
36
+ helper: helper?.text ?? config.helper ?? baseProps.helper,
37
+ state: helper?.state ?? baseProps.state,
38
+ priority: resolvedPriority,
39
+ size: resolvedSize,
40
+ block: config.block ?? baseProps.block ?? true,
41
+ className: config.className ?? baseProps.className,
42
+ labelProps: mergedLabelProps,
43
+ helperProps: mergedHelperProps,
44
+ };
45
+ };
@@ -0,0 +1,19 @@
1
+ import type { AuthPasswordRule } from "./types";
2
+
3
+ export const DEFAULT_PASSWORD_RULES: AuthPasswordRule[] = [
4
+ {
5
+ id: "letters",
6
+ label: "영문",
7
+ predicate: (value: string) => /[A-Za-z]/.test(value),
8
+ },
9
+ {
10
+ id: "numbers",
11
+ label: "숫자",
12
+ predicate: (value: string) => /[0-9]/.test(value),
13
+ },
14
+ {
15
+ id: "length",
16
+ label: "8자 이상",
17
+ predicate: (value: string) => value.length >= 8,
18
+ },
19
+ ];
@@ -0,0 +1,133 @@
1
+ import { useCallback, useEffect, useMemo } from "react";
2
+ import type { ReactNode } from "react";
3
+ import { useWatch } from "react-hook-form";
4
+ import type { FieldPath, FieldValues, UseFormReturn } from "react-hook-form";
5
+ import type { AuthPasswordHelperState } from "../types";
6
+
7
+ interface UseCheckPasswordMessages {
8
+ missing?: string;
9
+ mismatch?: string;
10
+ }
11
+
12
+ export interface UseCheckPasswordOptions<TValues extends FieldValues> {
13
+ form: UseFormReturn<TValues>;
14
+ passwordField: FieldPath<TValues>;
15
+ confirmField: FieldPath<TValues>;
16
+ helperText?: ReactNode;
17
+ messages?: UseCheckPasswordMessages;
18
+ }
19
+
20
+ export interface UseCheckPasswordReturn {
21
+ passwordValue?: string;
22
+ confirmPasswordValue?: string;
23
+ validator: (value: string) => true | string;
24
+ helper: AuthPasswordHelperState;
25
+ isMatched: boolean;
26
+ }
27
+
28
+ /**
29
+ * Password 확인 훅; 비밀번호/재확인 값의 일치 여부와 helper 상태를 계산한다.
30
+ * @hook
31
+ * @param {UseCheckPasswordOptions<TValues>} options 훅 옵션
32
+ * @param {UseFormReturn<TValues>} options.form react-hook-form 반환값
33
+ * @param {FieldPath<TValues>} options.passwordField 비밀번호 필드 이름
34
+ * @param {FieldPath<TValues>} options.confirmField 재확인 필드 이름
35
+ * @param {ReactNode} [options.helperText] 기본 helper 텍스트
36
+ * @param {UseCheckPasswordMessages} [options.messages] 검증 메시지
37
+ * @returns {UseCheckPasswordReturn} validator/helper/입력값 집합
38
+ */
39
+ export function useCheckPassword<TValues extends FieldValues>({
40
+ form,
41
+ passwordField,
42
+ confirmField,
43
+ helperText,
44
+ messages,
45
+ }: UseCheckPasswordOptions<TValues>): UseCheckPasswordReturn {
46
+ const missingMessage = messages?.missing ?? "비밀번호를 먼저 입력해 주세요.";
47
+ const mismatchMessage = messages?.mismatch ?? "비밀번호 불일치";
48
+ const passwordValue = useWatch({
49
+ control: form.control,
50
+ name: passwordField,
51
+ }) as string | undefined;
52
+ const confirmPasswordValue = useWatch({
53
+ control: form.control,
54
+ name: confirmField,
55
+ }) as string | undefined;
56
+
57
+ const validator = useCallback(
58
+ (value: string) => {
59
+ if (!value) {
60
+ return true;
61
+ }
62
+ const latestPassword = form.getValues(passwordField) as
63
+ | string
64
+ | undefined;
65
+ if (!latestPassword || latestPassword.length === 0) {
66
+ return missingMessage;
67
+ }
68
+ return latestPassword === value || mismatchMessage;
69
+ },
70
+ [form, mismatchMessage, missingMessage, passwordField],
71
+ );
72
+
73
+ const helper: AuthPasswordHelperState = useMemo(() => {
74
+ if (!confirmPasswordValue || confirmPasswordValue.length === 0) {
75
+ return {
76
+ text: helperText ?? undefined,
77
+ state: undefined,
78
+ };
79
+ }
80
+
81
+ if (!passwordValue || passwordValue.length === 0) {
82
+ return {
83
+ text: missingMessage,
84
+ state: "error",
85
+ };
86
+ }
87
+
88
+ if (confirmPasswordValue !== passwordValue) {
89
+ return {
90
+ text: mismatchMessage,
91
+ state: "error",
92
+ };
93
+ }
94
+
95
+ return {
96
+ text: helperText ?? undefined,
97
+ state: undefined,
98
+ };
99
+ }, [
100
+ confirmPasswordValue,
101
+ helperText,
102
+ mismatchMessage,
103
+ missingMessage,
104
+ passwordValue,
105
+ ]);
106
+
107
+ const isMatched =
108
+ typeof passwordValue === "string" &&
109
+ passwordValue.length > 0 &&
110
+ typeof confirmPasswordValue === "string" &&
111
+ confirmPasswordValue.length > 0 &&
112
+ confirmPasswordValue === passwordValue;
113
+
114
+ useEffect(() => {
115
+ if (!isMatched) {
116
+ return;
117
+ }
118
+ const fieldState = form.getFieldState(confirmField);
119
+ if (fieldState.error) {
120
+ form.clearErrors(confirmField);
121
+ }
122
+ }, [confirmField, form, isMatched]);
123
+
124
+ return {
125
+ passwordValue,
126
+ confirmPasswordValue,
127
+ validator,
128
+ helper,
129
+ isMatched,
130
+ };
131
+ }
132
+
133
+ export default useCheckPassword;
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8 2C11.3137 2 14 4.68629 14 8C14 11.3137 11.3137 14 8 14C4.68629 14 2 11.3137 2 8C2 4.68629 4.68629 2 8 2ZM10.9268 6.44141C10.7185 6.23363 10.381 6.23351 10.1729 6.44141L7.48535 9.12891L5.71094 7.35352C5.50266 7.14524 5.16433 7.14524 4.95605 7.35352C4.74799 7.56167 4.74821 7.89914 4.95605 8.10742L7.1084 10.2598C7.31665 10.468 7.655 10.4689 7.86328 10.2607L10.9277 7.19629C11.1358 6.98799 11.135 6.6496 10.9268 6.44141Z" fill="currentColor"/>
3
+ </svg>