# InsetDrawer

## Introduction

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.

```
<Canvas of={InsetDrawer.Playground} />
```

| Field                        | Description | Default |
| ---------------------------- | ----------- | ------- |
| Controls resolved at runtime | —           | —       |

> ⚠️ **Usage Warning** ⚠️
>
> Consider these factors before using InsetDrawer:
>
> - **Mobile-first design**: InsetDrawer works best on mobile; consider persistent sidebars on desktop
> - **Content hierarchy**: Don't hide critical content in drawers that users need frequently
> - **Anchor position**: Use `left` for navigation, `right` for details/filters, `bottom` for actions
> - **Accessibility**: Ensure proper focus management and keyboard navigation

## Usage

```tsx
import { InsetDrawer, DialogTitle, DialogContent, ModalClose, Sheet } from '@ceed/cds';

function FilterDrawer() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open Filters</Button>
      <InsetDrawer open={open} onClose={() => setOpen(false)}>
        <Sheet sx={{ p: 2, height: '100%' }}>
          <DialogTitle>Filters</DialogTitle>
          <ModalClose />
          <DialogContent>
            {/* Filter content */}
          </DialogContent>
        </Sheet>
      </InsetDrawer>
    </>
  );
}
```

## Examples

### Playground

Interactive example with controls for anchor position and size.

```
<Canvas of={InsetDrawer.Playground} />
```

### Anchors

Different anchor positions: left, right, top, bottom.

```
<Canvas of={InsetDrawer.Anchors} />
```

### Sizes

Available size options: small, medium, large.

```
<Canvas of={InsetDrawer.Sizes} />
```

### Navigation Menu

A mobile navigation menu using InsetDrawer.

```
<Canvas of={InsetDrawer.NavigationMenu} />
```

### Filter Panel

A filter panel with form controls sliding from the right.

```
<Canvas of={InsetDrawer.FilterPanel} />
```

## When to Use

### ✅ Good Use Cases

- **Mobile navigation**: Hamburger menu that slides in from the left
- **Filter panels**: Complex filters that would clutter the main UI
- **Detail views**: Additional information without navigating away
- **Settings panels**: Quick access to preferences or configurations
- **Shopping cart**: E-commerce cart preview on the side
- **Help/Support**: Context-sensitive help or chat interfaces

### ❌ When Not to Use

- **Critical actions**: Don't hide essential features in drawers
- **Primary navigation on desktop**: Consider a persistent sidebar instead
- **Simple choices**: Use Select or Dropdown for simple selections
- **Confirmations**: Use Dialog for confirmation messages
- **Notifications**: Use Toast or Alert for feedback messages
- **Large forms**: Complex forms should have their own page

## Common Use Cases

### Mobile Navigation Drawer

```tsx
function NavigationDrawer({ open, onClose, currentPath }) {
  const navItems = [
    { label: 'Dashboard', path: '/dashboard', icon: <DashboardIcon /> },
    { label: 'Users', path: '/users', icon: <PeopleIcon /> },
    { label: 'Settings', path: '/settings', icon: <SettingsIcon /> },
  ];

  return (
    <InsetDrawer open={open} onClose={onClose} anchor="left">
      <Sheet sx={{ width: 280, height: '100%', p: 2 }}>
        <Stack gap={1}>
          <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
            <Avatar src="/logo.png" />
            <Typography level="title-lg" sx={{ ml: 1 }}>My App</Typography>
          </Box>
          <Divider />
          <List>
            {navItems.map((item) => (
              <ListItem key={item.path}>
                <ListItemButton
                  selected={currentPath === item.path}
                  onClick={() => {
                    navigate(item.path);
                    onClose();
                  }}
                >
                  <ListItemDecorator>{item.icon}</ListItemDecorator>
                  {item.label}
                </ListItemButton>
              </ListItem>
            ))}
          </List>
        </Stack>
      </Sheet>
    </InsetDrawer>
  );
}
```

### Filter Panel

