@uniai-fe/uds-primitives 0.0.14 → 0.0.15

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
@@ -721,14 +721,18 @@
721
721
  --font-body-medium-line-height: 1.5em;
722
722
  --font-body-medium-letter-spacing: 0px;
723
723
  --font-body-medium-weight: 500;
724
- --font-body-small-size: 15px;
724
+ --font-body-small-size: 16px;
725
725
  --font-body-small-line-height: 1.5em;
726
726
  --font-body-small-letter-spacing: 0px;
727
727
  --font-body-small-weight: 400;
728
- --font-body-xsmall-size: 13px;
728
+ --font-body-xsmall-size: 15px;
729
729
  --font-body-xsmall-line-height: 1.5em;
730
730
  --font-body-xsmall-letter-spacing: 0px;
731
731
  --font-body-xsmall-weight: 400;
732
+ --font-body-xxsmall-size: 13px;
733
+ --font-body-xxsmall-line-height: 1.5em;
734
+ --font-body-xxsmall-letter-spacing: 0px;
735
+ --font-body-xxsmall-weight: 400;
732
736
  }
733
737
  }
734
738
  @layer theme.tokens.typography {
@@ -1928,271 +1932,6 @@ figure.chip {
1928
1932
  height: 100%;
1929
1933
  }
1930
1934
 
1931
- :where(.radix-themes, .theme-root, :root) {
1932
- /* dialog essentials */
1933
- --dialog-overlay-bg: rgba(5, 6, 12, 0.55);
1934
- --dialog-panel-width: 360px;
1935
- --dialog-panel-max-width: calc(100vw - var(--spacing-padding-10) * 2);
1936
- --dialog-panel-bg: var(--color-bg-surface-static-white);
1937
- --dialog-panel-radius: var(--theme-radius-large-1);
1938
- --dialog-panel-shadow: 0px 18px 40px rgba(8, 11, 30, 0.18);
1939
- --dialog-border-color: var(--color-border-standard-cool-gray);
1940
- --dialog-title-color: var(--color-label-strong);
1941
- --dialog-title-font-size: var(--font-heading-xsmall-size);
1942
- --dialog-title-line-height: var(--font-heading-xsmall-line-height);
1943
- --dialog-title-weight: var(--font-heading-xsmall-weight);
1944
- --dialog-body-color: var(--color-label-standard);
1945
- --dialog-body-font-size: var(--font-body-small-size);
1946
- --dialog-body-line-height: var(--font-body-small-line-height);
1947
- --dialog-description-color: var(--color-label-standard);
1948
- --dialog-description-font-size: var(--font-body-small-size);
1949
- --dialog-description-line-height: var(--font-body-small-line-height);
1950
- /* alert(notice) defaults */
1951
- --notice-dialog-section-padding-x: var(--spacing-padding-8);
1952
- --notice-dialog-section-padding-y: var(--spacing-padding-10);
1953
- --notice-dialog-action-height: 52px;
1954
- /* confirm defaults */
1955
- --confirm-dialog-header-padding-x: var(--spacing-padding-6);
1956
- --confirm-dialog-header-padding-y: var(--spacing-padding-7);
1957
- --confirm-dialog-body-padding-x: var(--spacing-padding-6);
1958
- --confirm-dialog-body-padding-y: var(--spacing-padding-7);
1959
- --confirm-dialog-footer-padding-x: var(--spacing-padding-6);
1960
- --confirm-dialog-footer-padding-y: var(--spacing-padding-7);
1961
- --confirm-dialog-actions-gap: var(--spacing-gap-5);
1962
- }
1963
-
1964
- .dialog-overlay {
1965
- position: fixed;
1966
- inset: 0;
1967
- background-color: var(--dialog-overlay-bg);
1968
- opacity: 0;
1969
- transition: opacity 0.2s ease;
1970
- will-change: opacity;
1971
- }
1972
- .dialog-overlay[data-state=open] {
1973
- opacity: 1;
1974
- }
1975
- .dialog-overlay[data-state=closed] {
1976
- opacity: 0;
1977
- pointer-events: none;
1978
- }
1979
-
1980
- @media (prefers-reduced-motion: reduce) {
1981
- .dialog-overlay {
1982
- transition: none;
1983
- }
1984
- }
1985
- .dialog-content {
1986
- position: fixed;
1987
- top: 50%;
1988
- left: 50%;
1989
- transform: translate(-50%, -50%);
1990
- width: min(var(--dialog-panel-width), var(--dialog-panel-max-width));
1991
- max-width: var(--dialog-panel-max-width);
1992
- max-height: calc(100vh - var(--spacing-padding-10) * 2);
1993
- background-color: var(--dialog-panel-bg);
1994
- border-radius: var(--dialog-panel-radius);
1995
- box-shadow: var(--dialog-panel-shadow);
1996
- display: flex;
1997
- flex-direction: column;
1998
- overflow: hidden;
1999
- overflow-y: auto;
2000
- overscroll-behavior: contain;
2001
- outline: none;
2002
- border: none;
2003
- gap: 0;
2004
- opacity: 0;
2005
- transition: opacity 0.2s ease, transform 0.2s ease;
2006
- }
2007
- .dialog-content[data-state=open] {
2008
- opacity: 1;
2009
- transform: translate(-50%, -50%);
2010
- }
2011
- .dialog-content[data-state=closed] {
2012
- opacity: 0;
2013
- transform: translate(-50%, calc(-50% + 12px));
2014
- pointer-events: none;
2015
- }
2016
-
2017
- @media (prefers-reduced-motion: reduce) {
2018
- .dialog-content {
2019
- transition: none;
2020
- }
2021
- }
2022
- .dialog-section {
2023
- padding: 0;
2024
- }
2025
- .dialog-section[data-section=actions] {
2026
- flex-shrink: 0;
2027
- }
2028
-
2029
- .dialog-title {
2030
- margin: 0;
2031
- color: var(--dialog-title-color);
2032
- font-size: var(--dialog-title-font-size);
2033
- line-height: var(--dialog-title-line-height);
2034
- font-weight: var(--dialog-title-weight);
2035
- }
2036
-
2037
- .dialog-description {
2038
- margin: 0;
2039
- color: var(--dialog-description-color);
2040
- font-size: var(--dialog-description-font-size);
2041
- line-height: var(--dialog-description-line-height);
2042
- }
2043
-
2044
- .dialog-button {
2045
- display: flex;
2046
- align-items: center;
2047
- justify-content: center;
2048
- width: 100%;
2049
- border: none;
2050
- border-radius: 0;
2051
- margin: 0;
2052
- padding: 0;
2053
- font-size: var(--font-body-medium-size);
2054
- line-height: var(--font-body-medium-line-height);
2055
- font-weight: var(--font-body-medium-weight);
2056
- cursor: pointer;
2057
- background: transparent;
2058
- color: var(--color-label-strong);
2059
- transition: background-color 0.15s ease, color 0.15s ease;
2060
- }
2061
-
2062
- .dialog-button[data-native-element=true] {
2063
- appearance: none;
2064
- }
2065
-
2066
- .notice-dialog-header,
2067
- .notice-dialog-body {
2068
- padding: var(--notice-dialog-section-padding-y) var(--notice-dialog-section-padding-x);
2069
- }
2070
-
2071
- .notice-dialog-header {
2072
- display: flex;
2073
- flex-direction: column;
2074
- gap: var(--spacing-gap-3);
2075
- text-align: center;
2076
- }
2077
-
2078
- .notice-dialog-body {
2079
- text-align: center;
2080
- }
2081
-
2082
- .notice-dialog-body p {
2083
- margin: 0 0 var(--spacing-gap-2);
2084
- color: var(--dialog-body-color);
2085
- font-size: var(--dialog-body-font-size);
2086
- line-height: 1.5em;
2087
- font-weight: var(--font-body-small-weight);
2088
- word-break: keep-all;
2089
- }
2090
-
2091
- .notice-dialog-body p:last-child {
2092
- margin-bottom: 0;
2093
- }
2094
-
2095
- .notice-dialog-actions {
2096
- border-top: 1px solid var(--dialog-border-color);
2097
- display: flex;
2098
- align-items: stretch;
2099
- flex-direction: row;
2100
- padding: 0;
2101
- gap: 0;
2102
- justify-content: center;
2103
- overflow: hidden;
2104
- --button-min-height: var(--notice-dialog-action-height);
2105
- --button-padding-inline: 0;
2106
- --button-padding-block: 0;
2107
- --button-border-radius: 0;
2108
- }
2109
-
2110
- .notice-dialog-actions > * {
2111
- flex: 1;
2112
- }
2113
-
2114
- .notice-dialog-actions .button {
2115
- display: flex;
2116
- align-items: center;
2117
- justify-content: center;
2118
- width: 100%;
2119
- border: none;
2120
- border-radius: 0;
2121
- margin: 0;
2122
- padding: 0;
2123
- font-size: var(--font-body-medium-size);
2124
- line-height: var(--font-body-medium-line-height);
2125
- font-weight: var(--font-body-medium-weight);
2126
- cursor: pointer;
2127
- background: transparent;
2128
- color: var(--color-label-strong);
2129
- transition: background-color 0.15s ease, color 0.15s ease;
2130
- background-color: var(--color-common-100);
2131
- color: var(--color-primary-default);
2132
- }
2133
- .notice-dialog-actions .button:hover {
2134
- background-color: var(--color-bg-surface-static-cool-gray);
2135
- }
2136
-
2137
- .notice-dialog-actions .button:not(:first-child),
2138
- .notice-dialog-actions [data-native-element=true]:not(:first-child) {
2139
- border-left: 1px solid var(--dialog-border-color);
2140
- }
2141
-
2142
- .confirm-dialog-header {
2143
- padding: var(--confirm-dialog-header-padding-y) var(--confirm-dialog-header-padding-x);
2144
- }
2145
-
2146
- .confirm-dialog-body {
2147
- padding: var(--confirm-dialog-body-padding-y) var(--confirm-dialog-body-padding-x);
2148
- }
2149
-
2150
- .confirm-dialog-header {
2151
- display: flex;
2152
- flex-direction: column;
2153
- gap: var(--spacing-gap-2);
2154
- text-align: center;
2155
- }
2156
-
2157
- .confirm-dialog-body {
2158
- display: flex;
2159
- flex-direction: column;
2160
- align-items: center;
2161
- gap: var(--spacing-gap-2);
2162
- text-align: center;
2163
- }
2164
-
2165
- .confirm-dialog-body p {
2166
- margin: 0;
2167
- color: var(--dialog-body-color);
2168
- font-size: var(--dialog-body-font-size);
2169
- line-height: 1.5em;
2170
- font-weight: var(--font-body-small-weight);
2171
- word-break: keep-all;
2172
- }
2173
-
2174
- .confirm-dialog-description {
2175
- margin: 0;
2176
- color: var(--dialog-body-color);
2177
- font-size: var(--dialog-body-font-size);
2178
- line-height: 1.5em;
2179
- font-weight: var(--font-body-small-weight);
2180
- word-break: keep-all;
2181
- }
2182
-
2183
- .confirm-dialog-actions {
2184
- display: flex;
2185
- align-items: stretch;
2186
- flex-direction: row;
2187
- padding: var(--confirm-dialog-footer-padding-y) var(--confirm-dialog-footer-padding-x);
2188
- gap: var(--confirm-dialog-actions-gap);
2189
- justify-content: center;
2190
- }
2191
-
2192
- .confirm-dialog-actions > * {
2193
- flex: 1;
2194
- }
2195
-
2196
1935
  :where(.radix-themes, .theme-root, :root) {
2197
1936
  --drawer-overlay-bg: rgba(0, 0, 0, 0.44);
2198
1937
  --drawer-surface-bg: var(--color-bg-surface-static-white);
@@ -2734,6 +2473,18 @@ figure.chip {
2734
2473
  color: var(--theme-input-helper-color);
2735
2474
  }
2736
2475
 
2476
+ .email-verification {
2477
+ display: flex;
2478
+ flex-direction: column;
2479
+ gap: var(--spacing-gap-4);
2480
+ }
2481
+
2482
+ .email-verification__countdown {
2483
+ font-size: var(--font-caption-medium-size);
2484
+ line-height: var(--font-caption-medium-line-height);
2485
+ color: var(--theme-input-helper-color);
2486
+ }
2487
+
2737
2488
  /* TODO(label): 스타일을 SOT 토큰 값으로 정의한다. */
2738
2489
  :where(.radix-themes, .theme-root, :root) {
2739
2490
  --theme-navigation-height: 86px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,144 @@
1
+ import type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from "react";
2
+ import { forwardRef, useCallback, useMemo } from "react";
3
+ import type { InputProps, InputState } from "../../types";
4
+ import { Text } from "./Base";
5
+ import { IdentificationInput } from "./Identification";
6
+
7
+ /**
8
+ * EmailVerificationInput props. 이메일 입력 + 인증 요청/코드 입력 옵션을 정의한다.
9
+ * @property {string} [value] 제어형 값.
10
+ * @property {string} [defaultValue] 비제어 초기값.
11
+ * @property {(value: string) => void} [onValueChange] 값 변경 시 호출.
12
+ * @property {ComponentPropsWithoutRef<"input">["onChange"]} [onChange] native onChange override.
13
+ * @property {() => void} [onRequestCode] 인증 요청 버튼 클릭 시 호출(optional).
14
+ * @property {string} [requestButtonLabel="인증번호 요청"] 인증 요청 버튼 라벨(optional).
15
+ * @property {boolean} [requestButtonDisabled] 인증 요청 버튼 disabled(optional).
16
+ * @property {ReactNode} [countdownText] 인증 제한 시간 안내 텍스트(optional).
17
+ * @property {boolean} [codeVisible] 인증번호 입력 UI 노출 여부(optional).
18
+ * @property {number} [codeLength=6] 인증번호 길이(optional).
19
+ * @property {ReactNode} [codeLabel] 인증번호 입력 label(optional).
20
+ * @property {ReactNode} [codeHelper] 인증번호 helper(optional).
21
+ * @property {InputState} [codeState] 인증번호 입력 상태(optional).
22
+ * @property {(code: string) => void} [onCodeComplete] 인증번호 입력 완료 시 호출(optional).
23
+ */
24
+ export interface EmailVerificationInputProps extends Omit<
25
+ InputProps,
26
+ "type" | "inputMode" | "pattern" | "onChange" | "value" | "defaultValue"
27
+ > {
28
+ value?: string;
29
+ defaultValue?: string;
30
+ onValueChange?: (value: string) => void;
31
+ onChange?: ComponentPropsWithoutRef<"input">["onChange"];
32
+ onRequestCode?: () => void;
33
+ requestButtonLabel?: string;
34
+ requestButtonDisabled?: boolean;
35
+ countdownText?: ReactNode;
36
+ codeVisible?: boolean;
37
+ codeLength?: number;
38
+ codeLabel?: ReactNode;
39
+ codeHelper?: ReactNode;
40
+ codeState?: InputState;
41
+ onCodeComplete?: (code: string) => void;
42
+ }
43
+
44
+ /**
45
+ * 이메일 인증 입력 컴포넌트; 이메일 입력 + 인증요청 버튼 + OneTimeCode 입력을 옵션으로 제공한다.
46
+ * @component
47
+ * @param {EmailVerificationInputProps} props 이메일 인증 props
48
+ * @param {string} [props.value] 제어형 이메일 값
49
+ * @param {string} [props.defaultValue] 비제어 이메일 초기값
50
+ * @param {(value: string) => void} [props.onValueChange] 이메일 변경 콜백
51
+ * @param {ComponentPropsWithoutRef<"input">["onChange"]} [props.onChange] native onChange override
52
+ * @param {() => void} [props.onRequestCode] 인증요청 버튼 클릭 시 호출
53
+ * @param {string} [props.requestButtonLabel] 인증요청 버튼 라벨
54
+ * @param {boolean} [props.requestButtonDisabled] 인증요청 버튼 disabled
55
+ * @param {ReactNode} [props.countdownText] 제한 시간 안내 텍스트
56
+ * @param {boolean} [props.codeVisible] 인증번호 입력 UI 노출 여부
57
+ * @param {number} [props.codeLength] 인증번호 길이
58
+ * @param {ReactNode} [props.codeLabel] 인증번호 label
59
+ * @param {ReactNode} [props.codeHelper] 인증번호 helper
60
+ * @param {InputState} [props.codeState] 인증번호 입력 상태
61
+ * @param {(code: string) => void} [props.onCodeComplete] 인증번호 입력 완료 시 호출
62
+ */
63
+ const EmailVerificationInput = forwardRef<
64
+ HTMLInputElement,
65
+ EmailVerificationInputProps
66
+ >(
67
+ (
68
+ {
69
+ value,
70
+ defaultValue,
71
+ onValueChange,
72
+ onChange,
73
+ onRequestCode,
74
+ requestButtonLabel = "인증번호 요청",
75
+ requestButtonDisabled,
76
+ countdownText,
77
+ codeVisible,
78
+ codeLength = 6,
79
+ codeLabel,
80
+ codeHelper,
81
+ codeState,
82
+ onCodeComplete,
83
+ right,
84
+ ...restProps
85
+ },
86
+ forwardedRef,
87
+ ) => {
88
+ const handleChange = useCallback(
89
+ (event: ChangeEvent<HTMLInputElement>) => {
90
+ onValueChange?.(event.currentTarget.value);
91
+ onChange?.(event);
92
+ },
93
+ [onChange, onValueChange],
94
+ );
95
+
96
+ const actionButton = useMemo(() => {
97
+ if (!onRequestCode) {
98
+ return null;
99
+ }
100
+
101
+ return (
102
+ <button
103
+ type="button"
104
+ className="input-action-button"
105
+ onClick={onRequestCode}
106
+ disabled={requestButtonDisabled}
107
+ >
108
+ {requestButtonLabel}
109
+ </button>
110
+ );
111
+ }, [onRequestCode, requestButtonDisabled, requestButtonLabel]);
112
+
113
+ return (
114
+ <div className="email-verification">
115
+ <Text
116
+ {...restProps}
117
+ ref={forwardedRef}
118
+ type="email"
119
+ inputMode="email"
120
+ value={value}
121
+ defaultValue={defaultValue}
122
+ onChange={handleChange}
123
+ right={right ?? actionButton}
124
+ />
125
+ {countdownText ? (
126
+ <div className="email-verification__countdown">{countdownText}</div>
127
+ ) : null}
128
+ {codeVisible ? (
129
+ <IdentificationInput
130
+ length={codeLength}
131
+ label={codeLabel}
132
+ helper={codeHelper}
133
+ state={codeState}
134
+ onComplete={onCodeComplete}
135
+ />
136
+ ) : null}
137
+ </div>
138
+ );
139
+ },
140
+ );
141
+
142
+ EmailVerificationInput.displayName = "EmailVerificationInput";
143
+
144
+ export { EmailVerificationInput };
@@ -10,9 +10,9 @@ import { Text } from "./Base";
10
10
  * @property {string} [defaultValue] 비제어 초기값(숫자/포맷 모두 허용).
11
11
  * @property {(value: string, digits: string) => void} [onValueChange] 포맷팅 값/숫자만 값을 함께 전달.
12
12
  * @property {ComponentPropsWithoutRef<"input">["onChange"]} [onChange] native onChange override.
13
- * @property {() => void} [onRequestCode] right 슬롯 버튼 클릭 시 호출.
14
- * @property {string} [requestButtonLabel="인증번호 요청"] right 슬롯 버튼 라벨.
15
- * @property {boolean} [requestButtonDisabled] right 슬롯 버튼 disabled 여부.
13
+ * @property {() => void} [onRequestCode] 인증 요청 버튼 클릭 시 호출(optional).
14
+ * @property {string} [requestButtonLabel="인증번호 요청"] 인증 요청 버튼 라벨(optional).
15
+ * @property {boolean} [requestButtonDisabled] 인증 요청 버튼 disabled(optional).
16
16
  */
17
17
  export interface PhoneInputProps extends Omit<
18
18
  InputProps,
@@ -35,10 +35,16 @@ const normalizeDigits = (value?: string) => (value ?? "").replace(/\D/g, "");
35
35
  const formatPhoneNumber = (digits: string) => maskPhone(digits);
36
36
 
37
37
  /**
38
- * PhoneInput — 휴대폰 번호 마스킹과 인증번호 요청 버튼을 제공하는 입력.
38
+ * 휴대폰 번호 입력 컴포넌트; 기본은 마스킹만 제공하고 인증 UI는 optional props로 노출한다.
39
39
  * @component
40
- * @param {PhoneInputProps} props Phone 전용 props.
41
- * 나머지 Text Input 공통 props(priority/size/state/helper 등)도 동일하게 사용할 수 있다.
40
+ * @param {PhoneInputProps} props 휴대폰 입력 props
41
+ * @param {string} [props.value] 제어형 포맷
42
+ * @param {string} [props.defaultValue] 비제어 초기값
43
+ * @param {(value: string, digits: string) => void} [props.onValueChange] 포맷/숫자 변경 콜백
44
+ * @param {ComponentPropsWithoutRef<"input">["onChange"]} [props.onChange] native onChange override
45
+ * @param {() => void} [props.onRequestCode] 인증 요청 버튼 클릭 시 호출
46
+ * @param {string} [props.requestButtonLabel] 인증 요청 버튼 라벨
47
+ * @param {boolean} [props.requestButtonDisabled] 인증 요청 버튼 disabled
42
48
  */
43
49
  const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
44
50
  (
@@ -2,9 +2,11 @@ export { Text } from "./Base";
2
2
  export { Text as Input } from "./Base";
3
3
  export { PasswordInput } from "./Password";
4
4
  export { PhoneInput } from "./Phone";
5
+ export { EmailVerificationInput } from "./EmailVerification";
5
6
  export { SearchInput } from "./Search";
6
7
  export { IdentificationInput } from "./Identification";
7
8
  export type { InputPasswordProps } from "./Password";
8
9
  export type { PhoneInputProps } from "./Phone";
10
+ export type { EmailVerificationInputProps } from "./EmailVerification";
9
11
  export type { SearchInputProps } from "./Search";
10
12
  export type { IdentificationInputProps } from "./Identification";
@@ -373,3 +373,15 @@
373
373
  font-size: var(--font-label-small-size);
374
374
  color: var(--theme-input-helper-color);
375
375
  }
376
+
377
+ .email-verification {
378
+ display: flex;
379
+ flex-direction: column;
380
+ gap: var(--spacing-gap-4);
381
+ }
382
+
383
+ .email-verification__countdown {
384
+ font-size: var(--font-caption-medium-size);
385
+ line-height: var(--font-caption-medium-line-height);
386
+ color: var(--theme-input-helper-color);
387
+ }
package/src/index.scss CHANGED
@@ -8,7 +8,6 @@
8
8
  @use "./components/calendar";
9
9
  @use "./components/checkbox";
10
10
  @use "./components/chip";
11
- @use "./components/dialog";
12
11
  @use "./components/drawer";
13
12
  @use "./components/dropdown";
14
13
  @use "./components/input";
package/src/index.tsx CHANGED
@@ -12,7 +12,6 @@ export * from "./components/radio";
12
12
  export * from "./components/select";
13
13
  export * from "./components/tab";
14
14
  export * from "./components/navigation";
15
- export * from "./components/dialog";
16
15
  export * from "./components/dropdown";
17
16
  export * from "./components/drawer";
18
17
  export * from "./components/scrollbar";
@@ -59,12 +59,12 @@ export interface InputFieldOptions {
59
59
  }
60
60
 
61
61
  /**
62
- * Input Field Config; attr + style + options + props
63
- * @interface InputFieldConfig
62
+ * Input Field Props; attr + style + options + control props
63
+ * @interface InputFieldProps
64
64
  * @template TProps
65
65
  * @template FieldElement
66
66
  */
67
- export interface InputFieldConfig<
67
+ export interface InputFieldProps<
68
68
  TProps extends InputProps = InputProps,
69
69
  FieldElement extends HTMLElement = HTMLInputElement,
70
70
  >