# Modal & Overlay Components

> Components: DModal, DOffcanvas

---

## DContextProvider Setup

All widgets MUST wrap in `<DContextProvider>`. Props: `availablePortals` (modal/offcanvas registry), `children`.

```tsx
// src/main.tsx
<DContextProvider
  availablePortals={{
    'my-modal': MyModal,
    'settings-modal': SettingsModal,
  }}
>
  <App />
</DContextProvider>
```

Without modals: `<DContextProvider><App /></DContextProvider>` (still required for portal system and theming).

```tsx
// ❌ These props DON'T EXIST: defaultCountry, locale, theme
```

---

## DModal

Modal dialog component.

---

### DModal Pattern

```tsx
import { DModal, DButton, useDPortalContext } from '@dynamic-framework/ui-react';

export default function MyModal() {
  const { closePortal } = useDPortalContext();
  return (
    <DModal name="my-modal" centered>
      <DModal.Header><h5>Modal Title</h5></DModal.Header>
      <DModal.Body><p>Content</p></DModal.Body>
      <DModal.Footer>
        <DButton text="Cancel" variant="outline" onClick={() => closePortal()} />
        <DButton text="Confirm" color="primary" onClick={() => { /* logic */ closePortal(); }} />
      </DModal.Footer>
    </DModal>
  );
}
// Register: availablePortals={{ 'my-modal': MyModal }}
```

---

**Props:**
- `name: string` - Unique modal identifier (used with useDPortalContext)
- `staticBackdrop?: boolean` - Prevent closing on backdrop click
- `centered?: boolean` - Center modal vertically
- `size?: 'sm' | 'lg' | 'xl'` - Modal size

**Subcomponents:**
- `DModal.Header` - Modal header
- `DModal.Body` - Modal body
- `DModal.Footer` - Modal footer

---

### ⚠️ CRITICAL: No modalManager Export

**Common mistake:** Trying to import `modalManager` from Dynamic Framework.

```tsx
// ❌ WRONG - modalManager doesn't exist
import { modalManager } from '@dynamic-framework/ui-react';
modalManager.open('my-modal');  // This will fail

// ✅ CORRECT - Use useDPortalContext
import { useDPortalContext } from '@dynamic-framework/ui-react';
const { openPortal, closePortal } = useDPortalContext();
openPortal('my-modal', {});
```

---

## DOffcanvas

### Rule #2: open vs openDefault (Controlled State)

**Use `open` for controlled state, `openDefault` only for initial uncontrolled state.**

**Affected components:** DOffcanvas, DModal, DCollapse

```tsx
// ✅ CORRECT - Controlled (state changes trigger open/close)
const [isOpen, setIsOpen] = useState(false);

<DOffcanvas
  open={isOpen}
  onClose={() => setIsOpen(false)}
  staticBackdrop={false}  // Allow click-outside to close
>
  <DOffcanvas.Header
    showCloseButton          // ✅ Shows X button
    onClose={() => setIsOpen(false)}
  >
    <h5>Title</h5>
  </DOffcanvas.Header>
  <DOffcanvas.Body>
    Content
  </DOffcanvas.Body>
</DOffcanvas>

// ❌ WRONG - Won't react to state changes
<DOffcanvas openDefault={isOpen} onClose={onClose}>
  <DOffcanvas.Header onClose={onClose}>  {/* ❌ No visible close button */}
    <h5>Title</h5>
  </DOffcanvas.Header>
</DOffcanvas>
```

**Key points:**
- Use `open` for controlled state (react to state changes)
- Use `openDefault` only for initial state in uncontrolled mode
- Add `showCloseButton` to Header for X button
- Set `staticBackdrop={false}` to allow closing by clicking outside

---

## Modal Registration (CRITICAL)

All portals MUST be registered in `DContextProvider.availablePortals` at app root. Key must match `openPortal()` name exactly. Cannot register dynamically.

```tsx
// Opening with props — modal receives them as component props
openPortal('user-edit', { userId: 123 });
function UserEditModal({ userId }: { userId: number }) { /* userId available */ }
```

Unregistered portal error: `"there is no component for portal my-modal"` — add to `availablePortals`.

---

## Best Practices

### Modal
- Open via `openPortal('name', {})` — DModal does NOT have `open`/`onClose` props
- Close from within via `closePortal()`
- Pass data via `openPortal` props, not external state
- Keep modal components in separate files (`src/components/modals/`)

### Offcanvas
- Always add `showCloseButton` to Header
- Use `staticBackdrop={false}` for click-outside closing
- Use `open` for controlled state (not `openDefault`)

### General
- Semantic button colors: `danger` for destructive, `primary` for confirm, `outline` for cancel
- Provide multiple close mechanisms: X button, Cancel button, click-outside, ESC (auto)

---

## Checklist

- DModal: registered in `availablePortals`, uses `useDPortalContext` (not `modalManager`), `name` matches key, opens with `openPortal('name', {})`, closes with `closePortal()`
- DOffcanvas: uses `open` (not `openDefault`), `showCloseButton` on Header, `onClose` provided, `staticBackdrop={false}` for click-outside
