HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/Users/fred/.codex/.tmp/plugins/plugins/wix/skills/wix-app/references/DASHBOARD_MODAL.md
# Wix Dashboard Modal

Dashboard modals are popup dialogs triggered from dashboard pages or plugins. They use the Dashboard SDK for lifecycle control via `openModal()` and `closeModal()`.

## Scaffold

Use `wix generate --params` with all required fields:

```bash
wix generate --params '{"extensionType":"DASHBOARD_MODAL","title":"<title>","folder":"<folder>"}'
```

| Field | Constraint |
| --- | --- |
| `title` | Display name for the modal. |
| `folder` | Lowercase alphanumeric and hyphens. |

The CLI generates the folder, the modal `.tsx`, the config file, the builder file, the UUID, and the `src/extensions.ts` registration. After scaffolding, implement the modal UI in the generated `.tsx`.

## Quick Reference

| Task | Method | Example |
|------|--------|---------|
| Open modal | `dashboard.openModal()` | `openModal({ modalId: "modal-id" })` |
| Pass data to modal | `params` in `openModal()` | `params: { userId: "123" }` |
| Read data in modal | `observeState()` | `dashboard.observeState((state) => { ... })` |
| Close modal | `dashboard.closeModal()` | `closeModal()` |
| Return data to parent | Pass data to `closeModal()` | `closeModal({ ... })` |
| Wait for modal close | `modalClosed` Promise | `const { modalClosed } = openModal(...);` |

## Opening a Modal

```typescript
import { dashboard } from "@wix/dashboard";

// Simple open
const result = await dashboard.openModal({
  modalId: "your-modal-id", // The id generated by the CLI for this modal
});

// Pass data to modal via params
const result = await dashboard.openModal({
  modalId: "your-modal-id",
  params: {
    userId: user.id,
    itemData: complexObject, // Objects are passed directly, no encoding needed
  },
});

// Get notified when the modal is closed
const { modalClosed } = dashboard.openModal({
  modalId: "your-modal-id",
});
const result = await modalClosed; // Resolves with data from closeModal()
```

## Receiving Data in Modal

Inside the modal, subscribe via `dashboard.observeState()` to access whatever was passed in `openModal({ params })`. The callback receives the params object as `state`:

```typescript
import { dashboard } from "@wix/dashboard";

dashboard.observeState((state) => {
  // state contains the keys you passed in `openModal({ params: { ... } })`
  console.log(state.userId, state.itemData);
});
```

Call it inside a `useEffect` if you want to set local React state from the params.

## Closing Modal

Call `closeModal()` from within the modal extension. The optional argument is data passed back to the opener (resolved via `modalClosed`).

```typescript
import { dashboard } from "@wix/dashboard";

dashboard.closeModal({ saved: true, itemId: "123" }); // arg is optional
```

The argument must be cloneable via the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) — strings, numbers, booleans, plain objects, arrays, Dates, Maps, Sets, ArrayBuffers. **Not supported:** functions, DOM nodes, class instances with methods, Symbols, Promises.

## Customizing Modal

Edit `<modal>.config.ts` (generated alongside the modal) to change the title and dimensions. The generated `.tsx` already imports and uses it.

```typescript
// <modal>.config.ts
export default {
  title: 'User Settings',
  width: 600,
  height: 500,
};
```

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Can't find modal ID | Check the modal's generated builder file (`extensions.ts`) `id` field |
| Using `extensionId` instead of `modalId` | Use `modalId` in `openModal()` |
| Can't access params in modal | Use `dashboard.observeState()` to read passed data |
| Modal won't close | Use `dashboard.closeModal()` from `@wix/dashboard` |

## Real-World Example

End-to-end edit-item flow. The scaffolded `<modal>.tsx` already wires up `CustomModalLayout` — the unique parts are on the opener side, in `observeState`, and in the save handler.

```typescript
// Dashboard Page: open the modal with the item to edit
const handleEdit = (item: Item) => {
  dashboard.openModal({
    modalId: "edit-item-modal-guid",
    params: { item }, // objects are passed directly via params
  });
};

// Modal: read params, save, toast, close
const [formData, setFormData] = useState<Item | null>(null);

useEffect(() => {
  dashboard.observeState((state) => {
    if (state.item) setFormData(state.item);
  });
}, []);

const handleSave = async () => {
  // ...your save logic...
  dashboard.showToast({ message: "Saved!", type: "success" });
  dashboard.closeModal();
};
```