@uniai-fe/uds-templates 0.6.30 → 0.7.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
@@ -958,8 +958,12 @@
958
958
  gap: var(--spacing-padding-5);
959
959
  }
960
960
 
961
+ .auth-login-keep-login {
962
+ align-self: flex-start;
963
+ }
964
+
961
965
  .auth-login-util-container {
962
- margin-top: 80px;
966
+ margin-top: var(--spacing-padding-10);
963
967
  display: flex;
964
968
  flex-direction: column;
965
969
  gap: var(--spacing-padding-8);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.6.30",
3
+ "version": "0.7.0",
4
4
  "description": "UNIAI Design System; UI Templates Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -52,13 +52,13 @@
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@tanstack/react-query": "^5",
55
- "@uniai-fe/uds-foundation": "^0.4.2",
56
- "@uniai-fe/uds-primitives": "^0.6.2",
57
- "@uniai-fe/util-api": "^0.1.0",
58
- "@uniai-fe/util-functions": "^0.2.3",
59
- "@uniai-fe/util-jotai": "^0.1.5",
60
- "@uniai-fe/util-next": "^0.2.0",
61
- "@uniai-fe/util-rtc": "^0.1.0",
55
+ "@uniai-fe/uds-foundation": "^0.4.8",
56
+ "@uniai-fe/uds-primitives": "^0.8.0",
57
+ "@uniai-fe/util-api": "^0.1.15",
58
+ "@uniai-fe/util-functions": "^0.3.0",
59
+ "@uniai-fe/util-jotai": "^0.1.9",
60
+ "@uniai-fe/util-next": "^0.3.0",
61
+ "@uniai-fe/util-rtc": "^0.1.4",
62
62
  "jotai": "^2",
63
63
  "next": "^15",
64
64
  "react": "^19",
@@ -107,5 +107,4 @@ export interface AuthPhoneAuthCodeTemplateProps {
107
107
  }
108
108
 
109
109
  export type AuthAuthCodeTemplateProps =
110
- | AuthEmailAuthCodeTemplateProps
111
- | AuthPhoneAuthCodeTemplateProps;
110
+ AuthEmailAuthCodeTemplateProps | AuthPhoneAuthCodeTemplateProps;
@@ -84,6 +84,7 @@ export function useAuthLoginForm({
84
84
  typeof passwordValue === "string"
85
85
  ? removeWhitespace(passwordValue)
86
86
  : "",
87
+ [fieldNames.keepLogin]: submittedValues[fieldNames.keepLogin] === true,
87
88
  };
88
89
  return onLogin(normalizedValues, event);
89
90
  });
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { useMemo } from "react";
4
- import { useForm } from "react-hook-form";
5
- import { Button } from "@uniai-fe/uds-primitives";
4
+ import { useController, useForm } from "react-hook-form";
5
+ import { Button, CheckboxField } from "@uniai-fe/uds-primitives";
6
6
  import type { FormEventHandler, SubmitEvent } from "react";
7
7
  import type { AuthLoginFieldOptions, AuthLoginFormValues } from "../types";
8
8
  import { useAuthLoginForm } from "../hooks";
