@ceed/cds 1.22.2 → 1.22.4
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/components/data-display/InfoSign.md +74 -91
- package/dist/components/data-display/Typography.md +363 -63
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Dialog.md +8 -4
- package/dist/components/feedback/Modal.md +7 -3
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -104
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterableCheckboxGroup.md +141 -20
- package/dist/components/inputs/FormControl.md +368 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/Input.md +203 -77
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +143 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +57 -308
- package/dist/components/navigation/Drawer.md +180 -0
- package/dist/components/navigation/Dropdown.md +98 -215
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +281 -650
- package/dist/components/navigation/Link.md +31 -348
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Accordions.md +49 -804
- package/dist/components/surfaces/Card.md +97 -157
- package/dist/components/surfaces/Divider.md +83 -234
- package/dist/components/surfaces/Sheet.md +152 -327
- package/dist/guides/ThemeProvider.md +89 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
Switch
|
|
5
|
+
The Switch component is a toggle input that lets users turn an option on or off. It is built on Joy UI's Switch and provides a variety of sizes, colors, variants, and decorator slots for icons or text.
|
|
6
|
+
|
|
7
|
+
Switches are best suited for binary settings that take effect immediately, such as enabling notifications, toggling dark mode, or activating a feature. Unlike a checkbox (which is often part of a form submission), a switch communicates an instant state change.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<Switch />
|
|
@@ -25,23 +27,18 @@ import { Switch } from '@ceed/cds';
|
|
|
25
27
|
function MyComponent() {
|
|
26
28
|
const [enabled, setEnabled] = useState(false);
|
|
27
29
|
|
|
28
|
-
return
|
|
30
|
+
return (
|
|
31
|
+
<Switch
|
|
32
|
+
checked={enabled}
|
|
33
|
+
onChange={(e) => setEnabled(e.target.checked)}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
29
36
|
}
|
|
30
37
|
```
|
|
31
38
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
### Basic Usage
|
|
35
|
-
|
|
36
|
-
가장 기본적인 Switch 사용법입니다.
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
<Switch />
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### With Labels
|
|
39
|
+
## With Labels
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
Use `FormControl` with `FormLabel` and `FormHelperText` to provide a label and description alongside the switch. The `orientation="horizontal"` layout places the label on the left and the switch on the right.
|
|
45
42
|
|
|
46
43
|
```tsx
|
|
47
44
|
<FormControl orientation="horizontal" sx={{
|
|
@@ -64,9 +61,9 @@ function MyComponent() {
|
|
|
64
61
|
</FormControl>
|
|
65
62
|
```
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
## Inline Labels
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
For simpler cases, wrap the switch inside a `Typography` component rendered as a `<label>`. This connects the text and switch semantically without requiring a `FormControl`.
|
|
70
67
|
|
|
71
68
|
```tsx
|
|
72
69
|
<Typography component="label" endDecorator={<Switch sx={{
|
|
@@ -76,9 +73,9 @@ Turn alarm on
|
|
|
76
73
|
</Typography>
|
|
77
74
|
```
|
|
78
75
|
|
|
79
|
-
|
|
76
|
+
## With Decorators
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
Use `startDecorator` and `endDecorator` to place icons or text around the switch. This is particularly useful for theme toggles or any context where visual cues reinforce the on/off meaning.
|
|
82
79
|
|
|
83
80
|
```tsx
|
|
84
81
|
<Switch color={dark ? 'primary' : 'danger'} slotProps={{
|
|
@@ -92,9 +89,9 @@ Turn alarm on
|
|
|
92
89
|
})} />} checked={dark} onChange={event => setDark(event.target.checked)} />
|
|
93
90
|
```
|
|
94
91
|
|
|
95
|
-
|
|
92
|
+
## Track Children
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
Display text or symbols inside the switch track using `slotProps.track.children`. This provides an always-visible label (e.g., "On" / "Off" or "I" / "0") directly on the track.
|
|
98
95
|
|
|
99
96
|
```tsx
|
|
100
97
|
<Stack direction="row" spacing={4}>
|
|
@@ -136,9 +133,9 @@ Turn alarm on
|
|
|
136
133
|
</Stack>
|
|
137
134
|
```
|
|
138
135
|
|
|
139
|
-
|
|
136
|
+
## Thumb Children
|
|
140
137
|
|
|
141
|
-
|
|
138
|
+
Place an icon inside the switch thumb using `slotProps.thumb.children`. This is useful for adding a small visual indicator that moves with the toggle.
|
|
142
139
|
|
|
143
140
|
```tsx
|
|
144
141
|
<Switch size="lg" slotProps={{
|
|
@@ -153,36 +150,96 @@ Turn alarm on
|
|
|
153
150
|
}} />
|
|
154
151
|
```
|
|
155
152
|
|
|
153
|
+
## All Variants Overview
|
|
154
|
+
|
|
155
|
+
A comprehensive view of all color and decoration combinations.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
<Stack direction="row" spacing={4}>
|
|
159
|
+
{([undefined, 'primary', 'warning', 'success', 'danger', 'neutral'] as const).map(color => <Stack key={color} spacing={2}>
|
|
160
|
+
<Switch color={color} />
|
|
161
|
+
<Switch color={color} checked />
|
|
162
|
+
<Switch color={color} startDecorator={<LightModeIcon />} endDecorator={<DarkModeIcon />} />
|
|
163
|
+
<Switch color={color} slotProps={{
|
|
164
|
+
input: {
|
|
165
|
+
'aria-label': 'Dark mode'
|
|
166
|
+
},
|
|
167
|
+
thumb: {
|
|
168
|
+
children: <DarkModeIcon fontSize="inherit" />
|
|
169
|
+
},
|
|
170
|
+
track: {
|
|
171
|
+
children: <React.Fragment>
|
|
172
|
+
<span>I</span>
|
|
173
|
+
<span>0</span>
|
|
174
|
+
</React.Fragment>,
|
|
175
|
+
sx: {
|
|
176
|
+
justifyContent: 'space-around'
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}} sx={{
|
|
180
|
+
'--Switch-thumbSize': '27px',
|
|
181
|
+
'--Switch-trackWidth': '52px',
|
|
182
|
+
'--Switch-trackHeight': '31px'
|
|
183
|
+
}} />
|
|
184
|
+
<Typography component="label" startDecorator={<Switch sx={{
|
|
185
|
+
ml: 1
|
|
186
|
+
}} color={color} />}>
|
|
187
|
+
Label
|
|
188
|
+
</Typography>
|
|
189
|
+
</Stack>)}
|
|
190
|
+
</Stack>
|
|
191
|
+
```
|
|
192
|
+
|
|
156
193
|
## Common Use Cases
|
|
157
194
|
|
|
158
195
|
### Settings Panel
|
|
159
196
|
|
|
160
|
-
설정 패널에서 기능을 활성화/비활성화할 때 사용합니다.
|
|
161
|
-
|
|
162
197
|
```tsx
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
function SettingsPanel() {
|
|
199
|
+
const [notifications, setNotifications] = useState(true);
|
|
200
|
+
const [autoSave, setAutoSave] = useState(false);
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<Stack spacing={3}>
|
|
204
|
+
<FormControl
|
|
205
|
+
orientation="horizontal"
|
|
206
|
+
sx={{ width: 400, justifyContent: 'space-between' }}
|
|
207
|
+
>
|
|
208
|
+
<div>
|
|
209
|
+
<FormLabel>Notifications</FormLabel>
|
|
210
|
+
<FormHelperText sx={{ mt: 0 }}>
|
|
211
|
+
Receive alerts when new messages arrive.
|
|
212
|
+
</FormHelperText>
|
|
213
|
+
</div>
|
|
214
|
+
<Switch
|
|
215
|
+
checked={notifications}
|
|
216
|
+
onChange={(e) => setNotifications(e.target.checked)}
|
|
217
|
+
color={notifications ? 'success' : 'neutral'}
|
|
218
|
+
/>
|
|
219
|
+
</FormControl>
|
|
220
|
+
|
|
221
|
+
<FormControl
|
|
222
|
+
orientation="horizontal"
|
|
223
|
+
sx={{ width: 400, justifyContent: 'space-between' }}
|
|
224
|
+
>
|
|
225
|
+
<div>
|
|
226
|
+
<FormLabel>Auto Save</FormLabel>
|
|
227
|
+
<FormHelperText sx={{ mt: 0 }}>
|
|
228
|
+
Automatically save your work every 30 seconds.
|
|
229
|
+
</FormHelperText>
|
|
230
|
+
</div>
|
|
231
|
+
<Switch
|
|
232
|
+
checked={autoSave}
|
|
233
|
+
onChange={(e) => setAutoSave(e.target.checked)}
|
|
234
|
+
/>
|
|
235
|
+
</FormControl>
|
|
236
|
+
</Stack>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
180
239
|
```
|
|
181
240
|
|
|
182
241
|
### Theme Toggle
|
|
183
242
|
|
|
184
|
-
다크 모드/라이트 모드 전환에 자주 사용됩니다.
|
|
185
|
-
|
|
186
243
|
```tsx
|
|
187
244
|
function ThemeToggle() {
|
|
188
245
|
const [darkMode, setDarkMode] = useState(false);
|
|
@@ -195,376 +252,86 @@ function ThemeToggle() {
|
|
|
195
252
|
startDecorator={<LightModeIcon />}
|
|
196
253
|
endDecorator={<DarkModeIcon />}
|
|
197
254
|
slotProps={{
|
|
198
|
-
input: { 'aria-label': '
|
|
255
|
+
input: { 'aria-label': 'Toggle dark mode' },
|
|
199
256
|
}}
|
|
200
257
|
/>
|
|
201
258
|
);
|
|
202
259
|
}
|
|
203
260
|
```
|
|
204
261
|
|
|
205
|
-
### Feature
|
|
206
|
-
|
|
207
|
-
기능의 활성화/비활성화를 제어할 때 사용합니다.
|
|
262
|
+
### Feature Flag with Status Text
|
|
208
263
|
|
|
209
264
|
```tsx
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<Typography level="title-md" sx={{ mb: 2 }}>
|
|
213
|
-
실험적 기능
|
|
214
|
-
</Typography>
|
|
215
|
-
|
|
216
|
-
<Stack spacing={2}>
|
|
217
|
-
<FormControl orientation="horizontal" sx={{ justifyContent: 'space-between' }}>
|
|
218
|
-
<FormLabel>베타 기능 활성화</FormLabel>
|
|
219
|
-
<Switch
|
|
220
|
-
checked={betaFeatures}
|
|
221
|
-
onChange={(e) => setBetaFeatures(e.target.checked)}
|
|
222
|
-
color="warning"
|
|
223
|
-
endDecorator={betaFeatures ? 'On' : 'Off'}
|
|
224
|
-
/>
|
|
225
|
-
</FormControl>
|
|
226
|
-
|
|
227
|
-
<FormControl orientation="horizontal" sx={{ justifyContent: 'space-between' }}>
|
|
228
|
-
<FormLabel>고급 모드</FormLabel>
|
|
229
|
-
<Switch checked={advancedMode} onChange={(e) => setAdvancedMode(e.target.checked)} disabled={!betaFeatures} />
|
|
230
|
-
</FormControl>
|
|
231
|
-
</Stack>
|
|
232
|
-
</CardContent>
|
|
233
|
-
</Card>
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### List Items with Switches
|
|
237
|
-
|
|
238
|
-
목록 아이템에서 각 항목을 개별적으로 제어할 때 사용합니다.
|
|
239
|
-
|
|
240
|
-
```tsx
|
|
241
|
-
<List>
|
|
242
|
-
{permissions.map((permission) => (
|
|
243
|
-
<ListItem
|
|
244
|
-
key={permission.id}
|
|
245
|
-
endAction={
|
|
246
|
-
<Switch
|
|
247
|
-
checked={permission.enabled}
|
|
248
|
-
onChange={(e) => handlePermissionToggle(permission.id, e.target.checked)}
|
|
249
|
-
size="sm"
|
|
250
|
-
color={permission.enabled ? 'success' : 'neutral'}
|
|
251
|
-
/>
|
|
252
|
-
}
|
|
253
|
-
>
|
|
254
|
-
<ListItemContent>
|
|
255
|
-
<Typography level="title-sm">{permission.name}</Typography>
|
|
256
|
-
<Typography level="body-sm" color="neutral">
|
|
257
|
-
{permission.description}
|
|
258
|
-
</Typography>
|
|
259
|
-
</ListItemContent>
|
|
260
|
-
</ListItem>
|
|
261
|
-
))}
|
|
262
|
-
</List>
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### Controlled Form
|
|
266
|
-
|
|
267
|
-
폼 상태 관리와 함께 사용하는 예제입니다.
|
|
268
|
-
|
|
269
|
-
```tsx
|
|
270
|
-
function UserPreferences() {
|
|
271
|
-
const [preferences, setPreferences] = useState({
|
|
272
|
-
emailNotifications: true,
|
|
273
|
-
smsNotifications: false,
|
|
274
|
-
pushNotifications: true,
|
|
275
|
-
darkMode: false,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const handleToggle = (key: keyof typeof preferences) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
279
|
-
setPreferences((prev) => ({
|
|
280
|
-
...prev,
|
|
281
|
-
[key]: event.target.checked,
|
|
282
|
-
}));
|
|
283
|
-
};
|
|
265
|
+
function FeatureToggle() {
|
|
266
|
+
const [betaEnabled, setBetaEnabled] = useState(false);
|
|
284
267
|
|
|
285
268
|
return (
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
/>
|
|
299
|
-
}
|
|
300
|
-
>
|
|
301
|
-
이메일 알림
|
|
302
|
-
</Typography>
|
|
303
|
-
|
|
304
|
-
<Typography
|
|
305
|
-
component="label"
|
|
306
|
-
endDecorator={
|
|
307
|
-
<Switch
|
|
308
|
-
checked={preferences.smsNotifications}
|
|
309
|
-
onChange={handleToggle('smsNotifications')}
|
|
310
|
-
sx={{ ml: 1 }}
|
|
311
|
-
/>
|
|
312
|
-
}
|
|
313
|
-
>
|
|
314
|
-
SMS 알림
|
|
315
|
-
</Typography>
|
|
316
|
-
|
|
317
|
-
<Typography
|
|
318
|
-
component="label"
|
|
319
|
-
endDecorator={
|
|
320
|
-
<Switch
|
|
321
|
-
checked={preferences.pushNotifications}
|
|
322
|
-
onChange={handleToggle('pushNotifications')}
|
|
323
|
-
sx={{ ml: 1 }}
|
|
324
|
-
/>
|
|
325
|
-
}
|
|
326
|
-
>
|
|
327
|
-
푸시 알림
|
|
328
|
-
</Typography>
|
|
329
|
-
</Stack>
|
|
330
|
-
|
|
331
|
-
<Divider />
|
|
332
|
-
|
|
333
|
-
<Typography
|
|
334
|
-
component="label"
|
|
335
|
-
endDecorator={
|
|
336
|
-
<Switch
|
|
337
|
-
checked={preferences.darkMode}
|
|
338
|
-
onChange={handleToggle('darkMode')}
|
|
339
|
-
startDecorator={<LightModeIcon />}
|
|
340
|
-
endDecorator={<DarkModeIcon />}
|
|
341
|
-
sx={{ ml: 1 }}
|
|
342
|
-
/>
|
|
343
|
-
}
|
|
344
|
-
>
|
|
345
|
-
다크 모드
|
|
346
|
-
</Typography>
|
|
347
|
-
|
|
348
|
-
<Button type="submit">설정 저장</Button>
|
|
349
|
-
</Stack>
|
|
350
|
-
</form>
|
|
269
|
+
<FormControl
|
|
270
|
+
orientation="horizontal"
|
|
271
|
+
sx={{ justifyContent: 'space-between' }}
|
|
272
|
+
>
|
|
273
|
+
<FormLabel>Enable Beta Features</FormLabel>
|
|
274
|
+
<Switch
|
|
275
|
+
checked={betaEnabled}
|
|
276
|
+
onChange={(e) => setBetaEnabled(e.target.checked)}
|
|
277
|
+
color="warning"
|
|
278
|
+
endDecorator={betaEnabled ? 'On' : 'Off'}
|
|
279
|
+
/>
|
|
280
|
+
</FormControl>
|
|
351
281
|
);
|
|
352
282
|
}
|
|
353
283
|
```
|
|
354
284
|
|
|
355
|
-
##
|
|
356
|
-
|
|
357
|
-
### Colors
|
|
358
|
-
|
|
359
|
-
다양한 색상을 적용할 수 있습니다.
|
|
360
|
-
|
|
361
|
-
```tsx
|
|
362
|
-
<Stack direction="row" spacing={2}>
|
|
363
|
-
<Switch color="primary" checked />
|
|
364
|
-
<Switch color="success" checked />
|
|
365
|
-
<Switch color="warning" checked />
|
|
366
|
-
<Switch color="danger" checked />
|
|
367
|
-
<Switch color="neutral" checked />
|
|
368
|
-
</Stack>
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### Sizes
|
|
372
|
-
|
|
373
|
-
스위치의 크기를 조절할 수 있습니다.
|
|
374
|
-
|
|
375
|
-
```tsx
|
|
376
|
-
<Stack direction="row" spacing={2} alignItems="center">
|
|
377
|
-
<Switch size="sm" checked />
|
|
378
|
-
<Switch size="md" checked />
|
|
379
|
-
<Switch size="lg" checked />
|
|
380
|
-
</Stack>
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### Variants
|
|
384
|
-
|
|
385
|
-
다양한 시각적 스타일을 적용할 수 있습니다.
|
|
386
|
-
|
|
387
|
-
```tsx
|
|
388
|
-
<Stack direction="row" spacing={2}>
|
|
389
|
-
<Switch variant="solid" checked />
|
|
390
|
-
<Switch variant="soft" checked />
|
|
391
|
-
<Switch variant="outlined" checked />
|
|
392
|
-
<Switch variant="plain" checked />
|
|
393
|
-
</Stack>
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### Custom Styling
|
|
285
|
+
## Best Practices
|
|
397
286
|
|
|
398
|
-
|
|
287
|
+
1. **Use switches for immediate effects**: Switches should apply their change instantly without requiring a "Save" button. If the setting requires a form submission, use a checkbox instead.
|
|
399
288
|
|
|
400
289
|
```tsx
|
|
290
|
+
// ✅ Immediate effect -- toggling notifications on/off
|
|
401
291
|
<Switch
|
|
402
|
-
checked={
|
|
403
|
-
onChange={(e) =>
|
|
404
|
-
sx={{
|
|
405
|
-
'--Switch-thumbSize': '24px',
|
|
406
|
-
'--Switch-trackWidth': '60px',
|
|
407
|
-
'--Switch-trackHeight': '32px',
|
|
408
|
-
'--Switch-trackRadius': '16px',
|
|
409
|
-
'--Switch-thumbRadius': '12px',
|
|
410
|
-
}}
|
|
292
|
+
checked={notificationsEnabled}
|
|
293
|
+
onChange={(e) => toggleNotifications(e.target.checked)}
|
|
411
294
|
/>
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## States
|
|
415
|
-
|
|
416
|
-
### Disabled State
|
|
417
295
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
<Switch disabled checked />
|
|
424
|
-
</Stack>
|
|
296
|
+
// ❌ Deferred effect requiring form submission -- use Checkbox instead
|
|
297
|
+
<form onSubmit={handleSubmit}>
|
|
298
|
+
<Switch checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />
|
|
299
|
+
<Button type="submit">Save</Button>
|
|
300
|
+
</form>
|
|
425
301
|
```
|
|
426
302
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
처리 중일 때의 시각적 피드백을 제공할 수 있습니다.
|
|
303
|
+
2. **Always provide a label**: Every switch needs a visible text label or an `aria-label` so users understand what it controls.
|
|
430
304
|
|
|
431
305
|
```tsx
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
setProcessing(true);
|
|
438
|
-
try {
|
|
439
|
-
// API 호출 시뮬레이션
|
|
440
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
441
|
-
setEnabled(event.target.checked);
|
|
442
|
-
} finally {
|
|
443
|
-
setProcessing(false);
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
return (
|
|
448
|
-
<Switch
|
|
449
|
-
checked={enabled}
|
|
450
|
-
onChange={handleToggle}
|
|
451
|
-
disabled={processing}
|
|
452
|
-
endDecorator={processing ? 'Processing...' : enabled ? 'On' : 'Off'}
|
|
453
|
-
/>
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
## Best Practices
|
|
459
|
-
|
|
460
|
-
1. **명확한 라벨**: 스위치가 무엇을 제어하는지 명확하게 표시하세요.
|
|
461
|
-
|
|
462
|
-
```tsx
|
|
463
|
-
// ✅ 명확한 라벨
|
|
464
|
-
<Typography component="label" endDecorator={<Switch />}>
|
|
465
|
-
이메일 알림 받기
|
|
466
|
-
</Typography>
|
|
306
|
+
// ✅ Clear label via FormControl
|
|
307
|
+
<FormControl orientation="horizontal">
|
|
308
|
+
<FormLabel>Email Notifications</FormLabel>
|
|
309
|
+
<Switch checked={enabled} onChange={handleChange} />
|
|
310
|
+
</FormControl>
|
|
467
311
|
|
|
468
|
-
// ❌
|
|
469
|
-
<
|
|
470
|
-
설정
|
|
471
|
-
</Typography>
|
|
312
|
+
// ❌ No label -- unclear what the switch does
|
|
313
|
+
<Switch checked={enabled} onChange={handleChange} />
|
|
472
314
|
```
|
|
473
315
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
3. **적절한 색상 사용**:
|
|
477
|
-
- 일반적인 기능: `primary` 또는 기본 색상
|
|
478
|
-
- 성공/활성화: `success`
|
|
479
|
-
- 주의사항: `warning`
|
|
480
|
-
- 위험한 기능: `danger`
|
|
481
|
-
|
|
482
|
-
4. **상태 표시**: 현재 상태를 명확히 보여주는 장식 요소를 활용하세요.
|
|
316
|
+
3. **Use semantic colors to reinforce meaning**: Apply `color="success"` for active/enabled states and `color="danger"` for risky toggles. Avoid using color as the sole indicator -- pair it with text.
|
|
483
317
|
|
|
484
318
|
```tsx
|
|
319
|
+
// ✅ Color + text indicator
|
|
485
320
|
<Switch
|
|
486
321
|
checked={enabled}
|
|
487
322
|
onChange={handleChange}
|
|
488
|
-
endDecorator={enabled ? '켜짐' : '꺼짐'}
|
|
489
323
|
color={enabled ? 'success' : 'neutral'}
|
|
324
|
+
endDecorator={enabled ? 'On' : 'Off'}
|
|
490
325
|
/>
|
|
491
326
|
```
|
|
492
327
|
|
|
493
|
-
|
|
328
|
+
4. **Handle async operations gracefully**: If toggling triggers an API call, disable the switch and show a processing indicator until the operation completes.
|
|
494
329
|
|
|
495
|
-
|
|
496
|
-
<Stack spacing={3}>
|
|
497
|
-
<Typography level="title-md">알림 설정</Typography>
|
|
498
|
-
<Stack spacing={1} sx={{ pl: 1 }}>
|
|
499
|
-
<Switch label="이메일 알림" />
|
|
500
|
-
<Switch label="SMS 알림" />
|
|
501
|
-
<Switch label="푸시 알림" />
|
|
502
|
-
</Stack>
|
|
503
|
-
</Stack>
|
|
504
|
-
```
|
|
330
|
+
5. **Group related switches logically**: Place related switches under a shared heading and use consistent spacing to create a scannable settings layout.
|
|
505
331
|
|
|
506
332
|
## Accessibility
|
|
507
333
|
|
|
508
|
-
Switch
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
- **Tab**: 스위치로 포커스 이동
|
|
513
|
-
- **Space**: 스위치 토글
|
|
514
|
-
- **Enter**: 스위치 토글 (라벨이 연결된 경우)
|
|
515
|
-
|
|
516
|
-
### ARIA 속성
|
|
517
|
-
|
|
518
|
-
- `role="switch"`: 스위치 역할 정의
|
|
519
|
-
- `aria-checked`: 현재 상태 표시
|
|
520
|
-
- `aria-label` 또는 `aria-labelledby`: 접근 가능한 이름 제공
|
|
521
|
-
|
|
522
|
-
### 추가 접근성 고려사항
|
|
523
|
-
|
|
524
|
-
```tsx
|
|
525
|
-
<Switch
|
|
526
|
-
slotProps={{
|
|
527
|
-
input: {
|
|
528
|
-
'aria-label': '다크 모드 활성화',
|
|
529
|
-
'aria-describedby': 'dark-mode-description',
|
|
530
|
-
},
|
|
531
|
-
}}
|
|
532
|
-
/>
|
|
533
|
-
<FormHelperText id="dark-mode-description">
|
|
534
|
-
화면을 어둡게 표시하여 눈의 피로를 줄입니다.
|
|
535
|
-
</FormHelperText>
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
### 색상 대비 및 시각적 표시
|
|
539
|
-
|
|
540
|
-
```tsx
|
|
541
|
-
// 색상에만 의존하지 않고 텍스트 표시도 함께 제공
|
|
542
|
-
<Switch
|
|
543
|
-
checked={enabled}
|
|
544
|
-
onChange={handleChange}
|
|
545
|
-
endDecorator={
|
|
546
|
-
<Typography level="body-xs" sx={{ minWidth: 32 }}>
|
|
547
|
-
{enabled ? 'ON' : 'OFF'}
|
|
548
|
-
</Typography>
|
|
549
|
-
}
|
|
550
|
-
/>
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
## Performance Considerations
|
|
554
|
-
|
|
555
|
-
1. **상태 최적화**: 불필요한 리렌더링을 방지하기 위해 상태를 적절히 관리하세요.
|
|
556
|
-
|
|
557
|
-
```tsx
|
|
558
|
-
// ✅ 개별 상태 관리
|
|
559
|
-
const [emailNotify, setEmailNotify] = useState(false);
|
|
560
|
-
const [smsNotify, setSmsNotify] = useState(false);
|
|
561
|
-
|
|
562
|
-
// ❌ 전체 객체 업데이트로 인한 불필요한 리렌더링
|
|
563
|
-
const [settings, setSettings] = useState({ email: false, sms: false });
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
2. **비동기 처리**: API 호출이 필요한 경우 적절한 로딩 상태를 제공하세요.
|
|
567
|
-
|
|
568
|
-
3. **메모이제이션**: 복잡한 핸들러는 useCallback으로 메모이제이션하세요.
|
|
569
|
-
|
|
570
|
-
Switch는 직관적이고 접근 가능한 토글 인터페이스를 제공하는 핵심 입력 컴포넌트입니다. 적절한 라벨링과 시각적 피드백을 통해 사용자 친화적인 설정 및 제어 인터페이스를 만들 수 있습니다.
|
|
334
|
+
- **Role and state**: The Switch renders an `<input type="checkbox" role="switch">` with `aria-checked` reflecting the current state. Screen readers announce it as a switch, not a checkbox.
|
|
335
|
+
- **Keyboard interaction**: `Tab` moves focus to the switch. `Space` toggles the state. No additional key bindings are needed.
|
|
336
|
+
- **Labeling**: Use `FormLabel`, a `<label>` wrapper (via `Typography component="label"`), or `slotProps.input['aria-label']` to provide an accessible name. Use `aria-describedby` to connect supplementary descriptions.
|
|
337
|
+
- **Visual indicators beyond color**: Do not rely solely on color to communicate state. Use `endDecorator` text (e.g., "On" / "Off") or track children to ensure the state is perceivable without color vision.
|