@uniai-fe/uds-templates 0.4.33 → 0.5.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/dist/styles.css CHANGED
@@ -1229,6 +1229,62 @@
1229
1229
  gap: var(--spacing-padding-5);
1230
1230
  }
1231
1231
 
1232
+ .auth-signup-type-options {
1233
+ display: flex;
1234
+ flex-direction: column;
1235
+ gap: var(--spacing-padding-6);
1236
+ }
1237
+
1238
+ .auth-signup-type-option-group {
1239
+ display: flex;
1240
+ flex-direction: column;
1241
+ gap: var(--spacing-padding-3);
1242
+ }
1243
+
1244
+ .auth-signup-type-question {
1245
+ margin: 0;
1246
+ color: var(--color-label-standard);
1247
+ font-size: var(--font-heading-xxsmall-size);
1248
+ line-height: 1.4;
1249
+ }
1250
+
1251
+ .auth-signup-type-option {
1252
+ display: flex;
1253
+ justify-content: flex-start;
1254
+ text-align: left;
1255
+ --button-default-padding-inline-large: 16px;
1256
+ --button-default-tertiary-outline-foreground: var(--color-label-alternative);
1257
+ }
1258
+ .auth-signup-type-option .button-left {
1259
+ display: flex;
1260
+ width: 24px;
1261
+ height: 24px;
1262
+ align-items: center;
1263
+ justify-content: center;
1264
+ flex-shrink: 0;
1265
+ }
1266
+ .auth-signup-type-option .button-left svg {
1267
+ display: block;
1268
+ width: 24px;
1269
+ height: 24px;
1270
+ }
1271
+ .auth-signup-type-option .button-label {
1272
+ flex: 1;
1273
+ text-align: left;
1274
+ }
1275
+
1276
+ .auth-signup-type-option-label {
1277
+ font-size: var(--font-body-large-size);
1278
+ line-height: 1.5;
1279
+ }
1280
+
1281
+ .auth-signup-type-description {
1282
+ margin: 0;
1283
+ color: var(--color-label-assistive);
1284
+ font-size: var(--font-body-xxsmall-size);
1285
+ line-height: 1.5;
1286
+ }
1287
+
1232
1288
  .auth-signup-verification {
1233
1289
  display: flex;
1234
1290
  flex-direction: column;
@@ -1238,34 +1294,47 @@
1238
1294
  .auth-signup-agreements {
1239
1295
  display: flex;
1240
1296
  flex-direction: column;
1241
- gap: var(--spacing-padding-4);
1297
+ gap: 12px;
1298
+ }
1299
+
1300
+ .auth-signup-agreement-all-field {
1301
+ gap: 0;
1242
1302
  }
1243
1303
 
1244
1304
  .auth-signup-agreement-all {
1245
- height: var(--theme-size-medium-3);
1246
- display: flex;
1247
- align-items: center;
1305
+ width: 100%;
1306
+ min-height: 56px;
1248
1307
  background: var(--color-bg-alternative-cool-gray, #f2f2f3);
1249
- border-radius: var(--theme-radius-large-2, 16px);
1250
- border-radius: var(--theme-radius-medium-3, 8px);
1251
- padding: 0 var(--spacing-padding-5);
1308
+ border-radius: 12px;
1309
+ padding: 8px 12px;
1310
+ gap: 6px;
1311
+ align-items: center;
1312
+ }
1313
+
1314
+ .auth-signup-agreement-all-checkbox {
1315
+ flex-shrink: 0;
1316
+ }
1317
+
1318
+ .auth-signup-agreement-all-label {
1319
+ color: var(--color-label-standard);
1320
+ font-size: var(--font-body-large-size);
1321
+ font-weight: 500;
1322
+ line-height: 1.5;
1252
1323
  }
1253
1324
 
1254
1325
  .auth-signup-agreements-list {
1255
1326
  width: 100%;
1256
- background: var(--color-common-100);
1257
- border-radius: var(--theme-radius-medium-3, 8px);
1258
- padding: var(--spacing-padding-3);
1259
1327
  display: flex;
1260
1328
  flex-direction: column;
1261
- gap: var(--spacing-padding-3);
1329
+ gap: 12px;
1330
+ padding: 4px 12px;
1262
1331
  }
1263
1332
 
1264
1333
  .auth-signup-agreement-row {
1265
1334
  display: flex;
1266
1335
  align-items: center;
1267
1336
  justify-content: space-between;
1268
- gap: var(--spacing-padding-3);
1337
+ gap: 8px;
1269
1338
  }
1270
1339
 
1271
1340
  .auth-signup-agreement-toggle {
@@ -1275,12 +1344,13 @@
1275
1344
  margin: 0;
1276
1345
  display: flex;
1277
1346
  align-items: center;
1278
- gap: var(--spacing-padding-3);
1347
+ gap: 8px;
1279
1348
  cursor: pointer;
1280
1349
  flex: 1;
1281
1350
  text-align: left;
1282
1351
  flex-wrap: nowrap;
1283
1352
  font-size: 14px;
1353
+ min-width: 0;
1284
1354
  }
1285
1355
 
1286
1356
  .auth-signup-agreement-icon {
@@ -1300,18 +1370,19 @@
1300
1370
 
1301
1371
  .auth-signup-agreement-toggle[data-checked=true] .auth-signup-agreement-icon {
1302
1372
  color: var(--color-primary-default);
1303
- border-color: var(--color-primary-default);
1304
1373
  }
1305
1374
 
1306
1375
  .auth-signup-agreement-label {
1307
- display: inline-flex;
1308
- gap: var(--spacing-padding-1);
1376
+ display: flex;
1377
+ gap: 0;
1309
1378
  align-items: baseline;
1310
1379
  flex-wrap: nowrap;
1380
+ min-width: 0;
1311
1381
  }
1312
1382
 
1313
1383
  .auth-signup-agreement-badge {
1314
1384
  font-size: 14px;
1385
+ font-weight: 400;
1315
1386
  color: var(--color-primary-default);
1316
1387
  }
1317
1388
  .auth-signup-agreement-badge[data-required=false] {
@@ -1322,7 +1393,9 @@
1322
1393
  font-size: 14px;
1323
1394
  color: var(--color-label-standard);
1324
1395
  font-weight: 400;
1325
- line-height: 1.4;
1396
+ line-height: 1.5;
1397
+ overflow: hidden;
1398
+ text-overflow: ellipsis;
1326
1399
  }
1327
1400
 
1328
1401
  .auth-signup-agreement-description {
@@ -1343,11 +1416,12 @@
1343
1416
  justify-content: center;
1344
1417
  width: 20px;
1345
1418
  height: 20px;
1419
+ flex-shrink: 0;
1346
1420
  }
1347
1421
  .auth-signup-agreement-detail svg {
1348
1422
  display: block;
1349
- width: 100%;
1350
- height: 100%;
1423
+ width: 16px;
1424
+ height: 16px;
1351
1425
  }
1352
1426
 
1353
1427
  .auth-signup-agreement-detail:disabled {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.4.33",
3
+ "version": "0.5.0",
4
4
  "description": "UNIAI Design System; UI Templates Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,3 +1,6 @@
1
+ export * from "./useSignupTypeSelectForm";
1
2
  export * from "./useSignupUserInfoForm";
3
+ export * from "./useSignupFarmCodeForm";
4
+ export * from "./useSignupAgreementForm";
2
5
  export * from "./useSignupVerificationForm";
3
6
  export * from "./useSignupAccountForm";
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import type {
5
+ UseSignupAgreementFormOptions,
6
+ UseSignupAgreementFormReturn,
7
+ } from "../types";
8
+
9
+ const DEFAULT_THIRD_PARTY_AGREEMENT_ID = "thirdParty";
10
+
11
+ /**
12
+ * 회원가입 Step4 훅; 약관 동의 상태 계산
13
+ * @hook
14
+ * @param {UseSignupAgreementFormOptions} options 훅 옵션
15
+ * @desc
16
+ * - 필수 약관 id 집합과 CTA 활성화 여부를 계산한다.
17
+ */
18
+ export function useSignupAgreementForm({
19
+ agreements,
20
+ agreementState,
21
+ isThirdPartyRequired,
22
+ thirdPartyAgreementId = DEFAULT_THIRD_PARTY_AGREEMENT_ID,
23
+ }: UseSignupAgreementFormOptions): UseSignupAgreementFormReturn {
24
+ const requiredAgreementIds = useMemo(() => {
25
+ return agreements
26
+ .filter(option => {
27
+ if (option.required) {
28
+ return true;
29
+ }
30
+
31
+ return isThirdPartyRequired && option.id === thirdPartyAgreementId;
32
+ })
33
+ .map(option => option.id);
34
+ }, [agreements, isThirdPartyRequired, thirdPartyAgreementId]);
35
+
36
+ const allRequiredChecked =
37
+ requiredAgreementIds.length > 0
38
+ ? requiredAgreementIds.every(id => Boolean(agreementState[id]))
39
+ : true;
40
+
41
+ return {
42
+ requiredAgreementIds,
43
+ allRequiredChecked,
44
+ disabled: !allRequiredChecked,
45
+ };
46
+ }
47
+
48
+ export default useSignupAgreementForm;
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { useWatch } from "react-hook-form";
5
+ import type { ReactNode } from "react";
6
+ import type {
7
+ AuthSignupFarmCodeFields,
8
+ AuthSignupFarmCodeValues,
9
+ AuthSignupFieldProps,
10
+ AuthSignupFormValues,
11
+ UseSignupFarmCodeFormOptions,
12
+ UseSignupFarmCodeFormReturn,
13
+ } from "../types";
14
+
15
+ /**
16
+ * 회원가입 Step3 훅; 농장 식별번호 입력
17
+ * @hook
18
+ * @template TFields
19
+ * @param {UseSignupFarmCodeFormOptions<TFields>} options 훅 옵션
20
+ * @desc
21
+ * - 1) form init → 2) register merge → 3) helper/state → 4) submit 순서
22
+ */
23
+ export function useSignupFarmCodeForm<
24
+ TFields extends Record<string, AuthSignupFieldProps> =
25
+ AuthSignupFarmCodeFields,
26
+ >({
27
+ fields,
28
+ form,
29
+ onSubmit,
30
+ }: UseSignupFarmCodeFormOptions<TFields>): UseSignupFarmCodeFormReturn<TFields> {
31
+ const values = useWatch({ control: form.control }) as
32
+ | AuthSignupFormValues
33
+ | undefined;
34
+
35
+ const register = useMemo(() => {
36
+ return (Object.keys(fields) as Array<keyof TFields>).reduce(
37
+ (acc, fieldKey) => {
38
+ const config = fields[fieldKey] as AuthSignupFieldProps;
39
+ const fieldName = config.attr?.name ?? String(fieldKey);
40
+ acc[fieldKey] = form.register(fieldName);
41
+ return acc;
42
+ },
43
+ {} as UseSignupFarmCodeFormReturn<TFields>["register"],
44
+ );
45
+ }, [fields, form]);
46
+
47
+ const helpers = useMemo(() => {
48
+ return (Object.keys(fields) as Array<keyof TFields>).reduce(
49
+ (acc, fieldKey) => {
50
+ const config = fields[fieldKey] as AuthSignupFieldProps;
51
+ const fieldName = config.attr?.name ?? String(fieldKey);
52
+ const state = form.getFieldState(fieldName);
53
+ acc[fieldKey] = {
54
+ text:
55
+ (state.error?.message as ReactNode | undefined) ?? config.helper,
56
+ state: state.invalid ? "error" : undefined,
57
+ };
58
+ return acc;
59
+ },
60
+ {} as UseSignupFarmCodeFormReturn<TFields>["helpers"],
61
+ );
62
+ }, [fields, form]);
63
+
64
+ const farmCodeFieldName =
65
+ fields.farmCode.attr?.name ??
66
+ ("farmCode" as keyof AuthSignupFarmCodeValues);
67
+ const farmCodeValue =
68
+ values?.[farmCodeFieldName as keyof AuthSignupFormValues] ?? "";
69
+ const normalizedFarmCode =
70
+ typeof farmCodeValue === "string"
71
+ ? farmCodeValue.replace(/\D/g, "")
72
+ : String(farmCodeValue ?? "").replace(/\D/g, "");
73
+
74
+ const disabled =
75
+ form.formState.isSubmitting || !/^\d{6}$/.test(normalizedFarmCode);
76
+
77
+ const onSubmitHandler = form.handleSubmit(onSubmit);
78
+
79
+ return {
80
+ register,
81
+ helpers,
82
+ disabled,
83
+ onSubmit: onSubmitHandler,
84
+ };
85
+ }
86
+
87
+ export default useSignupFarmCodeForm;
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect } from "react";
4
+ import { useWatch } from "react-hook-form";
5
+ import type {
6
+ AuthSignupFormValues,
7
+ AuthSignupTypeSelectValues,
8
+ AuthSignupTypeValue,
9
+ UseSignupTypeSelectFormOptions,
10
+ UseSignupTypeSelectFormReturn,
11
+ } from "../types";
12
+
13
+ /**
14
+ * 회원가입 Step1 훅; 가입 유형 선택
15
+ * @hook
16
+ * @param {UseSignupTypeSelectFormOptions} options 훅 옵션
17
+ * @desc
18
+ * - 1) form init → 2) register merge → 3) state/helper → 4) submit 순서
19
+ */
20
+ export function useSignupTypeSelectForm({
21
+ form,
22
+ onSubmit,
23
+ }: UseSignupTypeSelectFormOptions): UseSignupTypeSelectFormReturn {
24
+ useEffect(() => {
25
+ // 가입 유형은 커스텀 버튼으로 변경되므로 RHF에 명시적으로 등록한다.
26
+ form.register("signupType");
27
+ }, [form]);
28
+
29
+ const selectedType = useWatch({
30
+ control: form.control,
31
+ name: "signupType",
32
+ }) as AuthSignupTypeValue | undefined;
33
+
34
+ const handleValueChange = useCallback(
35
+ (value: AuthSignupTypeValue) => {
36
+ form.setValue("signupType", value, {
37
+ shouldDirty: true,
38
+ shouldTouch: true,
39
+ shouldValidate: true,
40
+ });
41
+ },
42
+ [form],
43
+ );
44
+
45
+ const disabled = form.formState.isSubmitting || !selectedType;
46
+ const onSubmitHandler = form.handleSubmit(onSubmit);
47
+
48
+ return {
49
+ selectedType,
50
+ onValueChange: handleValueChange,
51
+ disabled,
52
+ onSubmit: onSubmitHandler,
53
+ };
54
+ }
55
+
56
+ export default useSignupTypeSelectForm;
@@ -0,0 +1,15 @@
1
+ <svg
2
+ width="24"
3
+ height="24"
4
+ viewBox="0 0 24 24"
5
+ fill="none"
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ >
8
+ <path
9
+ d="M4 10.8995L9.65685 16.5563L19.5563 6.65683"
10
+ stroke="currentColor"
11
+ stroke-width="1.6"
12
+ stroke-linecap="round"
13
+ stroke-linejoin="round"
14
+ />
15
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M4 10.8995L9.65685 16.5563L19.5563 6.65683" stroke="#1A6AFF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
@@ -1,5 +1,8 @@
1
1
  import {
2
+ AuthSignupTypeSelectForm,
2
3
  AuthSignupUserInfoForm,
4
+ AuthSignupFarmCodeForm,
5
+ AuthSignupAgreementForm,
3
6
  AuthSignupVerificationForm,
4
7
  AuthSignupAccountForm,
5
8
  AuthSignupComplete,
@@ -12,8 +15,11 @@ import "./styles/signup.scss";
12
15
  export type * from "./types";
13
16
  export * from "./hooks";
14
17
  export {
18
+ AuthSignupTypeSelectForm,
15
19
  AuthSignupProvider,
16
20
  AuthSignupUserInfoForm,
21
+ AuthSignupFarmCodeForm,
22
+ AuthSignupAgreementForm,
17
23
  AuthSignupVerificationForm,
18
24
  AuthSignupAccountForm,
19
25
  AuthSignupComplete,
@@ -23,6 +29,10 @@ export {
23
29
  export const AuthSignup = {
24
30
  Provider: AuthSignupProvider,
25
31
  Template: AuthSignupTemplate,
32
+ StepTypeSelect: AuthSignupTypeSelectForm,
33
+ StepIdentity: AuthSignupUserInfoForm,
34
+ StepFarmCode: AuthSignupFarmCodeForm,
35
+ StepAgreement: AuthSignupAgreementForm,
26
36
  StepUserInfo: AuthSignupUserInfoForm,
27
37
  StepVerification: AuthSignupVerificationForm,
28
38
  StepAccount: AuthSignupAccountForm,
@@ -86,6 +86,7 @@ export function AuthSignupAccountForm({
86
86
  <div className="auth-signup-fields">
87
87
  <Form.Field.Template
88
88
  width={fields.accountId.template?.width ?? "full"}
89
+ state={helpers.accountId?.state === "error" ? "error" : undefined}
89
90
  className={clsx(
90
91
  "auth-signup-field",
91
92
  "auth-signup-field-account-id",
@@ -0,0 +1,229 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import { useMemo, useState } from "react";
5
+ import type { SubmitEvent } from "react";
6
+ import {
7
+ Button,
8
+ CheckboxField,
9
+ DrawerBody,
10
+ DrawerContent,
11
+ DrawerFooter,
12
+ DrawerHeader,
13
+ DrawerOverlay,
14
+ DrawerPortal,
15
+ DrawerRoot,
16
+ DrawerTitle,
17
+ } from "@uniai-fe/uds-primitives";
18
+ import type {
19
+ AuthSignupAgreementOption,
20
+ AuthSignupAgreementProps,
21
+ } from "../types";
22
+ import { useSignupAgreementForm } from "../hooks";
23
+ import CheckAgreeIcon from "../img/check-agree.svg";
24
+ import ChevronOpenDetailIcon from "../img/chevron-open-detail.svg";
25
+
26
+ const AGREEMENT_DETAIL_FALLBACK = (
27
+ <>
28
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
29
+ <p>Nulla facilisi. Integer porta, nisl at volutpat posuere, erat arcu.</p>
30
+ </>
31
+ );
32
+
33
+ /**
34
+ * 회원가입 Step4; 약관 동의 폼
35
+ * @component
36
+ * @param {AuthSignupAgreementProps} props agreement props
37
+ * @param {AuthSignupAgreementOption[]} props.agreements 약관 목록
38
+ * @param {Record<string, boolean>} props.agreementState 약관 체크 상태
39
+ * @param {boolean} [props.isThirdPartyRequired] 제3자 제공 동의 필수 여부
40
+ * @param {string} [props.thirdPartyAgreementId] 제3자 제공 동의 id
41
+ * @param {(agreementId: string) => void} props.onToggleAgreement 단일 약관 토글
42
+ * @param {(options?: AuthSignupAgreementToggleAllOptions) => void} props.onToggleAll 전체 동의 토글
43
+ * @param {(agreementId: string) => void} [props.onOpenAgreementDetail] 약관 상세 열기
44
+ * @param {React.FormHTMLAttributes<HTMLFormElement>} [props.formAttr] form attr
45
+ * @param {import("react").ReactNode} [props.submitLabel] CTA 라벨
46
+ */
47
+ export function AuthSignupAgreementForm({
48
+ agreements,
49
+ agreementState,
50
+ isThirdPartyRequired,
51
+ thirdPartyAgreementId,
52
+ onToggleAgreement,
53
+ onToggleAll,
54
+ onOpenAgreementDetail,
55
+ formAttr,
56
+ submitLabel,
57
+ submitDisabled,
58
+ onSubmit,
59
+ }: AuthSignupAgreementProps) {
60
+ const [openedAgreementId, setOpenedAgreementId] = useState<string | null>(
61
+ null,
62
+ );
63
+
64
+ const { requiredAgreementIds, allRequiredChecked, disabled } =
65
+ useSignupAgreementForm({
66
+ agreements,
67
+ agreementState,
68
+ isThirdPartyRequired,
69
+ thirdPartyAgreementId,
70
+ });
71
+
72
+ const openedAgreement = useMemo(() => {
73
+ if (!openedAgreementId) {
74
+ return null;
75
+ }
76
+
77
+ return agreements.find(option => option.id === openedAgreementId) ?? null;
78
+ }, [agreements, openedAgreementId]);
79
+
80
+ const handleCloseDrawer = () => {
81
+ setOpenedAgreementId(null);
82
+ };
83
+
84
+ const handleOpenAgreementDetail = (agreementId: string) => {
85
+ onOpenAgreementDetail?.(agreementId);
86
+ setOpenedAgreementId(agreementId);
87
+ };
88
+
89
+ // React 19 submit 이벤트 계약과 동일하게 SubmitEvent로 맞춘다.
90
+ const handleSubmit = (event: SubmitEvent<HTMLFormElement>) => {
91
+ formAttr?.onSubmit?.(event);
92
+
93
+ if (event.defaultPrevented) {
94
+ return;
95
+ }
96
+
97
+ event.preventDefault();
98
+ if (!disabled && !submitDisabled) {
99
+ onSubmit();
100
+ }
101
+ };
102
+
103
+ const renderAgreementLabel = (
104
+ option: AuthSignupAgreementOption,
105
+ required: boolean,
106
+ ) => (
107
+ <span className="auth-signup-agreement-label">
108
+ <span
109
+ className="auth-signup-agreement-badge"
110
+ data-required={required ? "true" : "false"}
111
+ >
112
+ [{required ? "필수" : "선택"}]
113
+ </span>
114
+ <span className="auth-signup-agreement-title">{option.label}</span>
115
+ </span>
116
+ );
117
+
118
+ return (
119
+ <form
120
+ className={clsx("auth-signup-form", "auth-signup-form-agreement")}
121
+ {...formAttr}
122
+ onSubmit={handleSubmit}
123
+ >
124
+ {agreements.length ? (
125
+ <section className="auth-signup-agreements" aria-label="약관 동의">
126
+ <CheckboxField
127
+ size="large"
128
+ checked={allRequiredChecked}
129
+ onCheckedChange={() => onToggleAll({ requiredOnly: true })}
130
+ fieldClassName="auth-signup-agreement-all-field"
131
+ labelWrapperClassName="auth-signup-agreement-all"
132
+ className="auth-signup-agreement-all-checkbox"
133
+ label={
134
+ <span className="auth-signup-agreement-all-label">
135
+ 필수 약관에 모두 동의하기
136
+ </span>
137
+ }
138
+ />
139
+ <div className="auth-signup-agreements-list">
140
+ {agreements.map(option => {
141
+ const checked = Boolean(agreementState[option.id]);
142
+ const required = requiredAgreementIds.includes(option.id);
143
+
144
+ return (
145
+ <div
146
+ className="auth-signup-agreement-row"
147
+ key={option.id}
148
+ data-required={required ? "true" : undefined}
149
+ data-checked={checked ? "true" : undefined}
150
+ >
151
+ {/* 약관 토글은 Figma 아이콘 사양에 맞춰 카드 버튼으로 렌더링한다. */}
152
+ <button
153
+ type="button"
154
+ className="auth-signup-agreement-toggle"
155
+ aria-pressed={checked}
156
+ data-checked={checked ? "true" : undefined}
157
+ onClick={() => onToggleAgreement(option.id)}
158
+ >
159
+ <span
160
+ className="auth-signup-agreement-icon"
161
+ aria-hidden="true"
162
+ >
163
+ <CheckAgreeIcon />
164
+ </span>
165
+ {renderAgreementLabel(option, required)}
166
+ </button>
167
+ <button
168
+ type="button"
169
+ className="auth-signup-agreement-detail"
170
+ aria-label={`${option.label} 약관 상세 보기`}
171
+ onClick={() => handleOpenAgreementDetail(option.id)}
172
+ >
173
+ <span aria-hidden="true">
174
+ <ChevronOpenDetailIcon />
175
+ </span>
176
+ </button>
177
+ </div>
178
+ );
179
+ })}
180
+ </div>
181
+ </section>
182
+ ) : null}
183
+ <Button.Default
184
+ type="submit"
185
+ fill="solid"
186
+ size="xlarge"
187
+ priority="primary"
188
+ block
189
+ disabled={disabled || Boolean(submitDisabled)}
190
+ >
191
+ {submitLabel ?? "다음"}
192
+ </Button.Default>
193
+ <DrawerRoot
194
+ open={Boolean(openedAgreement)}
195
+ onOpenChange={isOpen => {
196
+ if (!isOpen) {
197
+ handleCloseDrawer();
198
+ }
199
+ }}
200
+ >
201
+ <DrawerPortal>
202
+ <DrawerOverlay />
203
+ <DrawerContent>
204
+ <DrawerHeader>
205
+ <DrawerTitle>{openedAgreement?.label ?? "약관 상세"}</DrawerTitle>
206
+ </DrawerHeader>
207
+ <DrawerBody className="auth-signup-agreement-drawer-body">
208
+ {openedAgreement?.description ?? AGREEMENT_DETAIL_FALLBACK}
209
+ </DrawerBody>
210
+ <DrawerFooter>
211
+ <Button.Default
212
+ type="button"
213
+ fill="solid"
214
+ size="medium"
215
+ priority="primary"
216
+ block
217
+ onClick={handleCloseDrawer}
218
+ >
219
+ 닫기
220
+ </Button.Default>
221
+ </DrawerFooter>
222
+ </DrawerContent>
223
+ </DrawerPortal>
224
+ </DrawerRoot>
225
+ </form>
226
+ );
227
+ }
228
+
229
+ export default AuthSignupAgreementForm;