@wishket/design-system 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,94 @@
1
+ ---
2
+ id: INPUT-10
3
+ component: CheckboxList
4
+ version: 0.0.2
5
+ status: draft
6
+ ---
7
+
8
+ # 10. CheckboxList
9
+
10
+ **개정 이력**
11
+
12
+ - v0.0.1 (2026.09.22): 초안 작성
13
+ - v0.0.2 (2026.10.02): Properties를 코드(`CheckboxListProps`)와 일치시킴 — 필수 `data` · `type` 추가, 컴포넌트 속성(C-1)과 항목 속성(C-2) 분리 (#1292 리뷰 반영)
14
+
15
+ ## A. Anatomy
16
+
17
+ CheckboxList는 **Checkbox(Checkmark)** 와 **Label**로 구성되며, 선택적으로 **Supporting Text**를 가집니다. Checkbox는 개별 컴포넌트로 제공되어 자유롭게 조합해 사용할 수 있습니다.
18
+
19
+ | 요소 | 필수 여부 | 설명 |
20
+ | --- | --- | --- |
21
+ | Checkbox | 필수 | 선택 여부를 나타내는 체크 표시. 별도 컴포넌트 |
22
+ | Label | 필수 | 무엇을 선택하는지 설명하는 텍스트 |
23
+ | Supporting Text | 선택 | 라벨 아래 보조 설명. 선택의 결과나 조건을 덧붙일 때 사용 |
24
+
25
+ ## B. Description
26
+
27
+ ### 1. 정의
28
+
29
+ 사용자가 한 번에 다중 항목을 선택할 수 있도록 돕는 리스트 구조의 컴포넌트입니다.
30
+
31
+ ### 2. 사용 시점
32
+
33
+ - 여러 항목을 **동시에 선택**해야 할 때 사용합니다.
34
+ - 항목 하나를 **켜고 끄는 것**이 목적일 때도 사용합니다. (약관 동의, 알림 수신)
35
+ - 여러 선택지 중 **하나만** 골라야 한다면 **Radio List**를 사용합니다.
36
+ - 설정을 즉시 반영하는 on/off라면 **Switch / Toggle**을 사용합니다.
37
+ - 선택 결과를 Apply로 한 번에 반영하는 필터라면 **FilterList**를 사용합니다. (FilterList가 CheckboxList를 내부에서 사용합니다)
38
+
39
+ ### 3. 키워드
40
+
41
+ CheckboxList, 체크박스, 체크리스트, 다중 선택, 복수 선택, Checkbox, 동의
42
+
43
+ ### 4. Size
44
+
45
+ - CheckboxList는 **부모 컨테이너 너비를 100% 채웁니다.**
46
+ - Label이 길어지면 **줄바꿈(wrap)** 합니다. 말줄임하지 않습니다. 선택 대상은 끝까지 읽혀야 합니다.
47
+ - Label이 2줄이 되어도 Checkbox는 **첫 줄에 맞춰 정렬**합니다.
48
+
49
+ ### 5. Do / Don't
50
+
51
+ **Do**
52
+
53
+ - Label은 **선택했을 때 무엇이 되는지**를 긍정문으로 씁니다. (`마케팅 정보 수신에 동의합니다`)
54
+ - 항목은 가나다순·중요도 등 예측 가능한 순서로 정렬합니다.
55
+ - 선택 불가한 항목은 숨기지 말고 Disabled로 남깁니다.
56
+ - 오류는 Is Error로 표시하고, 이유는 Supporting Text로 설명합니다.
57
+
58
+ **Don't**
59
+
60
+ - Label을 부정문으로 쓰지 않습니다. (`수신을 원하지 않습니다Label`)
61
+ - Checkbox 하나만으로 두 가지 의미를 담지 않습니다.
62
+ - 선택 즉시 화면이 이동하거나 저장되게 만들지 않습니다.
63
+ - Label을 말줄임 처리하지 않습니다.
64
+
65
+ ## C. Properties
66
+
67
+ CheckboxList는 **항목 목록(`data`)** 을 넘겨 씁니다. 컴포넌트 자체 속성과 **항목 하나하나의 속성**을 나눠 적었습니다. 표의 이름이 코드와 다르면 비고에 코드 이름을 적었습니다.
68
+
69
+ ### C-1. 컴포넌트 속성
70
+
71
+ | Property | Type | Values | 비고 |
72
+ | --- | --- | --- | --- |
73
+ | Data | 항목 목록 | | **필수.** 코드 `data`. 아래 C-2 속성을 가진 항목들의 목록 |
74
+ | Type | Variant | `box` / `circle` / `sub` | 코드 `type`. 체크박스 모양 (Checkbox 문서와 동일) |
75
+ | ClassName | String | | 코드 `className`. 개발용 · 시각 변화는 지정에 따라 |
76
+
77
+ ### C-2. 항목(`data` 안) 속성
78
+
79
+ | Property | Type | Values | 비고 |
80
+ | --- | --- | --- | --- |
81
+ | Label | String | | 코드 `label`. 항목 라벨 · 피그마 추가 |
82
+ | Description | String | | 코드 `description`. 라벨 아래 보조 설명 · 피그마 `Supporting Text`(Boolean)에 해당 → 텍스트 속성으로 수정 |
83
+ | Checked | Boolean | True / False | 코드 `checked` |
84
+ | Disabled | Boolean | True / False | 코드 `disabled` |
85
+ | IsError | Boolean | True / False | 코드 `isError` |
86
+ | ErrorMessage | String | | 코드 `errorMessage`. 오류 문구 · 코드에만 존재 |
87
+ | State | Variant | Enabled / Hovered | 피그마에만 존재. Hovered는 마우스 hover로 자동 처리 |
88
+
89
+ > ```tsx
90
+ > <CheckboxList data={[
91
+ > { label: '마케팅 정보 수신에 동의합니다', checked: agreed, onChange: toggle },
92
+ > { label: '야간 알림 받기', description: '22시~08시', disabled: true },
93
+ > ]} />
94
+ > ```
@@ -0,0 +1,126 @@
1
+ ---
2
+ id: INPUT-13
3
+ component: ChoiceChip
4
+ version: 0.0.3
5
+ status: draft
6
+ ---
7
+
8
+ # 13. ChoiceChip
9
+
10
+ **개정 이력**
11
+
12
+ - v0.0.1 (2026.09.23): 초안 작성
13
+ - v0.0.2 (2026.10.01): 프론트매터 추가, Properties를 코드(`ChoiceChipProps`)·스토리북 기준으로 정리, State의 `Actived` 제거(`Checked`와 중복), 미결 항목을 `Z. 미결`로 분리
14
+ - v0.0.3 (2026.10.02): Properties 안내 문구 추가, `onChange` 추가 (#1295 리뷰 반영)
15
+
16
+ ## A. Anatomy
17
+
18
+ ChoiceChip은 **Container**와 **Text**로 구성되며, **Leading Icon**과 **Trailing Icon**을 가질 수 있습니다.
19
+
20
+ | 요소 | 필수 여부 | 설명 |
21
+ | --- | --- | --- |
22
+ | Container | 필수 | 배경·모서리를 담당하는 알약(pill) 형태의 영역. 전체가 클릭 영역 |
23
+ | Leading Icon | 선택 | 라벨 앞 아이콘. 흰 원형 배경 위에 놓입니다. 항목의 **종류를 구분** |
24
+ | Text | 필수 | 선택 항목의 이름 |
25
+ | Trailing Icon | 선택 | 라벨 뒤 아이콘. 선택 상태를 알리는 **Check Icon** |
26
+
27
+ ## B. Description
28
+
29
+ ### 1. 정의
30
+
31
+ 사용자가 하나 또는 여러 개의 옵션을 선택할 수 있도록 돕는 선택형 컴포넌트입니다. 선택 상태를 시각적으로 표현하며, 아이콘과 텍스트를 함께 사용할 수 있습니다.
32
+
33
+ **Action Button과 Chip**
34
+
35
+ Action Button과 Chip은 유사한 형태를 가진 컴포넌트이지만, 사용 목적과 제공하는 기능에 차이가 있습니다.
36
+
37
+ | | Action Button | Chip |
38
+ | --- | --- | --- |
39
+ | 목적 | 액션 실행 | 정보 표현 + 선택 표현 |
40
+ | 예시 | `완료`, `제출`, `다음`, `삭제` | 필터, 옵션 선택 |
41
+ | 라벨 표현 | 라벨만 봐도 액션이 예상됨 | 선택하거나 활성화된 내용을 표시 |
42
+ | 라벨 목적 | 어떠한 행동을 하는지 인지하는 것이 중요 | 현재 어떤 조건·정보가 활성 상태인지 확인하는 게 중요 |
43
+ | 사용 패턴 | 단일로도 사용 가능 | 2개 이상 그룹으로 사용 권장 |
44
+
45
+ ### 2. 사용 시점
46
+
47
+ - 선택지를 **나란히 늘어놓고 골라야** 할 때 사용합니다. (관심 분야, 기술 스택, 필터 조건)
48
+ - 선택 결과가 **즉시 보이는 것이 중요할 때** 사용합니다. 선택된 칩은 색으로 바로 구분됩니다.
49
+ - **2개 이상 그룹으로** 사용합니다. 하나만 있는 Chip은 버튼처럼 보입니다.
50
+ - 선택지마다 설명이 필요하다면 **CheckboxCard**를 사용합니다.
51
+ - 항목을 세로로 훑으며 고르는 것이 자연스럽다면 **CheckboxList**를 사용합니다.
52
+ - 액션을 실행하는 것이 목적이라면 **Button**을 사용합니다.
53
+ - ChoiceChip은 **낮은 시각 위계**를 가집니다. 화면의 주요 액션으로 쓰지 않습니다.
54
+
55
+ ### 3. 키워드
56
+
57
+ ChoiceChip, 칩, 초이스칩, Chip, 태그, 필터, 선택 항목, 다중 선택
58
+
59
+ ### 4. Size
60
+
61
+ - Size Variant는 없으며 단일 높이(40)입니다.
62
+ - 너비는 **콘텐츠에 맞춰 늘어납니다.** 고정 너비를 지정하지 않습니다.
63
+ - Leading Icon이 있는 경우 좌측 패딩이 4로 줄어듭니다. 아이콘의 원형 배경이 여백 역할을 합니다.
64
+
65
+ ### 4-1. Overflow
66
+
67
+ 1. 라벨은 **1줄로 고정**하며 줄바꿈하지 않습니다.
68
+ 2. 라벨이 길어질 경우를 대비해 **최대 너비를 정해두고**, 초과하면 말줄임(`…`) 처리한 뒤 전체 문구를 툴팁으로 제공합니다.
69
+ 3. Chip은 짧은 명사가 원칙입니다. 문장이 들어가야 한다면 Chip을 사용하지 않습니다.
70
+
71
+ ### 4-2. 나열 규칙
72
+
73
+ 1. ChoiceChip 사이 간격은 **최소 16**을 유지합니다. 클릭 오류를 막기 위한 기준입니다.
74
+ 2. 한 줄을 넘으면 **줄바꿈(wrap)** 합니다. 가로 스크롤하지 않습니다.
75
+ 3. 줄 간격도 16을 유지합니다.
76
+ 4. 한 그룹 안에서 `Label (Icon)`과 `Label`을 섞지 않습니다.
77
+
78
+ ### 4-3. Check Icon
79
+
80
+ - ChoiceChip이 **복수 선택 형태로 사용될 경우**, 선택된 상태를 명확히 구분하기 위해 **Check Icon을 반드시 함께 표시**합니다.
81
+ - 단일 선택으로 쓰는 경우에도 배경색만으로 구분하기 어렵다면 Check Icon을 함께 씁니다.
82
+
83
+ ### 5. Do / Don't
84
+
85
+ **Do**
86
+
87
+ - 라벨은 짧은 명사로 씁니다. (`안드로이드`, `PC프로그램`)
88
+ - 복수 선택이면 Check Icon을 함께 표시합니다.
89
+ - 한 그룹의 Chip은 아이콘 유무와 라벨의 결을 통일합니다.
90
+ - 선택된 Chip을 목록 앞쪽으로 재정렬하지 않습니다. 위치가 바뀌면 다시 찾아야 합니다.
91
+
92
+ **Don't**
93
+
94
+ - Chip 하나만 단독으로 쓰지 않습니다. 버튼으로 오해됩니다.
95
+ - Chip 사이 간격을 16보다 좁히지 않습니다.
96
+ - 라벨을 2줄로 표시하지 않습니다.
97
+ - 액션 실행(저장, 삭제)에 Chip을 쓰지 않습니다.
98
+
99
+ ## C. Properties
100
+
101
+ 스토리북(코드) 이름 기준입니다. 표의 Property는 읽기 쉽게 첫 글자를 대문자로 적었고, **코드에서는 첫 글자가 소문자**입니다 (`LeadingIcon` → `leadingIcon`). 화면에 보이는 속성뿐 아니라 **동작용(`on…`)·개발용 prop도 적습니다.** 비고에 피그마 이름을 함께 적었습니다.
102
+
103
+ | Property | Type | Values | 비고 |
104
+ | --- | --- | --- | --- |
105
+ | Label | String | | **필수.** 칩 라벨 |
106
+ | Checked | Boolean | True / False | |
107
+ | OnChange | Function | | 선택 상태가 바뀔 때 호출 · 동작용, 시각 변화 없음 |
108
+ | Error | Boolean | True / False | 스토리북 `error` 기준. 피그마 `Is Error` → `Error`로 이름 통일 |
109
+ | LeadingIcon | Instance | System Icon 이름 (Large) | 켜고 끄는 값이 아니라 **아이콘 이름**을 넣습니다. 피그마 `Variant = Label (Icon) / Label` 이 이 값의 유무에 해당 |
110
+ | TrailingIcon | Instance | System Icon 이름 (Medium) | 가이드상 Check Icon(`medium_check`)을 씁니다 (4-3) |
111
+ | State | Variant | Enabled / Hovered | 피그마에만 존재 · 코드에 Hovered 스타일 없음 (Z-1) |
112
+
113
+ > 선택 상태는 **`Checked` 하나로만** 표현합니다. 초안의 `Actived`는 `Checked = True`와 같은 뜻이라 State 값에서 뺐습니다 (피그마·스토리북 모두 `Actived` 없음).
114
+
115
+ > 아이콘 사용 예 (스토리북 기준)
116
+ >
117
+ > ```tsx
118
+ > <ChoiceChip label="알림 설정" leadingIcon="large_bell" trailingIcon="medium_check" />
119
+ > ```
120
+
121
+ ## Z. 미결 (구현하지 말 것)
122
+
123
+ > 아래는 **결정되지 않은 제안**입니다. 구현 기준은 위 A~C입니다. 결정되면 본문으로 옮기고 여기서 지웁니다.
124
+
125
+ 1. **Hovered · Disabled 상태**: 코드에는 선택(`checked`)·오류(`error`) 스타일만 있습니다. Hovered는 피그마에만 있고, Disabled는 피그마·코드 모두 없습니다(초안의 제안). 필요하면 디자인 변경 요청 이슈로 올립니다
126
+ 2. **오류 prop 이름 불일치**: ChoiceChip은 `error`, Checkbox·CheckboxCard 등은 `isError`를 씁니다. 코드 쪽 이름을 바꾸는 건 breaking change라 문서에서는 각 컴포넌트의 실제 이름을 그대로 적습니다. 통일 여부는 개발팀과 결정
@@ -0,0 +1,111 @@
1
+ ---
2
+ id: INPUT-16
3
+ component: CommentArea
4
+ version: 0.0.3
5
+ status: draft
6
+ ---
7
+
8
+ # 16. CommentArea
9
+
10
+ **개정 이력**
11
+
12
+ - v0.0.1 (2026.09.28): 초안 작성
13
+ - v0.0.2 (2026.10.01): 프론트매터 추가, Properties를 코드(`CommentAreaProps`)·스토리북 기준으로 정리, "추가 고려할 것"을 `Z. 미결`로 분리
14
+ - v0.0.3 (2026.10.02): `value` 필수 표기, `onChange` · `onCheck` · `onSubmit` · `onClose` · `className` 추가, Properties 안내 문구 통일, Z절 색 표기를 코드 토큰명으로 (#1295 리뷰 반영)
15
+
16
+ ## A. Anatomy
17
+
18
+ CommentArea는 **Container** 안에 **입력 영역**, **Guide**, **Button Area** 세 덩어리가 위에서 아래로 쌓인 구조입니다. 답글로 쓸 때는 좌측에 **Reply** 아이콘이 붙습니다.
19
+
20
+ | 요소 | 필수 여부 | 설명 |
21
+ | --- | --- | --- |
22
+ | Reply | 선택 | 답글임을 나타내는 좌측 들여쓰기 아이콘 |
23
+ | Container | 필수 | 배경·테두리·모서리를 담당하는 영역 |
24
+ | User ID | 선택 | 작성자의 아바타와 아이디. 입력 영역 상단 |
25
+ | Placeholder | 필수 | 실제로 글을 쓰는 입력 영역 |
26
+ | Scrollbar | 선택 | 입력 내용이 영역을 넘칠 때 표시 |
27
+ | Guide | 선택 | 작성 시 참고할 안내. 회색 배경 영역 |
28
+ | Bullet | 조건부 | Guide 안의 각 항목 앞 점 |
29
+ | Check List | 선택 | Button Area 좌측의 체크박스 (비밀글 등) |
30
+ | Cancel Button | 조건부 | 작성을 취소합니다. **답글(`isReply`)일 때만** 나타납니다 |
31
+ | **Submit Button** | 필수 | 작성한 내용을 등록합니다 |
32
+
33
+ ## B. Description
34
+
35
+ ### 1. 정의
36
+
37
+ 사용자가 게시물이나 항목에 대한 의견을 작성할 때 사용하는 컴포넌트입니다.
38
+
39
+ ### 2. 사용 시점
40
+
41
+ - 게시물·프로젝트 등에 **의견을 남길 수 있게** 할 때 사용합니다.
42
+ - 원글에 대한 **답글**을 작성할 때는 `Reply`를 켜서 들여쓴 형태로 사용합니다.
43
+ - 작성 전에 지켜야 할 규칙을 알려야 한다면 **Guide**를 함께 사용합니다.
44
+ - Placeholder 문구는 로그인 상태와 달라야 합니다. (`로그인 후 이용해주세요`)
45
+
46
+ ### 3. 키워드
47
+
48
+ CommentArea, 코멘트, 댓글, 답글, 의견 작성, 댓글 입력, Comment, Reply
49
+
50
+ ### 4. Size
51
+
52
+ - CommentArea는 **부모 컨테이너 너비를 100% 채웁니다.** Figma의 `400`은 예시 값입니다.
53
+ - `Reply`가 켜지면 좌측에 `16 + 16`만큼 들여쓰기가 생기고, Container가 나머지를 채웁니다.
54
+ - 입력 영역의 기본 높이는 **120**입니다.
55
+ - 입력한 내용이 높이를 넘으면 **스크롤을 반드시 노출**합니다.
56
+
57
+ ### 5. Do / Don't
58
+
59
+ **Do**
60
+
61
+ - 비로그인 상태에서는 클릭 시 로그인 페이지로 이동시킵니다.
62
+ - Guide 텍스트는 최대 2줄까지만 작성합니다.
63
+ - 입력 내용이 높이를 넘으면 스크롤을 노출합니다.
64
+ - Placeholder에는 **무엇을 쓰는 칸인지** 적습니다.
65
+
66
+ **Don't**
67
+
68
+ - 등록 버튼을 내용이 비어 있는데 활성 상태로 두지 않습니다.
69
+ - Guide를 3줄 이상으로 늘리지 않습니다.
70
+ - 입력 영역 높이를 내용에 따라 무한정 늘리지 않습니다. 스크롤로 처리합니다.
71
+ - 비로그인 상태에서 입력만 되고 등록 단계에서 막지 않습니다.
72
+
73
+ ## C. Properties
74
+
75
+ 스토리북(코드) 이름 기준입니다. 표의 Property는 읽기 쉽게 첫 글자를 대문자로 적었고, **코드에서는 첫 글자가 소문자**입니다 (`IsReply` → `isReply`). 화면에 보이는 속성뿐 아니라 **동작용(`on…`)·개발용 prop도 적습니다.** 비고에 피그마 이름을 함께 적었습니다.
76
+
77
+ | Property | Type | Values | 비고 |
78
+ | --- | --- | --- | --- |
79
+ | UserId | String | | 작성자 아이디. **값이 있으면 로그인 상태**, 없으면 비로그인(입력·등록·체크 비활성) · 피그마 `User ID`(Boolean)·`Is Logged`(Default/Guest)가 둘 다 이 값 하나에 해당 |
80
+ | ImgUrl | String | | 작성자 아바타 이미지 · 코드에만 존재 |
81
+ | Placeholder | String | | 입력 전 안내 문구 |
82
+ | Value | String | | **필수.** 입력한 내용. 비어 있으면 등록 버튼 비활성. 빠뜨리면 오류가 납니다 |
83
+ | OnChange | Function | | 입력 내용이 바뀔 때 호출 · 동작용 |
84
+ | GuideText | String / String 목록 | | Guide 문구. **값이 있으면 Guide가 나타납니다.** 여러 줄은 목록으로 · 피그마 `With Guide Text`(Boolean)에 해당 |
85
+ | OptionMessage | String | | Check List 라벨(예: `비밀글`). **값이 있고 로그인 상태일 때만** 나타납니다 · 피그마 `With Optional Check`에 해당 |
86
+ | IsSelected | Boolean | True / False | Check List 체크 여부 |
87
+ | OnCheck | Function | | Check List를 눌렀을 때 호출 · 동작용 |
88
+ | IsReply | Boolean | True / False | 답글 모드. Reply 아이콘과 **Cancel Button이 함께** 나타납니다 · 피그마 `Is Reply` |
89
+ | ButtonName | String | | **필수.** Submit Button 문구 |
90
+ | OnSubmit | Function | | Submit Button을 눌렀을 때 호출 · 동작용 |
91
+ | CancelButtonName | String | | Cancel Button 문구. 기본값 `취소` |
92
+ | OnClose | Function | | Cancel Button을 눌렀을 때 호출 (답글 모드) · 동작용 |
93
+ | Disabled | Boolean | True / False | 입력·체크·등록 모두 비활성 |
94
+ | Children | ReactNode | | 입력 영역 대신 넣을 내용. 비로그인 안내(`로그인 후 이용해주세요`)에 사용 (스토리북 `Guest`) |
95
+ | ClassName | String | | 개발용 스타일 지정 |
96
+
97
+ > 피그마의 `Variant`(Empty / Placeholder / Text), `State`(Enabled / Hovered / Focused), `Scrollbar`, `Cancel Button`은 **코드에 별도 값이 없습니다.** Variant는 입력 내용(`Value`)에 따라, Scrollbar는 내용이 넘칠 때 자동으로 정해지고, Cancel Button은 `IsReply`를 따라갑니다.
98
+ >
99
+ > ```tsx
100
+ > <CommentArea userId="wishket" placeholder="댓글을 입력하세요" guideText={['욕설은 삭제됩니다']}
101
+ > optionMessage="비밀글" isSelected={secret} onCheck={toggleSecret}
102
+ > buttonName="등록" value={value} onChange={onChange} onSubmit={submit} />
103
+ > ```
104
+
105
+ ## Z. 미결 (구현하지 말 것)
106
+
107
+ > 아래는 **결정되지 않은 제안**입니다. 구현 기준은 위 A~C입니다. 결정되면 본문으로 옮기고 여기서 지웁니다.
108
+
109
+ 1. **State 3개가 시각적으로 구분되지 않음**: Enabled·Hovered·Focused 모두 테두리가 `w-gray-200`으로 같고, Focused만 커서가 생깁니다(코드도 테두리 고정). Autocomplete와 Calendar는 Hovered·Focused에서 `primary-500`으로 바뀌는데 CommentArea만 다릅니다 — 입력 컴포넌트끼리 규칙을 맞출지 결정
110
+ 2. **`Empty` Variant의 용도가 불분명**: Placeholder 텍스트조차 없는 빈 상태인데, 그러면 무엇을 쓰는 칸인지 알 수 없습니다. 실제로 필요한 상태인지 확인 필요
111
+ 3. **피그마 속성 정리**: 피그마 `User ID`와 `Is Logged`는 코드에서 `userId` 하나로 정해지고, `Cancel Button`은 `isReply`를 따라갑니다. 피그마 속성을 코드 구조에 맞춰 합치거나 뺄지 결정
@@ -0,0 +1,145 @@
1
+ ---
2
+ id: INPUT-20
3
+ component: FileUploader
4
+ version: 0.0.3
5
+ status: draft
6
+ ---
7
+
8
+ # 20. FileUploader
9
+
10
+ **개정 이력**
11
+
12
+ - v0.0.1 (2026.09.29): 초안 작성
13
+ - v0.0.2 (2026.10.01): 프론트매터 추가, Properties를 코드(`FileUploaderProps`)·스토리북 기준으로 하나의 표로 정리, 용량 초과 처리 방식 명시, 용량 제한을 전체 합계 기준·프로젝트별 지정으로 확정, 미결 항목을 `Z. 미결`로 분리
14
+ - v0.0.3 (2026.10.02): Small 드롭존 높이 60 → 92 · 전체 368 → 400 정정(코드 패딩 기준), 본문 색 표기를 코드 토큰명(`primary-500` · `w-red-500`)으로, Properties 안내 문구 통일, Z절 번호 순서 정리 (#1295 리뷰 반영)
15
+
16
+ ## A. Anatomy
17
+
18
+ FileUploader는 **File 목록**과 **Uploader(드롭존)** 두 덩어리가 세로로 쌓인 구조입니다.
19
+
20
+ | 요소 | 필수 여부 | 설명 |
21
+ | --- | --- | --- |
22
+ | File | 선택 | 첨부된 파일 1건을 보여주는 행. 파일이 없으면 통째로 사라짐 |
23
+ | Formats | 선택 | 드롭존 안의 허용 파일 형식 안내 (`첨부 가능 파일 : JPG, PNG …`). `accept` 값으로 **자동 생성** |
24
+ | Uploader | 필수 | 클릭·드래그 앤 드롭으로 파일을 받는 영역 |
25
+ | Supporting Text | 선택 | 드롭존 하단 좌측의 도움말 |
26
+ | Max Size | 선택 | 드롭존 하단 우측의 용량 표시 (`0MB/40MB`). 최대 용량은 **프로젝트마다 다르게** 정합니다 (`40MB`는 예시) |
27
+ | Error Text | 선택 | 오류 시 Supporting Text **위**에 표시 |
28
+
29
+ **File 행의 내부 구성**
30
+
31
+ | 요소 | 필수 여부 | 설명 |
32
+ | --- | --- | --- |
33
+ | Clip Icon | 필수 | 좌측 클립 아이콘 (16 · `primary-500`) |
34
+ | File Name | 필수 | 파일명. 영역을 넘으면 말줄임 (확장자는 남김) |
35
+ | File Size | 필수 | 해당 파일의 용량 (`0KB`) |
36
+ | Delete | 필수 | 우측 × 아이콘 (14) |
37
+
38
+ ## B. Description
39
+
40
+ ### 1. 정의
41
+
42
+ 사용자가 파일을 선택하거나 드래그 앤 드롭으로 업로드할 수 있도록 하는 입력형 컴포넌트입니다.
43
+
44
+ ### 2. 사용 시점
45
+
46
+ - 사용자가 **파일을 첨부**해야 할 때 사용합니다. (포트폴리오, 산출물, 증빙 서류)
47
+ - 첨부한 파일을 **목록으로 확인하고 개별 삭제**할 수 있어야 할 때 사용합니다.
48
+ - 허용 형식이나 용량 제한을 **미리 알려줘야** 할 때 `Formats`와 `Supporting Text`를 함께 사용합니다.
49
+ - 파일이 아니라 텍스트를 입력받는 것이라면 **TextField / Textarea**를 사용합니다.
50
+ - 이미 올라간 파일을 보여주기만 하는 읽기 전용 목록이라면 FileUploader가 아니라 별도 목록을 구성합니다.
51
+
52
+ ### 3. 키워드
53
+
54
+ FileUploader, 파일 업로더, 파일 첨부, 업로드, 드래그 앤 드롭, Drag and Drop, Upload, 첨부파일
55
+
56
+ ### 4. Size
57
+
58
+ Size는 **드롭존의 형태**만 다릅니다. File 목록·Supporting Text는 두 Size가 동일합니다.
59
+
60
+ | Size | 코드 | 드롭존 Height | 구성 | 전체 Height (파일 5개 기준) | 사용 상황 |
61
+ | --- | --- | --- | --- | --- | --- |
62
+ | **Small** | `isSmall = true` | 92 | 아이콘 + 텍스트 **가로 1줄** · Formats 없음 | 400 | 폼 안의 부수적인 첨부 항목일 때. 세로 공간이 부족할 때 |
63
+ | **Medium** | 기본 | 146 | 아이콘 + 텍스트 **세로 2줄** (본문 + Formats) | 454 | 파일 첨부가 화면의 주요 행동일 때. 허용 형식 안내가 필요할 때 |
64
+
65
+ - **Small에는 Formats 줄이 없습니다.** 허용 형식을 알려야 한다면 Medium을 쓰거나 Supporting Text에 적습니다.
66
+ - Small의 드롭존 문구는 `text`로 바꿀 수 있습니다 (기본 `파일 업로드`).
67
+ - 한 화면 안에서는 같은 Size로 통일합니다.
68
+
69
+ ### 4-1. 첨부 방법
70
+
71
+ 1. 드롭존을 **클릭**하면 파일 선택창이 열립니다.
72
+ 2. 드롭존 위로 파일을 **끌어다 놓아도** 첨부됩니다. 두 방법 모두 항상 지원합니다.
73
+ 3. 첨부된 파일은 드롭존 **위쪽**에 최신 순이 아니라 **추가한 순서대로** 쌓습니다.
74
+ 4. 각 파일은 우측 × 로 **개별 삭제**합니다.
75
+
76
+ ### 4-2. 파일명과 용량
77
+
78
+ 1. 파일명이 영역을 초과하면 **말줄임(`…`)** 으로 축약합니다. 줄바꿈하지 않습니다.
79
+ 2. 축약된 파일명은 **툴팁으로 전체 이름**을 제공합니다. (코드 미구현 — Z-2)
80
+ 3. 각 파일 행에는 해당 파일의 용량을, 하단 우측에는 **전체 누적 용량 / 최대 용량**을 표기합니다.
81
+ 4. 용량 제한에 도달하기 전까지는 **파일 개수에 제한을 두지 않습니다.**
82
+ 5. 용량 제한은 **첨부한 파일 전체의 합계** 기준입니다. 하단 우측 `누적 / 최대`가 최대치를 넘는 일(`90MB/40MB`)이 없어야 합니다. 합계가 최대 용량을 넘게 만드는 파일은 첨부하지 않고 Error Text로 알립니다. (코드는 아직 파일당 기준 — Z-1)
83
+ 6. 최대 용량은 **고정값이 아닙니다.** 프로젝트·화면마다 필요한 값을 `maxFileSize`로 지정하고, 같은 값을 Supporting Text에도 적어 미리 알립니다. (`파일당 최대 40MB`, `최대 100MB` 등)
84
+
85
+ ### 4-3. Supporting Text · Error Text
86
+
87
+ 1. Supporting Text 자리는 오류 여부와 무관하게 **항상 확보**합니다.
88
+ 2. 오류가 발생하면 Error Text가 **Supporting Text 위에 추가**되고, 드롭존 테두리가 `w-red-500`으로 바뀝니다.
89
+ 3. 에러 텍스트는 **`하세요`/`주세요`로 끝맺습니다.** (`파일은 40MB 이하로 첨부해 주세요.`)
90
+ 4. 무엇이 잘못됐는지가 아니라 **무엇을 하면 되는지**를 적습니다.
91
+ 5. 용량을 넘는 파일은 **첨부되지 않고 빠집니다.** 이때 `onFileSizeExceeded`가 호출되므로, 여기서 `isError`와 `errorMessage`를 켜서 Error Text로 알립니다.
92
+
93
+ ### 5. Do / Don't
94
+
95
+ **Do**
96
+
97
+ - 직접 첨부와 드래그 앤 드롭을 **모두** 지원합니다.
98
+ - 용량 제한 전까지는 자유롭게 첨부할 수 있게 합니다.
99
+ - 파일명이 영역을 초과하면 `…`으로 축약합니다.
100
+ - 허용 형식이 정해져 있으면 Formats나 Supporting Text로 미리 알립니다.
101
+
102
+ **Don't**
103
+
104
+ - 드래그 앤 드롭만 지원하거나 클릭만 지원하지 않습니다.
105
+ - 용량 초과를 **브라우저 기본 `alert`** 로 알리지 않습니다. Error Text로 표시합니다.
106
+ - 파일명을 앞에서부터 자르지 않습니다. 확장자는 남깁니다.
107
+ - 첨부한 파일을 지울 방법 없이 목록만 쌓지 않습니다.
108
+
109
+ ## C. Properties
110
+
111
+ 스토리북(코드) 이름 기준입니다. 표의 Property는 읽기 쉽게 첫 글자를 대문자로 적었고, **코드에서는 첫 글자가 소문자**입니다 (`IsSmall` → `isSmall`). 화면에 보이는 속성뿐 아니라 **동작용(`on…`)·개발용 prop도 적습니다.** 비고에 피그마 쪽 상태를 함께 적었습니다.
112
+
113
+ | Property | Type | Values | 비고 |
114
+ | --- | --- | --- | --- |
115
+ | Files | File 목록 | | 첨부된 파일. **있으면 File 목록이 나타납니다** · 피그마 `File`(Boolean)·`Variant = File / Uploader`가 이 값의 유무에 해당 |
116
+ | OnChange | Function | | 파일이 추가·삭제될 때 호출 · 동작용, 시각 변화 없음 |
117
+ | Accept | String | 예: `image/*`, `.pdf,.doc` | 허용 파일 형식. Medium의 **Formats 문구가 이 값으로 자동 생성**됩니다 |
118
+ | Multiple | Boolean | True / False | 여러 파일 동시 선택. 기본값 True |
119
+ | MaxFileSize | Number | bytes | 최대 용량. **프로젝트마다 다르게 지정**합니다. 하단 우측 `누적/최대` 표시에 쓰임. 가이드는 **전체 합계** 기준이지만 코드는 아직 **파일 1개당** 기준으로 막습니다 (Z-1) · 피그마는 Supporting Text와 합쳐져 있음 |
120
+ | OnFileSizeExceeded | Function | | 용량을 넘는 파일이 들어왔을 때 호출 (4-3 5번) |
121
+ | IsSmall | Boolean | True / False | Size Small. 기본값 False(Medium) · 피그마 `Size = Small / Medium` → 이름 통일 필요 |
122
+ | Text | String | | Small 드롭존 문구. 기본값 `파일 업로드` |
123
+ | IsError | Boolean | True / False | 드롭존 Red 테두리 · 피그마 `Is Error` |
124
+ | ErrorMessage | String | | 오류 문구. Supporting Text 위에 표시 |
125
+ | SupportMessage | String | | 도움말 문구 · 피그마 `Supporting Text`(Boolean) → 텍스트 속성 추가 |
126
+ | Disabled | Boolean | True / False | 첨부·삭제 불가 · 피그마는 `Disabled` Boolean + `State = Disabled` **중복** |
127
+
128
+ > 피그마의 `State`(Enabled / Hovered / Disabled)는 코드에 따로 넣는 값이 없습니다. Hovered는 마우스를 올리거나 **파일을 끌어다 올릴 때** 자동으로 테두리가 진해지고, Disabled는 `disabled`를 따릅니다.
129
+ >
130
+ > ```tsx
131
+ > <FileUploader files={files} onChange={setFiles} accept=".pdf,.doc,.docx"
132
+ > maxFileSize={40 * 1024 * 1024} supportMessage="파일당 최대 40MB"
133
+ > onFileSizeExceeded={() => setError('파일은 40MB 이하로 첨부해 주세요.')}
134
+ > isError={!!error} errorMessage={error} />
135
+ > ```
136
+
137
+ ## Z. 미결 (구현하지 말 것)
138
+
139
+ > 아래는 **결정되지 않은 제안**입니다. 구현 기준은 위 A~C입니다. 결정되면 본문으로 옮기고 여기서 지웁니다.
140
+
141
+ 1. **용량 제한을 전체 합계 기준으로 변경 (개발팀 요청 필요)**: 기준은 **전체 합계**, 값은 **프로젝트마다 지정**으로 정했습니다(2026-10-01, 4-2 5·6번). 그런데 코드는 `maxFileSize`를 **파일 1개당** 기준으로만 막아, 여러 파일을 올리면 하단 우측이 `90MB/40MB`처럼 최대치를 넘어 표시됩니다. 합계 기준으로 막도록 디자인 변경 요청 이슈로 올려야 합니다
142
+ 2. **파일명 툴팁 (개발팀 전달)**: 4-2 2번의 "축약된 파일명은 툴팁으로 전체 이름 제공"이 코드에 구현되어 있지 않습니다
143
+ 3. **피그마 속성 정리**: 피그마 `Size`(Small / Medium)와 코드 `isSmall`, 피그마 `Disabled` Boolean과 `State = Disabled`의 중복, `Variant = File / Uploader`(코드는 `files` 유무로 자동) 정리 필요
144
+ 4. **스토리북 설명 (개발팀 전달)**: 스토리북 설명에 "크기 초과 시 alert 표시"라고 되어 있지만, 코드는 alert 없이 `onFileSizeExceeded`만 호출합니다. 설명이 예전 동작 기준입니다
145
+ 5. **피그마 예시 값 (피그마 수정)**: Policy "용량 제한 전까지 자유롭게" 예시에 500KB 파일 5개가 있는데 하단 우측이 `0MB/40MB`로 되어 있습니다. 누적 용량(`2.5MB/40MB`)으로 고쳐야 합니다
@@ -0,0 +1,132 @@
1
+ ---
2
+ id: INPUT-14
3
+ component: FilterChip
4
+ version: 0.0.3
5
+ status: draft
6
+ ---
7
+
8
+ # 14. FilterChip
9
+
10
+ **개정 이력**
11
+
12
+ - v0.0.1 (2026.09.23): 초안 작성
13
+ - v0.0.2 (2026.10.01): 프론트매터 추가, Properties를 코드(`FilterChipProps`)·스토리북 기준으로 정리, 피그마 ↔ 코드 상태 대응표 추가, 용도를 정렬(단일)에서 필터(다중)로 확정하고 정의·4-1·4-2·Do/Don't 수정, 미결 항목을 `Z. 미결`로 분리
14
+ - v0.0.3 (2026.10.02): Anatomy의 Leading Icon "기본값" 정정(코드에 기본값 없음), Properties 안내 문구와 `children` · `onClick` · `className` 추가, 4절 제목을 "배열 규칙"으로 (#1295 리뷰 반영)
15
+
16
+ ## A. Anatomy
17
+
18
+ FilterChip은 **Container**와 **Text**로 구성되며, **Leading Icon**과 **Trailing Icon**, 선택 시 **Count Badge**를 가질 수 있습니다.
19
+
20
+ | 요소 | 필수 여부 | 설명 |
21
+ | --- | --- | --- |
22
+ | Container | 필수 | 배경·테두리를 담당하는 알약(pill) 형태의 영역. 전체가 클릭 영역 |
23
+ | Leading Icon | 선택 | 라벨 앞 아이콘. **기본값이 없어** 지정하지 않으면 나타나지 않습니다. 보통 Filter 아이콘(`medium_filter`)을 씁니다 |
24
+ | Text | 필수 | 필터 그룹의 이름 (`카테고리`, `근무 형태`) |
25
+ | Trailing Icon | 선택 | 라벨 뒤 아이콘. 목록이 열린다는 것을 알리는 Chevron |
26
+ | Count Badge | 선택 | 우측 상단의 개수 표시. 선택된 항목 수를 알립니다 |
27
+
28
+ > `FilterChip`은 **Chip의 UI만 담당**합니다. 실제 선택 목록은 `FilterList`를 children으로 따로 전달합니다.
29
+
30
+ ## B. Description
31
+
32
+ ### 1. 정의
33
+
34
+ 사용자가 여러 조건을 선택해 콘텐츠를 걸러낼 수 있도록, 필터 목록(FilterList)을 열고 적용된 조건의 개수를 보여주는 칩 형태의 트리거 컴포넌트입니다.
35
+
36
+ ### 2. 사용 시점
37
+
38
+ - 목록 상단에서 **필터 조건을 고르거나 바꿀 때** 사용합니다.
39
+ - 조건을 **여러 개 동시에** 고를 수 있을 때 사용합니다. 고른 개수는 Count Badge로 보여줍니다.
40
+ - 조건이 **적용되어 있는지 항상 보여야** 할 때 사용합니다. 조건이 하나라도 적용되면 Chip이 선택 상태로 바뀝니다.
41
+ - FilterChip을 트리거로 쓰고 **FilterList**를 띄웁니다.
42
+ - **정렬(최신순·인기순처럼 하나만 고르는 선택)에는 사용하지 않습니다.** 선택하면 항상 숫자 배지가 붙기 때문입니다.
43
+ - 선택지를 처음부터 모두 펼쳐 보여줘야 한다면 **ChoiceChip**을 사용합니다.
44
+ - FilterChip은 **낮은 시각 위계**를 가집니다.
45
+ - 너비는 **콘텐츠에 맞춰 늘어납니다.** 고정 너비를 지정하지 않습니다.
46
+
47
+ **ChoiceChip과 FilterChip**
48
+
49
+ | | ChoiceChip | FilterChip |
50
+ | --- | --- | --- |
51
+ | 역할 | 선택지 자체 | 선택 목록을 여는 **트리거** |
52
+ | 배치 | 여러 개를 펼쳐 나열 | 목록 상단에 1~3개 |
53
+ | 선택 표현 | 배경색으로 구분 | 테두리 + Count Badge |
54
+ | 함께 쓰는 것 | 없음 | FilterList |
55
+
56
+ ### 3. 키워드
57
+
58
+ FilterChip, 필터칩, 칩, Chip, 필터, 필터 조건, 다중 선택, Filter
59
+
60
+ ### 4. 배열 규칙
61
+
62
+ 1. FilterChip 사이 간격은 **최소 16**을 유지합니다. 클릭 오류를 막기 위한 기준입니다.
63
+ 2. 목록 상단에 나란히 두며, 한 줄에 **3개 이내**를 권장합니다.
64
+ 3. 한 줄을 넘으면 줄바꿈합니다. 가로 스크롤하지 않습니다.
65
+
66
+ ### 4-1. 선택과 적용
67
+
68
+ - FilterList 안에서 조건을 **여러 개** 고를 수 있습니다.
69
+ - 고른 조건은 FilterList의 **Apply(적용하기)를 눌렀을 때** 결과와 Count Badge에 반영합니다.
70
+ - 목록을 열었다가 적용하지 않고 닫으면 **직전 상태를 유지**합니다.
71
+
72
+ ### 4-2. Count Badge
73
+
74
+ - Count Badge는 **조건이 1개 이상 적용되었을 때만** 노출합니다. 0개면 숨깁니다.
75
+ - 두 자리 수를 넘어가는 경우의 표기(`99+` 등)를 정해둡니다.
76
+
77
+ ### 5. Do / Don't
78
+
79
+ **Do**
80
+
81
+ - 라벨에는 **필터 그룹 이름**을 적고(`카테고리`), 적용된 개수는 Count Badge로 보여줍니다.
82
+ - 목록을 여는 Chip에는 Chevron을 함께 표시합니다.
83
+ - Chip 사이 간격 16을 유지합니다.
84
+ - 조건이 해제되면 Count Badge도 함께 사라지게 합니다.
85
+
86
+ **Don't**
87
+
88
+ - 정렬처럼 하나만 고르는 선택에 FilterChip을 쓰지 않습니다. 의미 없는 배지 `1`이 항상 붙습니다.
89
+ - 라벨을 2줄로 표시하지 않습니다.
90
+ - 선택된 상태를 테두리 색만으로 알리지 않습니다. 굵기·텍스트 색을 함께 바꿉니다.
91
+ - 목록이 열려 있는 동안 Chip이 화면 밖으로 밀려나게 두지 않습니다.
92
+
93
+ ## C. Properties
94
+
95
+ 스토리북(코드) 이름 기준입니다. 표의 Property는 읽기 쉽게 첫 글자를 대문자로 적었고, **코드에서는 첫 글자가 소문자**입니다 (`BadgeCount` → `badgeCount`). 화면에 보이는 속성뿐 아니라 **동작용(`on…`)·개발용 prop도 적습니다.** 비고에 피그마 이름을 함께 적었습니다.
96
+
97
+ | Property | Type | Values | 비고 |
98
+ | --- | --- | --- | --- |
99
+ | Text | String | | **필수.** 칩 라벨 · 피그마 추가 |
100
+ | Children | ReactNode | | **열렸을 때 보여줄 목록(FilterList)을 넣는 자리.** `isOpen`이 True일 때 칩 아래에 나타납니다 |
101
+ | OnClick | Function | | 칩을 눌렀을 때 호출. 보통 여기서 `isOpen`을 바꿔 목록을 열고 닫습니다 · 동작용 |
102
+ | LeadingIcon | Instance | System Icon 이름 | 켜고 끄는 값이 아니라 **아이콘 이름**을 넣습니다. **기본값 없음** (스토리북 예시는 `medium_filter`) |
103
+ | TrailingIcon | Instance | System Icon 이름 | 위와 동일. 표준은 `medium_arrow_right`(`›` 모양 Chevron) — 피그마·스토리북 동일 |
104
+ | BadgeCount | String | 숫자 문자열 (예: `"3"`) | 배지에 **표시할 숫자**. `0`보다 크면 배지가 나타나고 Chip이 선택 상태가 됩니다. 피그마 `Checked = True`에 해당 |
105
+ | IsOpen | Boolean | True / False | FilterList가 열려 있는 상태. 피그마 `State = Focused`에 해당 |
106
+ | ClassName | String | | 개발용 스타일 지정 |
107
+
108
+ **피그마 ↔ 코드 대응** (피그마 Filter Chip `Checked` × `State` 6종 기준)
109
+
110
+ | 피그마 | 코드 | 모습 |
111
+ | --- | --- | --- |
112
+ | `State = Enabled` | 기본 | 회색 테두리, 검정 텍스트 |
113
+ | `State = Hovered` | 마우스 hover (자동) | 테두리만 Primary, 텍스트는 검정 유지 |
114
+ | `State = Focused` | `isOpen = true` | 테두리 + 텍스트·아이콘 Primary |
115
+ | `Checked = True` | `badgeCount`가 0보다 큼 | 2px Primary 테두리 + 연한 Primary 배경 + 우측 상단 Count Badge |
116
+
117
+ > **`Checked` prop은 없습니다.** 선택 상태는 따로 켜는 값이 아니라 **`BadgeCount`가 0보다 크면 자동으로** 선택 표시가 됩니다. 그래서 코드에서는 **배지 없이 선택된 모습을 만들 수 없습니다.** FilterChip을 필터 전용으로 쓰는 이유입니다.
118
+ >
119
+ > ```tsx
120
+ > <FilterChip text="선택 항목" leadingIcon="medium_filter" trailingIcon="medium_arrow_right"
121
+ > isOpen={isOpen} badgeCount={String(selected.length)} onClick={toggle}>
122
+ > <FilterList ... />
123
+ > </FilterChip>
124
+ > ```
125
+
126
+ ## Z. 미결 (구현하지 말 것)
127
+
128
+ > 아래는 **결정되지 않은 제안**입니다. 구현 기준은 위 A~C입니다. 결정되면 본문으로 옮기고 여기서 지웁니다.
129
+
130
+ 1. **피그마 속성 이름 정리**: 피그마 `Checked` / `State = Focused`는 코드의 `badgeCount` / `isOpen`에 해당합니다(위 대응표). 피그마 이름을 코드 기준으로 바꿀지 결정. `docs/design/mapping.md` 예외 목록에 기록됨
131
+ 2. **피그마 문서 갱신**: FilterChip은 **필터(다중 선택) 용도로 확정**했습니다(2026-10-01). 피그마 페이지의 헤더 정의("정렬 기준을 선택…")와 Policy("단일 활성화", `최신순 / 인기순` 예시)는 아직 정렬 기준이라 필터 기준으로 고쳐야 합니다
132
+ 3. **정렬용 컴포넌트**: 정렬(하나만 선택)에 무엇을 쓸지는 아직 정하지 않았습니다