# Modal vs Offcanvas Component Guide

> **Props y API:** Disponibles vía MCP tool `widgets-get-component-props`. Este archivo documenta solo convenciones, gotchas y patrones específicos del proyecto.

## When to Use Which Component

### DModal - Use for:
- **Dialogs requiring user decision** (confirm, cancel, save)
- **Forms that interrupt workflow** (create account, add item)
- **Critical alerts or warnings** (destructive actions, errors)
- **Content that needs user focus** (terms & conditions, privacy policy)
- **Centered, blocking interactions** (login, payment)

**Key Characteristic:** Portal-based system with automatic backdrop and focus management.

### DOffcanvas - Use for:
- **Detail views** (policy details, transaction history, product specs)
- **Navigation menus** (mobile sidebar, settings panel)
- **Supplementary information** (help text, tooltips, filters)
- **Non-blocking content** (activity feed, notifications)
- **Side panels for extended content** (shopping cart, message thread)

**Key Characteristic:** Slide-in panel from edge of screen, less intrusive than modal.

---

## DModal Usage Pattern

### Portal-Based Pattern

DModal uses the portal system. Modals are **registered** in `DContextProvider.availablePortals` and **opened** via `openPortal(name, payload)`.

**Step 1: Create the modal component**

```tsx
// src/components/modals/MyModal.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>Modal content goes here</p>
      </DModal.Body>

      <DModal.Footer>
        <DButton
          text="Cancel"
          variant="outline"
          onClick={() => closePortal()}
        />
        <DButton
          text="Confirm"
          color="primary"
          onClick={() => {
            // Handle action
            closePortal();
          }}
        />
      </DModal.Footer>
    </DModal>
  );
}
```

**Step 2: Register in `src/main.tsx`**

```tsx
import { DContextProvider } from '@dynamic-framework/ui-react';
import MyModal from './components/modals/MyModal';

<DContextProvider
  availablePortals={{
    'my-modal': MyModal,
  }}
>
  <App />
</DContextProvider>
```

**Step 3: Open from any component**

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

export default function MyComponent() {
  const { openPortal } = useDPortalContext();

  return (
    <DButton
      text="Open Modal"
      onClick={() => openPortal('my-modal', {})}
    />
  );
}
```

**API:**
- `openPortal(name: string, payload: object)` — opens the registered component, payload is passed as props
- `closePortal()` — closes the topmost portal (no arguments)

### DModal Subcomponents

```tsx
<DModal.Header showCloseButton onClose={() => closePortal()}>
  <h5>Title</h5>
</DModal.Header>
<DModal.Body>{/* content */}</DModal.Body>
<DModal.Footer>
  <DButton text="Cancel" variant="outline" onClick={() => closePortal()} />
  <DButton text="Confirm" color="primary" />
</DModal.Footer>
```

### Portal Management

**Opening Modal:**
```tsx
// name must match a key in availablePortals
// payload is passed as props to the modal component
openPortal('my-modal', { userId: 123 });
```

**Closing Modal (pops top of stack, no arguments):**
```tsx
closePortal();  // No arguments — closes the topmost portal
```

**Check if Portal is Open:**
```tsx
const { stack } = useDPortalContext();
const isOpen = stack.some(p => p.name === 'my-modal');
```

### Common Modal Patterns

**Confirmation Dialog:**

```tsx
// src/components/modals/ConfirmDeleteModal.tsx
import { DModal, DButton, useDPortalContext } from '@dynamic-framework/ui-react';

export default function ConfirmDeleteModal({ onConfirm }: { onConfirm: () => void }) {
  const { closePortal } = useDPortalContext();

  return (
    <DModal name="delete-confirm" staticBackdrop size="sm" centered>
      <DModal.Header>
        <h5>Confirm Deletion</h5>
      </DModal.Header>
      <DModal.Body>
        <p>Are you sure you want to delete this item? This action cannot be undone.</p>
      </DModal.Body>
      <DModal.Footer>
        <DButton text="Cancel" variant="outline" onClick={() => closePortal()} />
        <DButton text="Delete" color="danger" onClick={() => { onConfirm(); closePortal(); }} />
      </DModal.Footer>
    </DModal>
  );
}

// Register in main.tsx: availablePortals={{ 'delete-confirm': ConfirmDeleteModal }}
// Open: openPortal('delete-confirm', { onConfirm: () => performDelete() })
```

**Form Modal:**

```tsx
// src/components/modals/CreateFormModal.tsx
import { useState, FormEvent } from 'react';
import { DModal, DInput, DButton, useDPortalContext } from '@dynamic-framework/ui-react';