```tsx
function ProductFilters({ open, onClose, filters, onApply }) {
  const [localFilters, setLocalFilters] = useState(filters);

  const handleApply = () => {
    onApply(localFilters);
    onClose();
  };

  const handleClear = () => {
    setLocalFilters({});
  };

  return (
    <InsetDrawer open={open} onClose={onClose} anchor="right" size="md">
      <Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
        <Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
          <DialogTitle>Filters</DialogTitle>
          <ModalClose />
        </Box>

        <DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
          <Stack gap={3}>
            <FormControl>
              <FormLabel>Category</FormLabel>
              <Select
                value={localFilters.category || ''}
                onChange={(_, value) =>
                  setLocalFilters({ ...localFilters, category: value })
                }
              >
                <Option value="electronics">Electronics</Option>
                <Option value="clothing">Clothing</Option>
                <Option value="books">Books</Option>
              </Select>
            </FormControl>

            <FormControl>
              <FormLabel>Price Range</FormLabel>
              <Stack direction="row" gap={1}>
                <Input
                  type="number"
                  placeholder="Min"
                  value={localFilters.minPrice || ''}
                  onChange={(e) =>
                    setLocalFilters({ ...localFilters, minPrice: e.target.value })
                  }
                />
                <Input
                  type="number"
                  placeholder="Max"
                  value={localFilters.maxPrice || ''}
                  onChange={(e) =>
                    setLocalFilters({ ...localFilters, maxPrice: e.target.value })
                  }
                />
              </Stack>
            </FormControl>

            <FormControl>
              <FormLabel>Rating</FormLabel>
              <RadioGroup
                value={localFilters.rating || ''}
                onChange={(e) =>
                  setLocalFilters({ ...localFilters, rating: e.target.value })
                }
              >
                {[4, 3, 2, 1].map((rating) => (
                  <Radio
                    key={rating}
                    value={String(rating)}
                    label={`${rating}+ stars`}
                  />
                ))}
              </RadioGroup>
            </FormControl>
          </Stack>
        </DialogContent>

        <Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
          <Stack direction="row" gap={1} justifyContent="space-between">
            <Button variant="outlined" color="neutral" onClick={handleClear}>
              Clear All
            </Button>
            <Button onClick={handleApply}>Apply Filters</Button>
          </Stack>
        </Box>
      </Sheet>
    </InsetDrawer>
  );
}
```

### Shopping Cart Drawer

```tsx
function CartDrawer({ open, onClose, items, onCheckout }) {
  const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);

  return (
    <InsetDrawer open={open} onClose={onClose} anchor="right" size="lg">
      <Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
        <Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
          <DialogTitle>Shopping Cart ({items.length})</DialogTitle>
          <ModalClose />
        </Box>

        <DialogContent sx={{ flex: 1, overflow: 'auto', p: 0 }}>
          {items.length === 0 ? (
            <Box sx={{ p: 4, textAlign: 'center' }}>
              <ShoppingCartIcon sx={{ fontSize: 48, color: 'neutral.400' }} />
              <Typography level="body-lg" sx={{ mt: 2 }}>
                Your cart is empty
              </Typography>
            </Box>
          ) : (
            <List>
              {items.map((item) => (
                <ListItem key={item.id}>
                  <ListItemDecorator>
                    <img
                      src={item.image}
                      alt={item.name}
                      style={{ width: 60, height: 60, objectFit: 'cover' }}
                    />
                  </ListItemDecorator>
                  <ListItemContent>
                    <Typography level="title-sm">{item.name}</Typography>
                    <Typography level="body-sm">
                      ${item.price} x {item.quantity}
                    </Typography>
                  </ListItemContent>
                  <IconButton size="sm" color="danger">
                    <DeleteIcon />
                  </IconButton>
                </ListItem>
              ))}
            </List>
          )}
        </DialogContent>

        {items.length > 0 && (
          <Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
            <Stack gap={2}>
              <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
                <Typography level="title-md">Total</Typography>
                <Typography level="title-md">${total.toFixed(2)}</Typography>
              </Box>
              <Button fullWidth onClick={onCheckout}>
                Proceed to Checkout
              </Button>
              <Button variant="outlined" fullWidth onClick={onClose}>
                Continue Shopping
              </Button>
            </Stack>
          </Box>
        )}
      </Sheet>
    </InsetDrawer>
  );
}
```

### Settings Drawer

