atomsystem 0.1.48 → 0.1.49
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 +153 -55
- package/atom.design.md +155 -0
- package/atom.uxPattern.md +431 -0
- package/components/ui/Badge.tsx +20 -0
- package/components/ui/Button.tsx +33 -0
- package/components/ui/Card.tsx +20 -0
- package/components/ui/Input.tsx +35 -0
- package/components/ui/Modal.tsx +54 -0
- package/components/ui/index.ts +5 -0
- package/css/atom-ui.css +216 -0
- package/dist/cli.cjs +105 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +97 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +7897 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +218 -0
- package/dist/index.d.ts +218 -0
- package/dist/index.js +7845 -451
- package/dist/index.js.map +1 -0
- package/package.json +120 -81
- package/presets/default.design.md +155 -0
- package/presets/uxPattern.md +431 -0
- package/skills/atomsystem.skill.md +104 -0
- package/tokens/token.json +319 -0
- package/tokens/tokens.json +318 -976
- package/app/templates/Animations/Animation.tsx +0 -282
- package/app/templates/Badge/Badge.tsx +0 -28
- package/app/templates/Badge/badge.css +0 -28
- package/app/templates/Button/Button.tsx +0 -31
- package/app/templates/Button/button.css +0 -135
- package/app/templates/Card/Card.tsx +0 -50
- package/app/templates/Card/card.css +0 -17
- package/app/templates/Checkbox/Checkbox.tsx +0 -55
- package/app/templates/Checkbox/checkbox.css +0 -38
- package/app/templates/CodeBlock/CodeBlock.tsx +0 -68
- package/app/templates/Collapse/Collapse.tsx +0 -207
- package/app/templates/Collapse/collapse.css +0 -24
- package/app/templates/Dropdown/Dropdown.css +0 -112
- package/app/templates/Dropdown/Dropdown.tsx +0 -103
- package/app/templates/Input/Input.tsx +0 -29
- package/app/templates/Input/input.css +0 -24
- package/app/templates/Label/Label.tsx +0 -22
- package/app/templates/Modal/Modal.tsx +0 -71
- package/app/templates/Modal/modal.css +0 -98
- package/app/templates/Navigation/Navigation.tsx +0 -169
- package/app/templates/Navigation/navigation.css +0 -59
- package/app/templates/Pagination/Pagination.tsx +0 -104
- package/app/templates/Pagination/pagination.css +0 -66
- package/app/templates/Radio/Radio.tsx +0 -45
- package/app/templates/Radio/radio.css +0 -29
- package/app/templates/Select/Select.tsx +0 -143
- package/app/templates/Select/select.css +0 -47
- package/app/templates/Sidebar/Sidebar.tsx +0 -139
- package/app/templates/Sidebar/sidebar.css +0 -4
- package/app/templates/TOC/TOC.tsx +0 -165
- package/app/templates/Tab/Tab.tsx +0 -117
- package/app/templates/Tab/tab.css +0 -56
- package/app/templates/Table/Table.tsx +0 -54
- package/app/templates/Table/table.css +0 -58
- package/app/templates/Theme/Theme.tsx +0 -122
- package/app/templates/Toast/Toast.tsx +0 -106
- package/app/templates/Toast/toast.css +0 -69
- package/build/build-tokens.js +0 -223
- package/build/css/_variables.css +0 -251
- package/build/typescript/theme.ts +0 -6
- package/css/text.css +0 -18
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# 🧩 AtomSystem UX Pattern Guide (atom.uxPattern.md)
|
|
2
|
+
|
|
3
|
+
> 사용자 경험(UX) 완성도, 접근성(A11y), 상태 피드백을 극대화하기 위해 사전 검증된 화면 패턴 명세서입니다.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. SaaS 데이터 분석 & KPI 대시보드 (`saas-dashboard`)
|
|
8
|
+
- **분류**: Analytics & SaaS (대시보드/데이터)
|
|
9
|
+
- **플랫폼**: `web` | **와이어프레임 구조**: `dashboard-grid`
|
|
10
|
+
- **개요**: 기간 필터(7d/30d), 4대 핵심 KPI 지표 카드, 전주 대비 증감 트렌드(+14.8%), 일별 추이 바 차트 및 정렬 가능한 데이터 테이블.
|
|
11
|
+
- **추천 아톰 컴포넌트**: `card`, `badge`, `button`, `tabs`, `table`
|
|
12
|
+
|
|
13
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
14
|
+
- [ ] KPI 요약 카드마다 전주/전월 대비 증감률(+, -) 및 시각적 색상(초록/빨강) 인디케이터 제공
|
|
15
|
+
- [ ] 날짜 범위 필터(오늘, 지난 7일, 지난 30일, 직접 설정) 원클릭 전환 탭 배치
|
|
16
|
+
- [ ] 차트 로딩 시 레이아웃 무너짐(CLS)을 방지하는 차트 전용 스켈레톤 UI 지원
|
|
17
|
+
- [ ] 데이터 테이블의 컬럼 정렬(오름차순/내림차순) 및 상태 배지(활성/대기/만료) 명확화
|
|
18
|
+
|
|
19
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
20
|
+
- ⚠️ 숫자 지표가 너무 많아 한눈에 핵심 성과를 파악할 수 없는 정보 과부하(Visual Noise)
|
|
21
|
+
- ⚠️ 데이터가 0건일 때 차트가 에러 나거나 깨진 채로 방치되는 빈 상태(Empty State) 결여
|
|
22
|
+
- ⚠️ 모바일 환경에서 테이블 가로 스크롤 안내가 없어 오른쪽 컬럼이 잘려 보이는 현상
|
|
23
|
+
|
|
24
|
+
### 🔄 상세 베리에이션
|
|
25
|
+
#### 비즈니스 성과 KPI 카드 & 시계열 차트 (`kpi-analytics-view`)
|
|
26
|
+
의사결정권자가 서비스 핵심 성과 지표(ARR, MAU, Churn)를 한눈에 파악하고 드릴다운할 수 있는 대시보드 패턴.
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
# [UX Pattern] B2B SaaS 성과 분석 대시보드 패턴
|
|
30
|
+
|
|
31
|
+
## 1. 개요
|
|
32
|
+
핵심 비즈니스 지표(KPI)와 시계열 트렌드를 시각화하여 사용자가 빠른 의사결정을 내릴 수 있도록 돕습니다.
|
|
33
|
+
|
|
34
|
+
## 2. 필수 구현 규칙
|
|
35
|
+
- **KPI Metrics Card**: 수치 + 단위 + 전기 대비 증감(Badge).
|
|
36
|
+
- **Date Range Picker / Tabs**: 7일, 30일, 90일 토글.
|
|
37
|
+
- **Data Table**: 정렬 가능한 헤더, 페이지네이션 또는 무한 스크롤.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. 1:1 고객지원 & AI 챗봇 상담 패턴 (`chat-support`)
|
|
44
|
+
- **분류**: Communication (채팅/CS)
|
|
45
|
+
- **플랫폼**: `mobile` | **와이어프레임 구조**: `chat-stream`
|
|
46
|
+
- **개요**: 말풍선 메시지 스트림, 추천 빠른 질문 칩(Quick Reply), 파일 첨부, 타이핑 중 인디케이터, 상담원 연결 상태 안내.
|
|
47
|
+
- **추천 아톰 컴포넌트**: `input`, `button`, `badge`, `card`, `alert`
|
|
48
|
+
|
|
49
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
50
|
+
- [ ] 새 메시지 수신 시 자동으로 스크롤을 최하단으로 부드럽게 내리는 자동 스크롤(Auto-scroll)
|
|
51
|
+
- [ ] 사용자가 자주 묻는 질문(FAQ)을 원클릭으로 보낼 수 있는 가로 스크롤 추천 칩 제공
|
|
52
|
+
- [ ] 상대방(또는 AI)이 응답 작성 중일 때 3개 점이 깜빡이는 타이핑 애니메이션(Typing Indicator) 노출
|
|
53
|
+
- [ ] 이미지/캡처 파일 첨부 시 전송 전 미리보기 및 용량 초과 인라인 에러
|
|
54
|
+
|
|
55
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
56
|
+
- ⚠️ 메시지를 보낸 후 전송 성공 여부를 알 수 없어 사용자가 같은 질문을 연타하는 문제
|
|
57
|
+
- ⚠️ 봇이 답변하지 못했을 때 인간 상담원 인계 경로(Escalation)를 제공하지 않음
|
|
58
|
+
- ⚠️ 모바일 키보드가 올라올 때 입력창이 키보드 뒤로 숨어버리는 뷰포트 버그
|
|
59
|
+
|
|
60
|
+
### 🔄 상세 베리에이션
|
|
61
|
+
#### 실시간 스트리밍 대화형 인터페이스 (`live-chatbot-stream`)
|
|
62
|
+
AI 모델의 토큰 단위 스트리밍 응답과 퀵 리플라이 칩을 결합한 모던 대화창.
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
# [UX Pattern] 1:1 고객지원 & AI 챗봇 패턴
|
|
66
|
+
|
|
67
|
+
## 1. 개요
|
|
68
|
+
사용자의 문의 사항을 실시간 대화 형태로 수렴하고 AI 어시스턴트가 즉시 해결합니다.
|
|
69
|
+
|
|
70
|
+
## 2. 필수 UX 명세
|
|
71
|
+
- **Message Bubble Layout**: 송신자 우측 정렬(Primary 컬러), 수신자 좌측 정렬(Muted Surface).
|
|
72
|
+
- **Quick Actions**: 가로 스크롤 추천 질문 칩.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 3. 본인확인 & KYC 인증신청 패턴 (`kyc-verification`)
|
|
79
|
+
- **분류**: Identity & Security (인증/보안)
|
|
80
|
+
- **플랫폼**: `mobile` | **와이어프레임 구조**: `kyc-camera`
|
|
81
|
+
- **개요**: 휴대폰 SMS/PASS 인증, 신분증/서류 촬영 업로드, 필수/선택 약관 일괄 동의, 실시간 심사 상태 안내.
|
|
82
|
+
- **추천 아톰 컴포넌트**: `input`, `button`, `selection`, `card`, `badge`, `alert`
|
|
83
|
+
|
|
84
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
85
|
+
- [ ] 휴대폰 6자리 인증번호 입력 시 3분 카운트다운 타이머 및 재전송(Resend) CTA 상시 제공
|
|
86
|
+
- [ ] [전체 동의] 토글 시 하위 필수(14세 이상, 개인정보 등) 및 선택 약관 일괄 동기화
|
|
87
|
+
- [ ] 신분증 파일 업로드 시 즉시 파일명 및 미리보기 섬네일 노출, 10MB 초과 파일 인라인 경고
|
|
88
|
+
- [ ] 제출 완료 후 [심사 대기중 / 보완 필요 / 승인 완료] 단계별 프로그레스 스텝 바 제공
|
|
89
|
+
|
|
90
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
91
|
+
- ⚠️ 인증번호 시간 초과 시 사용자에게 아무런 에러 메시지 없이 무응답 방치
|
|
92
|
+
- ⚠️ 필수 약관과 마케팅 선택 약관을 묶어두어 강제 동의하게 유도
|
|
93
|
+
- ⚠️ 파일 업로드 용량 제한을 업로드 실패 후에야 alert 팝업으로 띄우는 행위
|
|
94
|
+
|
|
95
|
+
### 🔄 상세 베리에이션
|
|
96
|
+
#### SMS OTP & 신분증 업로드 플로우 (`otp-id-verification`)
|
|
97
|
+
금융 및 본인확인이 필요한 서비스에서 이탈 없이 신속히 인증을 완결하는 패턴.
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
# [UX Pattern] 본인확인 & KYC 인증 플로우
|
|
101
|
+
|
|
102
|
+
## 1. 개요
|
|
103
|
+
금융, 결제, 성인인증 서비스에서 사용자의 신원을 안전하게 검증합니다.
|
|
104
|
+
|
|
105
|
+
## 2. 필수 규약
|
|
106
|
+
- **Timer**: 180초 카운트다운. 만료 시 입력 비활성화 및 재전송 활성화.
|
|
107
|
+
- **Terms Agreement**: 전체 동의 토글 시 자식 항목 일괄 변경.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 4. 블로그 글 작성 & 발행 에디터 패턴 (`blog-editor`)
|
|
114
|
+
- **분류**: Content & Publishing (콘텐츠/에디터)
|
|
115
|
+
- **플랫폼**: `web` | **와이어프레임 구조**: `editor-toolbar`
|
|
116
|
+
- **개요**: 제목/본문 리치 에디터, 태그 칩 입력기, 커버 이미지 등록, 30초 무중단 자동 임시저장, 공개 설정 모달.
|
|
117
|
+
- **추천 아톰 컴포넌트**: `input`, `button`, `badge`, `dialog`, `selection`, `tabs`
|
|
118
|
+
|
|
119
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
120
|
+
- [ ] 30초 주기 무중단 자동 임시저장(Autosave) 및 "마지막 저장: 방금 전" 상태 실시간 표기
|
|
121
|
+
- [ ] 제목 입력란에서 엔터 키 입력 시 본문 첫 단락으로 포커스 자동 전환
|
|
122
|
+
- [ ] 태그 입력 시 쉼표/엔터 입력 즉시 캡슐 칩(Chip)으로 변환 및 개별 삭제(X) 지원
|
|
123
|
+
- [ ] [발행하기] 클릭 시 공개 범위(전체/비공개/보호) 및 URL 슬러그, 발행 예약 일시 설정 모달 팝업
|
|
124
|
+
|
|
125
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
126
|
+
- ⚠️ 사용자가 실수로 뒤로가기나 탭을 닫을 때 작성 중인 본문 손실 경고창 미제공
|
|
127
|
+
- ⚠️ 에디터 본문 폰트 크기가 실제 발행된 화면과 달라 사용자가 레이아웃을 오인함
|
|
128
|
+
- ⚠️ 대표 이미지를 등록하지 않았을 때 기본 Fallback 이미지 규칙 부재
|
|
129
|
+
|
|
130
|
+
### 🔄 상세 베리에이션
|
|
131
|
+
#### 리치 마크다운 에디터 & 메타데이터 발행 (`markdown-rich-editor`)
|
|
132
|
+
글 작성자의 몰입감을 유지하면서 태그, 슬러그, 발행 일정을 손쉽게 설정하는 패턴.
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
# [UX Pattern] 블로그 글 작성 및 발행 패턴
|
|
136
|
+
|
|
137
|
+
## 1. 개요
|
|
138
|
+
콘텐츠 제작자가 방해받지 않고 글을 집필하고 안전하게 발행합니다.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 5. 지도 & 위치 기반 POI 탐색 패턴 (`map-location`)
|
|
145
|
+
- **분류**: Location & Mobility (지도/로케이션)
|
|
146
|
+
- **플랫폼**: `mobile` | **와이어프레임 구조**: `map-sheet`
|
|
147
|
+
- **개요**: 인터랙티브 지도 뷰포트, 현 지도 위치에서 재검색, 핀(Marker) 인터랙션, 바텀시트 장소 상세 정보.
|
|
148
|
+
- **추천 아톰 컴포넌트**: `card`, `button`, `badge`, `input`, `sheet`
|
|
149
|
+
|
|
150
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
151
|
+
- [ ] 지도 패닝/줌 이동 시 상단에 "현 위치에서 재검색" 플로팅 칩 자동 노출
|
|
152
|
+
- [ ] 지도 위 핀 마커 터치 시 하단 바텀시트가 매끄럽게 슬라이드 업되며 매장 핵심 정보 노출
|
|
153
|
+
- [ ] 현재 시각 기준 [영업 중 / 영업 종료] 여부를 컬러 배지(초록/회색)로 명확히 표시
|
|
154
|
+
- [ ] 모바일 한 손 조작을 위한 "길찾기" 및 "전화걸기" 주요 CTA 버튼 상시 하단 고정
|
|
155
|
+
|
|
156
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
157
|
+
- ⚠️ 지도를 스크롤할 때 웹 페이지 전체가 함께 위아래로 밀려 지도가 조작되지 않는 현상
|
|
158
|
+
- ⚠️ 마커가 겹칠 때 클러스터링(묶음) 처리가 안 되어 화면이 핀으로 뒤덮임
|
|
159
|
+
- ⚠️ 바텀시트가 전체 화면을 가려 지도 컨텍스트를 잃어버리는 현상
|
|
160
|
+
|
|
161
|
+
### 🔄 상세 베리에이션
|
|
162
|
+
#### 핀 마커 클릭 & 슬라이딩 바텀시트 (`poi-bottom-sheet`)
|
|
163
|
+
지도의 넓은 시야를 확보하면서 장소의 운영시간과 길찾기를 제공하는 패턴.
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
# [UX Pattern] 지도 및 위치 기반 POI 탐색 패턴
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 6. 항공권 / 여행 일정 예약 패턴 (`flight-booking`)
|
|
173
|
+
- **분류**: Travel & Booking (예약/일정)
|
|
174
|
+
- **플랫폼**: `web` | **와이어프레임 구조**: `flight-swap`
|
|
175
|
+
- **개요**: 출발지-도착지 원클릭 스왑(Swap), 왕복/편도 탭, 캘린더 날짜 범위 및 승객 수(성인/소아) 카운터.
|
|
176
|
+
- **추천 아톰 컴포넌트**: `tabs`, `input`, `button`, `badge`, `card`, `dialog`
|
|
177
|
+
|
|
178
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
179
|
+
- [ ] 출발지와 도착지를 한 번에 맞바꾸는 인터랙티브 360도 스왑 버튼 제공
|
|
180
|
+
- [ ] 왕복 선택 시 [가는 날 - 오는 날] 날짜 범위를 한 화면에서 선택하는 듀얼 캘린더
|
|
181
|
+
- [ ] 성인/소아/유아 승객 수 증감 (+/-) 카운터 및 유아 나이 기준 툴팁 제공
|
|
182
|
+
- [ ] 검색 중일 때 로딩 스켈레톤 및 직항/경유, 가격대별 필터 칩 상단 배치
|
|
183
|
+
|
|
184
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
185
|
+
- ⚠️ 오는 날을 가는 날보다 앞선 과거 날짜로 선택할 수 있게 방치하는 오류
|
|
186
|
+
- ⚠️ 출발지 선택 시 공항 코드(ICN, GMP)와 한글 도시명 검색이 분리되어 검색 실패 유발
|
|
187
|
+
- ⚠️ 유류할증료/세금이 최종 결제 단계에서 갑자기 추가되어 가격 불신 초래
|
|
188
|
+
|
|
189
|
+
### 🔄 상세 베리에이션
|
|
190
|
+
#### 멀티 파라미터 항공권 검색 폼 (`flight-search-bar`)
|
|
191
|
+
복잡한 날짜, 승객, 공항 조합을 오차 없이 빠르게 설정하는 패턴.
|
|
192
|
+
|
|
193
|
+
```html
|
|
194
|
+
# [UX Pattern] 항공권 검색 및 예약 플로우
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 7. 온보딩 튜토리얼 & 다단계 스텝 위저드 (`onboarding-wizard`)
|
|
201
|
+
- **분류**: User Onboarding (온보딩/가입)
|
|
202
|
+
- **플랫폼**: `mobile` | **와이어프레임 구조**: `wizard-steps`
|
|
203
|
+
- **개요**: 진행률 프로그레스 바(1~3단계), 역할 선택 카드(디자이너/엔지니어/기획자), 이전/다음 유효성 검사, 건너뛰기(Skip).
|
|
204
|
+
- **추천 아톰 컴포넌트**: `card`, `button`, `badge`, `selection`, `input`
|
|
205
|
+
|
|
206
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
207
|
+
- [ ] 현재 단계와 총 단계를 명시하는 상단 스텝 프로그레스 바(예: 2/3단계)
|
|
208
|
+
- [ ] 선택지를 큰 카드 형태로 배치하여 모바일 터치 타깃(최소 48px) 극대화
|
|
209
|
+
- [ ] 선택하지 않고 다음으로 넘어가지 못하도록 버튼 비활성화(Disabled) 상태 명확화
|
|
210
|
+
- [ ] 필수가 아닌 질문의 경우 우상단 "건너뛰기" 버튼을 제공하여 이탈률 방지
|
|
211
|
+
|
|
212
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
213
|
+
- ⚠️ 이전 단계로 돌아갔을 때 이미 입력한 데이터가 전부 초기화되는 버그
|
|
214
|
+
- ⚠️ 단계가 총 몇 단계인지 알려주지 않아 사용자가 끝없는 설문처럼 느껴 이탈
|
|
215
|
+
- ⚠️ 각 질문의 선택 이유를 설명하지 않아 사용자가 개인정보 수집에 거부감을 느낌
|
|
216
|
+
|
|
217
|
+
### 🔄 상세 베리에이션
|
|
218
|
+
#### 단계별 정보 수집 위저드 (`multi-step-form`)
|
|
219
|
+
복잡한 가입/온보딩 프로세스를 청킹(Chunking)하여 완료율을 극대화하는 패턴.
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
# [UX Pattern] 온보딩 및 다단계 위저드 패턴
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 8. 파일 드래그앤드롭 업로더 & 미디어 관리 (`file-uploader`)
|
|
229
|
+
- **분류**: File & Media (파일/업로드)
|
|
230
|
+
- **플랫폼**: `web` | **와이어프레임 구조**: `uploader-dropzone`
|
|
231
|
+
- **개요**: 대형 점선 드롭존(Dropzone), 실시간 업로드 진행률 바(0%~100%), 파일 형식/용량 검증, 첨부 파일 리스트 및 삭제.
|
|
232
|
+
- **추천 아톰 컴포넌트**: `card`, `button`, `badge`, `alert`, `input`
|
|
233
|
+
|
|
234
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
235
|
+
- [ ] 파일을 드롭존 위로 드래그했을 때 테두리 색상과 배경이 하이라이트되는 시각 피드백
|
|
236
|
+
- [ ] 허용 확장자(JPG, PNG, PDF) 및 최대 파일 크기(예: 최대 20MB)를 드롭존 내 명시
|
|
237
|
+
- [ ] 업로드 중인 파일마다 프로그레스 바 및 중단/취소(X) 버튼 제공
|
|
238
|
+
- [ ] 업로드 완료된 파일의 파일명, 용량, 성공 아이콘 및 개별 삭제 기능
|
|
239
|
+
|
|
240
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
241
|
+
- ⚠️ 허용되지 않은 확장자 파일을 올렸을 때 파일 선택 창이 닫힌 뒤에야 alert을 띄움
|
|
242
|
+
- ⚠️ 업로드 중 브라우저 탭을 닫으려고 할 때 경고창을 띄우지 않아 업로드 중단
|
|
243
|
+
- ⚠️ 이미지 파일인데 썸네일 미리보기를 제공하지 않고 텍스트 파일명만 노출함
|
|
244
|
+
|
|
245
|
+
### 🔄 상세 베리에이션
|
|
246
|
+
#### 드래그앤드롭존 & 진행률 리스트 (`dropzone-progress-list`)
|
|
247
|
+
다중 대용량 파일 첨부 시 시각적 안정성과 진행 상황을 전달하는 패턴.
|
|
248
|
+
|
|
249
|
+
```html
|
|
250
|
+
# [UX Pattern] 파일 드래그앤드롭 업로더 패턴
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 9. 계좌 송금 & 간편 이체 패턴 (`fintech-transfer`)
|
|
257
|
+
- **분류**: Fintech & Pay (핀테크/이체)
|
|
258
|
+
- **플랫폼**: `mobile` | **와이어프레임 구조**: `keypad-transfer`
|
|
259
|
+
- **개요**: 대형 금액 입력(실시간 한글 변환), 수수료 면제 안내 배지, 빠른 출금 계좌 선택, 확인 모달 및 완료 인터랙션.
|
|
260
|
+
- **추천 아톰 컴포넌트**: `button`, `input`, `dialog`, `badge`, `card`
|
|
261
|
+
|
|
262
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
263
|
+
- [ ] 숫자 입력 시 "5만 원", "150만 원" 등 실시간 한글 금액 단위 동시 변환 노출
|
|
264
|
+
- [ ] 출금 가능 잔액 초과 시 즉시 경고 텍스트 및 잔액 채우기 CTA 연계
|
|
265
|
+
- [ ] 최종 송금 직전 받는 분 이름, 은행, 계좌번호를 재확인하는 2단계 확인 다이얼로그
|
|
266
|
+
- [ ] 송금 성공 시 부드러운 체크 애니메이션과 잔액 갱신 및 카카오톡 영수증 공유 CTA
|
|
267
|
+
|
|
268
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
269
|
+
- ⚠️ 금액 단위 콤마(, ) 서식이 입력 중에 깨져 사용자가 숫자를 헷갈리게 만듦
|
|
270
|
+
- ⚠️ 확인 모달 없이 한 번 클릭으로 바로 돈이 빠져나가는 위험한 흐름
|
|
271
|
+
- ⚠️ 네트워크 지연 시 송금 버튼을 연타하여 중복 이체 사고 발생
|
|
272
|
+
|
|
273
|
+
### 🔄 상세 베리에이션
|
|
274
|
+
#### 숫자 키패드 송금 & 이중 확인 모달 (`two-step-transfer`)
|
|
275
|
+
오송금 위험을 원천 차단하면서 원터치로 송금을 완료하는 토스형 패턴.
|
|
276
|
+
|
|
277
|
+
```html
|
|
278
|
+
# [UX Pattern] 계좌 송금 및 간편 이체 패턴
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 10. 장바구니 & 주문 결제서 패턴 (`ecommerce-checkout`)
|
|
285
|
+
- **분류**: Commerce & Order (커머스/결제)
|
|
286
|
+
- **플랫폼**: `mobile` | **와이어프레임 구조**: `cart-sticky`
|
|
287
|
+
- **개요**: 수량 증감(+/-) 실시간 금액 재계산, 쿠폰/적립금 인라인 적용, 간편결제 수단 선택, 하단 결제 CTA 고정 바.
|
|
288
|
+
- **추천 아톰 컴포넌트**: `card`, `button`, `input`, `badge`, `selection`
|
|
289
|
+
|
|
290
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
291
|
+
- [ ] 수량 증감(+/-) 시 배송비 및 총 결제 예정 금액 실시간 재계산
|
|
292
|
+
- [ ] 보유 쿠폰 및 적립금 입력란에 [최대 사용] 원클릭 적용 버튼 제공
|
|
293
|
+
- [ ] 스크롤 위치와 상관없이 결제 금액과 [OOO원 결제하기] 버튼을 화면 하단 고정(Sticky)
|
|
294
|
+
- [ ] 품절 상품 발생 시 체크박스 자동 해제 및 품절 뱃지 명확히 표기
|
|
295
|
+
|
|
296
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
297
|
+
- ⚠️ 수량을 바꿀 때마다 화면 전체가 깜빡이며 리로드되는 구식 방식
|
|
298
|
+
- ⚠️ 최종 결제 금액에 배송비나 할인이 어떻게 계산되었는지 상세 내역 미표시
|
|
299
|
+
- ⚠️ 결제 실패 시 장바구니에 담아둔 상품 정보가 전부 날아가는 현상
|
|
300
|
+
|
|
301
|
+
### 🔄 상세 베리에이션
|
|
302
|
+
#### 주문서 결제 & 하단 고정 바 (`sticky-checkout-bar`)
|
|
303
|
+
결제 과정의 이탈을 줄이고 최종 결제 금액을 명료하게 유지하는 패턴.
|
|
304
|
+
|
|
305
|
+
```html
|
|
306
|
+
# [UX Pattern] 이커머스 장바구니 및 결제 주문서 패턴
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 11. 실시간 검색 & Empty UX 패턴 (`search-empty`)
|
|
313
|
+
- **분류**: Discovery (탐색/복구)
|
|
314
|
+
- **플랫폼**: `web` | **와이어프레임 구조**: `search-empty`
|
|
315
|
+
- **개요**: 300ms 디바운스 입력, 검색어 즉시 초기화(X) 버튼, 결과 없음 시 복구 추천 태그 및 최근 검색어 제공.
|
|
316
|
+
- **추천 아톰 컴포넌트**: `input`, `badge`, `card`, `button`
|
|
317
|
+
|
|
318
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
319
|
+
- [ ] 300ms 디바운스 적용으로 타자 칠 때마다 서버에 무의미한 API 요청 방지
|
|
320
|
+
- [ ] 결과가 없을 때 "검색 결과가 없습니다"와 함께 대안 추천 키워드 태그 3개 이상 제공
|
|
321
|
+
- [ ] 입력창 우측 원클릭 클리어(X) 버튼 및 최근 검색어 기록 로컬 저장
|
|
322
|
+
- [ ] 검색 중일 때는 인라인 스피너나 스켈레톤으로 진행 상태 명확화
|
|
323
|
+
|
|
324
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
325
|
+
- ⚠️ 결과가 없을 때 하얀 백지 화면만 덜렁 노출하여 사용자를 이탈시킴
|
|
326
|
+
- ⚠️ 타이핑 중 지우기(X) 버튼이 없어 백스페이스를 연타하게 만드는 불편함
|
|
327
|
+
- ⚠️ 오타 입력 시 유사 검색어 추천("혹시 이것을 찾으셨나요?") 미제공
|
|
328
|
+
|
|
329
|
+
### 🔄 상세 베리에이션
|
|
330
|
+
#### 실시간 자동완성 & 빈 상태 복구 흐름 (`autocomplete-recovery`)
|
|
331
|
+
사용자가 오타를 내거나 결과가 없어도 서비스 이탈을 막는 회복탄력적 UX.
|
|
332
|
+
|
|
333
|
+
```html
|
|
334
|
+
# [UX Pattern] 실시간 검색 및 결과 없음 복구 패턴
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 12. 개발자 기술 문서 & API Docs 패턴 (`developer-docs`)
|
|
341
|
+
- **분류**: Documentation & DX (기술문서/API)
|
|
342
|
+
- **플랫폼**: `web` | **와이어프레임 구조**: `docs-layout`
|
|
343
|
+
- **개요**: 좌측 계층 트리 내비게이션, 중앙 코드 스니펫(원클릭 복사/언어 탭), 인라인 API 파라미터 테이블, 우측 목차(On this page) 스크롤 스파이.
|
|
344
|
+
- **추천 아톰 컴포넌트**: `table`, `badge`, `tabs`, `button`, `card`
|
|
345
|
+
|
|
346
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
347
|
+
- [ ] 모든 코드 블록 상단에 프로그래밍 언어 배지 및 원클릭 복사(Copy) 피드백 제공
|
|
348
|
+
- [ ] 전역 단축키(Cmd+K / Ctrl+K) 지원 문서 통합 검색 오버레이 제공
|
|
349
|
+
- [ ] 현재 보고 있는 섹션을 우측 목차(TOC)에서 실시간 하이라이트하는 스크롤 스파이(Scroll Spy)
|
|
350
|
+
- [ ] API 엔드포인트마다 HTTP Method 배지(GET/POST/DELETE) 및 요청/응답 파라미터 타입 표기
|
|
351
|
+
|
|
352
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
353
|
+
- ⚠️ 긴 코드 블록에 가로 스크롤이 없어 오른쪽 코드가 강제로 잘려 보이는 현상
|
|
354
|
+
- ⚠️ 검색 결과를 클릭했을 때 해당 앵커(#section)로 정확히 이동하지 않는 딥링크 누락
|
|
355
|
+
- ⚠️ 버전 선택(v1, v2) 시 기존에 보고 있던 페이지 컨텍스트를 잃어버리는 현상
|
|
356
|
+
|
|
357
|
+
### 🔄 상세 베리에이션
|
|
358
|
+
#### 3단 분할 개발자 기술 레퍼런스 (`api-reference-3col`)
|
|
359
|
+
Stripe, Next.js 스타일. 좌측 탐색 사이드바, 중앙 상세 가이드, 우측 실시간 코드 예제 및 TOC.
|
|
360
|
+
|
|
361
|
+
```html
|
|
362
|
+
# [UX Pattern] 개발자 기술 문서 & API 레퍼런스 패턴
|
|
363
|
+
|
|
364
|
+
## 1. 개요
|
|
365
|
+
개발자가 빠르게 라이브러리를 설치하고 API 규격을 확인하여 복사-붙여넣기로 즉시 연동할 수 있는 3단 기술 문서 레이아웃입니다.
|
|
366
|
+
|
|
367
|
+
## 2. 필수 레이아웃 구조
|
|
368
|
+
- **Left Column (Sidebar)**: 카테고리 트리, 버전 실렉터, 검색창
|
|
369
|
+
- **Center Column (Content)**: 마크다운 렌더링, Callout(Note/Tip/Warning), 인터랙티브 코드 블록
|
|
370
|
+
- **Right Column (TOC)**: 페이지 내 목차 (H2/H3 계층)
|
|
371
|
+
|
|
372
|
+
## 3. 필수 인터랙션
|
|
373
|
+
- `navigator.clipboard.writeText()`: 코드 복사 시 "Copied!" 툴팁 피드백.
|
|
374
|
+
- `IntersectionObserver`: 본문 스크롤에 따른 목차 활성 링크 동기화.
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 13. 복잡한 엔터프라이즈 환경설정 & 다단계 폼 패턴 (`complex-form-settings`)
|
|
381
|
+
- **분류**: Settings & Configuration (복합 폼/환경설정)
|
|
382
|
+
- **플랫폼**: `web` | **와이어프레임 구조**: `complex-form`
|
|
383
|
+
- **개요**: 섹션 청킹(좌측 네비게이션), 조건부 종속 입력(Progressive Disclosure), 실시간 인라인 유효성 검사, 하단 플로팅 미저장 변경사항(Unsaved Changes) 바, 파괴적 설정 안전장치.
|
|
384
|
+
- **추천 아톰 컴포넌트**: `input`, `selection`, `button`, `badge`, `alert`, `tabs`, `card`
|
|
385
|
+
|
|
386
|
+
### ✅ UX 체크리스트 (Best Practices)
|
|
387
|
+
- [ ] 정보 구조 청킹(Chunking): 좌측 고정 네비게이션으로 섹션(기본 설정, 보안 정책, 결제/웹훅 등)을 명확히 분리하여 인지 과부하 방지
|
|
388
|
+
- [ ] 점진적 노출(Progressive Disclosure): "고급 설정" 또는 상위 토글 활성화 시에만 하위 조건부 인풋이 부드럽게 노출
|
|
389
|
+
- [ ] 실시간 인라인 유효성 검증: Blur 및 디바운스 실시간 검사로 에러 위치와 해결 힌트를 필드 바로 밑에 명확히 표기
|
|
390
|
+
- [ ] 미저장 변경사항 플로팅 바(Sticky Unsaved Bar): 필드 수정 시 화면 하단에 [저장되지 않은 변경사항이 있습니다 | 취소 | 저장] 바가 슬라이드 업
|
|
391
|
+
- [ ] 파괴적 액션 2단계 안전장치: 데이터 삭제/API 재발급 등 위험 작업 시 명시적 텍스트 입력 확인(예: 프로젝트명 직접 입력) 다이얼로그 제공
|
|
392
|
+
- [ ] 스마트 기본값(Sensible Defaults) & 헬퍼 툴팁: 복잡한 기술 용어 옆에 안내 툴팁과 원클릭 복사/생성 버튼 배치
|
|
393
|
+
|
|
394
|
+
### ❌ 지양해야 할 안티패턴 (Anti-Patterns)
|
|
395
|
+
- ⚠️ 수십 개의 인풋을 스크롤 없이 단일 페이지에 일렬 배치하여 사용자에게 시각적 공포와 인지 과부하를 주는 형태
|
|
396
|
+
- ⚠️ 제출 버튼을 눌렀을 때 화면 상단에서만 "입력값을 확인하세요"라는 모호한 alert을 띄우고 어디서 에러가 났는지 알려주지 않는 불친절함
|
|
397
|
+
- ⚠️ 설정값을 바꿨는데 페이지 이동 시 경고 없이 작성 내용이 모두 증발하는 데이터 유실 버그
|
|
398
|
+
- ⚠️ 비활성화된(Disabled) 필드인데 왜 비활성화되어 있는지 이유(상위 권한 필요, 필수 선행 조건 등)를 툴팁으로 설명하지 않음
|
|
399
|
+
|
|
400
|
+
### 🔄 상세 베리에이션
|
|
401
|
+
#### 엔터프라이즈 멀티 섹션 폼 & 조건부 필드 (`enterprise-settings-form`)
|
|
402
|
+
Vercel, Stripe, GitHub 스타일의 복잡한 시스템 설정 폼. 좌측 앵커 탭과 우측 본문, 실시간 검증 및 플로팅 세이브 바 지원.
|
|
403
|
+
|
|
404
|
+
```html
|
|
405
|
+
# [UX Pattern] 복잡한 엔터프라이즈 환경설정 & 다단계 폼 패턴
|
|
406
|
+
|
|
407
|
+
## 1. 패턴 개요
|
|
408
|
+
수십 가지의 인풋, 셀렉트, 토글 스위치가 존재하는 복잡한 비즈니스 설정 화면에서 사용자의 인지 부하(Cognitive Load)를 최소화하고, 입력 오류와 데이터 유실을 방지하는 엔터프라이즈급 폼 설계 패턴입니다.
|
|
409
|
+
|
|
410
|
+
## 2. 핵심 5대 UX 설계 원칙
|
|
411
|
+
|
|
412
|
+
### 1) 점진적 공개 (Progressive Disclosure)
|
|
413
|
+
- 상위 토글(예: "웹훅 활성화", "2단계 인증 강제")을 켰을 때만 종속된 하위 URL 입력창, 시크릿 키 등이 애니메이션과 함께 노출됩니다.
|
|
414
|
+
- 초기 진입 시 화면의 시각적 노이즈를 70% 이상 줄여줍니다.
|
|
415
|
+
|
|
416
|
+
### 2) 미저장 변경사항 고정 바 (Sticky Unsaved Changes Bar)
|
|
417
|
+
- 사용자가 어떤 필드라도 수정(Dirty 상태)하면 화면 하단에 플로팅 바가 솟아오릅니다.
|
|
418
|
+
- `[ 변경되지 않은 설정이 있습니다 | 되돌리기 | 변경사항 저장 ]`
|
|
419
|
+
- 페이지 최하단까지 스크롤하지 않고도 즉시 저장할 수 있습니다.
|
|
420
|
+
|
|
421
|
+
### 3) 실시간 인라인 검증 (Real-time Inline Validation)
|
|
422
|
+
- 폼 전체를 제출한 뒤에 서버 에러 팝업을 띄우지 않습니다.
|
|
423
|
+
- 사용자가 포커스를 잃었을 때(onBlur) 즉시 포맷을 검사하고, 유효할 때는 초록색 체크 아이콘, 오류 시 필드 바로 아래에 구체적인 수정 가이드를 붉은색 텍스트로 노출합니다.
|
|
424
|
+
|
|
425
|
+
### 4) 파괴적 작업 안전장치 (Confirmation Safeguard)
|
|
426
|
+
- '프로젝트 삭제'나 'API 키 재생성' 같은 복구 불가능한 액션은 빨간색 Danger Card로 별도 격리하며, 반드시 확인 문자열(예: `delete my-project`)을 직접 입력해야만 버튼이 활성화됩니다.
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
variant?: 'primary' | 'success' | 'warning' | 'destructive' | 'neutral';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
|
8
|
+
({ variant = 'neutral', className = '', children, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<span
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={`atom-badge atom-badge-${variant} ${className}`}
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</span>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
Badge.displayName = 'Badge';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
4
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
|
|
5
|
+
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
10
|
+
({ variant = 'primary', size = 'md', loading = false, disabled, className = '', children, ...props }, ref) => {
|
|
11
|
+
const variantClass = variant === 'primary' ? 'atom-btn-primary' : `atom-btn-${variant}`;
|
|
12
|
+
const sizeClass = size === 'sm' ? 'text-xs px-2.5 py-1.5' : size === 'lg' ? 'text-base px-5 py-3' : '';
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<button
|
|
16
|
+
ref={ref}
|
|
17
|
+
disabled={disabled || loading}
|
|
18
|
+
aria-disabled={disabled || loading}
|
|
19
|
+
className={`atom-btn ${variantClass} ${sizeClass} ${className}`}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{loading && (
|
|
23
|
+
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
|
|
24
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
25
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
|
26
|
+
</svg>
|
|
27
|
+
)}
|
|
28
|
+
{children}
|
|
29
|
+
</button>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
Button.displayName = 'Button';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
interactive?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
8
|
+
({ interactive = false, className = '', children, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={`atom-card ${interactive ? 'atom-card-interactive cursor-pointer' : ''} ${className}`}
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
Card.displayName = 'Card';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
4
|
+
label?: string;
|
|
5
|
+
error?: string;
|
|
6
|
+
helperText?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
10
|
+
({ label, error, helperText, className = '', id, ...props }, ref) => {
|
|
11
|
+
const inputId = id || React.useId();
|
|
12
|
+
return (
|
|
13
|
+
<div className="w-full flex flex-col gap-1.5">
|
|
14
|
+
{label && (
|
|
15
|
+
<label htmlFor={inputId} className="text-sm font-medium text-[var(--atom-text-primary)]">
|
|
16
|
+
{label}
|
|
17
|
+
</label>
|
|
18
|
+
)}
|
|
19
|
+
<input
|
|
20
|
+
ref={ref}
|
|
21
|
+
id={inputId}
|
|
22
|
+
aria-invalid={!!error}
|
|
23
|
+
className={`atom-input ${error ? 'atom-input-error' : ''} ${className}`}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
{error ? (
|
|
27
|
+
<span className="text-xs text-[var(--atom-destructive,#dc2626)]">{error}</span>
|
|
28
|
+
) : helperText ? (
|
|
29
|
+
<span className="text-xs text-[var(--atom-text-secondary,#8b95a1)]">{helperText}</span>
|
|
30
|
+
) : null}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
Input.displayName = 'Input';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ModalProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
title?: string;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children }) => {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
13
|
+
if (e.key === 'Escape') onClose();
|
|
14
|
+
};
|
|
15
|
+
if (isOpen) {
|
|
16
|
+
document.body.style.overflow = 'hidden';
|
|
17
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
18
|
+
}
|
|
19
|
+
return () => {
|
|
20
|
+
document.body.style.overflow = 'unset';
|
|
21
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
22
|
+
};
|
|
23
|
+
}, [isOpen, onClose]);
|
|
24
|
+
|
|
25
|
+
if (!isOpen) return null;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
role="dialog"
|
|
30
|
+
aria-modal="true"
|
|
31
|
+
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
|
|
32
|
+
onClick={onClose}
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
className="atom-card w-full max-w-lg p-6 bg-[var(--atom-bg-surface)] rounded-[var(--atom-radius-lg)] shadow-2xl relative animate-in fade-in zoom-in-95 duration-150"
|
|
36
|
+
onClick={(e) => e.stopPropagation()}
|
|
37
|
+
>
|
|
38
|
+
{title && (
|
|
39
|
+
<div className="flex items-center justify-between pb-4 border-b border-[var(--atom-border-subtle)]">
|
|
40
|
+
<h3 className="text-lg font-bold text-[var(--atom-text-primary)]">{title}</h3>
|
|
41
|
+
<button
|
|
42
|
+
onClick={onClose}
|
|
43
|
+
aria-label="닫기"
|
|
44
|
+
className="text-[var(--atom-text-secondary)] hover:text-[var(--atom-text-primary)] text-xl leading-none"
|
|
45
|
+
>
|
|
46
|
+
×
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
<div className="py-4">{children}</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|