@uniai-fe/uds-primitives 0.0.13 → 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 +18 -267
- package/package.json +3 -3
- package/src/components/input/markup/text/Base.tsx +7 -7
- package/src/components/input/markup/text/EmailVerification.tsx +144 -0
- package/src/components/input/markup/text/Phone.tsx +12 -6
- package/src/components/input/markup/text/index.ts +2 -0
- package/src/components/input/styles/index.scss +12 -0
- package/src/components/input/types/index.ts +5 -5
- package/src/index.scss +0 -1
- package/src/index.tsx +0 -1
- package/src/types/form-field.ts +3 -3
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:
|
|
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:
|
|
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.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "UNIAI Design System; Primitives Components Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
"@uniai-fe/util-functions": "workspace:*",
|
|
92
92
|
"eslint": "^9.39.2",
|
|
93
93
|
"prettier": "^3.7.4",
|
|
94
|
-
"react-hook-form": "^7.
|
|
95
|
-
"sass": "^1.97.
|
|
94
|
+
"react-hook-form": "^7.69.0",
|
|
95
|
+
"sass": "^1.97.1",
|
|
96
96
|
"typescript": "~5.9.3"
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -79,9 +79,9 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
|
|
|
79
79
|
block = false,
|
|
80
80
|
left,
|
|
81
81
|
right,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
clear,
|
|
83
|
+
success,
|
|
84
|
+
error,
|
|
85
85
|
label,
|
|
86
86
|
helper,
|
|
87
87
|
hideHelper,
|
|
@@ -167,13 +167,13 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
|
|
|
167
167
|
|
|
168
168
|
const statusSlot = useMemo(() => {
|
|
169
169
|
if (resolvedState === "success") {
|
|
170
|
-
return
|
|
170
|
+
return success ?? defaultStatusIcon;
|
|
171
171
|
}
|
|
172
172
|
if (resolvedState === "error") {
|
|
173
|
-
return
|
|
173
|
+
return error ?? defaultStatusIcon;
|
|
174
174
|
}
|
|
175
175
|
return null;
|
|
176
|
-
}, [defaultStatusIcon,
|
|
176
|
+
}, [defaultStatusIcon, error, resolvedState, success]);
|
|
177
177
|
|
|
178
178
|
const defaultClearIcon = useMemo(() => {
|
|
179
179
|
if (visualState === "active") {
|
|
@@ -182,7 +182,7 @@ const Text = forwardRef<HTMLInputElement, InputProps>(
|
|
|
182
182
|
return null;
|
|
183
183
|
}, [visualState]);
|
|
184
184
|
|
|
185
|
-
const effectiveClearIcon =
|
|
185
|
+
const effectiveClearIcon = clear ?? defaultClearIcon;
|
|
186
186
|
const showClearIcon = Boolean(
|
|
187
187
|
effectiveClearIcon && hasValue && resolvedState !== "disabled",
|
|
188
188
|
);
|
|
@@ -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]
|
|
14
|
-
* @property {string} [requestButtonLabel="인증번호 요청"]
|
|
15
|
-
* @property {boolean} [requestButtonDisabled]
|
|
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
|
-
*
|
|
38
|
+
* 휴대폰 번호 입력 컴포넌트; 기본은 마스킹만 제공하고 인증 UI는 optional props로 노출한다.
|
|
39
39
|
* @component
|
|
40
|
-
* @param {PhoneInputProps} props
|
|
41
|
-
*
|
|
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
|
+
}
|
|
@@ -30,12 +30,12 @@ type NativeInputProps = ComponentPropsWithoutRef<"input">;
|
|
|
30
30
|
/**
|
|
31
31
|
* 좌우 슬롯과 status 아이콘 정의.
|
|
32
32
|
*/
|
|
33
|
-
export interface
|
|
33
|
+
export interface InputIcon {
|
|
34
34
|
left?: ReactNode;
|
|
35
35
|
right?: ReactNode;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
clear?: ReactNode;
|
|
37
|
+
success?: ReactNode;
|
|
38
|
+
error?: ReactNode;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -51,7 +51,7 @@ export interface InputFeedback {
|
|
|
51
51
|
* 텍스트 입력의 핵심 props. native input 속성에서 size는 제외하고 left/right 등 슬롯을 별도로 정의한다.
|
|
52
52
|
*/
|
|
53
53
|
export interface InputProps
|
|
54
|
-
extends Omit<NativeInputProps, "size">,
|
|
54
|
+
extends Omit<NativeInputProps, "size">, InputIcon, InputFeedback {
|
|
55
55
|
/** semantic color/token 세트 */
|
|
56
56
|
priority?: InputPriority;
|
|
57
57
|
/** 높이/타이포 세트 */
|
package/src/index.scss
CHANGED
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";
|
package/src/types/form-field.ts
CHANGED
|
@@ -59,12 +59,12 @@ export interface InputFieldOptions {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Input Field
|
|
63
|
-
* @interface
|
|
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
|
|
67
|
+
export interface InputFieldProps<
|
|
68
68
|
TProps extends InputProps = InputProps,
|
|
69
69
|
FieldElement extends HTMLElement = HTMLInputElement,
|
|
70
70
|
>
|