@ceed/cds 1.28.1 → 1.29.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 +5 -5
- package/dist/components/DatePicker/DatePicker.d.ts +10 -0
- package/dist/components/DateRangePicker/DateRangePicker.d.ts +10 -0
- package/dist/components/data-display/Avatar.md +110 -69
- package/dist/components/data-display/Badge.md +91 -39
- package/dist/components/data-display/Chip.md +49 -20
- package/dist/components/data-display/DataTable.md +93 -0
- package/dist/components/data-display/InfoSign.md +12 -0
- package/dist/components/data-display/Table.md +72 -55
- package/dist/components/data-display/Tooltip.md +40 -40
- package/dist/components/data-display/Typography.md +53 -70
- package/dist/components/feedback/Alert.md +88 -72
- package/dist/components/feedback/CircularProgress.md +17 -0
- package/dist/components/feedback/Skeleton.md +17 -0
- package/dist/components/inputs/Button.md +94 -68
- package/dist/components/inputs/ButtonGroup.md +17 -0
- package/dist/components/inputs/Calendar.md +118 -457
- package/dist/components/inputs/Checkbox.md +185 -430
- package/dist/components/inputs/CurrencyInput.md +22 -0
- package/dist/components/inputs/DatePicker.md +84 -0
- package/dist/components/inputs/DateRangePicker.md +88 -0
- package/dist/components/inputs/FilterableCheckboxGroup.md +20 -3
- package/dist/components/inputs/FormControl.md +32 -2
- package/dist/components/inputs/IconButton.md +18 -0
- package/dist/components/inputs/Input.md +198 -136
- package/dist/components/inputs/MonthPicker.md +25 -0
- package/dist/components/inputs/MonthRangePicker.md +23 -0
- package/dist/components/inputs/PercentageInput.md +25 -0
- package/dist/components/inputs/RadioButton.md +23 -0
- package/dist/components/inputs/RadioList.md +20 -1
- package/dist/components/inputs/RadioTileGroup.md +37 -3
- package/dist/components/inputs/Select.md +56 -0
- package/dist/components/inputs/Slider.md +23 -0
- package/dist/components/inputs/Switch.md +20 -0
- package/dist/components/inputs/Textarea.md +32 -8
- package/dist/components/inputs/Uploader/Uploader.md +21 -0
- package/dist/components/layout/Box.md +52 -41
- package/dist/components/layout/Grid.md +87 -81
- package/dist/components/layout/Stack.md +88 -68
- package/dist/components/navigation/Breadcrumbs.md +57 -46
- package/dist/components/navigation/Drawer.md +17 -0
- package/dist/components/navigation/Dropdown.md +13 -0
- package/dist/components/navigation/IconMenuButton.md +17 -0
- package/dist/components/navigation/InsetDrawer.md +130 -292
- package/dist/components/navigation/Link.md +78 -0
- package/dist/components/navigation/Menu.md +17 -0
- package/dist/components/navigation/MenuButton.md +18 -0
- package/dist/components/navigation/Pagination.md +13 -0
- package/dist/components/navigation/Stepper.md +15 -0
- package/dist/components/navigation/Tabs.md +27 -0
- package/dist/components/surfaces/Accordions.md +804 -49
- package/dist/components/surfaces/Card.md +173 -97
- package/dist/components/surfaces/Divider.md +246 -82
- package/dist/components/surfaces/Sheet.md +15 -0
- package/dist/index.browser.js +2 -2
- package/dist/index.browser.js.map +3 -3
- package/dist/index.cjs +173 -6
- package/dist/index.js +173 -6
- package/framer/index.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
Checkbox
|
|
5
|
+
Checkbox is an input element that allows users to select one or more options from a set. Built on Joy UI's Checkbox with Framer Motion animation support, it is used in settings screens, forms, filters, multi-select lists, and agreement flows. It supports indeterminate state for "select all" patterns, decorators for custom content, and overlay mode for clickable card patterns.
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
<Checkbox />
|
|
@@ -25,7 +25,13 @@ import { Checkbox } from '@ceed/cds';
|
|
|
25
25
|
function MyComponent() {
|
|
26
26
|
const [checked, setChecked] = useState(false);
|
|
27
27
|
|
|
28
|
-
return
|
|
28
|
+
return (
|
|
29
|
+
<Checkbox
|
|
30
|
+
label="I agree"
|
|
31
|
+
checked={checked}
|
|
32
|
+
onChange={(e) => setChecked(e.target.checked)}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
29
35
|
}
|
|
30
36
|
```
|
|
31
37
|
|
|
@@ -33,26 +39,95 @@ function MyComponent() {
|
|
|
33
39
|
|
|
34
40
|
### Basic Usage
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
A simple checkbox with a label.
|
|
37
43
|
|
|
38
44
|
```tsx
|
|
39
45
|
<Checkbox label="Label" />
|
|
40
46
|
```
|
|
41
47
|
|
|
42
|
-
###
|
|
48
|
+
### Variants
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
Four visual styles are available: `solid` (default when checked), `soft`, `outlined`, and `plain`.
|
|
45
51
|
|
|
46
52
|
```tsx
|
|
47
|
-
<
|
|
53
|
+
<Stack spacing={2}>
|
|
54
|
+
<Checkbox label="Solid" variant="solid" defaultChecked />
|
|
55
|
+
<Checkbox label="Soft" variant="soft" defaultChecked />
|
|
56
|
+
<Checkbox label="Outlined" variant="outlined" defaultChecked />
|
|
57
|
+
<Checkbox label="Plain" variant="plain" defaultChecked />
|
|
58
|
+
</Stack>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Sizes
|
|
62
|
+
|
|
63
|
+
Three sizes are available: `sm`, `md` (default), and `lg`.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<Stack direction="row" spacing={2} alignItems="center">
|
|
67
|
+
<Checkbox label="Small" size="sm" defaultChecked />
|
|
68
|
+
<Checkbox label="Medium" size="md" defaultChecked />
|
|
69
|
+
<Checkbox label="Large" size="lg" defaultChecked />
|
|
70
|
+
</Stack>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Colors
|
|
74
|
+
|
|
75
|
+
Apply semantic colors to communicate meaning.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<Stack spacing={2}>
|
|
79
|
+
<Checkbox label="Primary" color="primary" defaultChecked />
|
|
80
|
+
<Checkbox label="Success" color="success" defaultChecked />
|
|
81
|
+
<Checkbox label="Warning" color="warning" defaultChecked />
|
|
82
|
+
<Checkbox label="Danger" color="danger" defaultChecked />
|
|
83
|
+
<Checkbox label="Neutral" color="neutral" defaultChecked />
|
|
84
|
+
</Stack>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## States
|
|
88
|
+
|
|
89
|
+
### Indeterminate
|
|
90
|
+
|
|
91
|
+
Represents a partially selected state, commonly used in "select all" patterns where some but not all children are selected.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<Checkbox
|
|
95
|
+
label="Partially selected"
|
|
96
|
+
indeterminate
|
|
97
|
+
onChange={handleChange}
|
|
98
|
+
/>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Disabled
|
|
102
|
+
|
|
103
|
+
A disabled checkbox cannot be interacted with.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<Stack spacing={1}>
|
|
107
|
+
<Checkbox label="Disabled (unchecked)" disabled />
|
|
108
|
+
<Checkbox label="Disabled (checked)" disabled defaultChecked />
|
|
109
|
+
<Checkbox label="Disabled (indeterminate)" disabled indeterminate />
|
|
110
|
+
</Stack>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Error
|
|
114
|
+
|
|
115
|
+
Use with `FormControl` to show validation errors.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<FormControl error>
|
|
119
|
+
<Checkbox label="Required agreement" required />
|
|
120
|
+
<FormHelperText>
|
|
121
|
+
<InfoOutlinedIcon />
|
|
122
|
+
This field is required.
|
|
123
|
+
</FormHelperText>
|
|
124
|
+
</FormControl>
|
|
48
125
|
```
|
|
49
126
|
|
|
50
127
|
## Common Use Cases
|
|
51
128
|
|
|
52
129
|
### Terms and Conditions
|
|
53
130
|
|
|
54
|
-
이용약관 동의에 체크박스를 사용하는 예제입니다.
|
|
55
|
-
|
|
56
131
|
```tsx
|
|
57
132
|
function TermsForm() {
|
|
58
133
|
const [agreements, setAgreements] = useState({
|
|
@@ -61,23 +136,22 @@ function TermsForm() {
|
|
|
61
136
|
marketing: false,
|
|
62
137
|
});
|
|
63
138
|
|
|
64
|
-
const handleChange = (key: keyof typeof agreements) => (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}));
|
|
139
|
+
const handleChange = (key: keyof typeof agreements) => (
|
|
140
|
+
event: React.ChangeEvent<HTMLInputElement>
|
|
141
|
+
) => {
|
|
142
|
+
setAgreements(prev => ({ ...prev, [key]: event.target.checked }));
|
|
69
143
|
};
|
|
70
144
|
|
|
71
145
|
return (
|
|
72
146
|
<Stack spacing={2}>
|
|
73
|
-
<Typography level="title-md"
|
|
147
|
+
<Typography level="title-md">Agreements</Typography>
|
|
74
148
|
|
|
75
149
|
<Checkbox
|
|
76
150
|
label={
|
|
77
151
|
<Typography>
|
|
78
|
-
<strong>(
|
|
152
|
+
<strong>(Required)</strong> I agree to the Terms of Service.
|
|
79
153
|
<Link href="/terms" target="_blank" sx={{ ml: 1 }}>
|
|
80
|
-
|
|
154
|
+
View Terms
|
|
81
155
|
</Link>
|
|
82
156
|
</Typography>
|
|
83
157
|
}
|
|
@@ -89,9 +163,9 @@ function TermsForm() {
|
|
|
89
163
|
<Checkbox
|
|
90
164
|
label={
|
|
91
165
|
<Typography>
|
|
92
|
-
<strong>(
|
|
166
|
+
<strong>(Required)</strong> I agree to the Privacy Policy.
|
|
93
167
|
<Link href="/privacy" target="_blank" sx={{ ml: 1 }}>
|
|
94
|
-
|
|
168
|
+
View Policy
|
|
95
169
|
</Link>
|
|
96
170
|
</Typography>
|
|
97
171
|
}
|
|
@@ -101,154 +175,49 @@ function TermsForm() {
|
|
|
101
175
|
/>
|
|
102
176
|
|
|
103
177
|
<Checkbox
|
|
104
|
-
label="(
|
|
178
|
+
label="(Optional) I agree to receive marketing communications."
|
|
105
179
|
checked={agreements.marketing}
|
|
106
180
|
onChange={handleChange('marketing')}
|
|
107
181
|
color="neutral"
|
|
108
182
|
/>
|
|
109
183
|
|
|
110
184
|
<Button disabled={!agreements.terms || !agreements.privacy} sx={{ mt: 2 }}>
|
|
111
|
-
|
|
185
|
+
Sign Up
|
|
112
186
|
</Button>
|
|
113
187
|
</Stack>
|
|
114
188
|
);
|
|
115
189
|
}
|
|
116
190
|
```
|
|
117
191
|
|
|
118
|
-
### Multi-Select List
|
|
119
|
-
|
|
120
|
-
목록에서 여러 항목을 선택할 때 사용합니다.
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
function TodoList() {
|
|
124
|
-
const [todos, setTodos] = useState([
|
|
125
|
-
{ id: 1, text: '회의 준비하기', completed: false },
|
|
126
|
-
{ id: 2, text: '보고서 작성하기', completed: true },
|
|
127
|
-
{ id: 3, text: '이메일 답장하기', completed: false },
|
|
128
|
-
]);
|
|
129
|
-
|
|
130
|
-
const handleToggle = (id: number) => {
|
|
131
|
-
setTodos((prev) => prev.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo)));
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<Stack spacing={1}>
|
|
136
|
-
<Typography level="title-md">할 일 목록</Typography>
|
|
137
|
-
{todos.map((todo) => (
|
|
138
|
-
<Checkbox
|
|
139
|
-
key={todo.id}
|
|
140
|
-
label={
|
|
141
|
-
<Typography
|
|
142
|
-
sx={{
|
|
143
|
-
textDecoration: todo.completed ? 'line-through' : 'none',
|
|
144
|
-
color: todo.completed ? 'neutral.500' : 'inherit',
|
|
145
|
-
}}
|
|
146
|
-
>
|
|
147
|
-
{todo.text}
|
|
148
|
-
</Typography>
|
|
149
|
-
}
|
|
150
|
-
checked={todo.completed}
|
|
151
|
-
onChange={() => handleToggle(todo.id)}
|
|
152
|
-
color={todo.completed ? 'success' : 'primary'}
|
|
153
|
-
/>
|
|
154
|
-
))}
|
|
155
|
-
</Stack>
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Filter Options
|
|
161
|
-
|
|
162
|
-
필터링 옵션에서 체크박스를 사용하는 예제입니다.
|
|
163
|
-
|
|
164
|
-
```tsx
|
|
165
|
-
function ProductFilter() {
|
|
166
|
-
const [filters, setFilters] = useState({
|
|
167
|
-
categories: [] as string[],
|
|
168
|
-
priceRanges: [] as string[],
|
|
169
|
-
brands: [] as string[],
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const handleCategoryChange = (category: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
173
|
-
setFilters((prev) => ({
|
|
174
|
-
...prev,
|
|
175
|
-
categories: event.target.checked ? [...prev.categories, category] : prev.categories.filter((c) => c !== category),
|
|
176
|
-
}));
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<Stack spacing={3}>
|
|
181
|
-
<Stack spacing={2}>
|
|
182
|
-
<Typography level="title-sm">카테고리</Typography>
|
|
183
|
-
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
184
|
-
<Checkbox
|
|
185
|
-
label="전자제품"
|
|
186
|
-
checked={filters.categories.includes('electronics')}
|
|
187
|
-
onChange={handleCategoryChange('electronics')}
|
|
188
|
-
size="sm"
|
|
189
|
-
/>
|
|
190
|
-
<Checkbox
|
|
191
|
-
label="의류"
|
|
192
|
-
checked={filters.categories.includes('clothing')}
|
|
193
|
-
onChange={handleCategoryChange('clothing')}
|
|
194
|
-
size="sm"
|
|
195
|
-
/>
|
|
196
|
-
<Checkbox
|
|
197
|
-
label="도서"
|
|
198
|
-
checked={filters.categories.includes('books')}
|
|
199
|
-
onChange={handleCategoryChange('books')}
|
|
200
|
-
size="sm"
|
|
201
|
-
/>
|
|
202
|
-
</Stack>
|
|
203
|
-
</Stack>
|
|
204
|
-
|
|
205
|
-
<Stack spacing={2}>
|
|
206
|
-
<Typography level="title-sm">가격대</Typography>
|
|
207
|
-
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
208
|
-
<Checkbox label="10만원 이하" size="sm" />
|
|
209
|
-
<Checkbox label="10-50만원" size="sm" />
|
|
210
|
-
<Checkbox label="50만원 이상" size="sm" />
|
|
211
|
-
</Stack>
|
|
212
|
-
</Stack>
|
|
213
|
-
</Stack>
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
192
|
### Select All Pattern
|
|
219
193
|
|
|
220
|
-
전체 선택/해제 패턴을 구현하는 예제입니다.
|
|
221
|
-
|
|
222
194
|
```tsx
|
|
223
195
|
function SelectAllExample() {
|
|
224
196
|
const [items, setItems] = useState([
|
|
225
|
-
{ id: 1, name: '
|
|
226
|
-
{ id: 2, name: '
|
|
227
|
-
{ id: 3, name: '
|
|
228
|
-
{ id: 4, name: '
|
|
197
|
+
{ id: 1, name: 'Item 1', selected: false },
|
|
198
|
+
{ id: 2, name: 'Item 2', selected: true },
|
|
199
|
+
{ id: 3, name: 'Item 3', selected: false },
|
|
200
|
+
{ id: 4, name: 'Item 4', selected: false },
|
|
229
201
|
]);
|
|
230
202
|
|
|
231
|
-
const selectedCount = items.filter(
|
|
203
|
+
const selectedCount = items.filter(item => item.selected).length;
|
|
232
204
|
const isAllSelected = selectedCount === items.length;
|
|
233
205
|
const isIndeterminate = selectedCount > 0 && selectedCount < items.length;
|
|
234
206
|
|
|
235
207
|
const handleSelectAll = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
236
|
-
setItems(
|
|
237
|
-
prev.map((item) => ({
|
|
238
|
-
...item,
|
|
239
|
-
selected: event.target.checked,
|
|
240
|
-
})),
|
|
241
|
-
);
|
|
208
|
+
setItems(prev => prev.map(item => ({ ...item, selected: event.target.checked })));
|
|
242
209
|
};
|
|
243
210
|
|
|
244
211
|
const handleItemToggle = (id: number) => {
|
|
245
|
-
setItems(
|
|
212
|
+
setItems(prev => prev.map(item =>
|
|
213
|
+
item.id === id ? { ...item, selected: !item.selected } : item
|
|
214
|
+
));
|
|
246
215
|
};
|
|
247
216
|
|
|
248
217
|
return (
|
|
249
218
|
<Stack spacing={2}>
|
|
250
219
|
<Checkbox
|
|
251
|
-
label={
|
|
220
|
+
label={`Select All (${selectedCount}/${items.length})`}
|
|
252
221
|
checked={isAllSelected}
|
|
253
222
|
indeterminate={isIndeterminate}
|
|
254
223
|
onChange={handleSelectAll}
|
|
@@ -259,7 +228,7 @@ function SelectAllExample() {
|
|
|
259
228
|
<Divider />
|
|
260
229
|
|
|
261
230
|
<Stack spacing={1} sx={{ pl: 2 }}>
|
|
262
|
-
{items.map(
|
|
231
|
+
{items.map(item => (
|
|
263
232
|
<Checkbox
|
|
264
233
|
key={item.id}
|
|
265
234
|
label={item.name}
|
|
@@ -274,175 +243,48 @@ function SelectAllExample() {
|
|
|
274
243
|
}
|
|
275
244
|
```
|
|
276
245
|
|
|
277
|
-
###
|
|
278
|
-
|
|
279
|
-
폼 검증과 함께 사용하는 체크박스 예제입니다.
|
|
246
|
+
### Filter Options
|
|
280
247
|
|
|
281
248
|
```tsx
|
|
282
|
-
function
|
|
283
|
-
const [
|
|
284
|
-
|
|
285
|
-
email: '',
|
|
286
|
-
agreeToTerms: false,
|
|
287
|
-
subscribeNewsletter: false,
|
|
249
|
+
function ProductFilter() {
|
|
250
|
+
const [filters, setFilters] = useState({
|
|
251
|
+
categories: [] as string[],
|
|
288
252
|
});
|
|
289
253
|
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!formData.email) {
|
|
302
|
-
newErrors.email = '이메일을 입력해주세요';
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (!formData.agreeToTerms) {
|
|
306
|
-
newErrors.agreeToTerms = '이용약관에 동의해주세요';
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
setErrors(newErrors);
|
|
310
|
-
|
|
311
|
-
if (Object.keys(newErrors).length === 0) {
|
|
312
|
-
console.log('폼 제출:', formData);
|
|
313
|
-
}
|
|
254
|
+
const handleCategoryChange = (category: string) => (
|
|
255
|
+
event: React.ChangeEvent<HTMLInputElement>
|
|
256
|
+
) => {
|
|
257
|
+
setFilters(prev => ({
|
|
258
|
+
...prev,
|
|
259
|
+
categories: event.target.checked
|
|
260
|
+
? [...prev.categories, category]
|
|
261
|
+
: prev.categories.filter(c => c !== category)
|
|
262
|
+
}));
|
|
314
263
|
};
|
|
315
264
|
|
|
316
265
|
return (
|
|
317
|
-
<
|
|
318
|
-
<
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
266
|
+
<Stack spacing={2}>
|
|
267
|
+
<Typography level="title-sm">Categories</Typography>
|
|
268
|
+
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
269
|
+
<Checkbox
|
|
270
|
+
label="Electronics"
|
|
271
|
+
checked={filters.categories.includes('electronics')}
|
|
272
|
+
onChange={handleCategoryChange('electronics')}
|
|
273
|
+
size="sm"
|
|
324
274
|
/>
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
<Input
|
|
332
|
-
placeholder="이메일"
|
|
333
|
-
type="email"
|
|
334
|
-
value={formData.email}
|
|
335
|
-
onChange={(e) => setFormData((prev) => ({ ...prev, email: e.target.value }))}
|
|
336
|
-
error={!!errors.email}
|
|
275
|
+
<Checkbox
|
|
276
|
+
label="Clothing"
|
|
277
|
+
checked={filters.categories.includes('clothing')}
|
|
278
|
+
onChange={handleCategoryChange('clothing')}
|
|
279
|
+
size="sm"
|
|
337
280
|
/>
|
|
338
|
-
{errors.email && (
|
|
339
|
-
<Typography level="body-xs" color="danger">
|
|
340
|
-
{errors.email}
|
|
341
|
-
</Typography>
|
|
342
|
-
)}
|
|
343
|
-
|
|
344
|
-
<FormControl error={!!errors.agreeToTerms}>
|
|
345
|
-
<Checkbox
|
|
346
|
-
label="이용약관 및 개인정보처리방침에 동의합니다"
|
|
347
|
-
checked={formData.agreeToTerms}
|
|
348
|
-
onChange={(e) => setFormData((prev) => ({ ...prev, agreeToTerms: e.target.checked }))}
|
|
349
|
-
required
|
|
350
|
-
/>
|
|
351
|
-
{errors.agreeToTerms && (
|
|
352
|
-
<FormHelperText>
|
|
353
|
-
<InfoOutlinedIcon />
|
|
354
|
-
{errors.agreeToTerms}
|
|
355
|
-
</FormHelperText>
|
|
356
|
-
)}
|
|
357
|
-
</FormControl>
|
|
358
|
-
|
|
359
281
|
<Checkbox
|
|
360
|
-
label="
|
|
361
|
-
checked={
|
|
362
|
-
onChange={(
|
|
363
|
-
|
|
364
|
-
variant="soft"
|
|
282
|
+
label="Books"
|
|
283
|
+
checked={filters.categories.includes('books')}
|
|
284
|
+
onChange={handleCategoryChange('books')}
|
|
285
|
+
size="sm"
|
|
365
286
|
/>
|
|
366
|
-
|
|
367
|
-
<Button type="submit">가입하기</Button>
|
|
368
287
|
</Stack>
|
|
369
|
-
</form>
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Data Table Row Selection
|
|
375
|
-
|
|
376
|
-
데이터 테이블에서 행 선택에 사용하는 예제입니다.
|
|
377
|
-
|
|
378
|
-
```tsx
|
|
379
|
-
function DataTableWithSelection() {
|
|
380
|
-
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
|
|
381
|
-
|
|
382
|
-
const users = [
|
|
383
|
-
{ id: 1, name: '김철수', email: 'kim@example.com', role: '관리자' },
|
|
384
|
-
{ id: 2, name: '이영희', email: 'lee@example.com', role: '사용자' },
|
|
385
|
-
{ id: 3, name: '박민수', email: 'park@example.com', role: '사용자' },
|
|
386
|
-
];
|
|
387
|
-
|
|
388
|
-
const handleRowSelect = (id: number) => {
|
|
389
|
-
const newSelected = new Set(selectedRows);
|
|
390
|
-
if (newSelected.has(id)) {
|
|
391
|
-
newSelected.delete(id);
|
|
392
|
-
} else {
|
|
393
|
-
newSelected.add(id);
|
|
394
|
-
}
|
|
395
|
-
setSelectedRows(newSelected);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const handleSelectAll = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
399
|
-
if (event.target.checked) {
|
|
400
|
-
setSelectedRows(new Set(users.map((user) => user.id)));
|
|
401
|
-
} else {
|
|
402
|
-
setSelectedRows(new Set());
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
return (
|
|
407
|
-
<Stack spacing={2}>
|
|
408
|
-
{selectedRows.size > 0 && (
|
|
409
|
-
<Alert color="primary">
|
|
410
|
-
{selectedRows.size}개 항목이 선택되었습니다.
|
|
411
|
-
<Button size="sm" variant="soft" sx={{ ml: 2 }}>
|
|
412
|
-
선택 항목 삭제
|
|
413
|
-
</Button>
|
|
414
|
-
</Alert>
|
|
415
|
-
)}
|
|
416
|
-
|
|
417
|
-
<Table>
|
|
418
|
-
<thead>
|
|
419
|
-
<tr>
|
|
420
|
-
<th style={{ width: 48 }}>
|
|
421
|
-
<Checkbox
|
|
422
|
-
size="sm"
|
|
423
|
-
checked={selectedRows.size === users.length}
|
|
424
|
-
indeterminate={selectedRows.size > 0 && selectedRows.size < users.length}
|
|
425
|
-
onChange={handleSelectAll}
|
|
426
|
-
/>
|
|
427
|
-
</th>
|
|
428
|
-
<th>이름</th>
|
|
429
|
-
<th>이메일</th>
|
|
430
|
-
<th>역할</th>
|
|
431
|
-
</tr>
|
|
432
|
-
</thead>
|
|
433
|
-
<tbody>
|
|
434
|
-
{users.map((user) => (
|
|
435
|
-
<tr key={user.id}>
|
|
436
|
-
<td>
|
|
437
|
-
<Checkbox size="sm" checked={selectedRows.has(user.id)} onChange={() => handleRowSelect(user.id)} />
|
|
438
|
-
</td>
|
|
439
|
-
<td>{user.name}</td>
|
|
440
|
-
<td>{user.email}</td>
|
|
441
|
-
<td>{user.role}</td>
|
|
442
|
-
</tr>
|
|
443
|
-
))}
|
|
444
|
-
</tbody>
|
|
445
|
-
</Table>
|
|
446
288
|
</Stack>
|
|
447
289
|
);
|
|
448
290
|
}
|
|
@@ -450,172 +292,85 @@ function DataTableWithSelection() {
|
|
|
450
292
|
|
|
451
293
|
## Props and Customization
|
|
452
294
|
|
|
453
|
-
###
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
### Variants
|
|
480
|
-
|
|
481
|
-
다양한 시각적 스타일을 적용할 수 있습니다.
|
|
482
|
-
|
|
483
|
-
```tsx
|
|
484
|
-
<Stack spacing={2}>
|
|
485
|
-
<Checkbox label="Solid" variant="solid" checked />
|
|
486
|
-
<Checkbox label="Soft" variant="soft" checked />
|
|
487
|
-
<Checkbox label="Outlined" variant="outlined" checked />
|
|
488
|
-
<Checkbox label="Plain" variant="plain" checked />
|
|
489
|
-
</Stack>
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
## States
|
|
493
|
-
|
|
494
|
-
### Indeterminate State
|
|
495
|
-
|
|
496
|
-
부분적으로 선택된 상태를 나타낼 때 사용합니다.
|
|
497
|
-
|
|
498
|
-
```tsx
|
|
499
|
-
<Checkbox label="부분 선택됨" indeterminate={true} onChange={handleChange} />
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
### Disabled State
|
|
503
|
-
|
|
504
|
-
비활성 상태의 체크박스입니다.
|
|
505
|
-
|
|
506
|
-
```tsx
|
|
507
|
-
<Stack spacing={1}>
|
|
508
|
-
<Checkbox label="비활성 (선택 안됨)" disabled />
|
|
509
|
-
<Checkbox label="비활성 (선택됨)" disabled checked />
|
|
510
|
-
<Checkbox label="비활성 (부분 선택)" disabled indeterminate />
|
|
511
|
-
</Stack>
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### Error State
|
|
515
|
-
|
|
516
|
-
오류 상태를 표시할 때 사용합니다.
|
|
517
|
-
|
|
518
|
-
```tsx
|
|
519
|
-
<FormControl error>
|
|
520
|
-
<Checkbox label="필수 동의 항목" required onChange={handleChange} />
|
|
521
|
-
<FormHelperText>
|
|
522
|
-
<InfoOutlinedIcon />이 항목은 필수로 선택해야 합니다.
|
|
523
|
-
</FormHelperText>
|
|
524
|
-
</FormControl>
|
|
525
|
-
```
|
|
295
|
+
### Key Props
|
|
296
|
+
|
|
297
|
+
| Prop | Type | Default | Description |
|
|
298
|
+
| ------------------- | -------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------- |
|
|
299
|
+
| `checked` | `boolean` | - | Whether the checkbox is checked (controlled) |
|
|
300
|
+
| `defaultChecked` | `boolean` | `false` | Initial checked state (uncontrolled) |
|
|
301
|
+
| `indeterminate` | `boolean` | `false` | Shows a partial selection state (dash icon instead of checkmark) |
|
|
302
|
+
| `label` | `ReactNode` | - | Label content displayed next to the checkbox |
|
|
303
|
+
| `onChange` | `(event: ChangeEvent<HTMLInputElement>) => void` | - | Callback when the checked state changes |
|
|
304
|
+
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'neutral'` | Color scheme |
|
|
305
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Checkbox size |
|
|
306
|
+
| `variant` | `'solid' \| 'soft' \| 'outlined' \| 'plain'` | `'solid'` | Visual style when checked |
|
|
307
|
+
| `disabled` | `boolean` | `false` | Disables the checkbox |
|
|
308
|
+
| `readOnly` | `boolean` | `false` | Makes the checkbox read-only |
|
|
309
|
+
| `required` | `boolean` | `false` | Marks the field as required |
|
|
310
|
+
| `name` | `string` | - | HTML name attribute for form submission |
|
|
311
|
+
| `value` | `string \| number` | - | Value attribute for form submission |
|
|
312
|
+
| `overlay` | `boolean` | `false` | Extends the clickable area to cover the parent container (useful for card-click patterns) |
|
|
313
|
+
| `disableIcon` | `boolean` | `false` | Hides the checkbox icon |
|
|
314
|
+
| `checkedIcon` | `ReactNode` | - | Custom icon for the checked state |
|
|
315
|
+
| `uncheckedIcon` | `ReactNode` | - | Custom icon for the unchecked state |
|
|
316
|
+
| `indeterminateIcon` | `ReactNode` | - | Custom icon for the indeterminate state |
|
|
317
|
+
| `endDecorator` | `ReactNode` | - | Content rendered after the checkbox label |
|
|
318
|
+
| `sx` | `SxProps` | - | Custom styles using the MUI system |
|
|
319
|
+
|
|
320
|
+
> **Note**: Checkbox also accepts all Joy UI Checkbox props and Framer Motion props.
|
|
526
321
|
|
|
527
322
|
## Best Practices
|
|
528
323
|
|
|
529
|
-
1.
|
|
324
|
+
1. **Use clear, descriptive labels.** The label should make it obvious what the user is agreeing to or selecting.
|
|
530
325
|
|
|
531
326
|
```tsx
|
|
532
|
-
// ✅
|
|
533
|
-
<Checkbox label="
|
|
327
|
+
// ✅ Good
|
|
328
|
+
<Checkbox label="I agree to receive marketing emails" />
|
|
534
329
|
|
|
535
|
-
// ❌
|
|
536
|
-
<Checkbox label="
|
|
330
|
+
// ❌ Bad
|
|
331
|
+
<Checkbox label="Agree" />
|
|
537
332
|
```
|
|
538
333
|
|
|
539
|
-
2.
|
|
334
|
+
2. **Group related checkboxes logically.** Use headings and indentation to create visual hierarchy.
|
|
540
335
|
|
|
541
336
|
```tsx
|
|
542
337
|
<Stack spacing={3}>
|
|
543
|
-
<Typography level="title-md"
|
|
338
|
+
<Typography level="title-md">Notification Settings</Typography>
|
|
544
339
|
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
545
|
-
<Checkbox label="
|
|
546
|
-
<Checkbox label="SMS
|
|
547
|
-
<Checkbox label="
|
|
340
|
+
<Checkbox label="Email notifications" />
|
|
341
|
+
<Checkbox label="SMS notifications" />
|
|
342
|
+
<Checkbox label="Push notifications" />
|
|
548
343
|
</Stack>
|
|
549
344
|
</Stack>
|
|
550
345
|
```
|
|
551
346
|
|
|
552
|
-
3.
|
|
347
|
+
3. **Distinguish required from optional.** Make it clear which selections are mandatory.
|
|
553
348
|
|
|
554
349
|
```tsx
|
|
555
|
-
<Checkbox
|
|
556
|
-
|
|
557
|
-
required
|
|
558
|
-
/>
|
|
559
|
-
<Checkbox
|
|
560
|
-
label="(선택) 뉴스레터 구독"
|
|
561
|
-
color="neutral"
|
|
562
|
-
/>
|
|
350
|
+
<Checkbox label={<><strong>(Required)</strong> Accept Terms</>} required />
|
|
351
|
+
<Checkbox label="(Optional) Subscribe to newsletter" color="neutral" />
|
|
563
352
|
```
|
|
564
353
|
|
|
565
|
-
4.
|
|
354
|
+
4. **Use the indeterminate state correctly.** Only apply it to a parent checkbox that represents a group of child checkboxes with mixed selection.
|
|
566
355
|
|
|
567
|
-
|
|
568
|
-
{
|
|
569
|
-
selectedCount > 0 && (
|
|
570
|
-
<Typography level="body-sm" color="primary">
|
|
571
|
-
{selectedCount}개 항목이 선택되었습니다.
|
|
572
|
-
</Typography>
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
```
|
|
356
|
+
5. **Prefer Checkbox over Switch** when the setting is a binary choice that is not applied immediately. Use Switch for instant toggle actions.
|
|
576
357
|
|
|
577
358
|
## Accessibility
|
|
578
359
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
360
|
+
- **Keyboard navigation**: Tab to focus, Space to toggle checked state.
|
|
361
|
+
- **ARIA attributes**: `role="checkbox"`, `aria-checked` reflects the current state (including `mixed` for indeterminate).
|
|
362
|
+
- **Label association**: The `label` prop is automatically associated with the checkbox input.
|
|
363
|
+
- **Error state**: When used inside `FormControl` with `error`, `aria-invalid` is applied.
|
|
364
|
+
- **Screen reader**: Announces as "\[label], checkbox, checked/unchecked/mixed".
|
|
582
365
|
|
|
583
|
-
|
|
584
|
-
- **Space**: 체크박스 선택/해제
|
|
585
|
-
- **Enter**: 링크나 버튼 라벨이 있는 경우 활성화
|
|
366
|
+
## Performance Tips
|
|
586
367
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
- `role="checkbox"`: 체크박스 역할 정의
|
|
590
|
-
- `aria-checked`: 현재 선택 상태 표시
|
|
591
|
-
- `aria-describedby`: 추가 설명과 연결
|
|
592
|
-
|
|
593
|
-
### 추가 접근성 고려사항
|
|
594
|
-
|
|
595
|
-
```tsx
|
|
596
|
-
<Checkbox
|
|
597
|
-
label="마케팅 정보 수신 동의"
|
|
598
|
-
aria-describedby="marketing-description"
|
|
599
|
-
/>
|
|
600
|
-
<FormHelperText id="marketing-description">
|
|
601
|
-
새로운 제품이나 할인 정보를 이메일로 받아보실 수 있습니다.
|
|
602
|
-
</FormHelperText>
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
## Performance Considerations
|
|
606
|
-
|
|
607
|
-
1. **상태 최적화**: 큰 목록에서는 상태 관리를 효율적으로 하세요.
|
|
368
|
+
Use `Set` instead of arrays for managing selection state in large lists:
|
|
608
369
|
|
|
609
370
|
```tsx
|
|
610
|
-
// ✅
|
|
371
|
+
// ✅ Good: O(1) lookup with Set
|
|
611
372
|
const [selected, setSelected] = useState(new Set<number>());
|
|
612
373
|
|
|
613
|
-
// ❌
|
|
374
|
+
// ❌ Bad: O(n) lookup with Array
|
|
614
375
|
const [selected, setSelected] = useState<number[]>([]);
|
|
615
376
|
```
|
|
616
|
-
|
|
617
|
-
2. **메모이제이션**: 복잡한 핸들러는 useCallback으로 메모이제이션하세요.
|
|
618
|
-
|
|
619
|
-
3. **가상화**: 매우 긴 목록에서는 가상화를 고려하세요.
|
|
620
|
-
|
|
621
|
-
Checkbox는 사용자가 다중 선택을 할 수 있는 직관적인 인터페이스를 제공하는 핵심 입력 컴포넌트입니다. 적절한 라벨링과 그룹화를 통해 명확하고 사용하기 쉬운 선택 인터페이스를 만들 수 있습니다.
|