@ceed/ads 1.23.3 → 1.23.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/Badge.md +71 -39
- package/dist/components/data-display/InfoSign.md +74 -98
- package/dist/components/data-display/Typography.md +310 -61
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -106
- package/dist/components/inputs/Calendar.md +98 -459
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterMenu.md +169 -19
- package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
- package/dist/components/inputs/FormControl.md +368 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +136 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +80 -322
- package/dist/components/navigation/Dropdown.md +92 -221
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +68 -738
- package/dist/components/navigation/Link.md +39 -298
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/ProfileMenu.md +45 -268
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Sheet.md +150 -333
- package/dist/guides/ThemeProvider.md +116 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
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
|
|
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 a Modal, which overlays the entire viewport, InsetDrawer slides in from a specific edge and pushes or covers only part of the interface.
|
|
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.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<Box sx={{
|
|
@@ -138,15 +140,6 @@ Booking options
|
|
|
138
140
|
| ---------------------------- | ----------- | ------- |
|
|
139
141
|
| Controls resolved at runtime | — | — |
|
|
140
142
|
|
|
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
|
-
|
|
150
143
|
## Usage
|
|
151
144
|
|
|
152
145
|
```tsx
|
|
@@ -172,145 +165,9 @@ function FilterDrawer() {
|
|
|
172
165
|
}
|
|
173
166
|
```
|
|
174
167
|
|
|
175
|
-
##
|
|
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
|
|
168
|
+
## Anchor Positions
|
|
312
169
|
|
|
313
|
-
|
|
170
|
+
InsetDrawer supports four anchor positions. Use `left` for navigation, `right` for details or filters, `top` for search bars, and `bottom` for mobile action sheets.
|
|
314
171
|
|
|
315
172
|
```tsx
|
|
316
173
|
<Box sx={{
|
|
@@ -357,9 +214,9 @@ Different anchor positions: left, right, top, bottom.
|
|
|
357
214
|
</Box>
|
|
358
215
|
```
|
|
359
216
|
|
|
360
|
-
|
|
217
|
+
## Sizes
|
|
361
218
|
|
|
362
|
-
|
|
219
|
+
Three size presets are available: `sm`, `md`, and `lg`. The size controls width for left/right anchors and height for top/bottom anchors.
|
|
363
220
|
|
|
364
221
|
```tsx
|
|
365
222
|
<Box sx={{
|
|
@@ -403,9 +260,9 @@ Available size options: small, medium, large.
|
|
|
403
260
|
</Box>
|
|
404
261
|
```
|
|
405
262
|
|
|
406
|
-
|
|
263
|
+
## Navigation Menu
|
|
407
264
|
|
|
408
|
-
A mobile navigation menu using InsetDrawer.
|
|
265
|
+
A typical mobile navigation menu pattern using InsetDrawer anchored to the left with a compact size.
|
|
409
266
|
|
|
410
267
|
```tsx
|
|
411
268
|
<Box>
|
|
@@ -437,9 +294,9 @@ A mobile navigation menu using InsetDrawer.
|
|
|
437
294
|
</Box>
|
|
438
295
|
```
|
|
439
296
|
|
|
440
|
-
|
|
297
|
+
## Filter Panel
|
|
441
298
|
|
|
442
|
-
A filter panel
|
|
299
|
+
A filter panel that slides in from the right, containing form controls for refining search results.
|
|
443
300
|
|
|
444
301
|
```tsx
|
|
445
302
|
<Box>
|
|
@@ -578,235 +435,8 @@ Booking options
|
|
|
578
435
|
</Box>
|
|
579
436
|
```
|
|
580
437
|
|
|
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
|
-
|
|
601
438
|
## Common Use Cases
|
|
602
439
|
|
|
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
|
-
|
|
810
440
|
### Settings Drawer
|
|
811
441
|
|
|
812
442
|
```tsx
|
|
@@ -823,7 +453,6 @@ function SettingsDrawer({ open, onClose }) {
|
|
|
823
453
|
<DialogTitle>Settings</DialogTitle>
|
|
824
454
|
<ModalClose />
|
|
825
455
|
<Divider sx={{ my: 2 }} />
|
|
826
|
-
|
|
827
456
|
<DialogContent>
|
|
828
457
|
<Stack gap={3}>
|
|
829
458
|
<FormControl orientation="horizontal">
|
|
@@ -838,7 +467,6 @@ function SettingsDrawer({ open, onClose }) {
|
|
|
838
467
|
}
|
|
839
468
|
/>
|
|
840
469
|
</FormControl>
|
|
841
|
-
|
|
842
470
|
<FormControl orientation="horizontal">
|
|
843
471
|
<Box sx={{ flex: 1 }}>
|
|
844
472
|
<FormLabel>Dark Mode</FormLabel>
|
|
@@ -851,20 +479,6 @@ function SettingsDrawer({ open, onClose }) {
|
|
|
851
479
|
}
|
|
852
480
|
/>
|
|
853
481
|
</FormControl>
|
|
854
|
-
|
|
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
482
|
</Stack>
|
|
869
483
|
</DialogContent>
|
|
870
484
|
</Sheet>
|
|
@@ -873,6 +487,34 @@ function SettingsDrawer({ open, onClose }) {
|
|
|
873
487
|
}
|
|
874
488
|
```
|
|
875
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
|
+
|
|
502
|
+
return (
|
|
503
|
+
<InsetDrawer open={open} onClose={onClose} anchor="right" size="lg">
|
|
504
|
+
<Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
505
|
+
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
506
|
+
<DialogTitle>Item Details</DialogTitle>
|
|
507
|
+
<ModalClose />
|
|
508
|
+
</Box>
|
|
509
|
+
<DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
|
510
|
+
{data ? <ItemDetails data={data} /> : <Skeleton variant="text" />}
|
|
511
|
+
</DialogContent>
|
|
512
|
+
</Sheet>
|
|
513
|
+
</InsetDrawer>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
876
518
|
### Bottom Sheet for Mobile Actions
|
|
877
519
|
|
|
878
520
|
```tsx
|
|
@@ -880,32 +522,18 @@ function ActionSheet({ open, onClose, onAction }) {
|
|
|
880
522
|
const actions = [
|
|
881
523
|
{ id: 'edit', label: 'Edit', icon: <EditIcon /> },
|
|
882
524
|
{ id: 'share', label: 'Share', icon: <ShareIcon /> },
|
|
883
|
-
{ id: 'duplicate', label: 'Duplicate', icon: <ContentCopyIcon /> },
|
|
884
525
|
{ id: 'delete', label: 'Delete', icon: <DeleteIcon />, color: 'danger' },
|
|
885
526
|
];
|
|
886
527
|
|
|
887
528
|
return (
|
|
888
529
|
<InsetDrawer open={open} onClose={onClose} anchor="bottom">
|
|
889
530
|
<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
|
-
/>
|
|
900
531
|
<List>
|
|
901
532
|
{actions.map((action) => (
|
|
902
533
|
<ListItem key={action.id}>
|
|
903
534
|
<ListItemButton
|
|
904
535
|
color={action.color}
|
|
905
|
-
onClick={() => {
|
|
906
|
-
onAction(action.id);
|
|
907
|
-
onClose();
|
|
908
|
-
}}
|
|
536
|
+
onClick={() => { onAction(action.id); onClose(); }}
|
|
909
537
|
>
|
|
910
538
|
<ListItemDecorator>{action.icon}</ListItemDecorator>
|
|
911
539
|
{action.label}
|
|
@@ -913,13 +541,7 @@ function ActionSheet({ open, onClose, onAction }) {
|
|
|
913
541
|
</ListItem>
|
|
914
542
|
))}
|
|
915
543
|
</List>
|
|
916
|
-
<Button
|
|
917
|
-
variant="soft"
|
|
918
|
-
color="neutral"
|
|
919
|
-
fullWidth
|
|
920
|
-
sx={{ mt: 1 }}
|
|
921
|
-
onClick={onClose}
|
|
922
|
-
>
|
|
544
|
+
<Button variant="soft" color="neutral" fullWidth sx={{ mt: 1 }} onClick={onClose}>
|
|
923
545
|
Cancel
|
|
924
546
|
</Button>
|
|
925
547
|
</Sheet>
|
|
@@ -928,353 +550,61 @@ function ActionSheet({ open, onClose, onAction }) {
|
|
|
928
550
|
}
|
|
929
551
|
```
|
|
930
552
|
|
|
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
|
-
|
|
1126
553
|
## Best Practices
|
|
1127
554
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
1. **Use appropriate anchor positions**: Match drawer position to content type
|
|
555
|
+
1. **Use appropriate anchor positions**: Match the drawer position to the content type for intuitive UX.
|
|
1131
556
|
|
|
1132
557
|
```tsx
|
|
1133
|
-
// ✅ Good: Navigation on left, details on right
|
|
1134
|
-
<InsetDrawer anchor="left"> {/* Navigation */}
|
|
1135
|
-
<InsetDrawer anchor="right"> {/*
|
|
1136
|
-
<InsetDrawer anchor="bottom">{/* Mobile
|
|
558
|
+
// ✅ Good: Navigation on the left, details on the right
|
|
559
|
+
<InsetDrawer anchor="left"> {/* Navigation menu */}
|
|
560
|
+
<InsetDrawer anchor="right"> {/* Detail panel or filters */}
|
|
561
|
+
<InsetDrawer anchor="bottom">{/* Mobile action sheet */}
|
|
1137
562
|
```
|
|
1138
563
|
|
|
1139
|
-
2. **Provide clear close affordances**:
|
|
564
|
+
2. **Provide clear close affordances**: Always give users multiple ways to close the drawer -- a close button, a cancel button, and backdrop click.
|
|
1140
565
|
|
|
1141
566
|
```tsx
|
|
1142
|
-
// ✅ Good: Multiple
|
|
567
|
+
// ✅ Good: Multiple close methods
|
|
1143
568
|
<InsetDrawer open={open} onClose={handleClose}>
|
|
1144
569
|
<Sheet>
|
|
1145
|
-
<ModalClose />
|
|
1146
|
-
<Button onClick={handleClose}>Cancel</Button>
|
|
570
|
+
<ModalClose />
|
|
571
|
+
<Button onClick={handleClose}>Cancel</Button>
|
|
1147
572
|
</Sheet>
|
|
1148
573
|
</InsetDrawer>
|
|
1149
574
|
```
|
|
1150
575
|
|
|
1151
|
-
3. **Use
|
|
576
|
+
3. **Structure content consistently**: Use a header/body/footer layout with clear dividers so users can quickly scan the drawer's content.
|
|
1152
577
|
|
|
1153
578
|
```tsx
|
|
1154
|
-
// ✅ Good: Clear structure
|
|
1155
|
-
<Sheet>
|
|
1156
|
-
<Box sx={{ borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
579
|
+
// ✅ Good: Clear header/body/footer structure
|
|
580
|
+
<Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
581
|
+
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
1157
582
|
<DialogTitle>Title</DialogTitle>
|
|
1158
583
|
<ModalClose />
|
|
1159
584
|
</Box>
|
|
1160
|
-
<DialogContent
|
|
1161
|
-
|
|
1162
|
-
|
|
585
|
+
<DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
|
586
|
+
{/* Scrollable content */}
|
|
587
|
+
</DialogContent>
|
|
588
|
+
<Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
589
|
+
<Button>Save</Button>
|
|
1163
590
|
</Box>
|
|
1164
591
|
</Sheet>
|
|
1165
592
|
```
|
|
1166
593
|
|
|
1167
|
-
4. **
|
|
1168
|
-
|
|
1169
|
-
```tsx
|
|
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
|
|
1180
|
-
<InsetDrawer>
|
|
1181
|
-
<Button>Complete Purchase</Button> {/* Should be on main page */}
|
|
1182
|
-
</InsetDrawer>
|
|
1183
|
-
```
|
|
1184
|
-
|
|
1185
|
-
2. **Don't nest drawers**: Leads to confusing UX
|
|
594
|
+
4. **Do not nest drawers**: Opening a drawer inside another drawer creates a confusing experience.
|
|
1186
595
|
|
|
1187
596
|
```tsx
|
|
1188
|
-
// ❌ Bad:
|
|
597
|
+
// ❌ Bad: Nested drawers
|
|
1189
598
|
<InsetDrawer>
|
|
1190
|
-
<
|
|
1191
|
-
<InsetDrawer open={nestedOpen}>{/* Nested drawer */}</InsetDrawer>
|
|
599
|
+
<InsetDrawer>{/* Avoid this pattern */}</InsetDrawer>
|
|
1192
600
|
</InsetDrawer>
|
|
1193
601
|
```
|
|
1194
602
|
|
|
1195
|
-
|
|
603
|
+
5. **Do not use for confirmations**: Use Dialog or Modal for simple confirmation prompts. InsetDrawer is designed for rich, scrollable content.
|
|
1196
604
|
|
|
1197
|
-
|
|
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
|
-
```
|
|
605
|
+
## Accessibility
|
|
1279
606
|
|
|
1280
|
-
InsetDrawer
|
|
607
|
+
- InsetDrawer traps focus within the drawer when open, ensuring keyboard users cannot accidentally interact with content behind it.
|
|
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.
|