```tsx
function SettingsDrawer({ open, onClose }) {
  const [settings, setSettings] = useState({
    notifications: true,
    darkMode: false,
    language: 'en',
  });

  return (
    <InsetDrawer open={open} onClose={onClose} anchor="right" size="sm">
      <Sheet sx={{ height: '100%', p: 2 }}>
        <DialogTitle>Settings</DialogTitle>
        <ModalClose />
        <Divider sx={{ my: 2 }} />

        <DialogContent>
          <Stack gap={3}>
            <FormControl orientation="horizontal">
              <Box sx={{ flex: 1 }}>
                <FormLabel>Notifications</FormLabel>
                <FormHelperText>Receive push notifications</FormHelperText>
              </Box>
              <Switch
                checked={settings.notifications}
                onChange={(e) =>
                  setSettings({ ...settings, notifications: e.target.checked })
                }
              />
            </FormControl>

            <FormControl orientation="horizontal">
              <Box sx={{ flex: 1 }}>
                <FormLabel>Dark Mode</FormLabel>
                <FormHelperText>Use dark color theme</FormHelperText>
              </Box>
              <Switch
                checked={settings.darkMode}
                onChange={(e) =>
                  setSettings({ ...settings, darkMode: e.target.checked })
                }
              />
            </FormControl>

            <FormControl>
              <FormLabel>Language</FormLabel>
              <Select
                value={settings.language}
                onChange={(_, value) =>
                  setSettings({ ...settings, language: value || 'en' })
                }
              >
                <Option value="en">English</Option>
                <Option value="ko">한국어</Option>
                <Option value="ja">日本語</Option>
              </Select>
            </FormControl>
          </Stack>
        </DialogContent>
      </Sheet>
    </InsetDrawer>
  );
}
```

### Bottom Sheet for Mobile Actions

```tsx
function ActionSheet({ open, onClose, onAction }) {
  const actions = [
    { id: 'edit', label: 'Edit', icon: <EditIcon /> },
    { id: 'share', label: 'Share', icon: <ShareIcon /> },
    { id: 'duplicate', label: 'Duplicate', icon: <ContentCopyIcon /> },
    { id: 'delete', label: 'Delete', icon: <DeleteIcon />, color: 'danger' },
  ];

  return (
    <InsetDrawer open={open} onClose={onClose} anchor="bottom">
      <Sheet sx={{ borderRadius: 'lg lg 0 0', p: 2 }}>
        <Box
          sx={{
            width: 40,
            height: 4,
            bgcolor: 'neutral.300',
            borderRadius: 'xl',
            mx: 'auto',
            mb: 2,
          }}
        />
        <List>
          {actions.map((action) => (
            <ListItem key={action.id}>
              <ListItemButton
                color={action.color}
                onClick={() => {
                  onAction(action.id);
                  onClose();
                }}
              >
                <ListItemDecorator>{action.icon}</ListItemDecorator>
                {action.label}
              </ListItemButton>
            </ListItem>
          ))}
        </List>
        <Button
          variant="soft"
          color="neutral"
          fullWidth
          sx={{ mt: 1 }}
          onClick={onClose}
        >
          Cancel
        </Button>
      </Sheet>
    </InsetDrawer>
  );
}
```

### Help/Support Panel

```tsx
function HelpDrawer({ open, onClose }) {
  const [searchQuery, setSearchQuery] = useState('');

  const faqItems = [
    { question: 'How do I reset my password?', answer: '...' },
    { question: 'How do I change my email?', answer: '...' },
    { question: 'How do I delete my account?', answer: '...' },
  ];

  return (
    <InsetDrawer open={open} onClose={onClose} anchor="right" size="lg">
      <Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
        <Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
          <DialogTitle>Help Center</DialogTitle>
          <ModalClose />
          <Input
            placeholder="Search for help..."
            startDecorator={<SearchIcon />}
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            sx={{ mt: 2 }}
          />
        </Box>

        <DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
          <Typography level="title-md" sx={{ mb: 2 }}>
            Frequently Asked Questions
          </Typography>
          <Accordion>
            {faqItems
              .filter((item) =>
                item.question.toLowerCase().includes(searchQuery.toLowerCase())
              )
              .map((item, index) => (
                <AccordionItem key={index}>
                  <AccordionSummary>{item.question}</AccordionSummary>
                  <AccordionDetails>{item.answer}</AccordionDetails>
                </AccordionItem>
              ))}
          </Accordion>
        </DialogContent>

        <Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
          <Typography level="body-sm" sx={{ mb: 1 }}>
            Can't find what you're looking for?
          </Typography>
          <Button fullWidth startDecorator={<ChatIcon />}>
            Contact Support
          </Button>
        </Box>
      </Sheet>
    </InsetDrawer>
  );
}
```

