@ceed/ads 1.29.1 → 1.30.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
- package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
- package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
- package/dist/components/SearchBar/SearchBar.d.ts +21 -0
- package/dist/components/SearchBar/index.d.ts +3 -0
- package/dist/components/data-display/Badge.md +39 -71
- package/dist/components/data-display/DataTable.md +1 -1
- package/dist/components/data-display/InfoSign.md +98 -74
- package/dist/components/data-display/Typography.md +97 -363
- package/dist/components/feedback/Dialog.md +62 -76
- package/dist/components/feedback/Modal.md +44 -259
- package/dist/components/feedback/llms.txt +0 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/components/inputs/Autocomplete.md +107 -356
- package/dist/components/inputs/ButtonGroup.md +106 -115
- package/dist/components/inputs/Calendar.md +459 -98
- package/dist/components/inputs/CurrencyInput.md +5 -183
- package/dist/components/inputs/DatePicker.md +431 -108
- package/dist/components/inputs/DateRangePicker.md +492 -131
- package/dist/components/inputs/FilterMenu.md +19 -169
- package/dist/components/inputs/FilterableCheckboxGroup.md +23 -123
- package/dist/components/inputs/IconButton.md +88 -137
- package/dist/components/inputs/Input.md +0 -5
- package/dist/components/inputs/MonthPicker.md +422 -95
- package/dist/components/inputs/MonthRangePicker.md +466 -89
- package/dist/components/inputs/PercentageInput.md +16 -185
- package/dist/components/inputs/RadioButton.md +35 -163
- package/dist/components/inputs/RadioTileGroup.md +61 -150
- package/dist/components/inputs/SearchBar.md +44 -0
- package/dist/components/inputs/Select.md +326 -222
- package/dist/components/inputs/Switch.md +376 -136
- package/dist/components/inputs/Textarea.md +10 -213
- package/dist/components/inputs/Uploader/Uploader.md +66 -145
- package/dist/components/inputs/llms.txt +1 -3
- package/dist/components/navigation/Breadcrumbs.md +322 -80
- package/dist/components/navigation/Dropdown.md +221 -92
- package/dist/components/navigation/IconMenuButton.md +502 -40
- package/dist/components/navigation/InsetDrawer.md +738 -68
- package/dist/components/navigation/Link.md +298 -39
- package/dist/components/navigation/Menu.md +285 -92
- package/dist/components/navigation/MenuButton.md +448 -55
- package/dist/components/navigation/Pagination.md +338 -47
- package/dist/components/navigation/ProfileMenu.md +268 -45
- package/dist/components/navigation/Stepper.md +28 -160
- package/dist/components/navigation/Tabs.md +316 -57
- package/dist/components/surfaces/Sheet.md +334 -151
- package/dist/index.browser.js +15 -13
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +289 -288
- package/dist/index.d.ts +1 -1
- package/dist/index.js +426 -369
- package/dist/llms.txt +1 -8
- package/framer/index.js +1 -1
- package/package.json +16 -15
- package/dist/chunks/rehype-accent-FZRUD7VI.js +0 -39
- package/dist/components/feedback/CircularProgress.md +0 -257
- package/dist/components/feedback/Skeleton.md +0 -280
- package/dist/components/inputs/FormControl.md +0 -361
- package/dist/components/inputs/RadioList.md +0 -241
- package/dist/components/inputs/Slider.md +0 -334
- package/dist/guides/ThemeProvider.md +0 -116
- package/dist/guides/llms.txt +0 -9
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
InsetDrawer is a slide-out panel that appears from any edge of the screen, providing a secondary space for navigation, filters, settings, or detailed content without leaving the current page. Unlike
|
|
6
|
-
|
|
7
|
-
It is commonly used for mobile navigation menus, filter panels, configuration sidebars, and detail views. The component supports four anchor positions (left, right, top, bottom) and three size presets, making it adaptable to a wide range of layout requirements.
|
|
5
|
+
InsetDrawer is a slide-out panel that appears from any edge of the screen, providing a secondary space for navigation, filters, settings, or detailed content without leaving the current page. Unlike Modal which overlays the entire screen, InsetDrawer slides in from an edge and can optionally allow interaction with the main content behind it. It's commonly used for mobile navigation menus, filter panels, and configuration sidebars.
|
|
8
6
|
|
|
9
7
|
```tsx
|
|
10
8
|
<Box sx={{
|
|
@@ -140,6 +138,15 @@ Booking options
|
|
|
140
138
|
| ---------------------------- | ----------- | ------- |
|
|
141
139
|
| Controls resolved at runtime | — | — |
|
|
142
140
|
|
|
141
|
+
> ⚠️ **Usage Warning** ⚠️
|
|
142
|
+
>
|
|
143
|
+
> Consider these factors before using InsetDrawer:
|
|
144
|
+
>
|
|
145
|
+
> - **Mobile-first design**: InsetDrawer works best on mobile; consider persistent sidebars on desktop
|
|
146
|
+
> - **Content hierarchy**: Don't hide critical content in drawers that users need frequently
|
|
147
|
+
> - **Anchor position**: Use `left` for navigation, `right` for details/filters, `bottom` for actions
|
|
148
|
+
> - **Accessibility**: Ensure proper focus management and keyboard navigation
|
|
149
|
+
|
|
143
150
|
## Usage
|
|
144
151
|
|
|
145
152
|
```tsx
|
|
@@ -165,9 +172,145 @@ function FilterDrawer() {
|
|
|
165
172
|
}
|
|
166
173
|
```
|
|
167
174
|
|
|
168
|
-
##
|
|
175
|
+
## Examples
|
|
176
|
+
|
|
177
|
+
### Playground
|
|
178
|
+
|
|
179
|
+
Interactive example with controls for anchor position and size.
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<Box sx={{
|
|
183
|
+
display: 'flex',
|
|
184
|
+
flexDirection: 'column',
|
|
185
|
+
gap: 2
|
|
186
|
+
}}>
|
|
187
|
+
<Button variant="outlined" color="neutral" startDecorator={<FilterListIcon />} onClick={() => setOpen(true)}>
|
|
188
|
+
Open Drawer
|
|
189
|
+
</Button>
|
|
190
|
+
|
|
191
|
+
<InsetDrawer {...args} open={open} onClose={() => setOpen(false)}>
|
|
192
|
+
<Sheet sx={{
|
|
193
|
+
borderRadius: 'md',
|
|
194
|
+
p: 2,
|
|
195
|
+
display: 'flex',
|
|
196
|
+
flexDirection: 'column',
|
|
197
|
+
gap: 2,
|
|
198
|
+
height: '100%',
|
|
199
|
+
overflow: 'auto'
|
|
200
|
+
}}>
|
|
201
|
+
<DialogTitle>Filters</DialogTitle>
|
|
202
|
+
<ModalClose />
|
|
203
|
+
<Divider sx={{
|
|
204
|
+
mt: 'auto'
|
|
205
|
+
}} />
|
|
206
|
+
<DialogContent>
|
|
207
|
+
<FormControl>
|
|
208
|
+
<FormLabel sx={{
|
|
209
|
+
typography: 'title-md',
|
|
210
|
+
fontWeight: 'bold'
|
|
211
|
+
}}>Property type</FormLabel>
|
|
212
|
+
<RadioGroup>
|
|
213
|
+
<Box sx={{
|
|
214
|
+
display: 'grid',
|
|
215
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
|
|
216
|
+
gap: 1.5
|
|
217
|
+
}}>
|
|
218
|
+
{[{
|
|
219
|
+
name: 'House',
|
|
220
|
+
icon: <HomeRoundedIcon />
|
|
221
|
+
}, {
|
|
222
|
+
name: 'Apartment',
|
|
223
|
+
icon: <ApartmentRoundedIcon />
|
|
224
|
+
}, {
|
|
225
|
+
name: 'Guesthouse',
|
|
226
|
+
icon: <MeetingRoomRoundedIcon />
|
|
227
|
+
}, {
|
|
228
|
+
name: 'Hotel',
|
|
229
|
+
icon: <HotelRoundedIcon />
|
|
230
|
+
}].map(item => <Card key={item.name} sx={{
|
|
231
|
+
boxShadow: 'none',
|
|
232
|
+
'&:hover': {
|
|
233
|
+
bgcolor: 'background.level1'
|
|
234
|
+
}
|
|
235
|
+
}}>
|
|
236
|
+
<CardContent>
|
|
237
|
+
{item.icon}
|
|
238
|
+
<Typography level="title-md">{item.name}</Typography>
|
|
239
|
+
</CardContent>
|
|
240
|
+
<Radio disableIcon overlay variant="outlined" color="neutral" value={item.name} sx={{
|
|
241
|
+
mt: -2
|
|
242
|
+
}} slotProps={{
|
|
243
|
+
action: {
|
|
244
|
+
sx: {
|
|
245
|
+
'&:hover': {
|
|
246
|
+
bgcolor: 'transparent'
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}} />
|
|
251
|
+
</Card>)}
|
|
252
|
+
</Box>
|
|
253
|
+
</RadioGroup>
|
|
254
|
+
</FormControl>
|
|
255
|
+
|
|
256
|
+
<Typography level="title-md" fontWeight="bold" sx={{
|
|
257
|
+
mt: 2
|
|
258
|
+
}}>
|
|
259
|
+
Booking options
|
|
260
|
+
</Typography>
|
|
261
|
+
<FormControl orientation="horizontal">
|
|
262
|
+
<Box sx={{
|
|
263
|
+
flex: 1,
|
|
264
|
+
pr: 1
|
|
265
|
+
}}>
|
|
266
|
+
<FormLabel sx={{
|
|
267
|
+
typography: 'title-sm'
|
|
268
|
+
}}>Instant booking</FormLabel>
|
|
269
|
+
<FormHelperText sx={{
|
|
270
|
+
typography: 'body-sm'
|
|
271
|
+
}}>
|
|
272
|
+
Listings that you can book without waiting for host approval.
|
|
273
|
+
</FormHelperText>
|
|
274
|
+
</Box>
|
|
275
|
+
<Switch />
|
|
276
|
+
</FormControl>
|
|
277
|
+
|
|
278
|
+
<FormControl orientation="horizontal">
|
|
279
|
+
<Box sx={{
|
|
280
|
+
flex: 1,
|
|
281
|
+
mt: 1,
|
|
282
|
+
mr: 1
|
|
283
|
+
}}>
|
|
284
|
+
<FormLabel sx={{
|
|
285
|
+
typography: 'title-sm'
|
|
286
|
+
}}>Self check-in</FormLabel>
|
|
287
|
+
<FormHelperText sx={{
|
|
288
|
+
typography: 'body-sm'
|
|
289
|
+
}}>
|
|
290
|
+
Easy access to the property when you arrive.
|
|
291
|
+
</FormHelperText>
|
|
292
|
+
</Box>
|
|
293
|
+
<Switch />
|
|
294
|
+
</FormControl>
|
|
295
|
+
</DialogContent>
|
|
296
|
+
|
|
297
|
+
<Divider sx={{
|
|
298
|
+
mt: 'auto'
|
|
299
|
+
}} />
|
|
300
|
+
<Stack direction="row" justifyContent="space-between" useFlexGap spacing={1}>
|
|
301
|
+
<Button variant="outlined" color="neutral">
|
|
302
|
+
Clear
|
|
303
|
+
</Button>
|
|
304
|
+
<Button onClick={() => setOpen(false)}>Show 165 properties</Button>
|
|
305
|
+
</Stack>
|
|
306
|
+
</Sheet>
|
|
307
|
+
</InsetDrawer>
|
|
308
|
+
</Box>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Anchors
|
|
169
312
|
|
|
170
|
-
|
|
313
|
+
Different anchor positions: left, right, top, bottom.
|
|
171
314
|
|
|
172
315
|
```tsx
|
|
173
316
|
<Box sx={{
|
|
@@ -214,9 +357,9 @@ InsetDrawer supports four anchor positions. Use `left` for navigation, `right` f
|
|
|
214
357
|
</Box>
|
|
215
358
|
```
|
|
216
359
|
|
|
217
|
-
|
|
360
|
+
### Sizes
|
|
218
361
|
|
|
219
|
-
|
|
362
|
+
Available size options: small, medium, large.
|
|
220
363
|
|
|
221
364
|
```tsx
|
|
222
365
|
<Box sx={{
|
|
@@ -260,9 +403,9 @@ Three size presets are available: `sm`, `md`, and `lg`. The size controls width
|
|
|
260
403
|
</Box>
|
|
261
404
|
```
|
|
262
405
|
|
|
263
|
-
|
|
406
|
+
### Navigation Menu
|
|
264
407
|
|
|
265
|
-
A
|
|
408
|
+
A mobile navigation menu using InsetDrawer.
|
|
266
409
|
|
|
267
410
|
```tsx
|
|
268
411
|
<Box>
|
|
@@ -294,9 +437,9 @@ A typical mobile navigation menu pattern using InsetDrawer anchored to the left
|
|
|
294
437
|
</Box>
|
|
295
438
|
```
|
|
296
439
|
|
|
297
|
-
|
|
440
|
+
### Filter Panel
|
|
298
441
|
|
|
299
|
-
A filter panel
|
|
442
|
+
A filter panel with form controls sliding from the right.
|
|
300
443
|
|
|
301
444
|
```tsx
|
|
302
445
|
<Box>
|
|
@@ -435,8 +578,235 @@ Booking options
|
|
|
435
578
|
</Box>
|
|
436
579
|
```
|
|
437
580
|
|
|
581
|
+
## When to Use
|
|
582
|
+
|
|
583
|
+
### ✅ Good Use Cases
|
|
584
|
+
|
|
585
|
+
- **Mobile navigation**: Hamburger menu that slides in from the left
|
|
586
|
+
- **Filter panels**: Complex filters that would clutter the main UI
|
|
587
|
+
- **Detail views**: Additional information without navigating away
|
|
588
|
+
- **Settings panels**: Quick access to preferences or configurations
|
|
589
|
+
- **Shopping cart**: E-commerce cart preview on the side
|
|
590
|
+
- **Help/Support**: Context-sensitive help or chat interfaces
|
|
591
|
+
|
|
592
|
+
### ❌ When Not to Use
|
|
593
|
+
|
|
594
|
+
- **Critical actions**: Don't hide essential features in drawers
|
|
595
|
+
- **Primary navigation on desktop**: Consider a persistent sidebar instead
|
|
596
|
+
- **Simple choices**: Use Select or Dropdown for simple selections
|
|
597
|
+
- **Confirmations**: Use Dialog for confirmation messages
|
|
598
|
+
- **Notifications**: Use Toast or Alert for feedback messages
|
|
599
|
+
- **Large forms**: Complex forms should have their own page
|
|
600
|
+
|
|
438
601
|
## Common Use Cases
|
|
439
602
|
|
|
603
|
+
### Mobile Navigation Drawer
|
|
604
|
+
|
|
605
|
+
```tsx
|
|
606
|
+
function NavigationDrawer({ open, onClose, currentPath }) {
|
|
607
|
+
const navItems = [
|
|
608
|
+
{ label: 'Dashboard', path: '/dashboard', icon: <DashboardIcon /> },
|
|
609
|
+
{ label: 'Users', path: '/users', icon: <PeopleIcon /> },
|
|
610
|
+
{ label: 'Settings', path: '/settings', icon: <SettingsIcon /> },
|
|
611
|
+
];
|
|
612
|
+
|
|
613
|
+
return (
|
|
614
|
+
<InsetDrawer open={open} onClose={onClose} anchor="left">
|
|
615
|
+
<Sheet sx={{ width: 280, height: '100%', p: 2 }}>
|
|
616
|
+
<Stack gap={1}>
|
|
617
|
+
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
618
|
+
<Avatar src="/logo.png" />
|
|
619
|
+
<Typography level="title-lg" sx={{ ml: 1 }}>My App</Typography>
|
|
620
|
+
</Box>
|
|
621
|
+
<Divider />
|
|
622
|
+
<List>
|
|
623
|
+
{navItems.map((item) => (
|
|
624
|
+
<ListItem key={item.path}>
|
|
625
|
+
<ListItemButton
|
|
626
|
+
selected={currentPath === item.path}
|
|
627
|
+
onClick={() => {
|
|
628
|
+
navigate(item.path);
|
|
629
|
+
onClose();
|
|
630
|
+
}}
|
|
631
|
+
>
|
|
632
|
+
<ListItemDecorator>{item.icon}</ListItemDecorator>
|
|
633
|
+
{item.label}
|
|
634
|
+
</ListItemButton>
|
|
635
|
+
</ListItem>
|
|
636
|
+
))}
|
|
637
|
+
</List>
|
|
638
|
+
</Stack>
|
|
639
|
+
</Sheet>
|
|
640
|
+
</InsetDrawer>
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Filter Panel
|
|
646
|
+
|
|
647
|
+
```tsx
|
|
648
|
+
function ProductFilters({ open, onClose, filters, onApply }) {
|
|
649
|
+
const [localFilters, setLocalFilters] = useState(filters);
|
|
650
|
+
|
|
651
|
+
const handleApply = () => {
|
|
652
|
+
onApply(localFilters);
|
|
653
|
+
onClose();
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const handleClear = () => {
|
|
657
|
+
setLocalFilters({});
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
return (
|
|
661
|
+
<InsetDrawer open={open} onClose={onClose} anchor="right" size="md">
|
|
662
|
+
<Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
663
|
+
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
664
|
+
<DialogTitle>Filters</DialogTitle>
|
|
665
|
+
<ModalClose />
|
|
666
|
+
</Box>
|
|
667
|
+
|
|
668
|
+
<DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
|
669
|
+
<Stack gap={3}>
|
|
670
|
+
<FormControl>
|
|
671
|
+
<FormLabel>Category</FormLabel>
|
|
672
|
+
<Select
|
|
673
|
+
value={localFilters.category || ''}
|
|
674
|
+
onChange={(_, value) =>
|
|
675
|
+
setLocalFilters({ ...localFilters, category: value })
|
|
676
|
+
}
|
|
677
|
+
>
|
|
678
|
+
<Option value="electronics">Electronics</Option>
|
|
679
|
+
<Option value="clothing">Clothing</Option>
|
|
680
|
+
<Option value="books">Books</Option>
|
|
681
|
+
</Select>
|
|
682
|
+
</FormControl>
|
|
683
|
+
|
|
684
|
+
<FormControl>
|
|
685
|
+
<FormLabel>Price Range</FormLabel>
|
|
686
|
+
<Stack direction="row" gap={1}>
|
|
687
|
+
<Input
|
|
688
|
+
type="number"
|
|
689
|
+
placeholder="Min"
|
|
690
|
+
value={localFilters.minPrice || ''}
|
|
691
|
+
onChange={(e) =>
|
|
692
|
+
setLocalFilters({ ...localFilters, minPrice: e.target.value })
|
|
693
|
+
}
|
|
694
|
+
/>
|
|
695
|
+
<Input
|
|
696
|
+
type="number"
|
|
697
|
+
placeholder="Max"
|
|
698
|
+
value={localFilters.maxPrice || ''}
|
|
699
|
+
onChange={(e) =>
|
|
700
|
+
setLocalFilters({ ...localFilters, maxPrice: e.target.value })
|
|
701
|
+
}
|
|
702
|
+
/>
|
|
703
|
+
</Stack>
|
|
704
|
+
</FormControl>
|
|
705
|
+
|
|
706
|
+
<FormControl>
|
|
707
|
+
<FormLabel>Rating</FormLabel>
|
|
708
|
+
<RadioGroup
|
|
709
|
+
value={localFilters.rating || ''}
|
|
710
|
+
onChange={(e) =>
|
|
711
|
+
setLocalFilters({ ...localFilters, rating: e.target.value })
|
|
712
|
+
}
|
|
713
|
+
>
|
|
714
|
+
{[4, 3, 2, 1].map((rating) => (
|
|
715
|
+
<Radio
|
|
716
|
+
key={rating}
|
|
717
|
+
value={String(rating)}
|
|
718
|
+
label={`${rating}+ stars`}
|
|
719
|
+
/>
|
|
720
|
+
))}
|
|
721
|
+
</RadioGroup>
|
|
722
|
+
</FormControl>
|
|
723
|
+
</Stack>
|
|
724
|
+
</DialogContent>
|
|
725
|
+
|
|
726
|
+
<Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
727
|
+
<Stack direction="row" gap={1} justifyContent="space-between">
|
|
728
|
+
<Button variant="outlined" color="neutral" onClick={handleClear}>
|
|
729
|
+
Clear All
|
|
730
|
+
</Button>
|
|
731
|
+
<Button onClick={handleApply}>Apply Filters</Button>
|
|
732
|
+
</Stack>
|
|
733
|
+
</Box>
|
|
734
|
+
</Sheet>
|
|
735
|
+
</InsetDrawer>
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Shopping Cart Drawer
|
|
741
|
+
|
|
742
|
+
```tsx
|
|
743
|
+
function CartDrawer({ open, onClose, items, onCheckout }) {
|
|
744
|
+
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
745
|
+
|
|
746
|
+
return (
|
|
747
|
+
<InsetDrawer open={open} onClose={onClose} anchor="right" size="lg">
|
|
748
|
+
<Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
749
|
+
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
750
|
+
<DialogTitle>Shopping Cart ({items.length})</DialogTitle>
|
|
751
|
+
<ModalClose />
|
|
752
|
+
</Box>
|
|
753
|
+
|
|
754
|
+
<DialogContent sx={{ flex: 1, overflow: 'auto', p: 0 }}>
|
|
755
|
+
{items.length === 0 ? (
|
|
756
|
+
<Box sx={{ p: 4, textAlign: 'center' }}>
|
|
757
|
+
<ShoppingCartIcon sx={{ fontSize: 48, color: 'neutral.400' }} />
|
|
758
|
+
<Typography level="body-lg" sx={{ mt: 2 }}>
|
|
759
|
+
Your cart is empty
|
|
760
|
+
</Typography>
|
|
761
|
+
</Box>
|
|
762
|
+
) : (
|
|
763
|
+
<List>
|
|
764
|
+
{items.map((item) => (
|
|
765
|
+
<ListItem key={item.id}>
|
|
766
|
+
<ListItemDecorator>
|
|
767
|
+
<img
|
|
768
|
+
src={item.image}
|
|
769
|
+
alt={item.name}
|
|
770
|
+
style={{ width: 60, height: 60, objectFit: 'cover' }}
|
|
771
|
+
/>
|
|
772
|
+
</ListItemDecorator>
|
|
773
|
+
<ListItemContent>
|
|
774
|
+
<Typography level="title-sm">{item.name}</Typography>
|
|
775
|
+
<Typography level="body-sm">
|
|
776
|
+
${item.price} x {item.quantity}
|
|
777
|
+
</Typography>
|
|
778
|
+
</ListItemContent>
|
|
779
|
+
<IconButton size="sm" color="danger">
|
|
780
|
+
<DeleteIcon />
|
|
781
|
+
</IconButton>
|
|
782
|
+
</ListItem>
|
|
783
|
+
))}
|
|
784
|
+
</List>
|
|
785
|
+
)}
|
|
786
|
+
</DialogContent>
|
|
787
|
+
|
|
788
|
+
{items.length > 0 && (
|
|
789
|
+
<Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
790
|
+
<Stack gap={2}>
|
|
791
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
792
|
+
<Typography level="title-md">Total</Typography>
|
|
793
|
+
<Typography level="title-md">${total.toFixed(2)}</Typography>
|
|
794
|
+
</Box>
|
|
795
|
+
<Button fullWidth onClick={onCheckout}>
|
|
796
|
+
Proceed to Checkout
|
|
797
|
+
</Button>
|
|
798
|
+
<Button variant="outlined" fullWidth onClick={onClose}>
|
|
799
|
+
Continue Shopping
|
|
800
|
+
</Button>
|
|
801
|
+
</Stack>
|
|
802
|
+
</Box>
|
|
803
|
+
)}
|
|
804
|
+
</Sheet>
|
|
805
|
+
</InsetDrawer>
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
440
810
|
### Settings Drawer
|
|
441
811
|
|
|
442
812
|
```tsx
|
|
@@ -453,6 +823,7 @@ function SettingsDrawer({ open, onClose }) {
|
|
|
453
823
|
<DialogTitle>Settings</DialogTitle>
|
|
454
824
|
<ModalClose />
|
|
455
825
|
<Divider sx={{ my: 2 }} />
|
|
826
|
+
|
|
456
827
|
<DialogContent>
|
|
457
828
|
<Stack gap={3}>
|
|
458
829
|
<FormControl orientation="horizontal">
|
|
@@ -467,6 +838,7 @@ function SettingsDrawer({ open, onClose }) {
|
|
|
467
838
|
}
|
|
468
839
|
/>
|
|
469
840
|
</FormControl>
|
|
841
|
+
|
|
470
842
|
<FormControl orientation="horizontal">
|
|
471
843
|
<Box sx={{ flex: 1 }}>
|
|
472
844
|
<FormLabel>Dark Mode</FormLabel>
|
|
@@ -479,35 +851,21 @@ function SettingsDrawer({ open, onClose }) {
|
|
|
479
851
|
}
|
|
480
852
|
/>
|
|
481
853
|
</FormControl>
|
|
482
|
-
</Stack>
|
|
483
|
-
</DialogContent>
|
|
484
|
-
</Sheet>
|
|
485
|
-
</InsetDrawer>
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### Detail View Drawer
|
|
491
|
-
|
|
492
|
-
```tsx
|
|
493
|
-
function DetailDrawer({ open, onClose, itemId }) {
|
|
494
|
-
const [data, setData] = useState(null);
|
|
495
|
-
|
|
496
|
-
useEffect(() => {
|
|
497
|
-
if (open && itemId) {
|
|
498
|
-
fetchItemDetails(itemId).then(setData);
|
|
499
|
-
}
|
|
500
|
-
}, [open, itemId]);
|
|
501
854
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
855
|
+
<FormControl>
|
|
856
|
+
<FormLabel>Language</FormLabel>
|
|
857
|
+
<Select
|
|
858
|
+
value={settings.language}
|
|
859
|
+
onChange={(_, value) =>
|
|
860
|
+
setSettings({ ...settings, language: value || 'en' })
|
|
861
|
+
}
|
|
862
|
+
>
|
|
863
|
+
<Option value="en">English</Option>
|
|
864
|
+
<Option value="ko">한국어</Option>
|
|
865
|
+
<Option value="ja">日本語</Option>
|
|
866
|
+
</Select>
|
|
867
|
+
</FormControl>
|
|
868
|
+
</Stack>
|
|
511
869
|
</DialogContent>
|
|
512
870
|
</Sheet>
|
|
513
871
|
</InsetDrawer>
|
|
@@ -522,18 +880,32 @@ function ActionSheet({ open, onClose, onAction }) {
|
|
|
522
880
|
const actions = [
|
|
523
881
|
{ id: 'edit', label: 'Edit', icon: <EditIcon /> },
|
|
524
882
|
{ id: 'share', label: 'Share', icon: <ShareIcon /> },
|
|
883
|
+
{ id: 'duplicate', label: 'Duplicate', icon: <ContentCopyIcon /> },
|
|
525
884
|
{ id: 'delete', label: 'Delete', icon: <DeleteIcon />, color: 'danger' },
|
|
526
885
|
];
|
|
527
886
|
|
|
528
887
|
return (
|
|
529
888
|
<InsetDrawer open={open} onClose={onClose} anchor="bottom">
|
|
530
889
|
<Sheet sx={{ borderRadius: 'lg lg 0 0', p: 2 }}>
|
|
890
|
+
<Box
|
|
891
|
+
sx={{
|
|
892
|
+
width: 40,
|
|
893
|
+
height: 4,
|
|
894
|
+
bgcolor: 'neutral.300',
|
|
895
|
+
borderRadius: 'xl',
|
|
896
|
+
mx: 'auto',
|
|
897
|
+
mb: 2,
|
|
898
|
+
}}
|
|
899
|
+
/>
|
|
531
900
|
<List>
|
|
532
901
|
{actions.map((action) => (
|
|
533
902
|
<ListItem key={action.id}>
|
|
534
903
|
<ListItemButton
|
|
535
904
|
color={action.color}
|
|
536
|
-
onClick={() => {
|
|
905
|
+
onClick={() => {
|
|
906
|
+
onAction(action.id);
|
|
907
|
+
onClose();
|
|
908
|
+
}}
|
|
537
909
|
>
|
|
538
910
|
<ListItemDecorator>{action.icon}</ListItemDecorator>
|
|
539
911
|
{action.label}
|
|
@@ -541,7 +913,13 @@ function ActionSheet({ open, onClose, onAction }) {
|
|
|
541
913
|
</ListItem>
|
|
542
914
|
))}
|
|
543
915
|
</List>
|
|
544
|
-
<Button
|
|
916
|
+
<Button
|
|
917
|
+
variant="soft"
|
|
918
|
+
color="neutral"
|
|
919
|
+
fullWidth
|
|
920
|
+
sx={{ mt: 1 }}
|
|
921
|
+
onClick={onClose}
|
|
922
|
+
>
|
|
545
923
|
Cancel
|
|
546
924
|
</Button>
|
|
547
925
|
</Sheet>
|
|
@@ -550,61 +928,353 @@ function ActionSheet({ open, onClose, onAction }) {
|
|
|
550
928
|
}
|
|
551
929
|
```
|
|
552
930
|
|
|
931
|
+
### Help/Support Panel
|
|
932
|
+
|
|
933
|
+
```tsx
|
|
934
|
+
function HelpDrawer({ open, onClose }) {
|
|
935
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
936
|
+
|
|
937
|
+
const faqItems = [
|
|
938
|
+
{ question: 'How do I reset my password?', answer: '...' },
|
|
939
|
+
{ question: 'How do I change my email?', answer: '...' },
|
|
940
|
+
{ question: 'How do I delete my account?', answer: '...' },
|
|
941
|
+
];
|
|
942
|
+
|
|
943
|
+
return (
|
|
944
|
+
<InsetDrawer open={open} onClose={onClose} anchor="right" size="lg">
|
|
945
|
+
<Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
946
|
+
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
947
|
+
<DialogTitle>Help Center</DialogTitle>
|
|
948
|
+
<ModalClose />
|
|
949
|
+
<Input
|
|
950
|
+
placeholder="Search for help..."
|
|
951
|
+
startDecorator={<SearchIcon />}
|
|
952
|
+
value={searchQuery}
|
|
953
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
954
|
+
sx={{ mt: 2 }}
|
|
955
|
+
/>
|
|
956
|
+
</Box>
|
|
957
|
+
|
|
958
|
+
<DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
|
959
|
+
<Typography level="title-md" sx={{ mb: 2 }}>
|
|
960
|
+
Frequently Asked Questions
|
|
961
|
+
</Typography>
|
|
962
|
+
<Accordion>
|
|
963
|
+
{faqItems
|
|
964
|
+
.filter((item) =>
|
|
965
|
+
item.question.toLowerCase().includes(searchQuery.toLowerCase())
|
|
966
|
+
)
|
|
967
|
+
.map((item, index) => (
|
|
968
|
+
<AccordionItem key={index}>
|
|
969
|
+
<AccordionSummary>{item.question}</AccordionSummary>
|
|
970
|
+
<AccordionDetails>{item.answer}</AccordionDetails>
|
|
971
|
+
</AccordionItem>
|
|
972
|
+
))}
|
|
973
|
+
</Accordion>
|
|
974
|
+
</DialogContent>
|
|
975
|
+
|
|
976
|
+
<Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
977
|
+
<Typography level="body-sm" sx={{ mb: 1 }}>
|
|
978
|
+
Can't find what you're looking for?
|
|
979
|
+
</Typography>
|
|
980
|
+
<Button fullWidth startDecorator={<ChatIcon />}>
|
|
981
|
+
Contact Support
|
|
982
|
+
</Button>
|
|
983
|
+
</Box>
|
|
984
|
+
</Sheet>
|
|
985
|
+
</InsetDrawer>
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
## Props and Customization
|
|
991
|
+
|
|
992
|
+
### Key Props
|
|
993
|
+
|
|
994
|
+
| Prop | Type | Default | Description |
|
|
995
|
+
| ---------- | ---------------------------------------- | -------- | ------------------------------------------------ |
|
|
996
|
+
| `open` | `boolean` | `false` | Controls whether the drawer is visible |
|
|
997
|
+
| `onClose` | `() => void` | - | Callback fired when the drawer requests to close |
|
|
998
|
+
| `anchor` | `'left' \| 'right' \| 'top' \| 'bottom'` | `'left'` | Edge from which the drawer slides in |
|
|
999
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the drawer |
|
|
1000
|
+
| `children` | `ReactNode` | - | Content to render inside the drawer |
|
|
1001
|
+
|
|
1002
|
+
### Anchor Positions
|
|
1003
|
+
|
|
1004
|
+
```tsx
|
|
1005
|
+
// Left drawer (navigation menus)
|
|
1006
|
+
<InsetDrawer anchor="left" open={open}>
|
|
1007
|
+
<Sheet sx={{ width: 280 }}>Navigation</Sheet>
|
|
1008
|
+
</InsetDrawer>
|
|
1009
|
+
|
|
1010
|
+
// Right drawer (details, filters)
|
|
1011
|
+
<InsetDrawer anchor="right" open={open}>
|
|
1012
|
+
<Sheet sx={{ width: 400 }}>Details</Sheet>
|
|
1013
|
+
</InsetDrawer>
|
|
1014
|
+
|
|
1015
|
+
// Top drawer (search, notifications)
|
|
1016
|
+
<InsetDrawer anchor="top" open={open}>
|
|
1017
|
+
<Sheet sx={{ height: 200 }}>Search</Sheet>
|
|
1018
|
+
</InsetDrawer>
|
|
1019
|
+
|
|
1020
|
+
// Bottom drawer (mobile actions)
|
|
1021
|
+
<InsetDrawer anchor="bottom" open={open}>
|
|
1022
|
+
<Sheet sx={{ maxHeight: '80vh' }}>Actions</Sheet>
|
|
1023
|
+
</InsetDrawer>
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
### Size Options
|
|
1027
|
+
|
|
1028
|
+
The `size` prop controls the width (for left/right anchors) or height (for top/bottom anchors):
|
|
1029
|
+
|
|
1030
|
+
```tsx
|
|
1031
|
+
// Small drawer (~280px)
|
|
1032
|
+
<InsetDrawer size="sm" anchor="right" />
|
|
1033
|
+
|
|
1034
|
+
// Medium drawer (~400px) - default
|
|
1035
|
+
<InsetDrawer size="md" anchor="right" />
|
|
1036
|
+
|
|
1037
|
+
// Large drawer (~600px)
|
|
1038
|
+
<InsetDrawer size="lg" anchor="right" />
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
### Custom Sizing
|
|
1042
|
+
|
|
1043
|
+
For more control, use `Sheet` with custom styles:
|
|
1044
|
+
|
|
1045
|
+
```tsx
|
|
1046
|
+
<InsetDrawer open={open} anchor="right">
|
|
1047
|
+
<Sheet sx={{ width: { xs: '100vw', sm: 450 }, height: '100%' }}>
|
|
1048
|
+
Content
|
|
1049
|
+
</Sheet>
|
|
1050
|
+
</InsetDrawer>
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### Drawer Content Structure
|
|
1054
|
+
|
|
1055
|
+
Use these components for consistent drawer layout:
|
|
1056
|
+
|
|
1057
|
+
```tsx
|
|
1058
|
+
<InsetDrawer open={open}>
|
|
1059
|
+
<Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
1060
|
+
{/* Header */}
|
|
1061
|
+
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
1062
|
+
<DialogTitle>Drawer Title</DialogTitle>
|
|
1063
|
+
<ModalClose />
|
|
1064
|
+
</Box>
|
|
1065
|
+
|
|
1066
|
+
{/* Scrollable Content */}
|
|
1067
|
+
<DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
|
1068
|
+
{/* Main content */}
|
|
1069
|
+
</DialogContent>
|
|
1070
|
+
|
|
1071
|
+
{/* Footer */}
|
|
1072
|
+
<Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
1073
|
+
<Stack direction="row" gap={1}>
|
|
1074
|
+
<Button variant="outlined">Cancel</Button>
|
|
1075
|
+
<Button>Save</Button>
|
|
1076
|
+
</Stack>
|
|
1077
|
+
</Box>
|
|
1078
|
+
</Sheet>
|
|
1079
|
+
</InsetDrawer>
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
## Accessibility
|
|
1083
|
+
|
|
1084
|
+
InsetDrawer includes built-in accessibility features:
|
|
1085
|
+
|
|
1086
|
+
### ARIA Attributes
|
|
1087
|
+
|
|
1088
|
+
- Drawer container has appropriate `role="dialog"` or `role="presentation"`
|
|
1089
|
+
- Focus is trapped within the drawer when open
|
|
1090
|
+
- Proper `aria-modal` attribute when modal
|
|
1091
|
+
|
|
1092
|
+
### Keyboard Navigation
|
|
1093
|
+
|
|
1094
|
+
- **Escape**: Close the drawer
|
|
1095
|
+
- **Tab**: Navigate between focusable elements within drawer
|
|
1096
|
+
- **Shift + Tab**: Navigate backwards
|
|
1097
|
+
|
|
1098
|
+
### Focus Management
|
|
1099
|
+
|
|
1100
|
+
```tsx
|
|
1101
|
+
// Focus automatically moves to drawer content when opened
|
|
1102
|
+
<InsetDrawer
|
|
1103
|
+
open={open}
|
|
1104
|
+
onClose={onClose}
|
|
1105
|
+
// Focus is trapped within drawer
|
|
1106
|
+
>
|
|
1107
|
+
<Sheet>
|
|
1108
|
+
{/* First focusable element receives focus */}
|
|
1109
|
+
<Input autoFocus placeholder="Search..." />
|
|
1110
|
+
</Sheet>
|
|
1111
|
+
</InsetDrawer>
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### Screen Reader Support
|
|
1115
|
+
|
|
1116
|
+
```tsx
|
|
1117
|
+
// Provide descriptive title for screen readers
|
|
1118
|
+
<InsetDrawer open={open}>
|
|
1119
|
+
<Sheet role="dialog" aria-labelledby="drawer-title">
|
|
1120
|
+
<DialogTitle id="drawer-title">Filters</DialogTitle>
|
|
1121
|
+
{/* Content */}
|
|
1122
|
+
</Sheet>
|
|
1123
|
+
</InsetDrawer>
|
|
1124
|
+
```
|
|
1125
|
+
|
|
553
1126
|
## Best Practices
|
|
554
1127
|
|
|
555
|
-
|
|
1128
|
+
### ✅ Do
|
|
1129
|
+
|
|
1130
|
+
1. **Use appropriate anchor positions**: Match drawer position to content type
|
|
556
1131
|
|
|
557
1132
|
```tsx
|
|
558
|
-
// ✅ Good: Navigation on
|
|
559
|
-
<InsetDrawer anchor="left"> {/* Navigation
|
|
560
|
-
<InsetDrawer anchor="right"> {/*
|
|
561
|
-
<InsetDrawer anchor="bottom">{/* Mobile
|
|
1133
|
+
// ✅ Good: Navigation on left, details on right
|
|
1134
|
+
<InsetDrawer anchor="left"> {/* Navigation */}
|
|
1135
|
+
<InsetDrawer anchor="right"> {/* Details/filters */}
|
|
1136
|
+
<InsetDrawer anchor="bottom">{/* Mobile actions */}
|
|
562
1137
|
```
|
|
563
1138
|
|
|
564
|
-
2. **Provide clear close affordances**:
|
|
1139
|
+
2. **Provide clear close affordances**: Include close button and backdrop click
|
|
565
1140
|
|
|
566
1141
|
```tsx
|
|
567
|
-
// ✅ Good: Multiple close
|
|
1142
|
+
// ✅ Good: Multiple ways to close
|
|
568
1143
|
<InsetDrawer open={open} onClose={handleClose}>
|
|
569
1144
|
<Sheet>
|
|
570
|
-
<ModalClose />
|
|
571
|
-
<Button onClick={handleClose}>Cancel</Button>
|
|
1145
|
+
<ModalClose /> {/* X button */}
|
|
1146
|
+
<Button onClick={handleClose}>Cancel</Button> {/* Cancel button */}
|
|
572
1147
|
</Sheet>
|
|
573
1148
|
</InsetDrawer>
|
|
574
1149
|
```
|
|
575
1150
|
|
|
576
|
-
3. **
|
|
1151
|
+
3. **Use consistent header/footer patterns**: Structure content predictably
|
|
577
1152
|
|
|
578
1153
|
```tsx
|
|
579
|
-
// ✅ Good: Clear
|
|
580
|
-
<Sheet
|
|
581
|
-
<Box sx={{
|
|
1154
|
+
// ✅ Good: Clear structure
|
|
1155
|
+
<Sheet>
|
|
1156
|
+
<Box sx={{ borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
582
1157
|
<DialogTitle>Title</DialogTitle>
|
|
583
1158
|
<ModalClose />
|
|
584
1159
|
</Box>
|
|
585
|
-
<DialogContent
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
<Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
589
|
-
<Button>Save</Button>
|
|
1160
|
+
<DialogContent>{/* Scrollable content */}</DialogContent>
|
|
1161
|
+
<Box sx={{ borderTop: '1px solid', borderColor: 'divider' }}>
|
|
1162
|
+
{/* Action buttons */}
|
|
590
1163
|
</Box>
|
|
591
1164
|
</Sheet>
|
|
592
1165
|
```
|
|
593
1166
|
|
|
594
|
-
4. **
|
|
1167
|
+
4. **Handle mobile responsiveness**: Full width on small screens
|
|
595
1168
|
|
|
596
1169
|
```tsx
|
|
597
|
-
//
|
|
1170
|
+
// ✅ Good: Responsive width
|
|
1171
|
+
<Sheet sx={{ width: { xs: '100vw', sm: 400 } }}>
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
### ❌ Don't
|
|
1175
|
+
|
|
1176
|
+
1. **Don't hide critical content**: Keep essential features accessible
|
|
1177
|
+
|
|
1178
|
+
```tsx
|
|
1179
|
+
// ❌ Bad: Primary action hidden in drawer
|
|
598
1180
|
<InsetDrawer>
|
|
599
|
-
<
|
|
1181
|
+
<Button>Complete Purchase</Button> {/* Should be on main page */}
|
|
600
1182
|
</InsetDrawer>
|
|
601
1183
|
```
|
|
602
1184
|
|
|
603
|
-
|
|
1185
|
+
2. **Don't nest drawers**: Leads to confusing UX
|
|
604
1186
|
|
|
605
|
-
|
|
1187
|
+
```tsx
|
|
1188
|
+
// ❌ Bad: Drawer within drawer
|
|
1189
|
+
<InsetDrawer>
|
|
1190
|
+
<Button onClick={() => setNestedOpen(true)}>Open Another</Button>
|
|
1191
|
+
<InsetDrawer open={nestedOpen}>{/* Nested drawer */}</InsetDrawer>
|
|
1192
|
+
</InsetDrawer>
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
3. **Don't use for confirmations**: Use Dialog instead
|
|
1196
|
+
|
|
1197
|
+
```tsx
|
|
1198
|
+
// ❌ Bad: Drawer for confirmation
|
|
1199
|
+
<InsetDrawer>
|
|
1200
|
+
<Typography>Are you sure?</Typography>
|
|
1201
|
+
<Button>Confirm</Button>
|
|
1202
|
+
</InsetDrawer>
|
|
1203
|
+
|
|
1204
|
+
// ✅ Good: Use Dialog
|
|
1205
|
+
<Dialog>
|
|
1206
|
+
<Typography>Are you sure?</Typography>
|
|
1207
|
+
<Button>Confirm</Button>
|
|
1208
|
+
</Dialog>
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
4. **Don't forget about keyboard users**: Ensure proper focus management
|
|
1212
|
+
|
|
1213
|
+
## Performance Considerations
|
|
1214
|
+
|
|
1215
|
+
### Lazy Loading Content
|
|
1216
|
+
|
|
1217
|
+
For drawers with heavy content, load data when drawer opens:
|
|
1218
|
+
|
|
1219
|
+
```tsx
|
|
1220
|
+
function DetailDrawer({ open, itemId }) {
|
|
1221
|
+
const [data, setData] = useState(null);
|
|
1222
|
+
|
|
1223
|
+
useEffect(() => {
|
|
1224
|
+
if (open && itemId) {
|
|
1225
|
+
fetchItemDetails(itemId).then(setData);
|
|
1226
|
+
}
|
|
1227
|
+
}, [open, itemId]);
|
|
1228
|
+
|
|
1229
|
+
return (
|
|
1230
|
+
<InsetDrawer open={open}>
|
|
1231
|
+
<Sheet>
|
|
1232
|
+
{data ? <ItemDetails data={data} /> : <Skeleton />}
|
|
1233
|
+
</Sheet>
|
|
1234
|
+
</InsetDrawer>
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
### Memoize Handlers
|
|
1240
|
+
|
|
1241
|
+
```tsx
|
|
1242
|
+
const handleClose = useCallback(() => {
|
|
1243
|
+
setOpen(false);
|
|
1244
|
+
}, []);
|
|
1245
|
+
|
|
1246
|
+
const handleApply = useCallback((filters) => {
|
|
1247
|
+
applyFilters(filters);
|
|
1248
|
+
setOpen(false);
|
|
1249
|
+
}, [applyFilters]);
|
|
1250
|
+
|
|
1251
|
+
<InsetDrawer open={open} onClose={handleClose}>
|
|
1252
|
+
<FilterPanel onApply={handleApply} />
|
|
1253
|
+
</InsetDrawer>
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
### Unmount When Closed
|
|
1257
|
+
|
|
1258
|
+
For memory-intensive content, conditionally render:
|
|
1259
|
+
|
|
1260
|
+
```tsx
|
|
1261
|
+
{open && (
|
|
1262
|
+
<InsetDrawer open={open} onClose={handleClose}>
|
|
1263
|
+
<HeavyContentComponent />
|
|
1264
|
+
</InsetDrawer>
|
|
1265
|
+
)}
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
### Avoid Layout Shifts
|
|
1269
|
+
|
|
1270
|
+
Use fixed dimensions to prevent content shifts:
|
|
1271
|
+
|
|
1272
|
+
```tsx
|
|
1273
|
+
<Sheet sx={{
|
|
1274
|
+
width: 400,
|
|
1275
|
+
height: '100%',
|
|
1276
|
+
// Content won't cause width changes
|
|
1277
|
+
}}>
|
|
1278
|
+
```
|
|
606
1279
|
|
|
607
|
-
|
|
608
|
-
- Pressing **Escape** closes the drawer, and **Tab** / **Shift+Tab** cycles through focusable elements inside.
|
|
609
|
-
- Use `DialogTitle` with a matching `id` and `aria-labelledby` on the Sheet to provide a descriptive label for screen readers.
|
|
610
|
-
- When the drawer closes, focus returns to the element that triggered it, maintaining a predictable navigation flow.
|
|
1280
|
+
InsetDrawer provides flexible secondary content areas that slide in from screen edges. Use it for navigation menus, filter panels, and detail views while keeping primary actions visible on the main screen. Always consider the user's workflow and ensure drawers enhance rather than hinder the experience.
|