@ceed/ads 1.13.3 → 1.15.0-next.1
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/Overview.md +11 -0
- package/dist/components/Input/Input.d.ts +9 -1
- package/dist/components/Menu/Menu.d.ts +2 -5
- package/dist/components/Stepper/Stepper.d.ts +6 -0
- package/dist/components/data-display/Avatar.md +428 -0
- package/dist/components/data-display/Badge.md +315 -0
- package/dist/components/data-display/Chip.md +301 -0
- package/dist/components/data-display/DataTable.md +452 -0
- package/dist/components/data-display/InfoSign.md +160 -0
- package/dist/components/data-display/Markdown.md +17 -0
- package/dist/components/data-display/Table.md +1330 -0
- package/dist/components/data-display/Tooltip.md +444 -0
- package/dist/components/data-display/Typography.md +271 -0
- package/dist/components/data-display/llms.txt +17 -0
- package/dist/components/feedback/Alert.md +663 -0
- package/dist/components/feedback/Dialog.md +33 -0
- package/dist/components/feedback/Modal.md +39 -0
- package/dist/components/feedback/llms.txt +11 -0
- package/dist/components/inputs/Autocomplete.md +103 -0
- package/dist/components/inputs/Button.md +334 -0
- package/dist/components/inputs/ButtonGroup.md +382 -0
- package/dist/components/inputs/Calendar.md +19 -0
- package/dist/components/inputs/Checkbox.md +649 -0
- package/dist/components/inputs/CurrencyInput.md +91 -0
- package/dist/components/inputs/DatePicker.md +67 -0
- package/dist/components/inputs/DateRangePicker.md +55 -0
- package/dist/components/inputs/FilterMenu.md +210 -0
- package/dist/components/inputs/IconButton.md +361 -0
- package/dist/components/inputs/Input.md +283 -0
- package/dist/components/inputs/MonthPicker.md +72 -0
- package/dist/components/inputs/MonthRangePicker.md +70 -0
- package/dist/components/inputs/PercentageInput.md +116 -0
- package/dist/components/inputs/RadioButton.md +350 -0
- package/dist/components/inputs/RadioTileGroup.md +418 -0
- package/dist/components/inputs/Select.md +56 -0
- package/dist/components/inputs/Switch.md +577 -0
- package/dist/components/inputs/Textarea.md +64 -0
- package/dist/components/inputs/Uploader/Uploader.md +238 -0
- package/dist/components/inputs/Uploader/llms.txt +9 -0
- package/dist/components/inputs/llms.txt +31 -0
- package/dist/components/layout/Box.md +997 -0
- package/dist/components/layout/Container.md +23 -0
- package/dist/components/layout/Grid.md +728 -0
- package/dist/components/layout/Stack.md +937 -0
- package/dist/components/layout/llms.txt +12 -0
- package/dist/components/llms.txt +14 -0
- package/dist/components/navigation/Breadcrumbs.md +51 -0
- package/dist/components/navigation/Dropdown.md +768 -0
- package/dist/components/navigation/IconMenuButton.md +35 -0
- package/dist/components/navigation/InsetDrawer.md +133 -0
- package/dist/components/navigation/Link.md +24 -0
- package/dist/components/navigation/Menu.md +957 -0
- package/dist/components/navigation/MenuButton.md +39 -0
- package/dist/components/navigation/NavigationGroup.md +17 -0
- package/dist/components/navigation/NavigationItem.md +17 -0
- package/dist/components/navigation/Navigator.md +17 -0
- package/dist/components/navigation/Pagination.md +17 -0
- package/dist/components/navigation/ProfileMenu.md +34 -0
- package/dist/components/navigation/Stepper.md +108 -0
- package/dist/components/navigation/Tabs.md +34 -0
- package/dist/components/navigation/llms.txt +22 -0
- package/dist/components/surfaces/Accordions.md +96 -0
- package/dist/components/surfaces/Card.md +786 -0
- package/dist/components/surfaces/Divider.md +762 -0
- package/dist/components/surfaces/Sheet.md +900 -0
- package/dist/components/surfaces/llms.txt +12 -0
- package/dist/index.cjs +47 -11
- package/dist/index.js +81 -45
- package/dist/llms.txt +75 -0
- package/framer/index.js +36 -36
- package/package.json +8 -4
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
# Checkbox
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Checkbox 컴포넌트는 사용자가 여러 옵션 중에서 하나 또는 여러 개를 선택할 수 있는 입력 요소입니다. Joy UI의 Checkbox를 기반으로 하며, Framer Motion 애니메이션 지원이 추가되었습니다. 설정 화면, 폼, 필터링, 다중 선택 목록 등에서 널리 사용됩니다.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Checkbox />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Field | Description | Default |
|
|
12
|
+
| -------- | ----------- | ------- |
|
|
13
|
+
| checked | — | — |
|
|
14
|
+
| disabled | — | — |
|
|
15
|
+
| label | — | — |
|
|
16
|
+
| variant | — | — |
|
|
17
|
+
| color | — | — |
|
|
18
|
+
| size | — | — |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Checkbox } from '@ceed/ads';
|
|
24
|
+
|
|
25
|
+
function MyComponent() {
|
|
26
|
+
const [checked, setChecked] = useState(false);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Checkbox
|
|
30
|
+
label="동의합니다"
|
|
31
|
+
checked={checked}
|
|
32
|
+
onChange={(e) => setChecked(e.target.checked)}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
### Basic Usage
|
|
41
|
+
|
|
42
|
+
가장 기본적인 Checkbox 사용법입니다.
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<Checkbox label="Label" />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### With Labels
|
|
49
|
+
|
|
50
|
+
체크박스에 라벨을 추가할 수 있습니다.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Checkbox />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Common Use Cases
|
|
57
|
+
|
|
58
|
+
### Terms and Conditions
|
|
59
|
+
|
|
60
|
+
이용약관 동의에 체크박스를 사용하는 예제입니다.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
function TermsForm() {
|
|
64
|
+
const [agreements, setAgreements] = useState({
|
|
65
|
+
terms: false,
|
|
66
|
+
privacy: false,
|
|
67
|
+
marketing: false,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const handleChange = (key: keyof typeof agreements) => (
|
|
71
|
+
event: React.ChangeEvent<HTMLInputElement>
|
|
72
|
+
) => {
|
|
73
|
+
setAgreements(prev => ({
|
|
74
|
+
...prev,
|
|
75
|
+
[key]: event.target.checked
|
|
76
|
+
}));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Stack spacing={2}>
|
|
81
|
+
<Typography level="title-md">약관 동의</Typography>
|
|
82
|
+
|
|
83
|
+
<Checkbox
|
|
84
|
+
label={
|
|
85
|
+
<Typography>
|
|
86
|
+
<strong>(필수)</strong> 이용약관에 동의합니다.
|
|
87
|
+
<Link href="/terms" target="_blank" sx={{ ml: 1 }}>
|
|
88
|
+
약관 보기
|
|
89
|
+
</Link>
|
|
90
|
+
</Typography>
|
|
91
|
+
}
|
|
92
|
+
checked={agreements.terms}
|
|
93
|
+
onChange={handleChange('terms')}
|
|
94
|
+
required
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<Checkbox
|
|
98
|
+
label={
|
|
99
|
+
<Typography>
|
|
100
|
+
<strong>(필수)</strong> 개인정보 처리방침에 동의합니다.
|
|
101
|
+
<Link href="/privacy" target="_blank" sx={{ ml: 1 }}>
|
|
102
|
+
방침 보기
|
|
103
|
+
</Link>
|
|
104
|
+
</Typography>
|
|
105
|
+
}
|
|
106
|
+
checked={agreements.privacy}
|
|
107
|
+
onChange={handleChange('privacy')}
|
|
108
|
+
required
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<Checkbox
|
|
112
|
+
label="(선택) 마케팅 정보 수신에 동의합니다."
|
|
113
|
+
checked={agreements.marketing}
|
|
114
|
+
onChange={handleChange('marketing')}
|
|
115
|
+
color="neutral"
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<Button
|
|
119
|
+
disabled={!agreements.terms || !agreements.privacy}
|
|
120
|
+
sx={{ mt: 2 }}
|
|
121
|
+
>
|
|
122
|
+
가입하기
|
|
123
|
+
</Button>
|
|
124
|
+
</Stack>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Multi-Select List
|
|
130
|
+
|
|
131
|
+
목록에서 여러 항목을 선택할 때 사용합니다.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
function TodoList() {
|
|
135
|
+
const [todos, setTodos] = useState([
|
|
136
|
+
{ id: 1, text: '회의 준비하기', completed: false },
|
|
137
|
+
{ id: 2, text: '보고서 작성하기', completed: true },
|
|
138
|
+
{ id: 3, text: '이메일 답장하기', completed: false },
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const handleToggle = (id: number) => {
|
|
142
|
+
setTodos(prev => prev.map(todo =>
|
|
143
|
+
todo.id === id ? { ...todo, completed: !todo.completed } : todo
|
|
144
|
+
));
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<Stack spacing={1}>
|
|
149
|
+
<Typography level="title-md">할 일 목록</Typography>
|
|
150
|
+
{todos.map(todo => (
|
|
151
|
+
<Checkbox
|
|
152
|
+
key={todo.id}
|
|
153
|
+
label={
|
|
154
|
+
<Typography
|
|
155
|
+
sx={{
|
|
156
|
+
textDecoration: todo.completed ? 'line-through' : 'none',
|
|
157
|
+
color: todo.completed ? 'neutral.500' : 'inherit',
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
{todo.text}
|
|
161
|
+
</Typography>
|
|
162
|
+
}
|
|
163
|
+
checked={todo.completed}
|
|
164
|
+
onChange={() => handleToggle(todo.id)}
|
|
165
|
+
color={todo.completed ? 'success' : 'primary'}
|
|
166
|
+
/>
|
|
167
|
+
))}
|
|
168
|
+
</Stack>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Filter Options
|
|
174
|
+
|
|
175
|
+
필터링 옵션에서 체크박스를 사용하는 예제입니다.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
function ProductFilter() {
|
|
179
|
+
const [filters, setFilters] = useState({
|
|
180
|
+
categories: [] as string[],
|
|
181
|
+
priceRanges: [] as string[],
|
|
182
|
+
brands: [] as string[],
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const handleCategoryChange = (category: string) => (
|
|
186
|
+
event: React.ChangeEvent<HTMLInputElement>
|
|
187
|
+
) => {
|
|
188
|
+
setFilters(prev => ({
|
|
189
|
+
...prev,
|
|
190
|
+
categories: event.target.checked
|
|
191
|
+
? [...prev.categories, category]
|
|
192
|
+
: prev.categories.filter(c => c !== category)
|
|
193
|
+
}));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Stack spacing={3}>
|
|
198
|
+
<Stack spacing={2}>
|
|
199
|
+
<Typography level="title-sm">카테고리</Typography>
|
|
200
|
+
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
201
|
+
<Checkbox
|
|
202
|
+
label="전자제품"
|
|
203
|
+
checked={filters.categories.includes('electronics')}
|
|
204
|
+
onChange={handleCategoryChange('electronics')}
|
|
205
|
+
size="sm"
|
|
206
|
+
/>
|
|
207
|
+
<Checkbox
|
|
208
|
+
label="의류"
|
|
209
|
+
checked={filters.categories.includes('clothing')}
|
|
210
|
+
onChange={handleCategoryChange('clothing')}
|
|
211
|
+
size="sm"
|
|
212
|
+
/>
|
|
213
|
+
<Checkbox
|
|
214
|
+
label="도서"
|
|
215
|
+
checked={filters.categories.includes('books')}
|
|
216
|
+
onChange={handleCategoryChange('books')}
|
|
217
|
+
size="sm"
|
|
218
|
+
/>
|
|
219
|
+
</Stack>
|
|
220
|
+
</Stack>
|
|
221
|
+
|
|
222
|
+
<Stack spacing={2}>
|
|
223
|
+
<Typography level="title-sm">가격대</Typography>
|
|
224
|
+
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
225
|
+
<Checkbox label="10만원 이하" size="sm" />
|
|
226
|
+
<Checkbox label="10-50만원" size="sm" />
|
|
227
|
+
<Checkbox label="50만원 이상" size="sm" />
|
|
228
|
+
</Stack>
|
|
229
|
+
</Stack>
|
|
230
|
+
</Stack>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Select All Pattern
|
|
236
|
+
|
|
237
|
+
전체 선택/해제 패턴을 구현하는 예제입니다.
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
function SelectAllExample() {
|
|
241
|
+
const [items, setItems] = useState([
|
|
242
|
+
{ id: 1, name: '항목 1', selected: false },
|
|
243
|
+
{ id: 2, name: '항목 2', selected: true },
|
|
244
|
+
{ id: 3, name: '항목 3', selected: false },
|
|
245
|
+
{ id: 4, name: '항목 4', selected: false },
|
|
246
|
+
]);
|
|
247
|
+
|
|
248
|
+
const selectedCount = items.filter(item => item.selected).length;
|
|
249
|
+
const isAllSelected = selectedCount === items.length;
|
|
250
|
+
const isIndeterminate = selectedCount > 0 && selectedCount < items.length;
|
|
251
|
+
|
|
252
|
+
const handleSelectAll = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
253
|
+
setItems(prev => prev.map(item => ({
|
|
254
|
+
...item,
|
|
255
|
+
selected: event.target.checked
|
|
256
|
+
})));
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const handleItemToggle = (id: number) => {
|
|
260
|
+
setItems(prev => prev.map(item =>
|
|
261
|
+
item.id === id ? { ...item, selected: !item.selected } : item
|
|
262
|
+
));
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<Stack spacing={2}>
|
|
267
|
+
<Checkbox
|
|
268
|
+
label={`전체 선택 (${selectedCount}/${items.length})`}
|
|
269
|
+
checked={isAllSelected}
|
|
270
|
+
indeterminate={isIndeterminate}
|
|
271
|
+
onChange={handleSelectAll}
|
|
272
|
+
variant="outlined"
|
|
273
|
+
sx={{ fontWeight: 'bold' }}
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
<Divider />
|
|
277
|
+
|
|
278
|
+
<Stack spacing={1} sx={{ pl: 2 }}>
|
|
279
|
+
{items.map(item => (
|
|
280
|
+
<Checkbox
|
|
281
|
+
key={item.id}
|
|
282
|
+
label={item.name}
|
|
283
|
+
checked={item.selected}
|
|
284
|
+
onChange={() => handleItemToggle(item.id)}
|
|
285
|
+
size="sm"
|
|
286
|
+
/>
|
|
287
|
+
))}
|
|
288
|
+
</Stack>
|
|
289
|
+
</Stack>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Form with Validation
|
|
295
|
+
|
|
296
|
+
폼 검증과 함께 사용하는 체크박스 예제입니다.
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
function RegistrationForm() {
|
|
300
|
+
const [formData, setFormData] = useState({
|
|
301
|
+
name: '',
|
|
302
|
+
email: '',
|
|
303
|
+
agreeToTerms: false,
|
|
304
|
+
subscribeNewsletter: false,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
308
|
+
|
|
309
|
+
const handleSubmit = (event: React.FormEvent) => {
|
|
310
|
+
event.preventDefault();
|
|
311
|
+
|
|
312
|
+
const newErrors: Record<string, string> = {};
|
|
313
|
+
|
|
314
|
+
if (!formData.name) {
|
|
315
|
+
newErrors.name = '이름을 입력해주세요';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!formData.email) {
|
|
319
|
+
newErrors.email = '이메일을 입력해주세요';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!formData.agreeToTerms) {
|
|
323
|
+
newErrors.agreeToTerms = '이용약관에 동의해주세요';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
setErrors(newErrors);
|
|
327
|
+
|
|
328
|
+
if (Object.keys(newErrors).length === 0) {
|
|
329
|
+
console.log('폼 제출:', formData);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<form onSubmit={handleSubmit}>
|
|
335
|
+
<Stack spacing={3}>
|
|
336
|
+
<Input
|
|
337
|
+
placeholder="이름"
|
|
338
|
+
value={formData.name}
|
|
339
|
+
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
|
340
|
+
error={!!errors.name}
|
|
341
|
+
/>
|
|
342
|
+
{errors.name && (
|
|
343
|
+
<Typography level="body-xs" color="danger">
|
|
344
|
+
{errors.name}
|
|
345
|
+
</Typography>
|
|
346
|
+
)}
|
|
347
|
+
|
|
348
|
+
<Input
|
|
349
|
+
placeholder="이메일"
|
|
350
|
+
type="email"
|
|
351
|
+
value={formData.email}
|
|
352
|
+
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
|
353
|
+
error={!!errors.email}
|
|
354
|
+
/>
|
|
355
|
+
{errors.email && (
|
|
356
|
+
<Typography level="body-xs" color="danger">
|
|
357
|
+
{errors.email}
|
|
358
|
+
</Typography>
|
|
359
|
+
)}
|
|
360
|
+
|
|
361
|
+
<FormControl error={!!errors.agreeToTerms}>
|
|
362
|
+
<Checkbox
|
|
363
|
+
label="이용약관 및 개인정보처리방침에 동의합니다"
|
|
364
|
+
checked={formData.agreeToTerms}
|
|
365
|
+
onChange={(e) => setFormData(prev => ({ ...prev, agreeToTerms: e.target.checked }))}
|
|
366
|
+
required
|
|
367
|
+
/>
|
|
368
|
+
{errors.agreeToTerms && (
|
|
369
|
+
<FormHelperText>
|
|
370
|
+
<InfoOutlinedIcon />
|
|
371
|
+
{errors.agreeToTerms}
|
|
372
|
+
</FormHelperText>
|
|
373
|
+
)}
|
|
374
|
+
</FormControl>
|
|
375
|
+
|
|
376
|
+
<Checkbox
|
|
377
|
+
label="뉴스레터 구독하기"
|
|
378
|
+
checked={formData.subscribeNewsletter}
|
|
379
|
+
onChange={(e) => setFormData(prev => ({ ...prev, subscribeNewsletter: e.target.checked }))}
|
|
380
|
+
color="success"
|
|
381
|
+
variant="soft"
|
|
382
|
+
/>
|
|
383
|
+
|
|
384
|
+
<Button type="submit">가입하기</Button>
|
|
385
|
+
</Stack>
|
|
386
|
+
</form>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Data Table Row Selection
|
|
392
|
+
|
|
393
|
+
데이터 테이블에서 행 선택에 사용하는 예제입니다.
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
function DataTableWithSelection() {
|
|
397
|
+
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
|
|
398
|
+
|
|
399
|
+
const users = [
|
|
400
|
+
{ id: 1, name: '김철수', email: 'kim@example.com', role: '관리자' },
|
|
401
|
+
{ id: 2, name: '이영희', email: 'lee@example.com', role: '사용자' },
|
|
402
|
+
{ id: 3, name: '박민수', email: 'park@example.com', role: '사용자' },
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
const handleRowSelect = (id: number) => {
|
|
406
|
+
const newSelected = new Set(selectedRows);
|
|
407
|
+
if (newSelected.has(id)) {
|
|
408
|
+
newSelected.delete(id);
|
|
409
|
+
} else {
|
|
410
|
+
newSelected.add(id);
|
|
411
|
+
}
|
|
412
|
+
setSelectedRows(newSelected);
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const handleSelectAll = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
416
|
+
if (event.target.checked) {
|
|
417
|
+
setSelectedRows(new Set(users.map(user => user.id)));
|
|
418
|
+
} else {
|
|
419
|
+
setSelectedRows(new Set());
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<Stack spacing={2}>
|
|
425
|
+
{selectedRows.size > 0 && (
|
|
426
|
+
<Alert color="primary">
|
|
427
|
+
{selectedRows.size}개 항목이 선택되었습니다.
|
|
428
|
+
<Button size="sm" variant="soft" sx={{ ml: 2 }}>
|
|
429
|
+
선택 항목 삭제
|
|
430
|
+
</Button>
|
|
431
|
+
</Alert>
|
|
432
|
+
)}
|
|
433
|
+
|
|
434
|
+
<Table>
|
|
435
|
+
<thead>
|
|
436
|
+
<tr>
|
|
437
|
+
<th style={{ width: 48 }}>
|
|
438
|
+
<Checkbox
|
|
439
|
+
size="sm"
|
|
440
|
+
checked={selectedRows.size === users.length}
|
|
441
|
+
indeterminate={selectedRows.size > 0 && selectedRows.size < users.length}
|
|
442
|
+
onChange={handleSelectAll}
|
|
443
|
+
/>
|
|
444
|
+
</th>
|
|
445
|
+
<th>이름</th>
|
|
446
|
+
<th>이메일</th>
|
|
447
|
+
<th>역할</th>
|
|
448
|
+
</tr>
|
|
449
|
+
</thead>
|
|
450
|
+
<tbody>
|
|
451
|
+
{users.map(user => (
|
|
452
|
+
<tr key={user.id}>
|
|
453
|
+
<td>
|
|
454
|
+
<Checkbox
|
|
455
|
+
size="sm"
|
|
456
|
+
checked={selectedRows.has(user.id)}
|
|
457
|
+
onChange={() => handleRowSelect(user.id)}
|
|
458
|
+
/>
|
|
459
|
+
</td>
|
|
460
|
+
<td>{user.name}</td>
|
|
461
|
+
<td>{user.email}</td>
|
|
462
|
+
<td>{user.role}</td>
|
|
463
|
+
</tr>
|
|
464
|
+
))}
|
|
465
|
+
</tbody>
|
|
466
|
+
</Table>
|
|
467
|
+
</Stack>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Props and Customization
|
|
473
|
+
|
|
474
|
+
### Colors
|
|
475
|
+
|
|
476
|
+
다양한 색상을 적용할 수 있습니다.
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
<Stack spacing={2}>
|
|
480
|
+
<Checkbox label="Primary" color="primary" checked />
|
|
481
|
+
<Checkbox label="Success" color="success" checked />
|
|
482
|
+
<Checkbox label="Warning" color="warning" checked />
|
|
483
|
+
<Checkbox label="Danger" color="danger" checked />
|
|
484
|
+
<Checkbox label="Neutral" color="neutral" checked />
|
|
485
|
+
</Stack>
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Sizes
|
|
489
|
+
|
|
490
|
+
체크박스의 크기를 조절할 수 있습니다.
|
|
491
|
+
|
|
492
|
+
```tsx
|
|
493
|
+
<Stack direction="row" spacing={2} alignItems="center">
|
|
494
|
+
<Checkbox label="Small" size="sm" checked />
|
|
495
|
+
<Checkbox label="Medium" size="md" checked />
|
|
496
|
+
<Checkbox label="Large" size="lg" checked />
|
|
497
|
+
</Stack>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Variants
|
|
501
|
+
|
|
502
|
+
다양한 시각적 스타일을 적용할 수 있습니다.
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
<Stack spacing={2}>
|
|
506
|
+
<Checkbox label="Solid" variant="solid" checked />
|
|
507
|
+
<Checkbox label="Soft" variant="soft" checked />
|
|
508
|
+
<Checkbox label="Outlined" variant="outlined" checked />
|
|
509
|
+
<Checkbox label="Plain" variant="plain" checked />
|
|
510
|
+
</Stack>
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## States
|
|
514
|
+
|
|
515
|
+
### Indeterminate State
|
|
516
|
+
|
|
517
|
+
부분적으로 선택된 상태를 나타낼 때 사용합니다.
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
<Checkbox
|
|
521
|
+
label="부분 선택됨"
|
|
522
|
+
indeterminate={true}
|
|
523
|
+
onChange={handleChange}
|
|
524
|
+
/>
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Disabled State
|
|
528
|
+
|
|
529
|
+
비활성 상태의 체크박스입니다.
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
<Stack spacing={1}>
|
|
533
|
+
<Checkbox label="비활성 (선택 안됨)" disabled />
|
|
534
|
+
<Checkbox label="비활성 (선택됨)" disabled checked />
|
|
535
|
+
<Checkbox label="비활성 (부분 선택)" disabled indeterminate />
|
|
536
|
+
</Stack>
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Error State
|
|
540
|
+
|
|
541
|
+
오류 상태를 표시할 때 사용합니다.
|
|
542
|
+
|
|
543
|
+
```tsx
|
|
544
|
+
<FormControl error>
|
|
545
|
+
<Checkbox
|
|
546
|
+
label="필수 동의 항목"
|
|
547
|
+
required
|
|
548
|
+
onChange={handleChange}
|
|
549
|
+
/>
|
|
550
|
+
<FormHelperText>
|
|
551
|
+
<InfoOutlinedIcon />
|
|
552
|
+
이 항목은 필수로 선택해야 합니다.
|
|
553
|
+
</FormHelperText>
|
|
554
|
+
</FormControl>
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
## Best Practices
|
|
558
|
+
|
|
559
|
+
1. **명확한 라벨**: 체크박스가 무엇을 의미하는지 명확하게 표시하세요.
|
|
560
|
+
|
|
561
|
+
```tsx
|
|
562
|
+
// ✅ 명확한 라벨
|
|
563
|
+
<Checkbox label="마케팅 정보 수신에 동의합니다" />
|
|
564
|
+
|
|
565
|
+
// ❌ 모호한 라벨
|
|
566
|
+
<Checkbox label="동의" />
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
2. **적절한 그룹화**: 관련된 체크박스들은 논리적으로 그룹화하세요.
|
|
570
|
+
|
|
571
|
+
```tsx
|
|
572
|
+
<Stack spacing={3}>
|
|
573
|
+
<Typography level="title-md">알림 설정</Typography>
|
|
574
|
+
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
575
|
+
<Checkbox label="이메일 알림" />
|
|
576
|
+
<Checkbox label="SMS 알림" />
|
|
577
|
+
<Checkbox label="푸시 알림" />
|
|
578
|
+
</Stack>
|
|
579
|
+
</Stack>
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
3. **필수/선택 구분**: 필수 선택 항목과 선택사항을 명확히 구분하세요.
|
|
583
|
+
|
|
584
|
+
```tsx
|
|
585
|
+
<Checkbox
|
|
586
|
+
label={<><strong>(필수)</strong> 이용약관에 동의</>}
|
|
587
|
+
required
|
|
588
|
+
/>
|
|
589
|
+
<Checkbox
|
|
590
|
+
label="(선택) 뉴스레터 구독"
|
|
591
|
+
color="neutral"
|
|
592
|
+
/>
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
4. **상태 피드백**: 사용자가 선택한 내용을 명확히 보여주세요.
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
598
|
+
{selectedCount > 0 && (
|
|
599
|
+
<Typography level="body-sm" color="primary">
|
|
600
|
+
{selectedCount}개 항목이 선택되었습니다.
|
|
601
|
+
</Typography>
|
|
602
|
+
)}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## Accessibility
|
|
606
|
+
|
|
607
|
+
Checkbox 컴포넌트는 다음과 같은 접근성 기능을 제공합니다:
|
|
608
|
+
|
|
609
|
+
### 키보드 탐색
|
|
610
|
+
|
|
611
|
+
- **Tab**: 체크박스로 포커스 이동
|
|
612
|
+
- **Space**: 체크박스 선택/해제
|
|
613
|
+
- **Enter**: 링크나 버튼 라벨이 있는 경우 활성화
|
|
614
|
+
|
|
615
|
+
### ARIA 속성
|
|
616
|
+
|
|
617
|
+
- `role="checkbox"`: 체크박스 역할 정의
|
|
618
|
+
- `aria-checked`: 현재 선택 상태 표시
|
|
619
|
+
- `aria-describedby`: 추가 설명과 연결
|
|
620
|
+
|
|
621
|
+
### 추가 접근성 고려사항
|
|
622
|
+
|
|
623
|
+
```tsx
|
|
624
|
+
<Checkbox
|
|
625
|
+
label="마케팅 정보 수신 동의"
|
|
626
|
+
aria-describedby="marketing-description"
|
|
627
|
+
/>
|
|
628
|
+
<FormHelperText id="marketing-description">
|
|
629
|
+
새로운 제품이나 할인 정보를 이메일로 받아보실 수 있습니다.
|
|
630
|
+
</FormHelperText>
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
## Performance Considerations
|
|
634
|
+
|
|
635
|
+
1. **상태 최적화**: 큰 목록에서는 상태 관리를 효율적으로 하세요.
|
|
636
|
+
|
|
637
|
+
```tsx
|
|
638
|
+
// ✅ Set을 사용한 효율적인 선택 관리
|
|
639
|
+
const [selected, setSelected] = useState(new Set<number>());
|
|
640
|
+
|
|
641
|
+
// ❌ 배열로 관리하는 비효율적 방법
|
|
642
|
+
const [selected, setSelected] = useState<number[]>([]);
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
2. **메모이제이션**: 복잡한 핸들러는 useCallback으로 메모이제이션하세요.
|
|
646
|
+
|
|
647
|
+
3. **가상화**: 매우 긴 목록에서는 가상화를 고려하세요.
|
|
648
|
+
|
|
649
|
+
Checkbox는 사용자가 다중 선택을 할 수 있는 직관적인 인터페이스를 제공하는 핵심 입력 컴포넌트입니다. 적절한 라벨링과 그룹화를 통해 명확하고 사용하기 쉬운 선택 인터페이스를 만들 수 있습니다.
|