@ceed/cds 1.34.0 → 1.35.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.
- package/dist/components/Accordions/Accordions.d.ts +1 -0
- package/dist/components/Alert/Alert.d.ts +5 -5
- package/dist/components/Autocomplete/Autocomplete.d.ts +2 -2
- package/dist/components/Avatar/Avatar.d.ts +7 -17
- package/dist/components/Box/Box.d.ts +1 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +6 -5
- package/dist/components/Button/Button.d.ts +3 -2
- package/dist/components/Calendar/Calendar.d.ts +1 -0
- package/dist/components/Card/Card.d.ts +1 -0
- package/dist/components/Checkbox/Checkbox.d.ts +1 -0
- package/dist/components/Chip/Chip.d.ts +1 -0
- package/dist/components/Container/Container.d.ts +6 -1
- package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
- package/dist/components/DialogActions/DialogActions.d.ts +1 -0
- package/dist/components/DialogContent/DialogContent.d.ts +1 -0
- package/dist/components/DialogFrame/DialogFrame.d.ts +1 -1
- package/dist/components/DialogTitle/DialogTitle.d.ts +1 -0
- package/dist/components/Divider/Divider.d.ts +1 -0
- package/dist/components/Drawer/Drawer.d.ts +1 -0
- package/dist/components/Dropdown/Dropdown.d.ts +28 -1
- package/dist/components/FormControl/FormControl.d.ts +1 -0
- package/dist/components/FormHelperText/FormHelperText.d.ts +1 -0
- package/dist/components/FormLabel/FormLabel.d.ts +1 -0
- package/dist/components/Grid/Grid.d.ts +1 -0
- package/dist/components/IconButton/IconButton.d.ts +3 -2
- package/dist/components/IconMenuButton/IconMenuButton.d.ts +7 -6
- package/dist/components/InfoSign/InfoSign.d.ts +3 -2
- package/dist/components/Input/Input.d.ts +8 -22
- package/dist/components/InsetDrawer/InsetDrawer.d.ts +1 -0
- package/dist/components/Markdown/Markdown.d.ts +9 -24
- package/dist/components/Menu/Menu.d.ts +2 -1
- package/dist/components/MenuButton/MenuButton.d.ts +10 -8
- package/dist/components/Modal/Modal.d.ts +4 -2
- package/dist/components/NavigationGroup/NavigationGroup.d.ts +3 -2
- package/dist/components/NavigationItem/NavigationItem.d.ts +3 -2
- package/dist/components/Navigator/Navigator.d.ts +5 -4
- package/dist/components/Pagination/Pagination.d.ts +1 -1
- package/dist/components/Radio/Radio.d.ts +1 -0
- package/dist/components/RadioList/RadioList.d.ts +3 -2
- package/dist/components/SearchBar/SearchBar.d.ts +6 -5
- package/dist/components/SearchBar/index.d.ts +3 -2
- package/dist/components/Select/Select.d.ts +12 -10
- package/dist/components/Sheet/Sheet.d.ts +1 -0
- package/dist/components/Stack/Stack.d.ts +1 -0
- package/dist/components/Stepper/Stepper.d.ts +2 -1
- package/dist/components/Switch/Switch.d.ts +1 -0
- package/dist/components/Table/Table.d.ts +7 -5
- package/dist/components/Tabs/Tabs.d.ts +1 -0
- package/dist/components/Textarea/Textarea.d.ts +8 -20
- package/dist/components/Tooltip/Tooltip.d.ts +1 -0
- package/dist/components/Typography/Typography.d.ts +1 -0
- package/dist/components/Uploader/Uploader.d.ts +18 -17
- package/dist/components/data-display/Avatar.md +60 -72
- package/dist/components/data-display/Badge.md +197 -181
- package/dist/components/data-display/Chip.md +164 -142
- package/dist/components/data-display/DataTable.md +843 -338
- package/dist/components/data-display/InfoSign.md +1 -3
- package/dist/components/data-display/Markdown.md +93 -125
- package/dist/components/data-display/Table.md +1453 -1007
- package/dist/components/data-display/Typography.md +113 -116
- package/dist/components/feedback/Alert.md +80 -86
- package/dist/components/feedback/CircularProgress.md +32 -36
- package/dist/components/feedback/Dialog.md +25 -17
- package/dist/components/feedback/Modal.md +296 -264
- package/dist/components/feedback/Skeleton.md +125 -89
- package/dist/components/index.d.ts +63 -4
- package/dist/components/inputs/Autocomplete.md +191 -95
- package/dist/components/inputs/Button.md +83 -83
- package/dist/components/inputs/ButtonGroup.md +195 -185
- package/dist/components/inputs/Calendar.md +25 -28
- package/dist/components/inputs/Checkbox.md +11 -29
- package/dist/components/inputs/CurrencyInput.md +4 -4
- package/dist/components/inputs/DatePicker.md +229 -110
- package/dist/components/inputs/DateRangePicker.md +248 -137
- package/dist/components/inputs/FilterableCheckboxGroup.md +115 -55
- package/dist/components/inputs/FormControl.md +75 -69
- package/dist/components/inputs/IconButton.md +229 -205
- package/dist/components/inputs/Input.md +131 -98
- package/dist/components/inputs/MonthPicker.md +186 -84
- package/dist/components/inputs/MonthRangePicker.md +73 -49
- package/dist/components/inputs/PercentageInput.md +15 -31
- package/dist/components/inputs/RadioButton.md +320 -256
- package/dist/components/inputs/RadioList.md +66 -50
- package/dist/components/inputs/RadioTileGroup.md +287 -170
- package/dist/components/inputs/SearchBar.md +154 -55
- package/dist/components/inputs/Select.md +106 -95
- package/dist/components/inputs/Slider.md +153 -102
- package/dist/components/inputs/Switch.md +193 -138
- package/dist/components/inputs/Textarea.md +15 -20
- package/dist/components/inputs/Uploader/Uploader.md +68 -39
- package/dist/components/layout/Box.md +841 -662
- package/dist/components/layout/Container.md +3 -11
- package/dist/components/layout/Grid.md +480 -394
- package/dist/components/layout/Stack.md +739 -566
- package/dist/components/navigation/Breadcrumbs.md +4 -4
- package/dist/components/navigation/Drawer.md +34 -25
- package/dist/components/navigation/Dropdown.md +745 -408
- package/dist/components/navigation/IconMenuButton.md +14 -6
- package/dist/components/navigation/InsetDrawer.md +8 -13
- package/dist/components/navigation/Link.md +1 -2
- package/dist/components/navigation/Menu.md +623 -502
- package/dist/components/navigation/MenuButton.md +18 -10
- package/dist/components/navigation/NavigationGroup.md +19 -50
- package/dist/components/navigation/NavigationItem.md +6 -6
- package/dist/components/navigation/Navigator.md +26 -28
- package/dist/components/navigation/Pagination.md +86 -75
- package/dist/components/navigation/Stepper.md +2 -12
- package/dist/components/navigation/Tabs.md +48 -36
- package/dist/components/surfaces/Accordions.md +89 -172
- package/dist/components/surfaces/Card.md +1094 -709
- package/dist/components/surfaces/Divider.md +562 -412
- package/dist/components/surfaces/Sheet.md +700 -518
- package/dist/guides/ThemeProvider.md +8 -8
- package/dist/index.browser.js +4 -4
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +1079 -1052
- package/dist/index.d.ts +2 -1
- package/dist/index.js +725 -690
- package/framer/index.js +1 -1
- package/package.json +32 -35
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# Dropdown
|
|
2
2
|
|
|
3
|
-
Dropdown is the stateful container that orchestrates the open/close behavior of a menu system. It acts as the invisible glue between a trigger element (
|
|
3
|
+
Dropdown is the stateful container that orchestrates the open/close behavior of a menu system. It acts as the invisible glue between a trigger element (`MenuButtonTrigger`) and the Menu that appears when activated. Dropdown itself renders no visible UI -- it provides context so that its children can coordinate with each other.
|
|
4
4
|
|
|
5
|
-
Dropdown is part of a **composition pattern** together with Menu
|
|
5
|
+
Dropdown is part of a **composition pattern** together with `MenuButtonTrigger`, `Menu`, and `MenuItem`. `MenuButtonTrigger` is the low-level trigger that connects to the Dropdown context; the standalone `MenuButton` and `IconMenuButton` are higher-level convenience components that bundle trigger + menu into a single prop-driven API. Use the Dropdown composition when you need full control over the trigger element, menu content, and layout. All of these symbols are imported from `@ceed/cds`.
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
<Dropdown>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</Dropdown>
|
|
9
|
+
<MenuButtonTrigger variant="outlined" endDecorator={<ExpandMoreIcon />}>
|
|
10
|
+
메뉴 열기
|
|
11
|
+
</MenuButtonTrigger>
|
|
12
|
+
<Menu>
|
|
13
|
+
<MenuItem>옵션 1</MenuItem>
|
|
14
|
+
<MenuItem>옵션 2</MenuItem>
|
|
15
|
+
<MenuItem>옵션 3</MenuItem>
|
|
16
|
+
</Menu>
|
|
17
|
+
</Dropdown>
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
| Field | Description | Default |
|
|
@@ -24,13 +24,12 @@ Dropdown is part of a **composition pattern** together with Menu, MenuItem, Menu
|
|
|
24
24
|
## Usage
|
|
25
25
|
|
|
26
26
|
```tsx
|
|
27
|
-
import { Dropdown, Menu, MenuItem } from '@ceed/cds';
|
|
28
|
-
import { MenuButton } from '@ceed/cds';
|
|
27
|
+
import { Dropdown, MenuButtonTrigger, Menu, MenuItem } from '@ceed/cds';
|
|
29
28
|
|
|
30
29
|
function MyComponent() {
|
|
31
30
|
return (
|
|
32
31
|
<Dropdown>
|
|
33
|
-
<
|
|
32
|
+
<MenuButtonTrigger>Open Menu</MenuButtonTrigger>
|
|
34
33
|
<Menu>
|
|
35
34
|
<MenuItem>Option 1</MenuItem>
|
|
36
35
|
<MenuItem>Option 2</MenuItem>
|
|
@@ -41,537 +40,865 @@ function MyComponent() {
|
|
|
41
40
|
}
|
|
42
41
|
```
|
|
43
42
|
|
|
43
|
+
> **`MenuButtonTrigger` vs `MenuButton`**: `MenuButtonTrigger` is the low-level trigger for the Dropdown composition (Joy UI's `MenuButton`, re-exported from `@ceed/cds` under this name). The separate `MenuButton` exported from `@ceed/cds` is a higher-level, self-contained component (driven by `buttonText` / `items` props) that renders its **own** Dropdown internally — it cannot be nested inside `<Dropdown>`.
|
|
44
|
+
|
|
44
45
|
## Composition Pattern
|
|
45
46
|
|
|
46
47
|
Dropdown, Menu, MenuItem, MenuButton, and IconMenuButton form a **menu component family**. Choose the right level of abstraction for your use case:
|
|
47
48
|
|
|
48
|
-
| Approach
|
|
49
|
-
|
|
|
50
|
-
| `MenuButton` (standalone)
|
|
51
|
-
| `IconMenuButton` (standalone)
|
|
52
|
-
| `Dropdown` + `
|
|
53
|
-
| `Dropdown` + custom trigger + `Menu`
|
|
49
|
+
| Approach | When to use |
|
|
50
|
+
| ----------------------------------------- | ------------------------------------------------------- |
|
|
51
|
+
| `MenuButton` (standalone) | Simple text-triggered menus with a list of items |
|
|
52
|
+
| `IconMenuButton` (standalone) | Icon-only triggers in space-constrained areas |
|
|
53
|
+
| `Dropdown` + `MenuButtonTrigger` + `Menu` | Custom menu content (headers, dividers, mixed elements) |
|
|
54
|
+
| `Dropdown` + custom trigger + `Menu` | Fully custom trigger elements |
|
|
55
|
+
|
|
56
|
+
## Custom Trigger Components
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
The Dropdown trigger is **not limited to a plain button**. `MenuButtonTrigger` is simply the element that wires the open/close behavior — `onClick`, `aria-haspopup`, `aria-expanded`, focus management, and a `ref` — into the Dropdown context. You can render that behavior into a different component in two ways:
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
- **`slots={{ root: YourComponent }}`** — replace the trigger entirely with another component (e.g. `Button`, `IconButton`). `MenuButtonTrigger` injects all of the trigger behavior into your component as the root slot. Pass props to it via `slotProps={{ root: { ... } }}`.
|
|
61
|
+
- **`slotProps={{ root: { component: 'a' } }}`** (or the `component` prop) — keep `MenuButtonTrigger`'s styling and behavior but render a different HTML element.
|
|
58
62
|
|
|
59
|
-
The
|
|
63
|
+
This is exactly how the higher-level `IconMenuButton` is built internally (`slots={{ root: IconButton }}`). The only requirement is that the chosen root is focusable and clickable — prefer button-like components (`Button`, `IconButton`). For non-interactive components like `Avatar`, wrap them in a button-like root rather than using them directly.
|
|
60
64
|
|
|
61
65
|
```tsx
|
|
62
|
-
|
|
63
|
-
<Dropdown>
|
|
64
|
-
<MenuButton>기본 메뉴</MenuButton>
|
|
65
|
-
<Menu>
|
|
66
|
-
<MenuItem>첫 번째 옵션</MenuItem>
|
|
67
|
-
<MenuItem>두 번째 옵션</MenuItem>
|
|
68
|
-
<MenuItem>세 번째 옵션</MenuItem>
|
|
69
|
-
</Menu>
|
|
70
|
-
</Dropdown>
|
|
66
|
+
import { Dropdown, MenuButtonTrigger, Menu, MenuItem, Button } from '@ceed/cds';
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</Stack>
|
|
68
|
+
// Any button-like component as the trigger
|
|
69
|
+
<Dropdown>
|
|
70
|
+
<MenuButtonTrigger slots={{ root: Button }} slotProps={{ root: { variant: 'soft', color: 'primary' } }}>
|
|
71
|
+
Open
|
|
72
|
+
</MenuButtonTrigger>
|
|
73
|
+
<Menu>
|
|
74
|
+
<MenuItem>Option 1</MenuItem>
|
|
75
|
+
<MenuItem>Option 2</MenuItem>
|
|
76
|
+
</Menu>
|
|
77
|
+
</Dropdown>;
|
|
83
78
|
```
|
|
84
79
|
|
|
85
|
-
|
|
80
|
+
> **Note**: Every component above — including `MenuButtonTrigger` — is imported from `@ceed/cds`. No direct `@mui/joy` import is required.
|
|
86
81
|
|
|
87
|
-
|
|
82
|
+
## Button as Dropdown Trigger
|
|
83
|
+
|
|
84
|
+
Replace the trigger with the HDS `Button` via `slots={{ root: Button }}` to match your button styling system.
|
|
88
85
|
|
|
89
86
|
```tsx
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
<Dropdown>
|
|
88
|
+
<MenuButtonTrigger
|
|
89
|
+
slots={{
|
|
90
|
+
root: Button
|
|
91
|
+
}}
|
|
92
|
+
slotProps={{
|
|
93
|
+
root: {
|
|
94
|
+
variant: "soft",
|
|
95
|
+
color: "primary",
|
|
96
|
+
endDecorator: <KeyboardArrowDownIcon />
|
|
97
|
+
}
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
HDS Button 트리거
|
|
101
|
+
</MenuButtonTrigger>
|
|
93
102
|
<Menu>
|
|
94
103
|
<MenuItem>옵션 1</MenuItem>
|
|
95
104
|
<MenuItem>옵션 2</MenuItem>
|
|
105
|
+
<MenuItem>옵션 3</MenuItem>
|
|
96
106
|
</Menu>
|
|
97
107
|
</Dropdown>
|
|
108
|
+
```
|
|
98
109
|
|
|
99
|
-
|
|
100
|
-
<MenuButton variant="outlined">Outlined</MenuButton>
|
|
101
|
-
<Menu>
|
|
102
|
-
<MenuItem>옵션 1</MenuItem>
|
|
103
|
-
<MenuItem>옵션 2</MenuItem>
|
|
104
|
-
</Menu>
|
|
105
|
-
</Dropdown>
|
|
110
|
+
## IconButton Trigger
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
<MenuButton variant="soft">Soft</MenuButton>
|
|
109
|
-
<Menu>
|
|
110
|
-
<MenuItem>옵션 1</MenuItem>
|
|
111
|
-
<MenuItem>옵션 2</MenuItem>
|
|
112
|
-
</Menu>
|
|
113
|
-
</Dropdown>
|
|
112
|
+
For space-constrained areas like table rows or card headers, use an `IconButton` as the trigger. Always provide an `aria-label`.
|
|
114
113
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
```tsx
|
|
115
|
+
<Dropdown>
|
|
116
|
+
<MenuButtonTrigger
|
|
117
|
+
slots={{
|
|
118
|
+
root: IconButton
|
|
119
|
+
}}
|
|
120
|
+
slotProps={{
|
|
121
|
+
root: {
|
|
122
|
+
variant: "plain",
|
|
123
|
+
color: "neutral",
|
|
124
|
+
"aria-label": "추가 작업"
|
|
125
|
+
}
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<MoreVertIcon />
|
|
129
|
+
</MenuButtonTrigger>
|
|
130
|
+
<Menu placement="bottom-end">
|
|
131
|
+
<MenuItem>
|
|
132
|
+
<EditIcon
|
|
133
|
+
sx={{
|
|
134
|
+
mr: 1
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
편집
|
|
138
|
+
</MenuItem>
|
|
139
|
+
<MenuItem>
|
|
140
|
+
<DeleteIcon
|
|
141
|
+
sx={{
|
|
142
|
+
mr: 1,
|
|
143
|
+
color: "danger.500"
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
<Typography color="danger">삭제</Typography>
|
|
147
|
+
</MenuItem>
|
|
120
148
|
</Menu>
|
|
121
149
|
</Dropdown>
|
|
122
|
-
</Stack>
|
|
123
150
|
```
|
|
124
151
|
|
|
125
|
-
|
|
152
|
+
## Avatar Trigger
|
|
126
153
|
|
|
127
|
-
|
|
154
|
+
Profile menus often use an avatar as the trigger. Wrap the `Avatar` in a button-like `IconButton` root so the trigger stays keyboard- and screen-reader-accessible.
|
|
128
155
|
|
|
129
156
|
```tsx
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
157
|
+
<Dropdown>
|
|
158
|
+
<MenuButtonTrigger
|
|
159
|
+
slots={{
|
|
160
|
+
root: IconButton
|
|
161
|
+
}}
|
|
162
|
+
slotProps={{
|
|
163
|
+
root: {
|
|
164
|
+
variant: "plain",
|
|
165
|
+
color: "neutral",
|
|
166
|
+
"aria-label": "프로필 메뉴",
|
|
167
|
+
sx: {
|
|
168
|
+
borderRadius: "50%",
|
|
169
|
+
p: 0
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<Avatar size="sm" variant="soft" color="primary">
|
|
175
|
+
김
|
|
176
|
+
</Avatar>
|
|
177
|
+
</MenuButtonTrigger>
|
|
178
|
+
<Menu
|
|
179
|
+
placement="bottom-end"
|
|
180
|
+
sx={{
|
|
181
|
+
minWidth: 180
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
<Stack px={2} py={1} spacing={0.5}>
|
|
185
|
+
<Typography level="title-sm" fontWeight="bold">
|
|
186
|
+
김철수
|
|
187
|
+
</Typography>
|
|
188
|
+
<Typography level="body-xs" color="neutral">
|
|
189
|
+
kim@example.com
|
|
190
|
+
</Typography>
|
|
191
|
+
</Stack>
|
|
192
|
+
<ListDivider />
|
|
193
|
+
<MenuItem>
|
|
194
|
+
<PersonIcon
|
|
195
|
+
sx={{
|
|
196
|
+
mr: 1
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
내 프로필
|
|
200
|
+
</MenuItem>
|
|
201
|
+
<MenuItem>
|
|
202
|
+
<LogoutIcon
|
|
203
|
+
sx={{
|
|
204
|
+
mr: 1
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
로그아웃
|
|
208
|
+
</MenuItem>
|
|
138
209
|
</Menu>
|
|
139
210
|
</Dropdown>
|
|
211
|
+
```
|
|
140
212
|
|
|
141
|
-
|
|
142
|
-
<MenuButton color="neutral" variant="soft">
|
|
143
|
-
Neutral
|
|
144
|
-
</MenuButton>
|
|
145
|
-
<Menu>
|
|
146
|
-
<MenuItem>옵션 1</MenuItem>
|
|
147
|
-
<MenuItem>옵션 2</MenuItem>
|
|
148
|
-
</Menu>
|
|
149
|
-
</Dropdown>
|
|
213
|
+
## Custom Trigger Element
|
|
150
214
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
215
|
+
To keep `MenuButtonTrigger`'s behavior and styling but render a different HTML tag, set `slotProps={{ root: { component: 'a' } }}`.
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
<Dropdown>
|
|
219
|
+
<MenuButtonTrigger
|
|
220
|
+
variant="outlined"
|
|
221
|
+
slotProps={{
|
|
222
|
+
root: {
|
|
223
|
+
component: "a",
|
|
224
|
+
role: "button"
|
|
225
|
+
}
|
|
226
|
+
}}
|
|
227
|
+
endDecorator={<ExpandMoreIcon />}
|
|
228
|
+
sx={{
|
|
229
|
+
cursor: "pointer"
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
앵커(a) 엘리먼트 트리거
|
|
233
|
+
</MenuButtonTrigger>
|
|
155
234
|
<Menu>
|
|
156
235
|
<MenuItem>옵션 1</MenuItem>
|
|
157
236
|
<MenuItem>옵션 2</MenuItem>
|
|
158
237
|
</Menu>
|
|
159
238
|
</Dropdown>
|
|
239
|
+
```
|
|
160
240
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
241
|
+
## useMenuButton Hook Trigger
|
|
242
|
+
|
|
243
|
+
When you want a fully custom trigger element without using `MenuButtonTrigger` at all, use the `useMenuButton` hook. Call it inside a child of `<Dropdown>` and spread `getRootProps()` onto any element — the hook wires `onClick`, `aria-*`, and the `ref` into the Dropdown context. Render a native `<button>` (or add keyboard handling) so the trigger stays accessible.
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import { Dropdown, useMenuButton, Menu, MenuItem, Box } from '@ceed/cds';
|
|
247
|
+
|
|
248
|
+
function HookTrigger({ children }) {
|
|
249
|
+
const { getRootProps, open } = useMenuButton();
|
|
250
|
+
return (
|
|
251
|
+
<Box component="button" {...getRootProps()} sx={{ cursor: 'pointer' /* custom styles */ }}>
|
|
252
|
+
{children}
|
|
253
|
+
</Box>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
<Dropdown>
|
|
258
|
+
<HookTrigger>Custom trigger</HookTrigger>
|
|
259
|
+
<Menu>
|
|
260
|
+
<MenuItem>Option 1</MenuItem>
|
|
261
|
+
<MenuItem>Option 2</MenuItem>
|
|
262
|
+
</Menu>
|
|
263
|
+
</Dropdown>;
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<Dropdown>
|
|
268
|
+
<HookTrigger>완전 커스텀 트리거 (useMenuButton)</HookTrigger>
|
|
165
269
|
<Menu>
|
|
166
270
|
<MenuItem>옵션 1</MenuItem>
|
|
167
271
|
<MenuItem>옵션 2</MenuItem>
|
|
272
|
+
<MenuItem>옵션 3</MenuItem>
|
|
168
273
|
</Menu>
|
|
169
274
|
</Dropdown>
|
|
275
|
+
```
|
|
170
276
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
277
|
+
> **Note**: A trigger is always required — Dropdown itself renders no UI and only provides open/close state plus the anchor context. The trigger can be `MenuButtonTrigger` (optionally with `slots` / `component`) or your own element wired via `useMenuButton`. Both read the same Dropdown context, and both are available from `@ceed/cds`.
|
|
278
|
+
|
|
279
|
+
## useMenuButton with Button
|
|
280
|
+
|
|
281
|
+
You can also spread `getRootProps()` onto the HDS `Button`. Because `Button` renders a native `<button>`, it already provides focusability, Enter/Space activation, and the `button` role — so accessibility is preserved by adding only the `aria-*` / `onClick` / `ref` from the hook. `getRootProps()` does not include `onKeyDown`, which is why a native button (not a `div`) matters here.
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { Dropdown, useMenuButton, Menu, MenuItem, Button } from '@ceed/cds';
|
|
285
|
+
|
|
286
|
+
function HookButton({ children }) {
|
|
287
|
+
const { getRootProps, open } = useMenuButton();
|
|
288
|
+
return (
|
|
289
|
+
<Button {...getRootProps()} variant={open ? 'soft' : 'outlined'} color="neutral">
|
|
290
|
+
{children}
|
|
291
|
+
</Button>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
<Dropdown>
|
|
298
|
+
<HookButton>useMenuButton + Button</HookButton>
|
|
175
299
|
<Menu>
|
|
176
300
|
<MenuItem>옵션 1</MenuItem>
|
|
177
301
|
<MenuItem>옵션 2</MenuItem>
|
|
302
|
+
<MenuItem>옵션 3</MenuItem>
|
|
178
303
|
</Menu>
|
|
179
304
|
</Dropdown>
|
|
180
|
-
</Stack>
|
|
181
305
|
```
|
|
182
306
|
|
|
183
|
-
|
|
307
|
+
> **Tip**: For a plain button trigger, prefer `MenuButtonTrigger slots={{ root: Button }}` — it is more concise and wires the menu keyboard behavior for you. Reach for `useMenuButton` when you need a non-button or deeply custom trigger element.
|
|
308
|
+
|
|
309
|
+
## Dropdown Basic
|
|
310
|
+
|
|
311
|
+
The simplest Dropdown pairs a `MenuButtonTrigger` with a Menu of items.
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
<Stack direction="row" spacing={2}>
|
|
315
|
+
<Dropdown>
|
|
316
|
+
<MenuButtonTrigger>기본 메뉴</MenuButtonTrigger>
|
|
317
|
+
<Menu>
|
|
318
|
+
<MenuItem>첫 번째 옵션</MenuItem>
|
|
319
|
+
<MenuItem>두 번째 옵션</MenuItem>
|
|
320
|
+
<MenuItem>세 번째 옵션</MenuItem>
|
|
321
|
+
</Menu>
|
|
322
|
+
</Dropdown>
|
|
323
|
+
|
|
324
|
+
<Dropdown>
|
|
325
|
+
<MenuButtonTrigger variant="outlined" endDecorator={<ExpandMoreIcon />}>
|
|
326
|
+
아이콘 포함
|
|
327
|
+
</MenuButtonTrigger>
|
|
328
|
+
<Menu>
|
|
329
|
+
<MenuItem>옵션 A</MenuItem>
|
|
330
|
+
<MenuItem>옵션 B</MenuItem>
|
|
331
|
+
<MenuItem>옵션 C</MenuItem>
|
|
332
|
+
</Menu>
|
|
333
|
+
</Dropdown>
|
|
334
|
+
</Stack>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Dropdown Button Variants
|
|
338
|
+
|
|
339
|
+
`MenuButtonTrigger` supports `plain`, `outlined`, `soft`, and `solid` variants to match your design context.
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
<Stack direction="row" spacing={2} flexWrap="wrap">
|
|
343
|
+
<Dropdown>
|
|
344
|
+
<MenuButtonTrigger variant="plain">Plain</MenuButtonTrigger>
|
|
345
|
+
<Menu>
|
|
346
|
+
<MenuItem>옵션 1</MenuItem>
|
|
347
|
+
<MenuItem>옵션 2</MenuItem>
|
|
348
|
+
</Menu>
|
|
349
|
+
</Dropdown>
|
|
350
|
+
|
|
351
|
+
<Dropdown>
|
|
352
|
+
<MenuButtonTrigger variant="outlined">Outlined</MenuButtonTrigger>
|
|
353
|
+
<Menu>
|
|
354
|
+
<MenuItem>옵션 1</MenuItem>
|
|
355
|
+
<MenuItem>옵션 2</MenuItem>
|
|
356
|
+
</Menu>
|
|
357
|
+
</Dropdown>
|
|
358
|
+
|
|
359
|
+
<Dropdown>
|
|
360
|
+
<MenuButtonTrigger variant="soft">Soft</MenuButtonTrigger>
|
|
361
|
+
<Menu>
|
|
362
|
+
<MenuItem>옵션 1</MenuItem>
|
|
363
|
+
<MenuItem>옵션 2</MenuItem>
|
|
364
|
+
</Menu>
|
|
365
|
+
</Dropdown>
|
|
366
|
+
|
|
367
|
+
<Dropdown>
|
|
368
|
+
<MenuButtonTrigger variant="solid">Solid</MenuButtonTrigger>
|
|
369
|
+
<Menu>
|
|
370
|
+
<MenuItem>옵션 1</MenuItem>
|
|
371
|
+
<MenuItem>옵션 2</MenuItem>
|
|
372
|
+
</Menu>
|
|
373
|
+
</Dropdown>
|
|
374
|
+
</Stack>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Dropdown Button Colors
|
|
378
|
+
|
|
379
|
+
Apply semantic colors to the trigger button for different emphasis levels.
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
<Stack direction="row" spacing={2} flexWrap="wrap">
|
|
383
|
+
<Dropdown>
|
|
384
|
+
<MenuButtonTrigger color="primary" variant="soft">
|
|
385
|
+
Primary
|
|
386
|
+
</MenuButtonTrigger>
|
|
387
|
+
<Menu>
|
|
388
|
+
<MenuItem>옵션 1</MenuItem>
|
|
389
|
+
<MenuItem>옵션 2</MenuItem>
|
|
390
|
+
</Menu>
|
|
391
|
+
</Dropdown>
|
|
392
|
+
|
|
393
|
+
<Dropdown>
|
|
394
|
+
<MenuButtonTrigger color="neutral" variant="soft">
|
|
395
|
+
Neutral
|
|
396
|
+
</MenuButtonTrigger>
|
|
397
|
+
<Menu>
|
|
398
|
+
<MenuItem>옵션 1</MenuItem>
|
|
399
|
+
<MenuItem>옵션 2</MenuItem>
|
|
400
|
+
</Menu>
|
|
401
|
+
</Dropdown>
|
|
402
|
+
|
|
403
|
+
<Dropdown>
|
|
404
|
+
<MenuButtonTrigger color="danger" variant="soft">
|
|
405
|
+
Danger
|
|
406
|
+
</MenuButtonTrigger>
|
|
407
|
+
<Menu>
|
|
408
|
+
<MenuItem>옵션 1</MenuItem>
|
|
409
|
+
<MenuItem>옵션 2</MenuItem>
|
|
410
|
+
</Menu>
|
|
411
|
+
</Dropdown>
|
|
412
|
+
|
|
413
|
+
<Dropdown>
|
|
414
|
+
<MenuButtonTrigger color="success" variant="soft">
|
|
415
|
+
Success
|
|
416
|
+
</MenuButtonTrigger>
|
|
417
|
+
<Menu>
|
|
418
|
+
<MenuItem>옵션 1</MenuItem>
|
|
419
|
+
<MenuItem>옵션 2</MenuItem>
|
|
420
|
+
</Menu>
|
|
421
|
+
</Dropdown>
|
|
422
|
+
|
|
423
|
+
<Dropdown>
|
|
424
|
+
<MenuButtonTrigger color="warning" variant="soft">
|
|
425
|
+
Warning
|
|
426
|
+
</MenuButtonTrigger>
|
|
427
|
+
<Menu>
|
|
428
|
+
<MenuItem>옵션 1</MenuItem>
|
|
429
|
+
<MenuItem>옵션 2</MenuItem>
|
|
430
|
+
</Menu>
|
|
431
|
+
</Dropdown>
|
|
432
|
+
</Stack>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Dropdown Menu Placements
|
|
184
436
|
|
|
185
437
|
Control where the menu appears relative to the trigger using the `placement` prop on Menu.
|
|
186
438
|
|
|
187
439
|
```tsx
|
|
188
|
-
<Stack
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
<
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
</
|
|
440
|
+
<Stack
|
|
441
|
+
direction="row"
|
|
442
|
+
spacing={2}
|
|
443
|
+
sx={{
|
|
444
|
+
mt: 4
|
|
445
|
+
}}
|
|
446
|
+
>
|
|
447
|
+
<Dropdown>
|
|
448
|
+
<MenuButtonTrigger variant="outlined">Bottom Start</MenuButtonTrigger>
|
|
449
|
+
<Menu placement="bottom-start">
|
|
450
|
+
<MenuItem>옵션 1</MenuItem>
|
|
451
|
+
<MenuItem>옵션 2</MenuItem>
|
|
452
|
+
<MenuItem>옵션 3</MenuItem>
|
|
453
|
+
</Menu>
|
|
454
|
+
</Dropdown>
|
|
199
455
|
|
|
200
|
-
<Dropdown>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
</Dropdown>
|
|
456
|
+
<Dropdown>
|
|
457
|
+
<MenuButtonTrigger variant="outlined">Bottom</MenuButtonTrigger>
|
|
458
|
+
<Menu placement="bottom">
|
|
459
|
+
<MenuItem>옵션 1</MenuItem>
|
|
460
|
+
<MenuItem>옵션 2</MenuItem>
|
|
461
|
+
<MenuItem>옵션 3</MenuItem>
|
|
462
|
+
</Menu>
|
|
463
|
+
</Dropdown>
|
|
208
464
|
|
|
209
|
-
<Dropdown>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
</Dropdown>
|
|
465
|
+
<Dropdown>
|
|
466
|
+
<MenuButtonTrigger variant="outlined">Bottom End</MenuButtonTrigger>
|
|
467
|
+
<Menu placement="bottom-end">
|
|
468
|
+
<MenuItem>옵션 1</MenuItem>
|
|
469
|
+
<MenuItem>옵션 2</MenuItem>
|
|
470
|
+
<MenuItem>옵션 3</MenuItem>
|
|
471
|
+
</Menu>
|
|
472
|
+
</Dropdown>
|
|
217
473
|
|
|
218
|
-
<Dropdown>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
</Dropdown>
|
|
226
|
-
</Stack>
|
|
474
|
+
<Dropdown>
|
|
475
|
+
<MenuButtonTrigger variant="outlined">Top Start</MenuButtonTrigger>
|
|
476
|
+
<Menu placement="top-start">
|
|
477
|
+
<MenuItem>옵션 1</MenuItem>
|
|
478
|
+
<MenuItem>옵션 2</MenuItem>
|
|
479
|
+
<MenuItem>옵션 3</MenuItem>
|
|
480
|
+
</Menu>
|
|
481
|
+
</Dropdown>
|
|
482
|
+
</Stack>
|
|
227
483
|
```
|
|
228
484
|
|
|
229
|
-
|
|
485
|
+
## Dropdown with Icons
|
|
230
486
|
|
|
231
487
|
Add icons to menu items and decorators to the trigger button for richer visual communication.
|
|
232
488
|
|
|
233
489
|
```tsx
|
|
234
490
|
<Dropdown>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
491
|
+
<MenuButtonTrigger
|
|
492
|
+
variant="outlined"
|
|
493
|
+
startDecorator={<PersonIcon />}
|
|
494
|
+
endDecorator={<ExpandMoreIcon />}
|
|
495
|
+
>
|
|
496
|
+
사용자 메뉴
|
|
497
|
+
</MenuButtonTrigger>
|
|
498
|
+
<Menu>
|
|
499
|
+
<MenuItem>
|
|
500
|
+
<PersonIcon
|
|
501
|
+
sx={{
|
|
502
|
+
mr: 1
|
|
503
|
+
}}
|
|
504
|
+
/>
|
|
505
|
+
프로필 보기
|
|
506
|
+
</MenuItem>
|
|
507
|
+
<MenuItem>
|
|
508
|
+
<SettingsIcon
|
|
509
|
+
sx={{
|
|
510
|
+
mr: 1
|
|
511
|
+
}}
|
|
512
|
+
/>
|
|
513
|
+
설정
|
|
514
|
+
</MenuItem>
|
|
515
|
+
<ListDivider />
|
|
516
|
+
<MenuItem>
|
|
517
|
+
<LogoutIcon
|
|
518
|
+
sx={{
|
|
519
|
+
mr: 1
|
|
520
|
+
}}
|
|
521
|
+
/>
|
|
522
|
+
로그아웃
|
|
523
|
+
</MenuItem>
|
|
524
|
+
</Menu>
|
|
525
|
+
</Dropdown>
|
|
260
526
|
```
|
|
261
527
|
|
|
262
|
-
|
|
528
|
+
## Dropdown Actions Menu
|
|
263
529
|
|
|
264
530
|
Build action menus for edit, delete, and other operations. Use `color="danger"` on destructive items.
|
|
265
531
|
|
|
266
532
|
```tsx
|
|
267
533
|
<Dropdown>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
534
|
+
<MenuButtonTrigger
|
|
535
|
+
variant="plain"
|
|
536
|
+
color="neutral"
|
|
537
|
+
endDecorator={<ExpandMoreIcon />}
|
|
538
|
+
sx={{
|
|
539
|
+
fontSize: "sm"
|
|
540
|
+
}}
|
|
541
|
+
>
|
|
542
|
+
작업
|
|
543
|
+
</MenuButtonTrigger>
|
|
544
|
+
<Menu>
|
|
545
|
+
<MenuItem>
|
|
546
|
+
<EditIcon
|
|
547
|
+
sx={{
|
|
548
|
+
mr: 1,
|
|
549
|
+
fontSize: "sm"
|
|
550
|
+
}}
|
|
551
|
+
/>
|
|
552
|
+
편집
|
|
553
|
+
</MenuItem>
|
|
554
|
+
<MenuItem>
|
|
555
|
+
<DeleteIcon
|
|
556
|
+
sx={{
|
|
557
|
+
mr: 1,
|
|
558
|
+
fontSize: "sm",
|
|
559
|
+
color: "danger.500"
|
|
560
|
+
}}
|
|
561
|
+
/>
|
|
562
|
+
<Typography color="danger">삭제</Typography>
|
|
563
|
+
</MenuItem>
|
|
564
|
+
</Menu>
|
|
565
|
+
</Dropdown>
|
|
291
566
|
```
|
|
292
567
|
|
|
293
|
-
|
|
568
|
+
## Dropdown Filter Menu
|
|
294
569
|
|
|
295
570
|
Use Dropdown as an interactive filter selector with state management.
|
|
296
571
|
|
|
297
572
|
```tsx
|
|
298
573
|
<Dropdown>
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
574
|
+
<MenuButtonTrigger
|
|
575
|
+
variant="outlined"
|
|
576
|
+
startDecorator={<FilterListIcon />}
|
|
577
|
+
endDecorator={<ExpandMoreIcon />}
|
|
578
|
+
>
|
|
579
|
+
필터: {selectedFilter}
|
|
580
|
+
</MenuButtonTrigger>
|
|
581
|
+
<Menu>
|
|
582
|
+
<MenuItem onClick={() => setSelectedFilter("전체")}>전체</MenuItem>
|
|
583
|
+
<MenuItem onClick={() => setSelectedFilter("활성")}>활성</MenuItem>
|
|
584
|
+
<MenuItem onClick={() => setSelectedFilter("비활성")}>비활성</MenuItem>
|
|
585
|
+
<MenuItem onClick={() => setSelectedFilter("대기중")}>대기중</MenuItem>
|
|
586
|
+
</Menu>
|
|
587
|
+
</Dropdown>
|
|
309
588
|
```
|
|
310
589
|
|
|
311
|
-
|
|
590
|
+
## Dropdown Profile
|
|
312
591
|
|
|
313
592
|
Combine custom content (user info header) with standard menu items for profile menus.
|
|
314
593
|
|
|
315
594
|
```tsx
|
|
316
595
|
<Dropdown>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
596
|
+
<MenuButtonTrigger
|
|
597
|
+
variant="plain"
|
|
598
|
+
color="neutral"
|
|
599
|
+
startDecorator={
|
|
600
|
+
<Avatar size="sm" variant="soft" color="primary">
|
|
601
|
+
김
|
|
602
|
+
</Avatar>
|
|
603
|
+
}
|
|
604
|
+
endDecorator={<ExpandMoreIcon />}
|
|
605
|
+
sx={{
|
|
606
|
+
gap: 1,
|
|
607
|
+
padding: 1
|
|
608
|
+
}}
|
|
609
|
+
>
|
|
610
|
+
김철수
|
|
611
|
+
</MenuButtonTrigger>
|
|
612
|
+
<Menu
|
|
613
|
+
sx={{
|
|
614
|
+
minWidth: 200
|
|
615
|
+
}}
|
|
616
|
+
>
|
|
617
|
+
<Stack px={2} py={1.5} spacing={0.5}>
|
|
618
|
+
<Typography level="title-sm" fontWeight="bold">
|
|
619
|
+
김철수
|
|
620
|
+
</Typography>
|
|
621
|
+
<Typography level="body-xs" color="neutral">
|
|
622
|
+
kim@example.com
|
|
623
|
+
</Typography>
|
|
624
|
+
</Stack>
|
|
625
|
+
|
|
626
|
+
<ListDivider />
|
|
627
|
+
|
|
628
|
+
<MenuItem>
|
|
629
|
+
<PersonIcon
|
|
630
|
+
sx={{
|
|
631
|
+
mr: 1
|
|
632
|
+
}}
|
|
633
|
+
/>
|
|
634
|
+
내 프로필
|
|
635
|
+
</MenuItem>
|
|
636
|
+
<MenuItem>
|
|
637
|
+
<SettingsIcon
|
|
638
|
+
sx={{
|
|
639
|
+
mr: 1
|
|
640
|
+
}}
|
|
641
|
+
/>
|
|
642
|
+
계정 설정
|
|
643
|
+
</MenuItem>
|
|
644
|
+
|
|
645
|
+
<ListDivider />
|
|
646
|
+
|
|
647
|
+
<MenuItem>
|
|
648
|
+
<LogoutIcon
|
|
649
|
+
sx={{
|
|
650
|
+
mr: 1
|
|
651
|
+
}}
|
|
652
|
+
/>
|
|
653
|
+
로그아웃
|
|
654
|
+
</MenuItem>
|
|
655
|
+
</Menu>
|
|
656
|
+
</Dropdown>
|
|
361
657
|
```
|
|
362
658
|
|
|
363
|
-
|
|
659
|
+
## Dropdown Nested Menus
|
|
364
660
|
|
|
365
661
|
Place multiple Dropdown instances side by side to create menu bar patterns.
|
|
366
662
|
|
|
367
663
|
```tsx
|
|
368
664
|
<Stack direction="row" spacing={3}>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
665
|
+
<Dropdown>
|
|
666
|
+
<MenuButtonTrigger variant="outlined" endDecorator={<ExpandMoreIcon />}>
|
|
667
|
+
파일
|
|
668
|
+
</MenuButtonTrigger>
|
|
669
|
+
<Menu>
|
|
670
|
+
<MenuItem>새 파일</MenuItem>
|
|
671
|
+
<MenuItem>열기</MenuItem>
|
|
672
|
+
<MenuItem disabled>최근 파일</MenuItem>
|
|
673
|
+
<ListDivider />
|
|
674
|
+
<MenuItem>저장</MenuItem>
|
|
675
|
+
<MenuItem>다른 이름으로 저장</MenuItem>
|
|
676
|
+
<ListDivider />
|
|
677
|
+
<MenuItem>인쇄</MenuItem>
|
|
678
|
+
</Menu>
|
|
679
|
+
</Dropdown>
|
|
384
680
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
</Stack>
|
|
681
|
+
<Dropdown>
|
|
682
|
+
<MenuButtonTrigger variant="outlined" endDecorator={<ExpandMoreIcon />}>
|
|
683
|
+
편집
|
|
684
|
+
</MenuButtonTrigger>
|
|
685
|
+
<Menu>
|
|
686
|
+
<MenuItem>실행 취소</MenuItem>
|
|
687
|
+
<MenuItem>다시 실행</MenuItem>
|
|
688
|
+
<ListDivider />
|
|
689
|
+
<MenuItem>잘라내기</MenuItem>
|
|
690
|
+
<MenuItem>복사</MenuItem>
|
|
691
|
+
<MenuItem>붙여넣기</MenuItem>
|
|
692
|
+
<ListDivider />
|
|
693
|
+
<MenuItem>모두 선택</MenuItem>
|
|
694
|
+
<MenuItem>찾기</MenuItem>
|
|
695
|
+
</Menu>
|
|
696
|
+
</Dropdown>
|
|
697
|
+
</Stack>
|
|
402
698
|
```
|
|
403
699
|
|
|
404
|
-
|
|
700
|
+
## Dropdown Controlled State
|
|
405
701
|
|
|
406
702
|
Use the `open` and `onOpenChange` props to programmatically control the menu state.
|
|
407
703
|
|
|
408
704
|
```tsx
|
|
409
705
|
<Stack spacing={2}>
|
|
410
|
-
|
|
706
|
+
<Typography level="body-sm">메뉴 상태: {open ? "열림" : "닫힘"}</Typography>
|
|
411
707
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
708
|
+
<Dropdown open={open} onOpenChange={(event, isOpen) => setOpen(isOpen)}>
|
|
709
|
+
<MenuButtonTrigger variant="outlined" endDecorator={<ExpandMoreIcon />}>
|
|
710
|
+
제어된 메뉴
|
|
711
|
+
</MenuButtonTrigger>
|
|
712
|
+
<Menu>
|
|
713
|
+
<MenuItem onClick={() => setOpen(false)}>옵션 1</MenuItem>
|
|
714
|
+
<MenuItem onClick={() => setOpen(false)}>옵션 2</MenuItem>
|
|
715
|
+
<MenuItem onClick={() => setOpen(false)}>옵션 3</MenuItem>
|
|
716
|
+
</Menu>
|
|
717
|
+
</Dropdown>
|
|
422
718
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
</Stack>
|
|
719
|
+
<Button variant="soft" size="sm" onClick={() => setOpen(!open)}>
|
|
720
|
+
{open ? "메뉴 닫기" : "메뉴 열기"}
|
|
721
|
+
</Button>
|
|
722
|
+
</Stack>
|
|
427
723
|
```
|
|
428
724
|
|
|
429
|
-
|
|
725
|
+
## Dropdown Disabled State
|
|
430
726
|
|
|
431
727
|
Disable the entire trigger button or individual menu items.
|
|
432
728
|
|
|
433
729
|
```tsx
|
|
434
730
|
<Stack direction="row" spacing={2}>
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
731
|
+
<Dropdown>
|
|
732
|
+
<MenuButtonTrigger disabled>비활성 메뉴</MenuButtonTrigger>
|
|
733
|
+
<Menu>
|
|
734
|
+
<MenuItem>접근 불가</MenuItem>
|
|
735
|
+
</Menu>
|
|
736
|
+
</Dropdown>
|
|
441
737
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
</Stack>
|
|
738
|
+
<Dropdown>
|
|
739
|
+
<MenuButtonTrigger variant="outlined">일부 옵션 비활성</MenuButtonTrigger>
|
|
740
|
+
<Menu>
|
|
741
|
+
<MenuItem>활성 옵션</MenuItem>
|
|
742
|
+
<MenuItem disabled>비활성 옵션</MenuItem>
|
|
743
|
+
<MenuItem>활성 옵션 2</MenuItem>
|
|
744
|
+
</Menu>
|
|
745
|
+
</Dropdown>
|
|
746
|
+
</Stack>
|
|
451
747
|
```
|
|
452
748
|
|
|
453
|
-
|
|
749
|
+
## Dropdown Long Menu
|
|
454
750
|
|
|
455
751
|
Apply `maxHeight` and `overflow: 'auto'` to Menu for scrollable lists with many items.
|
|
456
752
|
|
|
457
753
|
```tsx
|
|
458
754
|
<Dropdown>
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
755
|
+
<MenuButtonTrigger variant="outlined" endDecorator={<ExpandMoreIcon />}>
|
|
756
|
+
긴 목록 메뉴
|
|
757
|
+
</MenuButtonTrigger>
|
|
758
|
+
<Menu
|
|
759
|
+
sx={{
|
|
760
|
+
maxHeight: 300,
|
|
761
|
+
overflow: "auto"
|
|
762
|
+
}}
|
|
763
|
+
>
|
|
764
|
+
{Array.from(
|
|
765
|
+
{
|
|
766
|
+
length: 20
|
|
767
|
+
},
|
|
768
|
+
(_, index) => (
|
|
769
|
+
<MenuItem key={index}>옵션 {index + 1}</MenuItem>
|
|
770
|
+
)
|
|
771
|
+
)}
|
|
772
|
+
</Menu>
|
|
773
|
+
</Dropdown>
|
|
471
774
|
```
|
|
472
775
|
|
|
473
|
-
|
|
776
|
+
## Dropdown Custom Styling
|
|
474
777
|
|
|
475
778
|
Customize borders, shadows, border-radius, and gradients through the `sx` prop.
|
|
476
779
|
|
|
477
780
|
```tsx
|
|
478
781
|
<Stack direction="row" spacing={2}>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
</
|
|
508
|
-
|
|
782
|
+
<Dropdown>
|
|
783
|
+
<MenuButtonTrigger
|
|
784
|
+
variant="soft"
|
|
785
|
+
color="primary"
|
|
786
|
+
endDecorator={<ExpandMoreIcon />}
|
|
787
|
+
sx={{
|
|
788
|
+
borderRadius: "xl",
|
|
789
|
+
fontWeight: "bold"
|
|
790
|
+
}}
|
|
791
|
+
>
|
|
792
|
+
커스텀 버튼
|
|
793
|
+
</MenuButtonTrigger>
|
|
794
|
+
<Menu
|
|
795
|
+
sx={{
|
|
796
|
+
borderRadius: "md",
|
|
797
|
+
boxShadow: "lg",
|
|
798
|
+
border: "2px solid",
|
|
799
|
+
borderColor: "primary.200"
|
|
800
|
+
}}
|
|
801
|
+
>
|
|
802
|
+
<MenuItem
|
|
803
|
+
sx={{
|
|
804
|
+
borderRadius: "sm",
|
|
805
|
+
mx: 0.5,
|
|
806
|
+
my: 0.25
|
|
807
|
+
}}
|
|
808
|
+
>
|
|
809
|
+
둥근 아이템 1
|
|
810
|
+
</MenuItem>
|
|
811
|
+
<MenuItem
|
|
812
|
+
sx={{
|
|
813
|
+
borderRadius: "sm",
|
|
814
|
+
mx: 0.5,
|
|
815
|
+
my: 0.25
|
|
816
|
+
}}
|
|
817
|
+
>
|
|
818
|
+
둥근 아이템 2
|
|
819
|
+
</MenuItem>
|
|
820
|
+
<MenuItem
|
|
821
|
+
sx={{
|
|
822
|
+
borderRadius: "sm",
|
|
823
|
+
mx: 0.5,
|
|
824
|
+
my: 0.25
|
|
825
|
+
}}
|
|
826
|
+
>
|
|
827
|
+
둥근 아이템 3
|
|
828
|
+
</MenuItem>
|
|
829
|
+
</Menu>
|
|
830
|
+
</Dropdown>
|
|
509
831
|
|
|
510
|
-
<Dropdown>
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
</
|
|
832
|
+
<Dropdown>
|
|
833
|
+
<MenuButtonTrigger
|
|
834
|
+
variant="outlined"
|
|
835
|
+
sx={{
|
|
836
|
+
background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
|
|
837
|
+
border: 0,
|
|
838
|
+
borderRadius: 3,
|
|
839
|
+
color: "white",
|
|
840
|
+
"&:hover": {
|
|
841
|
+
background: "linear-gradient(45deg, #FE6B8B 60%, #FF8E53 100%)"
|
|
842
|
+
}
|
|
843
|
+
}}
|
|
844
|
+
>
|
|
845
|
+
그라데이션 버튼
|
|
846
|
+
</MenuButtonTrigger>
|
|
847
|
+
<Menu>
|
|
848
|
+
<MenuItem>스타일링된 메뉴</MenuItem>
|
|
849
|
+
<MenuItem>아이템 2</MenuItem>
|
|
850
|
+
</Menu>
|
|
851
|
+
</Dropdown>
|
|
852
|
+
</Stack>
|
|
528
853
|
```
|
|
529
854
|
|
|
530
|
-
##
|
|
531
|
-
|
|
532
|
-
### Navigation Menu
|
|
855
|
+
## Navigation Menu
|
|
533
856
|
|
|
534
857
|
```tsx
|
|
535
858
|
<Dropdown>
|
|
536
|
-
<
|
|
859
|
+
<MenuButtonTrigger variant="plain">Menu</MenuButtonTrigger>
|
|
537
860
|
<Menu>
|
|
538
|
-
<MenuItem component="a" href="/home">
|
|
539
|
-
|
|
540
|
-
|
|
861
|
+
<MenuItem component="a" href="/home">
|
|
862
|
+
Home
|
|
863
|
+
</MenuItem>
|
|
864
|
+
<MenuItem component="a" href="/about">
|
|
865
|
+
About
|
|
866
|
+
</MenuItem>
|
|
867
|
+
<MenuItem component="a" href="/products">
|
|
868
|
+
Products
|
|
869
|
+
</MenuItem>
|
|
541
870
|
<ListDivider />
|
|
542
|
-
<MenuItem component="a" href="/contact">
|
|
871
|
+
<MenuItem component="a" href="/contact">
|
|
872
|
+
Contact
|
|
873
|
+
</MenuItem>
|
|
543
874
|
</Menu>
|
|
544
875
|
</Dropdown>
|
|
545
876
|
```
|
|
546
877
|
|
|
547
|
-
|
|
878
|
+
## Filter Selector
|
|
548
879
|
|
|
549
880
|
```tsx
|
|
550
881
|
const [filter, setFilter] = useState('All');
|
|
551
882
|
|
|
552
883
|
<Dropdown>
|
|
553
|
-
<
|
|
554
|
-
variant="outlined"
|
|
555
|
-
startDecorator={<FilterListIcon />}
|
|
556
|
-
endDecorator={<ExpandMoreIcon />}
|
|
557
|
-
>
|
|
884
|
+
<MenuButtonTrigger variant="outlined" startDecorator={<FilterListIcon />} endDecorator={<ExpandMoreIcon />}>
|
|
558
885
|
Filter: {filter}
|
|
559
|
-
</
|
|
886
|
+
</MenuButtonTrigger>
|
|
560
887
|
<Menu>
|
|
561
888
|
<MenuItem onClick={() => setFilter('All')}>All</MenuItem>
|
|
562
889
|
<MenuItem onClick={() => setFilter('Active')}>Active</MenuItem>
|
|
563
890
|
<MenuItem onClick={() => setFilter('Inactive')}>Inactive</MenuItem>
|
|
564
891
|
</Menu>
|
|
565
|
-
</Dropdown
|
|
892
|
+
</Dropdown>;
|
|
566
893
|
```
|
|
567
894
|
|
|
568
|
-
|
|
895
|
+
## Controlled State with External Toggle
|
|
569
896
|
|
|
570
897
|
```tsx
|
|
571
898
|
const [open, setOpen] = useState(false);
|
|
572
899
|
|
|
573
900
|
<Dropdown open={open} onOpenChange={(event, isOpen) => setOpen(isOpen)}>
|
|
574
|
-
<
|
|
901
|
+
<MenuButtonTrigger>Controlled Menu</MenuButtonTrigger>
|
|
575
902
|
<Menu>
|
|
576
903
|
<MenuItem onClick={() => setOpen(false)}>Option A</MenuItem>
|
|
577
904
|
<MenuItem onClick={() => setOpen(false)}>Option B</MenuItem>
|
|
@@ -587,26 +914,30 @@ const [open, setOpen] = useState(false);
|
|
|
587
914
|
|
|
588
915
|
### Key Props
|
|
589
916
|
|
|
590
|
-
| Prop | Type | Default | Description
|
|
591
|
-
| -------------- | -------------------------------- | ------- |
|
|
592
|
-
| `children` | `ReactNode` | - | Dropdown content (
|
|
593
|
-
| `open` | `boolean` | - | Controlled open state
|
|
594
|
-
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled)
|
|
595
|
-
| `onOpenChange` | `(event, open: boolean) => void` | - | Callback when open state changes
|
|
917
|
+
| Prop | Type | Default | Description |
|
|
918
|
+
| -------------- | -------------------------------- | ------- | ------------------------------------------- |
|
|
919
|
+
| `children` | `ReactNode` | - | Dropdown content (MenuButtonTrigger + Menu) |
|
|
920
|
+
| `open` | `boolean` | - | Controlled open state |
|
|
921
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
|
|
922
|
+
| `onOpenChange` | `(event, open: boolean) => void` | - | Callback when open state changes |
|
|
596
923
|
|
|
597
|
-
> **Note**: Dropdown accepts all Joy UI Dropdown props. It is a state management wrapper — style props go on Menu and
|
|
924
|
+
> **Note**: Dropdown accepts all Joy UI Dropdown props. It is a state management wrapper — style props go on Menu and `MenuButtonTrigger`.
|
|
598
925
|
|
|
599
926
|
## Best Practices
|
|
600
927
|
|
|
601
928
|
- **Use the right abstraction level.** If you only need a simple list of text items, prefer `MenuButton` or `IconMenuButton` standalone components. Use `Dropdown` + `Menu` when you need dividers, custom headers, or mixed content inside the menu.
|
|
602
929
|
|
|
603
930
|
```tsx
|
|
604
|
-
{
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
{
|
|
931
|
+
{
|
|
932
|
+
/* ✅ Good: Simple list -- use MenuButton directly */
|
|
933
|
+
}
|
|
934
|
+
<MenuButton buttonText="Actions" items={[{ text: 'Edit' }, { text: 'Delete' }]} />;
|
|
935
|
+
|
|
936
|
+
{
|
|
937
|
+
/* ✅ Good: Complex content -- use Dropdown composition */
|
|
938
|
+
}
|
|
608
939
|
<Dropdown>
|
|
609
|
-
<
|
|
940
|
+
<MenuButtonTrigger>User</MenuButtonTrigger>
|
|
610
941
|
<Menu>
|
|
611
942
|
<Box sx={{ px: 2, py: 1 }}>
|
|
612
943
|
<Typography level="title-sm">User Name</Typography>
|
|
@@ -615,29 +946,35 @@ const [open, setOpen] = useState(false);
|
|
|
615
946
|
<MenuItem>Settings</MenuItem>
|
|
616
947
|
<MenuItem>Logout</MenuItem>
|
|
617
948
|
</Menu>
|
|
618
|
-
</Dropdown
|
|
949
|
+
</Dropdown>;
|
|
619
950
|
```
|
|
620
951
|
|
|
621
952
|
- **Group related items with ListDivider.** Visually separate logical groups of menu items so users can scan options quickly.
|
|
622
953
|
|
|
623
954
|
```tsx
|
|
624
|
-
{
|
|
955
|
+
{
|
|
956
|
+
/* ✅ Good: Grouped by function */
|
|
957
|
+
}
|
|
625
958
|
<Menu>
|
|
626
959
|
<MenuItem>Edit</MenuItem>
|
|
627
960
|
<MenuItem>Duplicate</MenuItem>
|
|
628
961
|
<ListDivider />
|
|
629
962
|
<MenuItem color="danger">Delete</MenuItem>
|
|
630
|
-
</Menu
|
|
963
|
+
</Menu>;
|
|
631
964
|
```
|
|
632
965
|
|
|
633
966
|
- **Indicate dropdown affordance.** Use `endDecorator={<ExpandMoreIcon />}` on the trigger button so users understand it opens a menu.
|
|
634
967
|
|
|
635
968
|
```tsx
|
|
636
|
-
{
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
{
|
|
640
|
-
|
|
969
|
+
{
|
|
970
|
+
/* ✅ Good: Clear dropdown indicator */
|
|
971
|
+
}
|
|
972
|
+
<MenuButtonTrigger endDecorator={<ExpandMoreIcon />}>Options</MenuButtonTrigger>;
|
|
973
|
+
|
|
974
|
+
{
|
|
975
|
+
/* ❌ Avoid: No visual hint that a menu will appear */
|
|
976
|
+
}
|
|
977
|
+
<MenuButtonTrigger>Options</MenuButtonTrigger>;
|
|
641
978
|
```
|
|
642
979
|
|
|
643
980
|
- **Keep menu item count reasonable.** Aim for 3-7 items. For longer lists, apply `maxHeight` with scrolling or consider a different UI pattern like a dialog or searchable list.
|