export default function CreateFormModal() {
  const { closePortal } = useDPortalContext();
  const [name, setName] = useState('');

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    // Submit logic
    closePortal();
  };

  return (
    <DModal name="create-form" size="lg">
      <DModal.Header>
        <h5>Create New Item</h5>
      </DModal.Header>
      <DModal.Body>
        <form id="create-form" onSubmit={handleSubmit}>
          <DInput label="Name" value={name} onChange={setName} />
        </form>
      </DModal.Body>
      <DModal.Footer>
        <DButton text="Cancel" variant="outline" onClick={() => closePortal()} />
        <DButton text="Create" color="primary" type="submit" form="create-form" />
      </DModal.Footer>
    </DModal>
  );
}

// Register in main.tsx: availablePortals={{ 'create-form': CreateFormModal }}
// Open: openPortal('create-form', {})
```

---

## DOffcanvas Usage Pattern

### Portal-Based Pattern

DOffcanvas uses the same portal system as DModal — register in `availablePortals`, open with `openPortal`.

**Step 1: Create the offcanvas component**

```tsx
// src/components/offcanvas/DetailPanel.tsx
import { DOffcanvas, DButton, useDPortalContext } from '@dynamic-framework/ui-react';

export default function DetailPanel() {
  const { closePortal } = useDPortalContext();

  return (
    <DOffcanvas name="detail-panel" openFrom="end">
      <DOffcanvas.Header showCloseButton onClose={() => closePortal()}>
        <h5>Panel Title</h5>
      </DOffcanvas.Header>

      <DOffcanvas.Body>
        <p>Panel content goes here</p>
      </DOffcanvas.Body>

      <DOffcanvas.Footer>
        <DButton text="Close" variant="outline" onClick={() => closePortal()} />
        <DButton text="Save" color="primary" onClick={() => closePortal()} />
      </DOffcanvas.Footer>
    </DOffcanvas>
  );
}

// Register in main.tsx: availablePortals={{ 'detail-panel': DetailPanel }}
```

**Step 2: Open from any component**

```tsx
const { openPortal } = useDPortalContext();
<DButton text="Open Panel" onClick={() => openPortal('detail-panel', {})} />
```

> **IMPORTANT:** DOffcanvas does NOT have `open`, `onClose`, or `placement` props.
> It is controlled entirely through the portal system (`openPortal` / `closePortal`).

### DOffcanvas Subcomponents

**DOffcanvas.Header**
```tsx
<DOffcanvas.Header showCloseButton onClose={() => closePortal()}>
  <div className="d-flex align-items-center gap-3">
    <DIcon icon="FileText" size="24px" color="primary" />
    <div>
      <h5 className="mb-0">Title</h5>
      <small className="text-muted">Subtitle</small>
    </div>
  </div>
</DOffcanvas.Header>
```

**DOffcanvas.Body**
```tsx
<DOffcanvas.Body>
  {/* Scrollable content */}
</DOffcanvas.Body>
```

**DOffcanvas.Footer**
```tsx
<DOffcanvas.Footer>
  <DButton text="Close" variant="outline" onClick={() => closePortal()} />
  <DButton text="Save" onClick={handleSave} />
</DOffcanvas.Footer>
```

### Common Offcanvas Patterns

**Detail View Panel:**

```tsx
// src/components/offcanvas/ItemDetailPanel.tsx
import { DOffcanvas, DButton, useDPortalContext } from '@dynamic-framework/ui-react';

export default function ItemDetailPanel({ item }: { item: Item }) {
  const { closePortal } = useDPortalContext();

  return (
    <DOffcanvas name="item-detail" openFrom="end">
      <DOffcanvas.Header showCloseButton onClose={() => closePortal()}>
        <h5>{item.title}</h5>
      </DOffcanvas.Header>
      <DOffcanvas.Body>
        {/* Display item details */}
      </DOffcanvas.Body>
      <DOffcanvas.Footer>
        <DButton text="Close" variant="outline" onClick={() => closePortal()} />
      </DOffcanvas.Footer>
    </DOffcanvas>
  );
}

// Register in main.tsx: availablePortals={{ 'item-detail': ItemDetailPanel }}
// Open: openPortal('item-detail', { item })
```

**Filter Panel:**

```tsx
// src/components/offcanvas/FilterPanel.tsx
import { DOffcanvas, DButton, useDPortalContext } from '@dynamic-framework/ui-react';

