Components

Dialogs

Dialogs in @wayvo-ai/core use the Popup component, which provides resizable and movable dialogs perfect for forms.

Popup is a resizable, movable dialog component:

import { Popup, showSuccess, showError } from '@wayvo-ai/core/ui';

<Popup
  title="Edit Entity"
  onClose={handleClose}
  width={520}
  height={430}
  footer={
    <>
      <Button variant="outline" onClick={handleClose}>Cancel</Button>
      <Button onClick={handleSave}>Save</Button>
    </>
  }
>
  {/* Form content */}
</Popup>

Complete Dialog Pattern

'use client';

import { useCallback, useState } from 'react';
import { Popup, useCurrentRowSync, useIsStoreDirty, useIsStorePosting, showSuccess, showError } from '@wayvo-ai/core/ui';
import type { Entity } from '@/lib/common/ds/types/module/Entity';
import type { Store } from '@wayvo-ai/core/common';
import { TextInput } from '@wayvo-ai/core/ui';

interface Props {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  store: Store<Entity>;
  editingRow?: Entity | null;
}

export function EntityDialog({ open, onOpenChange, store, editingRow }: Props) {
  const row = useCurrentRowSync(store);
  const isDirty = useIsStoreDirty(store);
  const isPosting = useIsStorePosting(store);

  const handleSave = async () => {
    try {
      const success = await store.save();
      if (success) {
        showSuccess(editingRow ? 'Entity updated' : 'Entity created');
        onOpenChange(false);
      }
    } catch {
      showError('Failed to save');
    }
  };

  const handleClose = () => {
    store.resetStore();  // Discard unsaved changes
    onOpenChange(false);
  };

  if (!open) return null;

  return (
    <Popup
      title={editingRow ? 'Edit Entity' : 'Add Entity'}
      onClose={handleClose}
      width={520}
      height={430}
      footer={
        <>
          <Button variant="outline" onClick={handleClose} disabled={isPosting}>
            Cancel
          </Button>
          <Button onClick={handleSave} disabled={isPosting || !isDirty || !row}>
            {isPosting && <Loader2 className="mr-2 size-4 animate-spin" />}
            Save
          </Button>
        </>
      }
    >
      {row && (
        <div className="grid gap-4">
          <TextInput
            label="Name"
            value={row.name || ''}
            onChange={(value) => store.setValue('name', value)}
            required
          />
          <TextInput
            label="Description"
            value={row.description || ''}
            onChange={(value) => store.setValue('description', value)}
          />
        </div>
      )}
    </Popup>
  );
}

Dialog Initialization

IMPORTANT: Initialize the store BEFORE opening the dialog, not in useEffect:

// ✅ Correct - Initialize in click handler
const handleAdd = useCallback(() => {
  store.createNew({
    partialRecord: { status: 'draft' },
  });
  setIsDialogOpen(true);
}, [store]);

// ❌ Wrong - Causes timing issues
useEffect(() => {
  if (open) {
    store.createNew({ partialRecord: { status: 'draft' } });
  }
}, [open, store]);
Form SizeDimensionsUse Case
Small (3-4 fields)~520x430Simple forms
Medium (5-6 fields)~520x530Standard forms
Large (7+ fields)~560x580+Complex forms
Settings dialogs~720x850Multi-section dialogs
PropTypeDescription
titlestringDialog title (required)
descriptionstringOptional subtitle
onClose() => voidClose handler (required)
footerReactNodeFooter content (typically buttons)
widthnumberInitial width in pixels
heightnumberInitial height in pixels
minWidthnumberMinimum resize width
minHeightnumberMinimum resize height
resizablebooleanEnable resize handle (default: true)

Usage in Parent Component

function ParentComponent() {
  const store = useEntityDialogStore();
  const [isDialogOpen, setIsDialogOpen] = useState(false);
  const [editingRow, setEditingRow] = useState<Entity | null>(null);

  const handleAdd = useCallback(() => {
    store.createNew({ partialRecord: { status: 'draft' } });
    setEditingRow(null);
    setIsDialogOpen(true);
  }, [store]);

  const handleEdit = useCallback((row: Entity) => {
    store.setCurrentRow(row);
    setEditingRow(row);
    setIsDialogOpen(true);
  }, [store]);

  return (
    <>
      <Button onClick={handleAdd}>Add Entity</Button>
      <EntityDialog
        open={isDialogOpen}
        onOpenChange={setIsDialogOpen}
        store={store}
        editingRow={editingRow}
      />
    </>
  );
}

Next Steps

Previous
Forms