@@ -23,6 +23,7 @@ import AuthLoginFormFieldPassword from "./Password";
23
23
  export default function AuthLoginFormField({
24
24
  idField,
25
25
  passwordField,
26
+ keepLoginField,
26
27
  texts,
27
28
  isSubmitting,
28
29
  formAttr,
@@ -31,16 +32,20 @@ export default function AuthLoginFormField({
31
32
  // RHF register 이름은 옵션 name이 있으면 그대로 사용하고, 없으면 key 기반으로 fallback한다.
32
33
  const idFieldName = idField?.name ?? "userId";
33
34
  const passwordFieldName = passwordField?.name ?? "userPassword";
35
+ const keepLoginFieldName = keepLoginField?.name ?? "keepLogin";
34
36
 
35
37
  // 1) RHF 기본값: 필드별 defaultValue가 있으면 채워 넣어 최초 렌더에서 바로 반영한다.
36
38
  const defaultValues = useMemo(
37
39
  () => ({
38
40
  [idFieldName]: idField?.defaultValue ?? "",
39
41
  [passwordFieldName]: passwordField?.defaultValue ?? "",
42
+ [keepLoginFieldName]: keepLoginField?.defaultChecked ?? false,
40
43
  }),
41
44
  [
42
45
  idField?.defaultValue,
43
46
  idFieldName,
47
+ keepLoginField?.defaultChecked,
48
+ keepLoginFieldName,
44
49
  passwordField?.defaultValue,
45
50
  passwordFieldName,
46
51
  ],
@@ -56,12 +61,18 @@ export default function AuthLoginFormField({
56
61
  fieldNames: {
57
62
  id: idFieldName,
58
63
  password: passwordFieldName,
64
+ keepLogin: keepLoginFieldName,
59
65
  },
60
66
  form,
61
67
  isSubmitting,
62
68
  onLogin,
63
69
  });
64
70
 
71
+ const { field: keepLoginController } = useController({
72
+ control: form.control,
73
+ name: keepLoginFieldName,
74
+ });
75
+
65
76
  const handleFormSubmit: FormEventHandler<HTMLFormElement> = event => {
66
77
  const nativeEvent =
67
78
  event.nativeEvent as unknown as SubmitEvent<HTMLFormElement>;
@@ -106,6 +117,9 @@ export default function AuthLoginFormField({
106
117
  const idLabel = idField?.label ?? texts?.idLabel ?? "아이디";
107
118
  const passwordLabel =
108
119
  passwordField?.label ?? texts?.passwordLabel ?? "비밀번호";
120
+ const keepLoginLabel =
121
+ keepLoginField?.label ?? texts?.keepLoginLabel ?? "자동 로그인";
122
+ const keepLoginHelper = keepLoginField?.helperText ?? texts?.keepLoginHelper;
109
123
  const isLoginSubmitting = Boolean(isSubmitting);
110
124
  // Button loading은 readonly/aria-busy 같은 상태 의미를 제공하지만, 라벨은 자동 변경하지 않는다.
111
125
  // pending 중에는 눈에 보이는 문구도 바꿔 사용자가 "요청 처리 중"임을 즉시 알 수 있게 한다.
@@ -124,6 +138,17 @@ export default function AuthLoginFormField({
124
138
  // disabled 대신 readOnly를 써서 제출 중 값 보존과 UI 잠금을 함께 맞춘다.
125
139
  readOnly: isLoginSubmitting || passwordField?.inputProps?.readOnly,
126
140
  };
141
+ const isKeepLoginDisabled =
142
+ isLoginSubmitting || Boolean(keepLoginField?.disabled);
143
+ const keepLoginCheckboxProps = { ...(keepLoginField ?? {}) };
144
+ const keepLoginFieldClassName = keepLoginCheckboxProps.fieldClassName;
145
+
146
+ delete keepLoginCheckboxProps.defaultChecked;
147
+ delete keepLoginCheckboxProps.disabled;
148
+ delete keepLoginCheckboxProps.fieldClassName;
149
+ delete keepLoginCheckboxProps.helperText;
150
+ delete keepLoginCheckboxProps.label;
151
+ delete keepLoginCheckboxProps.name;
127
152
 
128
153
  // aria-busy는 외부 로그인 요청 진행 상태만 전달한다.
129
154
  // 실제 사용자 피드백 modal/문구는 서비스 앱의 Modal.Alert/Dialog 흐름에서 처리한다.
@@ -163,6 +188,22 @@ export default function AuthLoginFormField({
163
188
  inputProps={passwordInputProps}
164
189
  templateProps={passwordField?.templateProps}
165
190
  />
191
+ <CheckboxField
192
+ {...keepLoginCheckboxProps}
193
+ ref={keepLoginController.ref}
194
+ name={keepLoginFieldName}
195
+ checked={keepLoginController.value === true}
196
+ disabled={isKeepLoginDisabled}
197
+ label={keepLoginLabel}
198
+ helperText={keepLoginHelper}
199
+ fieldClassName={["auth-login-keep-login", keepLoginFieldClassName]
200
+ .filter(Boolean)
201
+ .join(" ")}
202
+ onBlur={keepLoginController.onBlur}
203
+ onCheckedChange={checked => {
204
+ keepLoginController.onChange(checked === true);
205
+ }}
206
+ />
166
207
  <Button.Default
167
208
  type="submit"
168
209
  fill="solid"
@@ -11,8 +11,12 @@
11
11
  gap: var(--spacing-padding-5);
12
12
  }
13
13
 
14
+ .auth-login-keep-login {
15
+ align-self: flex-start;
16
+ }
17
+
14
18
  .auth-login-util-container {
15
- margin-top: 80px;
19
+ margin-top: var(--spacing-padding-10);
16
20
  display: flex;
17
21
  flex-direction: column;
18
22
  gap: var(--spacing-padding-8);
@@ -5,6 +5,7 @@ import type { API_Res_Base } from "@uniai-fe/util-api";
5
5
  * @route /auth/user/login
6
6
  * @property {string} username - 사용자 ID
7
7
  * @property {string} password - 비밀번호
8
+ * @property {boolean} [keep_login] - 자동 로그인 유지 여부
8
9
  */
9
10
  export interface API_Req_Login {
10
11
  /**
@@ -15,6 +16,10 @@ export interface API_Req_Login {
15
16
  * 비밀번호
16
17
  */
17
18
  password: string;
19
+ /**
20
+ * 자동 로그인 유지 여부
21
+ */
22
+ keep_login?: boolean;
18
23
  }
19
24
 
20
25
  /**
@@ -10,9 +10,9 @@ import type { InputState } from "@uniai-fe/uds-primitives";
10
10
  * 로그인 폼 값 타입
11
11
  * @typedef {AuthLoginFormValues}
12
12
  * @desc
13
- * - 모든 로그인 필드 입력값을 문자열 레코드로 취급한다.
13
+ * - id/password는 문자열, keepLogin 같은 선택 필드는 boolean으로 취급한다.
14
14
  */
15
- export type AuthLoginFormValues = Record<string, string>;
15
+ export type AuthLoginFormValues = Record<string, string | boolean>;
16
16
 
17
17
  /**
18
18
  * 로그인 훅 옵션; useAuthLoginForm 설정
@@ -20,8 +20,8 @@ export type AuthLoginFormValues = Record<string, string>;
20
20
  * @desc
21
21
  * - 필드 정의, useForm 반환 객체, 로그인 핸들러를 전달한다.
22
22
  * - 필드 타입은 제네릭으로 확장 가능하다.
23
- * @property {UseFormReturn<Record<string, string>>} form RHF useForm 반환 객체
24
- * @property {SubmitHandler<Record<string, string>>} onLogin 제출 콜백
23
+ * @property {UseFormReturn<AuthLoginFormValues>} form RHF useForm 반환 객체
24
+ * @property {SubmitHandler<AuthLoginFormValues>} onLogin 제출 콜백
25
25
  */
26
26
  export interface AuthLoginFieldNames {
27
27
  /**
@@ -32,6 +32,10 @@ export interface AuthLoginFieldNames {
32
32
  * 비밀번호 필드 name
33
33
  */
34
34
  password: string;
35
+ /**
36
+ * 자동 로그인 checkbox field name
37
+ */
38
+ keepLogin: string;
35
39
  }
36
40
 
37
41
  /**
@@ -69,7 +73,7 @@ export interface UseAuthLoginFormOptions {
69
73
  * @desc
70
74
  * - register/onSubmit/disabled/helpers 네 축만 노출한다.
71
75
  * - 필드는 제네릭 기반으로 key를 유지한다.
72
- * @property {Record<keyof TFields, UseFormRegisterReturn>} register 필드별 register 함수
76
+ * @property {{ id: UseFormRegisterReturn; password: UseFormRegisterReturn }} register 필드별 register 함수
73
77
  * @property {ReturnType<UseFormReturn["handleSubmit"]>} onSubmit 로그인 제출 핸들러
74
78
  * @property {boolean} disabled 버튼 disabled 여부
75
79
  * @property {Record<keyof TFields, { text?: string; state?: string }>} helpers 필드별 helper 상태
@@ -7,7 +7,9 @@ import type {
7
7
  InputState,
8
8
  InputPasswordProps,
9
9
  InputProps,
10
+ CheckboxFieldProps,
10
11
  } from "@uniai-fe/uds-primitives";
12
+ import type { AuthLoginFormValues } from "./hooks";
11
13
 
12
14
  /**
13
15
  * Form.Field helper 상태 타입
@@ -216,12 +218,55 @@ export interface AuthLoginIdFieldOptions extends AuthLoginFieldProps<InputProps>
216
218
  */
217
219
  export interface AuthLoginPasswordFieldOptions extends AuthLoginFieldProps<InputPasswordProps> {}
218
220
 
221
+ /**
222
+ * 자동 로그인 checkbox 필드 옵션
223
+ * @extends Omit<CheckboxFieldProps, "checked" | "defaultChecked" | "onCheckedChange" | "label" | "helperText" | "disabled" | "name">
224
+ * @property {string} [name] RHF checkbox field name
225
+ * @property {ReactNode} [label] checkbox label
226
+ * @property {ReactNode} [helperText] checkbox helper text
227
+ * @property {boolean} [defaultChecked=false] 최초 checked 여부
228
+ * @property {boolean} [disabled] checkbox disabled 여부
229
+ */
230
+ export interface AuthLoginKeepLoginFieldOptions extends Omit<
231
+ CheckboxFieldProps,
232
+ | "checked"
233
+ | "defaultChecked"
234
+ | "onCheckedChange"
235
+ | "label"
236
+ | "helperText"
237
+ | "disabled"
238
+ | "name"
239
+ > {
240
+ /**
241
+ * RHF checkbox field name
242
+ */
243
+ name?: string;
244
+ /**
245
+ * checkbox label
246
+ */
247
+ label?: ReactNode;
248
+ /**
249
+ * checkbox helper text
250
+ */
251
+ helperText?: ReactNode;
252
+ /**
253
+ * 최초 checked 여부
254
+ */
255
+ defaultChecked?: boolean;
256
+ /**
257
+ * checkbox disabled 여부
258
+ */
259
+ disabled?: boolean;
260
+ }
261
+
219
262
  /**
220
263
  * 로그인 필드 문구 옵션
221
264
  * @property {ReactNode} [idLabel] 아이디 필드 기본 라벨
222
265
  * @property {string} [idPlaceholder] 아이디 필드 기본 placeholder
223
266
  * @property {ReactNode} [passwordLabel] 비밀번호 필드 기본 라벨
224
267
  * @property {string} [passwordPlaceholder] 비밀번호 필드 기본 placeholder
268
+ * @property {ReactNode} [keepLoginLabel] 자동 로그인 checkbox 기본 라벨
269
+ * @property {ReactNode} [keepLoginHelper] 자동 로그인 checkbox helper
225
270
  * @property {ReactNode} [submit] submit 버튼 라벨
226
271
  * @property {ReactNode} [submitting] 로그인 요청 pending 중 submit 버튼 라벨
227
272
  */
@@ -242,6 +287,14 @@ export interface AuthLoginFieldTexts {
242
287
  * 비밀번호 필드 기본 placeholder
243
288
  */
244
289
  passwordPlaceholder?: string;
290
+ /**
291
+ * 자동 로그인 checkbox 기본 라벨
292
+ */
293
+ keepLoginLabel?: ReactNode;
294
+ /**
295
+ * 자동 로그인 checkbox helper
296
+ */
297
+ keepLoginHelper?: ReactNode;
245
298
  /**
246
299
  * submit 버튼 라벨
247
300
  */
@@ -285,10 +338,11 @@ export interface AuthLoginTexts
285
338
  * 로그인 필드 옵션; form attr + 제출 핸들러 포함
286
339
  * @property {AuthLoginIdFieldOptions} [idField] 로그인 아이디 필드 옵션
287
340
  * @property {AuthLoginPasswordFieldOptions} [passwordField] 로그인 비밀번호 필드 옵션
341
+ * @property {AuthLoginKeepLoginFieldOptions} [keepLoginField] 자동 로그인 checkbox 필드 옵션
288
342
  * @property {AuthLoginFieldTexts} [texts] 로그인 form 기본 문구 옵션
289
343
  * @property {boolean} [isSubmitting] 외부 로그인 요청 pending 여부
290
344
  * @property {React.FormHTMLAttributes<HTMLFormElement>} [formAttr] <form /> attributes
291
- * @property {SubmitHandler<Record<string, string>>} onLogin 로그인 콜백 이벤트
345
+ * @property {SubmitHandler<AuthLoginFormValues>} onLogin 로그인 콜백 이벤트
292
346
  */
293
347
  export interface AuthLoginFieldOptions {
294
348
  /**
@@ -299,6 +353,10 @@ export interface AuthLoginFieldOptions {
299
353
  * 비밀번호 필드 설정
300
354
  */
301
355
  passwordField?: AuthLoginPasswordFieldOptions;
356
+ /**
357
+ * 자동 로그인 checkbox 필드 설정
358
+ */
359
+ keepLoginField?: AuthLoginKeepLoginFieldOptions;
302
360
  /**
303
361
  * 로그인 form 기본 문구 옵션
304
362
  */
@@ -318,7 +376,7 @@ export interface AuthLoginFieldOptions {
318
376
  /**
319
377
  * 로그인 핸들러
320
378
  */
321
- onLogin: SubmitHandler<Record<string, string>>;
379
+ onLogin: SubmitHandler<AuthLoginFormValues>;
322
380
  }
323
381
 
324
382
  /**
@@ -32,8 +32,7 @@ export function useSignupAccountForm<
32
32
  passwordRules,
33
33
  }: UseSignupAccountFormOptions<TFields>): UseSignupAccountFormReturn<TFields> {
34
34
  const values = useWatch({ control: form.control }) as
35
- | AuthSignupFormValues
36
- | undefined;
35
+ AuthSignupFormValues | undefined;
37
36
 
38
37
  const passwordFieldName = useMemo(() => {
39
38
  const config = (fields as Partial<AuthSignupAccountFields>).password;
@@ -88,12 +87,10 @@ export function useSignupAccountForm<
88
87
 
89
88
  const passwordValue =
90
89
  (values?.[passwordFieldName as keyof AuthSignupFormValues] as
91
- | string
92
- | undefined) ?? "";
90
+ string | undefined) ?? "";
93
91
  const confirmPasswordValue =
94
92
  (values?.[confirmFieldName as keyof AuthSignupFormValues] as
95
- | string
96
- | undefined) ?? "";
93
+ string | undefined) ?? "";
97
94
 
98
95
  const normalizedPasswordRules: AuthPasswordRule[] =
99
96
  passwordRules && passwordRules.length > 0
@@ -29,8 +29,7 @@ export function useSignupFarmCodeForm<
29
29
  onSubmit,
30
30
  }: UseSignupFarmCodeFormOptions<TFields>): UseSignupFarmCodeFormReturn<TFields> {
31
31
  const values = useWatch({ control: form.control }) as
32
- | AuthSignupFormValues
33
- | undefined;
32
+ AuthSignupFormValues | undefined;
34
33
 
35
34
  const register = useMemo(() => {
36
35
  return (Object.keys(fields) as Array<keyof TFields>).reduce(
@@ -33,8 +33,7 @@ export function useSignupUserInfoForm<
33
33
  activeFields,
34
34
  }: UseSignupUserInfoFormOptions<TFields>): UseSignupUserInfoFormReturn<TFields> {
35
35
  const values = useWatch({ control: form.control }) as
36
- | AuthSignupFormValues
37
- | undefined;
36
+ AuthSignupFormValues | undefined;
38
37
 
39
38
  const register = useMemo(() => {
40
39
  return (Object.keys(fields) as Array<keyof TFields>).reduce(
@@ -28,8 +28,7 @@ export function useSignupVerificationForm<
28
28
  onSubmit,
29
29
  }: UseSignupVerificationFormOptions<TFields>): UseSignupVerificationFormReturn<TFields> {
30
30
  const values = useWatch({ control: form.control }) as
31
- | AuthSignupFormValues
32
- | undefined;
31
+ AuthSignupFormValues | undefined;
33
32
 
34
33
  const register = useMemo(() => {
35
34
  return (Object.keys(fields) as Array<keyof TFields>).reduce(
@@ -57,8 +57,8 @@ export function AuthSignupAccountForm({
57
57
  ("password" as keyof AuthSignupAccountValues);
58
58
  const confirmFieldName =
59
59
  (fields.confirmPassword.attr?.name as
60
- | keyof AuthSignupAccountValues
61
- | undefined) ?? ("confirmPassword" as keyof AuthSignupAccountValues);
60
+ keyof AuthSignupAccountValues | undefined) ??
61
+ ("confirmPassword" as keyof AuthSignupAccountValues);
62
62
 
63
63
  const resolvedPasswordRules =
64
64
  passwordRules && passwordRules.length > 0
@@ -25,15 +25,13 @@ export function initCompanyList({
25
25
  renderKey: companyRenderKey,
26
26
  group_code: group.group_code,
27
27
  selected: group_index === 0,
28
- cam_list: d.cam_list.map(
29
- (cam): CctvCompanyCameraList => ({
30
- renderKey: `${companyRenderKey}/camera_${cam.cam_id}`,
31
- ...cam,
32
- company_id: d.company_id,
33
- company_name: d.company_name,
34
- selected: false,
35
- }),
36
- ),
28
+ cam_list: d.cam_list.map((cam): CctvCompanyCameraList => ({
29
+ renderKey: `${companyRenderKey}/camera_${cam.cam_id}`,
30
+ ...cam,
31
+ company_id: d.company_id,
32
+ company_name: d.company_name,
33
+ selected: false,
34
+ })),
37
35
  };
38
36
  }),
39
37
  };
@@ -10,10 +10,7 @@ import type {
10
10
  * Service Inquiry; 노출 필드 키
11
11
  */
12
12
  export type ServiceInquiryFieldKey =
13
- | "farm_name"
14
- | "contact"
15
- | "inquiry_type"
16
- | "text";
13
+ "farm_name" | "contact" | "inquiry_type" | "text";
17
14
 
18
15
  /**
19
16
  * Service Inquiry Form; field 입력 모드
@@ -219,10 +219,4 @@ export type KMA_Res_AlertLevel = "예비특보" | "주의보" | "경보";
219
219
  * @see https://apihub.kma.go.kr/static/html/attach/wrn_table.html
220
220
  */
221
221
  export type KMA_Res_AlertCommand =
222
- | "발표"
223
- | "대치"
224
- | "해제"
225
- | "대치해제"
226
- | "연장"
227
- | "변경"
228
- | "변경해제";
222
+ "발표" | "대치" | "해제" | "대치해제" | "연장" | "변경" | "변경해제";