export default function FilterPanel({ onApply, onReset }: { onApply: () => void; onReset: () => void }) {
  const { closePortal } = useDPortalContext();

  return (
    <DOffcanvas name="filters" openFrom="end">
      <DOffcanvas.Header showCloseButton onClose={() => closePortal()}>
        <h5>Filter Options</h5>
      </DOffcanvas.Header>
      <DOffcanvas.Body>
        {/* Filter form fields */}
      </DOffcanvas.Body>
      <DOffcanvas.Footer>
        <DButton text="Reset" variant="outline" onClick={onReset} />
        <DButton text="Apply" color="primary" onClick={() => { onApply(); closePortal(); }} />
      </DOffcanvas.Footer>
    </DOffcanvas>
  );
}

// Register in main.tsx: availablePortals={{ 'filters': FilterPanel }}
// Open: openPortal('filters', { onApply: applyFilters, onReset: resetFilters })
```

---

## Common Mistakes

### Mistake #1: Using DModal with `open` prop

```tsx
// ❌ WRONG - DModal doesn't have `open` prop
<DModal open={isOpen} onClose={() => setIsOpen(false)}>
  {/* ... */}
</DModal>
```

**Fix:** Register modal in `availablePortals` and use portal context:
```tsx
// ✅ CORRECT — register in main.tsx, then open via context
const { openPortal } = useDPortalContext();
openPortal('modal-id', {});  // payload object, not JSX
```

### Mistake #2: Using DOffcanvas with `open`/`onClose`/`placement` props

```tsx
// ❌ WRONG - These props don't exist on DOffcanvas
<DOffcanvas open={isOpen} onClose={onClose} placement="end">
  {/* ... */}
</DOffcanvas>
```

**Fix:** Register in `availablePortals` and use `openFrom` prop in the component:
```tsx
// ✅ CORRECT — register in main.tsx, then open via context
openPortal('my-panel', {});
closePortal();  // No arguments
```

### Mistake #3: Passing arguments to closePortal

```tsx
// ❌ WRONG - closePortal takes NO arguments
closePortal('modal-id');
```

**Fix:** closePortal is stack-based and always pops the topmost portal:
```tsx
// ✅ CORRECT
closePortal();
```

### Mistake #4: Portal name mismatch

```tsx
// ❌ WRONG - availablePortals key doesn't match DModal name prop
availablePortals={{ 'my-id': MyModal }}  // key: 'my-id'
// but in MyModal: <DModal name="different-id">  // name: 'different-id'
```

**Fix:** The `availablePortals` key and the `DModal name` prop must match:
```tsx
// ✅ CORRECT
availablePortals={{ 'my-modal': MyModal }}
// In MyModal: <DModal name="my-modal">
```

### Mistake #5: Using `portals` instead of `stack`

```tsx
// ❌ WRONG - Property is called `stack`, not `portals`
const { portals } = useDPortalContext();
const isOpen = portals.some(p => p.id === 'my-modal');
```

**Fix:**
```tsx
// ✅ CORRECT
const { stack } = useDPortalContext();
const isOpen = stack.some(p => p.name === 'my-modal');
```

---

## Decision Tree

Use this flowchart to choose the right component:

```
Does the user need to make a decision or complete a critical action?
├─ YES → Use DModal
│   ├─ Confirm/Cancel action
│   ├─ Fill out form
│   ├─ Accept terms
│   └─ Login/Authentication
│
└─ NO → Is this supplementary or detail information?
    └─ YES → Use DOffcanvas
        ├─ View details
        ├─ Filter options
        ├─ Navigation menu
        └─ Help/documentation
```

---

## Summary: Quick Reference

| Aspect | DModal | DOffcanvas |
|--------|--------|------------|
| **Pattern** | Portal-based with `useDPortalContext` | Portal-based with `useDPortalContext` |
| **Backdrop** | Automatic | Automatic (via portal system) |
| **Position** | Centered overlay | Slides from edge (`openFrom`: start/end/top/bottom) |
| **Use Case** | Decisions, forms, alerts | Details, navigation, filters |
| **State** | Managed by portal `name` | Managed by portal `name` |
| **Open** | `openPortal('name', {})` | `openPortal('name', {})` |
| **Close** | `closePortal()` (no args) | `closePortal()` (no args) |
| **Size** | `size`: sm/lg/xl | `openFrom`: start/end/top/bottom |
| **Example** | Confirm delete, login | Policy details, shopping cart |

---

> See also: `modyo://docs/widgets/components-overlay-widgets` for DPopover and DTooltip.