## Props and Customization

### Key Props

| Prop       | Type                                     | Default  | Description                                      |
| ---------- | ---------------------------------------- | -------- | ------------------------------------------------ |
| `open`     | `boolean`                                | `false`  | Controls whether the drawer is visible           |
| `onClose`  | `() => void`                             | -        | Callback fired when the drawer requests to close |
| `anchor`   | `'left' \| 'right' \| 'top' \| 'bottom'` | `'left'` | Edge from which the drawer slides in             |
| `size`     | `'sm' \| 'md' \| 'lg'`                   | `'md'`   | Size of the drawer                               |
| `children` | `ReactNode`                              | -        | Content to render inside the drawer              |

### Anchor Positions

```tsx
// Left drawer (navigation menus)
<InsetDrawer anchor="left" open={open}>
  <Sheet sx={{ width: 280 }}>Navigation</Sheet>
</InsetDrawer>

// Right drawer (details, filters)
<InsetDrawer anchor="right" open={open}>
  <Sheet sx={{ width: 400 }}>Details</Sheet>
</InsetDrawer>

// Top drawer (search, notifications)
<InsetDrawer anchor="top" open={open}>
  <Sheet sx={{ height: 200 }}>Search</Sheet>
</InsetDrawer>

// Bottom drawer (mobile actions)
<InsetDrawer anchor="bottom" open={open}>
  <Sheet sx={{ maxHeight: '80vh' }}>Actions</Sheet>
</InsetDrawer>
```

### Size Options

The `size` prop controls the width (for left/right anchors) or height (for top/bottom anchors):

```tsx
// Small drawer (~280px)
<InsetDrawer size="sm" anchor="right" />

// Medium drawer (~400px) - default
<InsetDrawer size="md" anchor="right" />

// Large drawer (~600px)
<InsetDrawer size="lg" anchor="right" />
```

### Custom Sizing

For more control, use `Sheet` with custom styles:

```tsx
<InsetDrawer open={open} anchor="right">
  <Sheet sx={{ width: { xs: '100vw', sm: 450 }, height: '100%' }}>
    Content
  </Sheet>
</InsetDrawer>
```

### Drawer Content Structure

Use these components for consistent drawer layout:

```tsx
<InsetDrawer open={open}>
  <Sheet sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
    {/* Header */}
    <Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
      <DialogTitle>Drawer Title</DialogTitle>
      <ModalClose />
    </Box>

    {/* Scrollable Content */}
    <DialogContent sx={{ flex: 1, overflow: 'auto', p: 2 }}>
      {/* Main content */}
    </DialogContent>

    {/* Footer */}
    <Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
      <Stack direction="row" gap={1}>
        <Button variant="outlined">Cancel</Button>
        <Button>Save</Button>
      </Stack>
    </Box>
  </Sheet>
</InsetDrawer>
```

## Accessibility

InsetDrawer includes built-in accessibility features:

### ARIA Attributes

- Drawer container has appropriate `role="dialog"` or `role="presentation"`
- Focus is trapped within the drawer when open
- Proper `aria-modal` attribute when modal

### Keyboard Navigation

- **Escape**: Close the drawer
- **Tab**: Navigate between focusable elements within drawer
- **Shift + Tab**: Navigate backwards

### Focus Management

```tsx
// Focus automatically moves to drawer content when opened
<InsetDrawer
  open={open}
  onClose={onClose}
  // Focus is trapped within drawer
>
  <Sheet>
    {/* First focusable element receives focus */}
    <Input autoFocus placeholder="Search..." />
  </Sheet>
</InsetDrawer>
```

