@uniai-fe/uds-primitives 0.0.16 → 0.0.18
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/README.md +9 -1
- package/dist/styles.css +130 -35
- package/package.json +2 -2
- package/src/components/checkbox/img/check-large.svg +1 -1
- package/src/components/checkbox/img/check-medium.svg +1 -1
- package/src/components/checkbox/markup/Checkbox.tsx +6 -3
- package/src/components/checkbox/styles/index.scss +38 -25
- package/src/components/input/markup/text/AuthCode.tsx +145 -0
- package/src/components/input/markup/text/Base.tsx +71 -58
- package/src/components/input/markup/text/{EmailVerification.tsx → Email.tsx} +50 -31
- package/src/components/input/markup/text/InputUtilityButton.tsx +46 -0
- package/src/components/input/markup/text/Phone.tsx +65 -7
- package/src/components/input/markup/text/index.ts +4 -4
- package/src/components/input/styles/index.scss +104 -13
- package/src/components/input/types/index.ts +1 -0
- package/src/components/input/markup/text/Identification.tsx +0 -159
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @uniai-fe/uds-primitives
|
|
2
2
|
|
|
3
|
-
`@uniai-fe/uds-foundation` 토큰 위에 Radix UI 컴포넌트를 얇게 감싼 **기초 UI 컴포넌트 컬렉션**입니다. Next.js 등 React 런타임에서 바로 import해 버튼·입력·네비게이션 등 공통 요소를 일관된 스타일로 사용할 수 있습니다.
|
|
3
|
+
`@uniai-fe/uds-foundation` 토큰 위에 Radix UI 컴포넌트를 얇게 감싼 **기초 UI 컴포넌트 컬렉션**입니다. Next.js 등 React 런타임에서 바로 import해 버튼·입력·네비게이션 등 공통 요소를 일관된 스타일로 사용할 수 있습니다. Templates(`@uniai-fe/uds-templates`) 패키지에서 사용하는 Phone Input/Email Verification Input/OneTimeCode Input 등의 인증 시나리오 컴포넌트도 이곳에서 제공합니다.
|
|
4
4
|
|
|
5
5
|
## 설치
|
|
6
6
|
|
|
@@ -192,8 +192,16 @@ src/components/{category}/
|
|
|
192
192
|
## 문서
|
|
193
193
|
|
|
194
194
|
- `CONTEXT.md` 및 `CONTEXT-*.md`: 각 컴포넌트의 상태/진행/디자인 근거
|
|
195
|
+
- `CONTEXT-INPUT.md`: Phone/Email/OneTimeCode 등 인증 입력 시나리오 규칙을 포함하며, templates `CONTEXT-SIGNUP*.md`와 항상 동기화해야 한다.
|
|
195
196
|
- `RADIX-SIZE-GUIDE.md`: primitives 사이즈 체계와 Radix 매핑 규칙
|
|
196
197
|
|
|
198
|
+
### Signup 인증 입력 컴포넌트
|
|
199
|
+
|
|
200
|
+
- **PhoneInput**: 기본은 마스킹된 전화번호 입력만 제공한다. `onRequestCode`(optional)를 주입하면 인증요청 버튼이 우측 right 슬롯에 노출된다. Step1 User Info에서는 단순 입력만 필요하므로 optional props를 생략한다.
|
|
201
|
+
- **EmailInput**: 이메일 입력 + 인증요청 버튼 + countdown + OneTimeCode 입력을 하나로 묶는다. `countdownText`, `codeVisible`, `codeLength`, `codeLabel`, `codeHelper`, `codeState`, `onCodeComplete`로 Step2 Verify & Agreement 상태를 제어한다.
|
|
202
|
+
- **AuthCodeInput**: length 지정형 OneTimeCode grid. EmailInput 내부에서 사용하지만 서비스 앱도 직접 import할 수 있다.
|
|
203
|
+
- 변경 시에는 다음 문서를 함께 업데이트한다: `packages/design/primitives/docs/CONTEXT-INPUT.md`, `packages/design/templates/docs/CONTEXT-SIGNUP.md`, `CONTEXT-SIGNUP-FLOW.md`, `packages/design/templates/docs/STORYBOOK.md`, `apps/design-storybook/src/stories/templates/auth/AuthSignup.stories.tsx`.
|
|
204
|
+
|
|
197
205
|
* **컨벤션**: 모든 컴포넌트/스토리/문서는 slot/prefix/suffix 용어를 사용하지 않고, 레이아웃 기준(`header/body/footer`, 2단 구조는 `upper/lower`)과 `util*` 키워드를 사용한다. 인터랙션 함수는 `on*` 접두사를 사용하고, JSDoc `@param`은 depth 전체를 풀어 쓴다.
|
|
198
206
|
|
|
199
207
|
필요한 컨텍스트를 확인한 뒤 컴포넌트를 import해 사용하면 됩니다.
|
package/dist/styles.css
CHANGED
|
@@ -1663,13 +1663,15 @@
|
|
|
1663
1663
|
--theme-checkbox-surface: var(--color-common-100);
|
|
1664
1664
|
--theme-checkbox-surface-selected: var(--color-primary-default);
|
|
1665
1665
|
--theme-checkbox-surface-disabled: var(--color-neutral-95);
|
|
1666
|
+
--theme-checkbox-surface-selected-disabled: rgba(26, 106, 255, 0.28);
|
|
1666
1667
|
--theme-checkbox-label-color: var(--color-label-strong);
|
|
1667
1668
|
--theme-checkbox-label-disabled: var(--color-label-disabled);
|
|
1668
1669
|
--theme-checkbox-helper-color: var(--color-label-neutral);
|
|
1669
1670
|
--theme-checkbox-helper-disabled: var(--color-label-disabled);
|
|
1670
|
-
--theme-checkbox-icon-
|
|
1671
|
+
--theme-checkbox-icon-default: transparent;
|
|
1672
|
+
--theme-checkbox-icon-selected: var(--color-common-100);
|
|
1673
|
+
--theme-checkbox-icon-disabled-selected: var(--color-common-100);
|
|
1671
1674
|
--theme-checkbox-focus-ring: rgba(2, 84, 255, 0.32);
|
|
1672
|
-
--theme-checkbox-disabled-selected-opacity: 0.28;
|
|
1673
1675
|
}
|
|
1674
1676
|
|
|
1675
1677
|
.checkbox {
|
|
@@ -1695,7 +1697,6 @@
|
|
|
1695
1697
|
border-radius: var(--theme-checkbox-control-radius-large);
|
|
1696
1698
|
}
|
|
1697
1699
|
.checkbox[data-disabled=true] {
|
|
1698
|
-
opacity: 0.6;
|
|
1699
1700
|
cursor: not-allowed;
|
|
1700
1701
|
}
|
|
1701
1702
|
|
|
@@ -1703,49 +1704,64 @@
|
|
|
1703
1704
|
box-shadow: 0 0 0 2px var(--theme-checkbox-focus-ring);
|
|
1704
1705
|
}
|
|
1705
1706
|
|
|
1706
|
-
.checkbox-
|
|
1707
|
+
.checkbox-surface {
|
|
1707
1708
|
inline-size: var(--theme-checkbox-indicator-size-medium);
|
|
1708
1709
|
block-size: var(--theme-checkbox-indicator-size-medium);
|
|
1709
1710
|
display: inline-flex;
|
|
1710
1711
|
align-items: center;
|
|
1711
1712
|
justify-content: center;
|
|
1712
|
-
color: var(--theme-checkbox-icon-color);
|
|
1713
1713
|
border: var(--theme-checkbox-border-width) solid var(--theme-checkbox-border-color);
|
|
1714
1714
|
border-radius: var(--theme-checkbox-control-radius-medium);
|
|
1715
1715
|
background-color: var(--theme-checkbox-surface);
|
|
1716
|
-
transition: background-color 0.15s ease, border-color 0.15s ease
|
|
1717
|
-
}
|
|
1718
|
-
.checkbox-indicator svg {
|
|
1719
|
-
display: block;
|
|
1720
|
-
inline-size: auto;
|
|
1721
|
-
block-size: auto;
|
|
1722
|
-
max-inline-size: 100%;
|
|
1723
|
-
max-block-size: 100%;
|
|
1716
|
+
transition: background-color 0.15s ease, border-color 0.15s ease;
|
|
1724
1717
|
}
|
|
1725
1718
|
|
|
1726
|
-
.checkbox[data-size=large] .checkbox-
|
|
1719
|
+
.checkbox[data-size=large] .checkbox-surface {
|
|
1727
1720
|
inline-size: var(--theme-checkbox-indicator-size-large);
|
|
1728
1721
|
block-size: var(--theme-checkbox-indicator-size-large);
|
|
1729
1722
|
border-radius: var(--theme-checkbox-control-radius-large);
|
|
1730
1723
|
}
|
|
1731
1724
|
|
|
1732
|
-
.checkbox[data-state=checked] .checkbox-
|
|
1733
|
-
.checkbox[data-state=indeterminate] .checkbox-
|
|
1725
|
+
.checkbox[data-state=checked] .checkbox-surface,
|
|
1726
|
+
.checkbox[data-state=indeterminate] .checkbox-surface {
|
|
1734
1727
|
background-color: var(--theme-checkbox-surface-selected);
|
|
1735
1728
|
border-color: var(--theme-checkbox-border-selected);
|
|
1736
1729
|
}
|
|
1737
1730
|
|
|
1738
|
-
.checkbox[data-disabled=true] .checkbox-
|
|
1731
|
+
.checkbox[data-disabled=true] .checkbox-surface {
|
|
1739
1732
|
background-color: var(--theme-checkbox-surface-disabled);
|
|
1740
1733
|
border-color: var(--theme-checkbox-border-color);
|
|
1741
|
-
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.checkbox[data-disabled=true][data-state=checked] .checkbox-surface,
|
|
1737
|
+
.checkbox[data-disabled=true][data-state=indeterminate] .checkbox-surface {
|
|
1738
|
+
background-color: var(--theme-checkbox-surface-selected-disabled);
|
|
1739
|
+
border-color: var(--theme-checkbox-border-color);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
.checkbox-indicator {
|
|
1743
|
+
display: inline-flex;
|
|
1744
|
+
align-items: center;
|
|
1745
|
+
justify-content: center;
|
|
1746
|
+
color: var(--theme-checkbox-icon-default);
|
|
1747
|
+
transition: color 0.15s ease;
|
|
1748
|
+
}
|
|
1749
|
+
.checkbox-indicator svg {
|
|
1750
|
+
display: block;
|
|
1751
|
+
inline-size: auto;
|
|
1752
|
+
block-size: auto;
|
|
1753
|
+
max-inline-size: 100%;
|
|
1754
|
+
max-block-size: 100%;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.checkbox[data-state=checked] .checkbox-indicator,
|
|
1758
|
+
.checkbox[data-state=indeterminate] .checkbox-indicator {
|
|
1759
|
+
color: var(--theme-checkbox-icon-selected);
|
|
1742
1760
|
}
|
|
1743
1761
|
|
|
1744
1762
|
.checkbox[data-disabled=true][data-state=checked] .checkbox-indicator,
|
|
1745
1763
|
.checkbox[data-disabled=true][data-state=indeterminate] .checkbox-indicator {
|
|
1746
|
-
|
|
1747
|
-
border-color: var(--theme-checkbox-border-selected);
|
|
1748
|
-
opacity: var(--theme-checkbox-disabled-selected-opacity);
|
|
1764
|
+
color: var(--theme-checkbox-icon-disabled-selected);
|
|
1749
1765
|
}
|
|
1750
1766
|
|
|
1751
1767
|
.checkbox-field {
|
|
@@ -2165,6 +2181,8 @@ figure.chip {
|
|
|
2165
2181
|
--theme-input-radius-tertiary: var(--theme-radius-large-2);
|
|
2166
2182
|
--theme-input-label-color: var(--color-label-standard);
|
|
2167
2183
|
--theme-input-helper-color: var(--color-label-neutral);
|
|
2184
|
+
--theme-input-helper-success-color: var(--color-success);
|
|
2185
|
+
--theme-input-helper-error-color: var(--color-error);
|
|
2168
2186
|
--theme-input-helper-disabled-color: var(--color-label-disabled);
|
|
2169
2187
|
--theme-input-label-accent-color: var(--color-primary-default);
|
|
2170
2188
|
--theme-input-label-error-color: var(--color-error);
|
|
@@ -2248,24 +2266,53 @@ figure.chip {
|
|
|
2248
2266
|
padding-block: var(--spacing-padding-4);
|
|
2249
2267
|
background-color: transparent;
|
|
2250
2268
|
}
|
|
2269
|
+
.input-field[data-priority=secondary][data-state=active], .input-field[data-priority=secondary][data-state=focused] {
|
|
2270
|
+
border-bottom-color: var(--theme-input-border-active);
|
|
2271
|
+
border-bottom-width: var(--theme-input-border-width-emphasis);
|
|
2272
|
+
}
|
|
2273
|
+
.input-field[data-priority=secondary][data-state=success] {
|
|
2274
|
+
border-bottom-color: var(--theme-input-border-success);
|
|
2275
|
+
border-bottom-width: var(--theme-input-border-width-emphasis);
|
|
2276
|
+
}
|
|
2277
|
+
.input-field[data-priority=secondary][data-state=error] {
|
|
2278
|
+
border-bottom-color: var(--theme-input-border-error);
|
|
2279
|
+
border-bottom-width: var(--theme-input-border-width-emphasis);
|
|
2280
|
+
}
|
|
2281
|
+
.input-field[data-priority=secondary][data-state=disabled] {
|
|
2282
|
+
border-bottom-color: var(--theme-input-border-underline-disabled);
|
|
2283
|
+
border-bottom-width: var(--theme-input-border-width-default);
|
|
2284
|
+
}
|
|
2251
2285
|
.input-field[data-priority=tertiary] {
|
|
2252
2286
|
border-radius: var(--theme-input-radius-tertiary);
|
|
2253
2287
|
background-color: var(--theme-input-surface);
|
|
2254
2288
|
min-height: var(--theme-input-height-tertiary);
|
|
2255
|
-
flex-wrap: wrap;
|
|
2256
2289
|
row-gap: var(--spacing-gap-1);
|
|
2257
2290
|
column-gap: var(--theme-input-gap);
|
|
2291
|
+
flex-wrap: wrap;
|
|
2292
|
+
align-items: center;
|
|
2293
|
+
}
|
|
2294
|
+
.input-field[data-priority=tertiary] .input-field__control {
|
|
2295
|
+
display: grid;
|
|
2296
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
2297
|
+
column-gap: var(--theme-input-gap);
|
|
2298
|
+
row-gap: var(--spacing-gap-1);
|
|
2299
|
+
align-items: center;
|
|
2300
|
+
flex: 1 1 auto;
|
|
2301
|
+
min-width: 0;
|
|
2258
2302
|
}
|
|
2259
2303
|
.input-field[data-priority=tertiary] .input-inline-label {
|
|
2260
|
-
|
|
2304
|
+
grid-column: 1/-1;
|
|
2305
|
+
margin: 0;
|
|
2306
|
+
align-self: flex-start;
|
|
2261
2307
|
}
|
|
2262
2308
|
.input-field[data-priority=tertiary] .input-element {
|
|
2263
2309
|
min-height: var(--theme-size-medium-2);
|
|
2264
2310
|
width: auto;
|
|
2265
2311
|
flex: 1 1 auto;
|
|
2266
2312
|
}
|
|
2267
|
-
.input-field[data-priority=tertiary] .input-
|
|
2268
|
-
|
|
2313
|
+
.input-field[data-priority=tertiary] .input-field__utilities {
|
|
2314
|
+
align-self: center;
|
|
2315
|
+
margin-left: 0;
|
|
2269
2316
|
}
|
|
2270
2317
|
.input-field:not([data-priority=secondary])[data-state=active], .input-field:not([data-priority=secondary])[data-state=focused] {
|
|
2271
2318
|
border-color: var(--theme-input-border-active);
|
|
@@ -2311,6 +2358,25 @@ figure.chip {
|
|
|
2311
2358
|
box-shadow: none;
|
|
2312
2359
|
}
|
|
2313
2360
|
|
|
2361
|
+
.input-field__control {
|
|
2362
|
+
display: flex;
|
|
2363
|
+
align-items: center;
|
|
2364
|
+
gap: var(--theme-input-gap);
|
|
2365
|
+
flex: 1 1 auto;
|
|
2366
|
+
min-width: 0;
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
.input-field__utilities {
|
|
2370
|
+
display: flex;
|
|
2371
|
+
align-items: center;
|
|
2372
|
+
gap: var(--spacing-gap-2, 8px);
|
|
2373
|
+
flex-shrink: 0;
|
|
2374
|
+
margin-left: var(--spacing-gap-3, 12px);
|
|
2375
|
+
}
|
|
2376
|
+
.input-field__utilities .input-affix {
|
|
2377
|
+
margin-left: 0;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2314
2380
|
.input-inline-label {
|
|
2315
2381
|
order: -2;
|
|
2316
2382
|
flex-basis: 100%;
|
|
@@ -2340,7 +2406,7 @@ figure.chip {
|
|
|
2340
2406
|
}
|
|
2341
2407
|
|
|
2342
2408
|
.input-affix {
|
|
2343
|
-
display:
|
|
2409
|
+
display: flex;
|
|
2344
2410
|
align-items: center;
|
|
2345
2411
|
justify-content: center;
|
|
2346
2412
|
min-width: 20px;
|
|
@@ -2414,8 +2480,7 @@ figure.chip {
|
|
|
2414
2480
|
background-color: transparent;
|
|
2415
2481
|
}
|
|
2416
2482
|
|
|
2417
|
-
.input-password-toggle
|
|
2418
|
-
.input-action-button {
|
|
2483
|
+
.input-password-toggle {
|
|
2419
2484
|
border: none;
|
|
2420
2485
|
background: transparent;
|
|
2421
2486
|
color: var(--theme-input-label-accent-color);
|
|
@@ -2425,8 +2490,7 @@ figure.chip {
|
|
|
2425
2490
|
padding: 0;
|
|
2426
2491
|
cursor: pointer;
|
|
2427
2492
|
}
|
|
2428
|
-
.input-password-toggle:disabled
|
|
2429
|
-
.input-action-button:disabled {
|
|
2493
|
+
.input-password-toggle:disabled {
|
|
2430
2494
|
color: var(--theme-input-helper-disabled-color);
|
|
2431
2495
|
cursor: not-allowed;
|
|
2432
2496
|
}
|
|
@@ -2473,16 +2537,47 @@ figure.chip {
|
|
|
2473
2537
|
color: var(--theme-input-helper-color);
|
|
2474
2538
|
}
|
|
2475
2539
|
|
|
2476
|
-
.email-verification
|
|
2540
|
+
.email-verification,
|
|
2541
|
+
.phone-verification {
|
|
2477
2542
|
display: flex;
|
|
2478
2543
|
flex-direction: column;
|
|
2479
2544
|
gap: var(--spacing-gap-4);
|
|
2480
2545
|
}
|
|
2481
2546
|
|
|
2482
|
-
.
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2547
|
+
.auth-code-input__actions,
|
|
2548
|
+
.email-verification__code-actions,
|
|
2549
|
+
.phone-verification__code-actions {
|
|
2550
|
+
display: flex;
|
|
2551
|
+
align-items: center;
|
|
2552
|
+
justify-content: flex-end;
|
|
2553
|
+
gap: var(--spacing-gap-3);
|
|
2554
|
+
min-width: 0;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
.auth-code-input__countdown,
|
|
2558
|
+
.email-verification__countdown,
|
|
2559
|
+
.phone-verification__countdown {
|
|
2560
|
+
display: flex;
|
|
2561
|
+
align-items: center;
|
|
2562
|
+
font-weight: 500;
|
|
2563
|
+
font-style: normal;
|
|
2564
|
+
font-size: 13px;
|
|
2565
|
+
line-height: 1em;
|
|
2566
|
+
letter-spacing: -0.0025em;
|
|
2567
|
+
color: var(--color-primary-default);
|
|
2568
|
+
flex-shrink: 0;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
.button.input-utility-button {
|
|
2572
|
+
min-height: 32px;
|
|
2573
|
+
padding: var(--spacing-padding-2, 4px) var(--spacing-padding-6, 24px);
|
|
2574
|
+
border-radius: var(--shape-rounded-1, 8px);
|
|
2575
|
+
}
|
|
2576
|
+
.button.input-utility-button .button-label {
|
|
2577
|
+
font-size: var(--font-body-xxsmall-size);
|
|
2578
|
+
line-height: var(--font-body-xxsmall-line-height);
|
|
2579
|
+
letter-spacing: var(--font-body-xxsmall-letter-spacing);
|
|
2580
|
+
font-weight: var(--font-body-xxsmall-weight);
|
|
2486
2581
|
}
|
|
2487
2582
|
|
|
2488
2583
|
/* TODO(label): 스타일을 SOT 토큰 값으로 정의한다. */
|
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.18",
|
|
4
4
|
"description": "UNIAI Design System; Primitives Components Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
|
-
"packageManager": "pnpm@10.26.
|
|
17
|
+
"packageManager": "pnpm@10.26.2",
|
|
18
18
|
"engines": {
|
|
19
19
|
"node": ">=24",
|
|
20
20
|
"pnpm": ">=10"
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<path d="M0.699997 3.52844L4.23553 7.06398L10.5995 0.700012" stroke="
|
|
2
|
+
<path d="M0.699997 3.52844L4.23553 7.06398L10.5995 0.700012" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
3
|
</svg>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<path d="M0.170857 3.35307C-0.0569492 3.12526 -0.0569492 2.75569 0.170857 2.52788C0.398662 2.30008 0.768239 2.30008 0.996045 2.52788L3.52973 5.06157L8.42044 0.170859C8.64824 -0.0569466 9.01782 -0.056947 9.24562 0.170859C9.47343 0.398665 9.47343 0.768242 9.24562 0.996047L3.94232 6.29935C3.83293 6.40874 3.68444 6.47025 3.52973 6.47025C3.37502 6.47025 3.22653 6.40874 3.11713 6.29935L0.170857 3.35307Z" fill="
|
|
2
|
+
<path d="M0.170857 3.35307C-0.0569492 3.12526 -0.0569492 2.75569 0.170857 2.52788C0.398662 2.30008 0.768239 2.30008 0.996045 2.52788L3.52973 5.06157L8.42044 0.170859C8.64824 -0.0569466 9.01782 -0.056947 9.24562 0.170859C9.47343 0.398665 9.47343 0.768242 9.24562 0.996047L3.94232 6.29935C3.83293 6.40874 3.68444 6.47025 3.52973 6.47025C3.37502 6.47025 3.22653 6.40874 3.11713 6.29935L0.170857 3.35307Z" fill="currentColor"/>
|
|
3
3
|
</svg>
|
|
@@ -6,6 +6,7 @@ import CheckLargeIcon from "../img/check-large.svg";
|
|
|
6
6
|
import CheckMediumIcon from "../img/check-medium.svg";
|
|
7
7
|
|
|
8
8
|
const CHECKBOX_CLASSNAME = "checkbox";
|
|
9
|
+
const CHECKBOX_SURFACE_CLASSNAME = "checkbox-surface";
|
|
9
10
|
const CHECKBOX_INDICATOR_CLASSNAME = "checkbox-indicator";
|
|
10
11
|
const CHECKBOX_FIELD_CLASSNAME = "checkbox-field";
|
|
11
12
|
const CHECKBOX_LABEL_WRAPPER_CLASSNAME = "checkbox-label-wrapper";
|
|
@@ -46,9 +47,11 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
|
|
|
46
47
|
className={clsx(CHECKBOX_CLASSNAME, className)}
|
|
47
48
|
{...restProps}
|
|
48
49
|
>
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
50
|
+
<span className={CHECKBOX_SURFACE_CLASSNAME} aria-hidden="true">
|
|
51
|
+
<CheckboxPrimitive.Indicator className={CHECKBOX_INDICATOR_CLASSNAME}>
|
|
52
|
+
<IndicatorIcon aria-hidden />
|
|
53
|
+
</CheckboxPrimitive.Indicator>
|
|
54
|
+
</span>
|
|
52
55
|
</CheckboxPrimitive.Root>
|
|
53
56
|
);
|
|
54
57
|
},
|
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
--theme-checkbox-surface: var(--color-common-100);
|
|
14
14
|
--theme-checkbox-surface-selected: var(--color-primary-default);
|
|
15
15
|
--theme-checkbox-surface-disabled: var(--color-neutral-95);
|
|
16
|
+
--theme-checkbox-surface-selected-disabled: rgba(26, 106, 255, 0.28);
|
|
16
17
|
--theme-checkbox-label-color: var(--color-label-strong);
|
|
17
18
|
--theme-checkbox-label-disabled: var(--color-label-disabled);
|
|
18
19
|
--theme-checkbox-helper-color: var(--color-label-neutral);
|
|
19
20
|
--theme-checkbox-helper-disabled: var(--color-label-disabled);
|
|
20
|
-
--theme-checkbox-icon-
|
|
21
|
+
--theme-checkbox-icon-default: transparent;
|
|
22
|
+
--theme-checkbox-icon-selected: var(--color-common-100);
|
|
23
|
+
--theme-checkbox-icon-disabled-selected: var(--color-common-100);
|
|
21
24
|
--theme-checkbox-focus-ring: rgba(2, 84, 255, 0.32);
|
|
22
|
-
--theme-checkbox-disabled-selected-opacity: 0.28;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
.checkbox {
|
|
@@ -51,7 +53,6 @@
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
&[data-disabled="true"] {
|
|
54
|
-
opacity: 0.6;
|
|
55
56
|
cursor: not-allowed;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -60,58 +61,70 @@
|
|
|
60
61
|
box-shadow: 0 0 0 2px var(--theme-checkbox-focus-ring);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
.checkbox-
|
|
64
|
+
.checkbox-surface {
|
|
64
65
|
inline-size: var(--theme-checkbox-indicator-size-medium);
|
|
65
66
|
block-size: var(--theme-checkbox-indicator-size-medium);
|
|
66
67
|
display: inline-flex;
|
|
67
68
|
align-items: center;
|
|
68
69
|
justify-content: center;
|
|
69
|
-
color: var(--theme-checkbox-icon-color);
|
|
70
70
|
border: var(--theme-checkbox-border-width) solid
|
|
71
71
|
var(--theme-checkbox-border-color);
|
|
72
72
|
border-radius: var(--theme-checkbox-control-radius-medium);
|
|
73
73
|
background-color: var(--theme-checkbox-surface);
|
|
74
74
|
transition:
|
|
75
75
|
background-color 0.15s ease,
|
|
76
|
-
border-color 0.15s ease
|
|
77
|
-
color 0.15s ease,
|
|
78
|
-
opacity 0.15s ease;
|
|
79
|
-
|
|
80
|
-
svg {
|
|
81
|
-
display: block;
|
|
82
|
-
inline-size: auto;
|
|
83
|
-
block-size: auto;
|
|
84
|
-
max-inline-size: 100%;
|
|
85
|
-
max-block-size: 100%;
|
|
86
|
-
}
|
|
76
|
+
border-color 0.15s ease;
|
|
87
77
|
}
|
|
88
78
|
|
|
89
|
-
.checkbox[data-size="large"] .checkbox-
|
|
79
|
+
.checkbox[data-size="large"] .checkbox-surface {
|
|
90
80
|
inline-size: var(--theme-checkbox-indicator-size-large);
|
|
91
81
|
block-size: var(--theme-checkbox-indicator-size-large);
|
|
92
82
|
border-radius: var(--theme-checkbox-control-radius-large);
|
|
93
83
|
}
|
|
94
84
|
|
|
95
85
|
// 인디케이터 영역(16x16 / 20x20)에만 상태 색상을 입혀 Figma 레이아웃과 동일하게 유지한다.
|
|
96
|
-
.checkbox[data-state="checked"] .checkbox-
|
|
97
|
-
.checkbox[data-state="indeterminate"] .checkbox-
|
|
86
|
+
.checkbox[data-state="checked"] .checkbox-surface,
|
|
87
|
+
.checkbox[data-state="indeterminate"] .checkbox-surface {
|
|
98
88
|
background-color: var(--theme-checkbox-surface-selected);
|
|
99
89
|
border-color: var(--theme-checkbox-border-selected);
|
|
100
90
|
}
|
|
101
91
|
|
|
102
|
-
.checkbox[data-disabled="true"] .checkbox-
|
|
92
|
+
.checkbox[data-disabled="true"] .checkbox-surface {
|
|
103
93
|
background-color: var(--theme-checkbox-surface-disabled);
|
|
104
94
|
border-color: var(--theme-checkbox-border-color);
|
|
105
|
-
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.checkbox[data-disabled="true"][data-state="checked"] .checkbox-surface,
|
|
98
|
+
.checkbox[data-disabled="true"][data-state="indeterminate"] .checkbox-surface {
|
|
99
|
+
background-color: var(--theme-checkbox-surface-selected-disabled);
|
|
100
|
+
border-color: var(--theme-checkbox-border-color);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.checkbox-indicator {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
color: var(--theme-checkbox-icon-default);
|
|
108
|
+
transition: color 0.15s ease;
|
|
109
|
+
|
|
110
|
+
svg {
|
|
111
|
+
display: block;
|
|
112
|
+
inline-size: auto;
|
|
113
|
+
block-size: auto;
|
|
114
|
+
max-inline-size: 100%;
|
|
115
|
+
max-block-size: 100%;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.checkbox[data-state="checked"] .checkbox-indicator,
|
|
120
|
+
.checkbox[data-state="indeterminate"] .checkbox-indicator {
|
|
121
|
+
color: var(--theme-checkbox-icon-selected);
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
.checkbox[data-disabled="true"][data-state="checked"] .checkbox-indicator,
|
|
109
125
|
.checkbox[data-disabled="true"][data-state="indeterminate"]
|
|
110
126
|
.checkbox-indicator {
|
|
111
|
-
|
|
112
|
-
background-color: var(--theme-checkbox-surface-selected);
|
|
113
|
-
border-color: var(--theme-checkbox-border-selected);
|
|
114
|
-
opacity: var(--theme-checkbox-disabled-selected-opacity);
|
|
127
|
+
color: var(--theme-checkbox-icon-disabled-selected);
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
.checkbox-field {
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { forwardRef, useCallback, useMemo, useState } from "react";
|
|
3
|
+
import { Text } from "./Base";
|
|
4
|
+
import { InputUtilityButton } from "./InputUtilityButton";
|
|
5
|
+
import type { InputUtilityButtonClickHandler } from "./InputUtilityButton";
|
|
6
|
+
import type { InputProps } from "../../types";
|
|
7
|
+
|
|
8
|
+
const normalizeDigits = (value?: string) => (value ?? "").replace(/\D/g, "");
|
|
9
|
+
const clampLength = (length?: number) => Math.max(4, Math.min(8, length ?? 6));
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* AuthCodeInput props. 이메일/휴대폰 컴포넌트와 동일한 UX로 인증코드를 입력한다.
|
|
13
|
+
* @property {string} [value] 제어형 값.
|
|
14
|
+
* @property {string} [defaultValue] 비제어 초기값.
|
|
15
|
+
* @property {(value: string) => void} [onValueChange] 값 변경 시 호출.
|
|
16
|
+
* @property {ComponentPropsWithoutRef<"input">["onChange"]} [onChange] native onChange override.
|
|
17
|
+
* @property {number} [length=6] 허용 자리수(4~8 사이로 보정).
|
|
18
|
+
* @property {(code: string) => void} [onComplete] length만큼 입력되면 호출.
|
|
19
|
+
* @property {string} [placeholder="인증코드 입력"] placeholder 텍스트.
|
|
20
|
+
* @property {ReactNode} [countdownText] 제한 시간 텍스트.
|
|
21
|
+
* @property {ReactNode} [countdownActionLabel="시간연장"] 제한 시간 연장 버튼 라벨.
|
|
22
|
+
* @property {InputUtilityButtonClickHandler} [onCountdownAction] 제한 시간 연장 핸들러.
|
|
23
|
+
* @property {boolean} [countdownActionDisabled] 제한 시간 연장 버튼 disabled.
|
|
24
|
+
*/
|
|
25
|
+
export interface AuthCodeInputProps extends Omit<
|
|
26
|
+
InputProps,
|
|
27
|
+
"type" | "inputMode" | "pattern" | "onChange" | "value" | "defaultValue"
|
|
28
|
+
> {
|
|
29
|
+
value?: string;
|
|
30
|
+
defaultValue?: string;
|
|
31
|
+
onValueChange?: (value: string) => void;
|
|
32
|
+
onChange?: ComponentPropsWithoutRef<"input">["onChange"];
|
|
33
|
+
length?: number;
|
|
34
|
+
onComplete?: (code: string) => void;
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
countdownText?: ReactNode;
|
|
37
|
+
countdownActionLabel?: ReactNode;
|
|
38
|
+
onCountdownAction?: InputUtilityButtonClickHandler;
|
|
39
|
+
countdownActionDisabled?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_PLACEHOLDER = "인증코드 입력";
|
|
43
|
+
const DEFAULT_COUNTDOWN_ACTION_LABEL = "시간연장";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* AuthCodeInput — Text Input priority secondary 스타일로 구성된 인증번호 입력.
|
|
47
|
+
* @component
|
|
48
|
+
* @param {AuthCodeInputProps} props 인증코드 입력 props
|
|
49
|
+
*/
|
|
50
|
+
const AuthCodeInput = forwardRef<HTMLInputElement, AuthCodeInputProps>(
|
|
51
|
+
(
|
|
52
|
+
{
|
|
53
|
+
value,
|
|
54
|
+
defaultValue,
|
|
55
|
+
onValueChange,
|
|
56
|
+
onChange,
|
|
57
|
+
length = 6,
|
|
58
|
+
onComplete,
|
|
59
|
+
placeholder = DEFAULT_PLACEHOLDER,
|
|
60
|
+
countdownText,
|
|
61
|
+
countdownActionLabel = DEFAULT_COUNTDOWN_ACTION_LABEL,
|
|
62
|
+
onCountdownAction,
|
|
63
|
+
countdownActionDisabled,
|
|
64
|
+
priority = "secondary",
|
|
65
|
+
right,
|
|
66
|
+
...restProps
|
|
67
|
+
},
|
|
68
|
+
forwardedRef,
|
|
69
|
+
) => {
|
|
70
|
+
const safeLength = clampLength(length);
|
|
71
|
+
const [innerValue, setInnerValue] = useState(() =>
|
|
72
|
+
normalizeDigits(defaultValue),
|
|
73
|
+
);
|
|
74
|
+
const isControlled = value !== undefined;
|
|
75
|
+
const resolvedValue = isControlled ? normalizeDigits(value) : innerValue;
|
|
76
|
+
|
|
77
|
+
const handleChange = useCallback(
|
|
78
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
79
|
+
const digits = normalizeDigits(event.currentTarget.value).slice(
|
|
80
|
+
0,
|
|
81
|
+
safeLength,
|
|
82
|
+
);
|
|
83
|
+
if (!isControlled) {
|
|
84
|
+
setInnerValue(digits);
|
|
85
|
+
}
|
|
86
|
+
if (event.currentTarget.value !== digits) {
|
|
87
|
+
event.currentTarget.value = digits;
|
|
88
|
+
}
|
|
89
|
+
onValueChange?.(digits);
|
|
90
|
+
if (digits.length === safeLength) {
|
|
91
|
+
onComplete?.(digits);
|
|
92
|
+
}
|
|
93
|
+
onChange?.(event);
|
|
94
|
+
},
|
|
95
|
+
[isControlled, onChange, onComplete, onValueChange, safeLength],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const countdownActions = useMemo(() => {
|
|
99
|
+
if (!countdownText && !onCountdownAction) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="auth-code-input__actions">
|
|
105
|
+
{countdownText ? (
|
|
106
|
+
<span className="auth-code-input__countdown">{countdownText}</span>
|
|
107
|
+
) : null}
|
|
108
|
+
{onCountdownAction ? (
|
|
109
|
+
<InputUtilityButton
|
|
110
|
+
priority="tertiary"
|
|
111
|
+
onClick={onCountdownAction}
|
|
112
|
+
disabled={countdownActionDisabled}
|
|
113
|
+
>
|
|
114
|
+
{countdownActionLabel}
|
|
115
|
+
</InputUtilityButton>
|
|
116
|
+
) : null}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}, [
|
|
120
|
+
countdownActionDisabled,
|
|
121
|
+
countdownActionLabel,
|
|
122
|
+
countdownText,
|
|
123
|
+
onCountdownAction,
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<Text
|
|
128
|
+
{...restProps}
|
|
129
|
+
ref={forwardedRef}
|
|
130
|
+
priority={priority}
|
|
131
|
+
type="text"
|
|
132
|
+
inputMode="numeric"
|
|
133
|
+
placeholder={placeholder}
|
|
134
|
+
value={resolvedValue}
|
|
135
|
+
onChange={handleChange}
|
|
136
|
+
maxLength={safeLength}
|
|
137
|
+
right={right ?? countdownActions}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
AuthCodeInput.displayName = "AuthCodeInput";
|
|
144
|
+
|
|
145
|
+
export { AuthCodeInput };
|