@ceed/ads 1.35.1 → 1.36.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/README.md +85 -95
- 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/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/Dropdown/Dropdown.d.ts +28 -1
- package/dist/components/FilterMenu/components/MonthRange.d.ts +11 -0
- package/dist/components/FilterMenu/types.d.ts +5 -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/ProfileMenu/ProfileMenu.d.ts +2 -2
- package/dist/components/Radio/Radio.d.ts +1 -0
- package/dist/components/RadioList/RadioList.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/ThemeProvider/ThemeProvider.d.ts +4 -2
- 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 +101 -104
- 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 -265
- package/dist/components/feedback/Skeleton.md +125 -89
- package/dist/components/index.d.ts +60 -1
- 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/FilterMenu.md +138 -8
- 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 +82 -60
- 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 +182 -116
- package/dist/components/navigation/Dropdown.md +732 -391
- package/dist/components/navigation/IconMenuButton.md +14 -6
- package/dist/components/navigation/InsetDrawer.md +550 -378
- package/dist/components/navigation/Link.md +104 -94
- 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/ProfileMenu.md +65 -43
- package/dist/components/navigation/Stepper.md +2 -12
- package/dist/components/navigation/Tabs.md +209 -183
- 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 +65 -40
- package/dist/index.browser.js +4 -4
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +1643 -1550
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1303 -1202
- 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/ads`.
|
|
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/ads';
|
|
28
|
-
import { MenuButton } from '@ceed/ads';
|
|
27
|
+
import { Dropdown, MenuButtonTrigger, Menu, MenuItem } from '@ceed/ads';
|
|
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,83 +40,338 @@ 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/ads` under this name). The separate `MenuButton` exported from `@ceed/ads` 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 |
|
|
54
55
|
|
|
55
|
-
##
|
|
56
|
+
## Custom Trigger Components
|
|
56
57
|
|
|
57
|
-
The
|
|
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:
|
|
59
|
+
|
|
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.
|
|
62
|
+
|
|
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.
|
|
58
64
|
|
|
59
65
|
```tsx
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
import { Dropdown, MenuButtonTrigger, Menu, MenuItem, Button } from '@ceed/ads';
|
|
67
|
+
|
|
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>;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
> **Note**: Every component above — including `MenuButtonTrigger` — is imported from `@ceed/ads`. No direct `@mui/joy` import is required.
|
|
81
|
+
|
|
82
|
+
## Button as Dropdown Trigger
|
|
83
|
+
|
|
84
|
+
Replace the trigger with the HDS `Button` via `slots={{ root: Button }}` to match your button styling system.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
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>
|
|
63
102
|
<Menu>
|
|
64
|
-
<MenuItem
|
|
65
|
-
<MenuItem
|
|
66
|
-
<MenuItem
|
|
103
|
+
<MenuItem>옵션 1</MenuItem>
|
|
104
|
+
<MenuItem>옵션 2</MenuItem>
|
|
105
|
+
<MenuItem>옵션 3</MenuItem>
|
|
67
106
|
</Menu>
|
|
68
107
|
</Dropdown>
|
|
108
|
+
```
|
|
69
109
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
## IconButton Trigger
|
|
111
|
+
|
|
112
|
+
For space-constrained areas like table rows or card headers, use an `IconButton` as the trigger. Always provide an `aria-label`.
|
|
113
|
+
|
|
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>
|
|
78
148
|
</Menu>
|
|
79
149
|
</Dropdown>
|
|
80
|
-
</Stack>
|
|
81
150
|
```
|
|
82
151
|
|
|
83
|
-
##
|
|
152
|
+
## Avatar Trigger
|
|
84
153
|
|
|
85
|
-
|
|
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.
|
|
86
155
|
|
|
87
156
|
```tsx
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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>
|
|
94
209
|
</Menu>
|
|
95
210
|
</Dropdown>
|
|
211
|
+
```
|
|
96
212
|
|
|
97
|
-
|
|
98
|
-
|
|
213
|
+
## Custom Trigger Element
|
|
214
|
+
|
|
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>
|
|
99
234
|
<Menu>
|
|
100
235
|
<MenuItem>옵션 1</MenuItem>
|
|
101
236
|
<MenuItem>옵션 2</MenuItem>
|
|
102
237
|
</Menu>
|
|
103
238
|
</Dropdown>
|
|
239
|
+
```
|
|
104
240
|
|
|
105
|
-
|
|
106
|
-
|
|
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/ads';
|
|
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>
|
|
107
269
|
<Menu>
|
|
108
270
|
<MenuItem>옵션 1</MenuItem>
|
|
109
271
|
<MenuItem>옵션 2</MenuItem>
|
|
272
|
+
<MenuItem>옵션 3</MenuItem>
|
|
110
273
|
</Menu>
|
|
111
274
|
</Dropdown>
|
|
275
|
+
```
|
|
112
276
|
|
|
113
|
-
|
|
114
|
-
|
|
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/ads`.
|
|
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/ads';
|
|
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>
|
|
115
299
|
<Menu>
|
|
116
300
|
<MenuItem>옵션 1</MenuItem>
|
|
117
301
|
<MenuItem>옵션 2</MenuItem>
|
|
302
|
+
<MenuItem>옵션 3</MenuItem>
|
|
118
303
|
</Menu>
|
|
119
304
|
</Dropdown>
|
|
120
|
-
|
|
305
|
+
```
|
|
306
|
+
|
|
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>
|
|
121
375
|
```
|
|
122
376
|
|
|
123
377
|
## Dropdown Button Colors
|
|
@@ -126,56 +380,56 @@ Apply semantic colors to the trigger button for different emphasis levels.
|
|
|
126
380
|
|
|
127
381
|
```tsx
|
|
128
382
|
<Stack direction="row" spacing={2} flexWrap="wrap">
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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>
|
|
138
392
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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>
|
|
148
402
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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>
|
|
158
412
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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>
|
|
168
422
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
</Stack>
|
|
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>
|
|
179
433
|
```
|
|
180
434
|
|
|
181
435
|
## Dropdown Menu Placements
|
|
@@ -183,45 +437,49 @@ Apply semantic colors to the trigger button for different emphasis levels.
|
|
|
183
437
|
Control where the menu appears relative to the trigger using the `placement` prop on Menu.
|
|
184
438
|
|
|
185
439
|
```tsx
|
|
186
|
-
<Stack
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
</
|
|
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>
|
|
197
455
|
|
|
198
|
-
<Dropdown>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
</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>
|
|
206
464
|
|
|
207
|
-
<Dropdown>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
</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>
|
|
215
473
|
|
|
216
|
-
<Dropdown>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
</Dropdown>
|
|
224
|
-
</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>
|
|
225
483
|
```
|
|
226
484
|
|
|
227
485
|
## Dropdown with Icons
|
|
@@ -230,31 +488,41 @@ Add icons to menu items and decorators to the trigger button for richer visual c
|
|
|
230
488
|
|
|
231
489
|
```tsx
|
|
232
490
|
<Dropdown>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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>
|
|
258
526
|
```
|
|
259
527
|
|
|
260
528
|
## Dropdown Actions Menu
|
|
@@ -263,29 +531,38 @@ Build action menus for edit, delete, and other operations. Use `color="danger"`
|
|
|
263
531
|
|
|
264
532
|
```tsx
|
|
265
533
|
<Dropdown>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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>
|
|
289
566
|
```
|
|
290
567
|
|
|
291
568
|
## Dropdown Filter Menu
|
|
@@ -294,16 +571,20 @@ Use Dropdown as an interactive filter selector with state management.
|
|
|
294
571
|
|
|
295
572
|
```tsx
|
|
296
573
|
<Dropdown>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
<
|
|
305
|
-
|
|
306
|
-
|
|
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>
|
|
307
588
|
```
|
|
308
589
|
|
|
309
590
|
## Dropdown Profile
|
|
@@ -312,50 +593,67 @@ Combine custom content (user info header) with standard menu items for profile m
|
|
|
312
593
|
|
|
313
594
|
```tsx
|
|
314
595
|
<Dropdown>
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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>
|
|
359
657
|
```
|
|
360
658
|
|
|
361
659
|
## Dropdown Nested Menus
|
|
@@ -364,39 +662,39 @@ Place multiple Dropdown instances side by side to create menu bar patterns.
|
|
|
364
662
|
|
|
365
663
|
```tsx
|
|
366
664
|
<Stack direction="row" spacing={3}>
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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>
|
|
382
680
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
</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>
|
|
400
698
|
```
|
|
401
699
|
|
|
402
700
|
## Dropdown Controlled State
|
|
@@ -405,23 +703,23 @@ Use the `open` and `onOpenChange` props to programmatically control the menu sta
|
|
|
405
703
|
|
|
406
704
|
```tsx
|
|
407
705
|
<Stack spacing={2}>
|
|
408
|
-
|
|
706
|
+
<Typography level="body-sm">메뉴 상태: {open ? "열림" : "닫힘"}</Typography>
|
|
409
707
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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>
|
|
420
718
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
</Stack>
|
|
719
|
+
<Button variant="soft" size="sm" onClick={() => setOpen(!open)}>
|
|
720
|
+
{open ? "메뉴 닫기" : "메뉴 열기"}
|
|
721
|
+
</Button>
|
|
722
|
+
</Stack>
|
|
425
723
|
```
|
|
426
724
|
|
|
427
725
|
## Dropdown Disabled State
|
|
@@ -430,22 +728,22 @@ Disable the entire trigger button or individual menu items.
|
|
|
430
728
|
|
|
431
729
|
```tsx
|
|
432
730
|
<Stack direction="row" spacing={2}>
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
731
|
+
<Dropdown>
|
|
732
|
+
<MenuButtonTrigger disabled>비활성 메뉴</MenuButtonTrigger>
|
|
733
|
+
<Menu>
|
|
734
|
+
<MenuItem>접근 불가</MenuItem>
|
|
735
|
+
</Menu>
|
|
736
|
+
</Dropdown>
|
|
439
737
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
</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>
|
|
449
747
|
```
|
|
450
748
|
|
|
451
749
|
## Dropdown Long Menu
|
|
@@ -454,18 +752,25 @@ Apply `maxHeight` and `overflow: 'auto'` to Menu for scrollable lists with many
|
|
|
454
752
|
|
|
455
753
|
```tsx
|
|
456
754
|
<Dropdown>
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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>
|
|
469
774
|
```
|
|
470
775
|
|
|
471
776
|
## Dropdown Custom Styling
|
|
@@ -474,68 +779,98 @@ Customize borders, shadows, border-radius, and gradients through the `sx` prop.
|
|
|
474
779
|
|
|
475
780
|
```tsx
|
|
476
781
|
<Stack direction="row" spacing={2}>
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
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>
|
|
507
831
|
|
|
508
|
-
<Dropdown>
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
</
|
|
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>
|
|
526
853
|
```
|
|
527
854
|
|
|
528
855
|
## Navigation Menu
|
|
529
856
|
|
|
530
857
|
```tsx
|
|
531
858
|
<Dropdown>
|
|
532
|
-
<
|
|
859
|
+
<MenuButtonTrigger variant="plain">Menu</MenuButtonTrigger>
|
|
533
860
|
<Menu>
|
|
534
|
-
<MenuItem component="a" href="/home">
|
|
535
|
-
|
|
536
|
-
|
|
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>
|
|
537
870
|
<ListDivider />
|
|
538
|
-
<MenuItem component="a" href="/contact">
|
|
871
|
+
<MenuItem component="a" href="/contact">
|
|
872
|
+
Contact
|
|
873
|
+
</MenuItem>
|
|
539
874
|
</Menu>
|
|
540
875
|
</Dropdown>
|
|
541
876
|
```
|
|
@@ -546,19 +881,15 @@ Customize borders, shadows, border-radius, and gradients through the `sx` prop.
|
|
|
546
881
|
const [filter, setFilter] = useState('All');
|
|
547
882
|
|
|
548
883
|
<Dropdown>
|
|
549
|
-
<
|
|
550
|
-
variant="outlined"
|
|
551
|
-
startDecorator={<FilterListIcon />}
|
|
552
|
-
endDecorator={<ExpandMoreIcon />}
|
|
553
|
-
>
|
|
884
|
+
<MenuButtonTrigger variant="outlined" startDecorator={<FilterListIcon />} endDecorator={<ExpandMoreIcon />}>
|
|
554
885
|
Filter: {filter}
|
|
555
|
-
</
|
|
886
|
+
</MenuButtonTrigger>
|
|
556
887
|
<Menu>
|
|
557
888
|
<MenuItem onClick={() => setFilter('All')}>All</MenuItem>
|
|
558
889
|
<MenuItem onClick={() => setFilter('Active')}>Active</MenuItem>
|
|
559
890
|
<MenuItem onClick={() => setFilter('Inactive')}>Inactive</MenuItem>
|
|
560
891
|
</Menu>
|
|
561
|
-
</Dropdown
|
|
892
|
+
</Dropdown>;
|
|
562
893
|
```
|
|
563
894
|
|
|
564
895
|
## Controlled State with External Toggle
|
|
@@ -567,7 +898,7 @@ const [filter, setFilter] = useState('All');
|
|
|
567
898
|
const [open, setOpen] = useState(false);
|
|
568
899
|
|
|
569
900
|
<Dropdown open={open} onOpenChange={(event, isOpen) => setOpen(isOpen)}>
|
|
570
|
-
<
|
|
901
|
+
<MenuButtonTrigger>Controlled Menu</MenuButtonTrigger>
|
|
571
902
|
<Menu>
|
|
572
903
|
<MenuItem onClick={() => setOpen(false)}>Option A</MenuItem>
|
|
573
904
|
<MenuItem onClick={() => setOpen(false)}>Option B</MenuItem>
|
|
@@ -583,26 +914,30 @@ const [open, setOpen] = useState(false);
|
|
|
583
914
|
|
|
584
915
|
### Key Props
|
|
585
916
|
|
|
586
|
-
| Prop | Type | Default | Description
|
|
587
|
-
| -------------- | -------------------------------- | ------- |
|
|
588
|
-
| `children` | `ReactNode` | - | Dropdown content (
|
|
589
|
-
| `open` | `boolean` | - | Controlled open state
|
|
590
|
-
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled)
|
|
591
|
-
| `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 |
|
|
592
923
|
|
|
593
|
-
> **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`.
|
|
594
925
|
|
|
595
926
|
## Best Practices
|
|
596
927
|
|
|
597
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.
|
|
598
929
|
|
|
599
930
|
```tsx
|
|
600
|
-
{
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
{
|
|
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
|
+
}
|
|
604
939
|
<Dropdown>
|
|
605
|
-
<
|
|
940
|
+
<MenuButtonTrigger>User</MenuButtonTrigger>
|
|
606
941
|
<Menu>
|
|
607
942
|
<Box sx={{ px: 2, py: 1 }}>
|
|
608
943
|
<Typography level="title-sm">User Name</Typography>
|
|
@@ -611,29 +946,35 @@ const [open, setOpen] = useState(false);
|
|
|
611
946
|
<MenuItem>Settings</MenuItem>
|
|
612
947
|
<MenuItem>Logout</MenuItem>
|
|
613
948
|
</Menu>
|
|
614
|
-
</Dropdown
|
|
949
|
+
</Dropdown>;
|
|
615
950
|
```
|
|
616
951
|
|
|
617
952
|
- **Group related items with ListDivider.** Visually separate logical groups of menu items so users can scan options quickly.
|
|
618
953
|
|
|
619
954
|
```tsx
|
|
620
|
-
{
|
|
955
|
+
{
|
|
956
|
+
/* ✅ Good: Grouped by function */
|
|
957
|
+
}
|
|
621
958
|
<Menu>
|
|
622
959
|
<MenuItem>Edit</MenuItem>
|
|
623
960
|
<MenuItem>Duplicate</MenuItem>
|
|
624
961
|
<ListDivider />
|
|
625
962
|
<MenuItem color="danger">Delete</MenuItem>
|
|
626
|
-
</Menu
|
|
963
|
+
</Menu>;
|
|
627
964
|
```
|
|
628
965
|
|
|
629
966
|
- **Indicate dropdown affordance.** Use `endDecorator={<ExpandMoreIcon />}` on the trigger button so users understand it opens a menu.
|
|
630
967
|
|
|
631
968
|
```tsx
|
|
632
|
-
{
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
{
|
|
636
|
-
|
|
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>;
|
|
637
978
|
```
|
|
638
979
|
|
|
639
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.
|