### Screen Reader Support

```tsx
// Provide descriptive title for screen readers
<InsetDrawer open={open}>
  <Sheet role="dialog" aria-labelledby="drawer-title">
    <DialogTitle id="drawer-title">Filters</DialogTitle>
    {/* Content */}
  </Sheet>
</InsetDrawer>
```

## Best Practices

### ✅ Do

1. **Use appropriate anchor positions**: Match drawer position to content type

```tsx
// ✅ Good: Navigation on left, details on right
<InsetDrawer anchor="left">  {/* Navigation */}
<InsetDrawer anchor="right"> {/* Details/filters */}
<InsetDrawer anchor="bottom">{/* Mobile actions */}
```

2. **Provide clear close affordances**: Include close button and backdrop click

```tsx
// ✅ Good: Multiple ways to close
<InsetDrawer open={open} onClose={handleClose}>
  <Sheet>
    <ModalClose /> {/* X button */}
    <Button onClick={handleClose}>Cancel</Button> {/* Cancel button */}
  </Sheet>
</InsetDrawer>
```

3. **Use consistent header/footer patterns**: Structure content predictably

```tsx
// ✅ Good: Clear structure
<Sheet>
  <Box sx={{ borderBottom: '1px solid', borderColor: 'divider' }}>
    <DialogTitle>Title</DialogTitle>
    <ModalClose />
  </Box>
  <DialogContent>{/* Scrollable content */}</DialogContent>
  <Box sx={{ borderTop: '1px solid', borderColor: 'divider' }}>
    {/* Action buttons */}
  </Box>
</Sheet>
```

4. **Handle mobile responsiveness**: Full width on small screens

```tsx
// ✅ Good: Responsive width
<Sheet sx={{ width: { xs: '100vw', sm: 400 } }}>
```

### ❌ Don't

1. **Don't hide critical content**: Keep essential features accessible

```tsx
// ❌ Bad: Primary action hidden in drawer
<InsetDrawer>
  <Button>Complete Purchase</Button> {/* Should be on main page */}
</InsetDrawer>
```

2. **Don't nest drawers**: Leads to confusing UX

```tsx
// ❌ Bad: Drawer within drawer
<InsetDrawer>
  <Button onClick={() => setNestedOpen(true)}>Open Another</Button>
  <InsetDrawer open={nestedOpen}>{/* Nested drawer */}</InsetDrawer>
</InsetDrawer>
```

3. **Don't use for confirmations**: Use Dialog instead

```tsx
// ❌ Bad: Drawer for confirmation
<InsetDrawer>
  <Typography>Are you sure?</Typography>
  <Button>Confirm</Button>
</InsetDrawer>

// ✅ Good: Use Dialog
<Dialog>
  <Typography>Are you sure?</Typography>
  <Button>Confirm</Button>
</Dialog>
```

4. **Don't forget about keyboard users**: Ensure proper focus management

## Performance Considerations

### Lazy Loading Content

For drawers with heavy content, load data when drawer opens:

```tsx
function DetailDrawer({ open, itemId }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    if (open && itemId) {
      fetchItemDetails(itemId).then(setData);
    }
  }, [open, itemId]);

  return (
    <InsetDrawer open={open}>
      <Sheet>
        {data ? <ItemDetails data={data} /> : <Skeleton />}
      </Sheet>
    </InsetDrawer>
  );
}
```

### Memoize Handlers

```tsx
const handleClose = useCallback(() => {
  setOpen(false);
}, []);

const handleApply = useCallback((filters) => {
  applyFilters(filters);
  setOpen(false);
}, [applyFilters]);

<InsetDrawer open={open} onClose={handleClose}>
  <FilterPanel onApply={handleApply} />
</InsetDrawer>
```

### Unmount When Closed

For memory-intensive content, conditionally render:

```tsx
{open && (
  <InsetDrawer open={open} onClose={handleClose}>
    <HeavyContentComponent />
  </InsetDrawer>
)}
```

### Avoid Layout Shifts

Use fixed dimensions to prevent content shifts:

```tsx
<Sheet sx={{
  width: 400,
  height: '100%',
  // Content won't cause width changes
}}>
```

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.
