npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dialogkit

v0.0.2

Published

Dialog component and helpers for React

Downloads

0

Readme

dialogkit

A powerful and flexible dialog system for React with built-in animations and full TypeScript support.

Features

  • 🎯 Fully Typed - Complete TypeScript generics support with type inference (createDialog<TProps, TResult>)
  • 🎨 Customizable - Complete control over styling and behavior
  • Animated - Built-in animation presets (scale, fade, slide)
  • 📱 Responsive - Pre-configured variants (bottom sheet, side sheet, prompts)
  • 🎣 Hook-based - Simple and intuitive React hooks API
  • 🔒 Accessible - Keyboard navigation (ESC to close) and focus management

Installation

# pnpm (recommended)
pnpm add dialogkit

# npm
npm install dialogkit

# yarn
yarn add dialogkit

Note: @dialogkit/core is automatically installed as a dependency - no need to install it separately.

API

Generic Signature

All dialogs are created using TypeScript generics for complete type safety:

createDialog<TProps, TResult>(
  render: (controller: DialogController<TProps, TResult>) => React.ReactNode,
  config?: CreateDialogConfig
): DialogHook<TProps, TResult>

Where:

  • TProps - The type of properties passed when opening the dialog
  • TResult - The type of data returned when the dialog is submitted

Basic Usage

Creating a Simple Dialog

Fully typed with TypeScript generics - Use createDialog<TProps, TResult> for complete type safety:

import { createDialog } from 'dialogkit';

// Generic signature: createDialog<TProps, TResult>
const useConfirmDialog = createDialog<
  { message: string }, // Props type
  boolean // Return type
>(({ props, submit, cancel }) => (
  <div className="p-6 bg-white rounded-lg shadow-xl">
    <h2 className="text-xl font-bold mb-4">Confirm</h2>
    <p className="mb-6">{props.message}</p>
    <div className="flex gap-2">
      <button onClick={() => submit(true)}>Yes</button>
      <button onClick={() => cancel()}>No</button>
    </div>
  </div>
));

// Use in component - fully typed!
function MyComponent() {
  const { open } = useConfirmDialog();

  const handleAction = async () => {
    const result = await open({ message: 'Are you sure?' });
    // result is fully typed based on your generic parameters

    if (result.status === 'submitted') {
      console.log('User confirmed:', result.data); // data is typed as boolean
    } else if (result.status === 'cancelled') {
      console.log('User cancelled');
    }
  };

  return <button onClick={handleAction}>Delete</button>;
}

Pre-configured Dialog Variants

Bottom Sheet

Perfect for mobile-style bottom sheets that slide up from the bottom.

import { createDialog, DialogConfigs } from 'dialogkit';

// Fully typed with generics - void for dialogs that don't return data
const useBottomSheet = createDialog<
  { title: string }, // Props type
  void // No return data
>(
  ({ props, cancel }) => (
    <div className="w-full bg-white p-6 rounded-t-xl">
      <h2>{props.title}</h2>
      <button onClick={cancel}>Close</button>
    </div>
  ),
  DialogConfigs.bottomSheet
);

// Usage
const { open } = useBottomSheet();
await open({ title: 'Settings' });

Side Sheet

A drawer-style panel that slides in from the right.

import { createDialog, DialogConfigs } from 'dialogkit';

// Typed with createDialog<TProps, TResult>
const useSideSheet = createDialog<
  { content: string }, // Props type
  void // No return data
>(
  ({ props, cancel }) => (
    <div className="h-full w-96 bg-white p-6">
      <h2>Details</h2>
      <p>{props.content}</p>
      <button onClick={cancel}>Close</button>
    </div>
  ),
  DialogConfigs.sideSheet
);

// Usage
const { open } = useSideSheet();
await open({ content: 'Detailed information here' });

Prompt Dialog

Top-aligned dialogs perfect for notifications and alerts.

import { createDialog, DialogConfigs } from 'dialogkit';

const usePrompt = createDialog<{ message: string }, void>(
  ({ props, cancel }) => (
    <div className="bg-white p-4 rounded-lg shadow-lg">
      <p>{props.message}</p>
      <button onClick={cancel}>Dismiss</button>
    </div>
  ),
  DialogConfigs.prompt
);

// Usage
const { open } = usePrompt();
await open({ message: 'Action completed successfully!' });

Custom Animations

Using Built-in Animations

import { createDialog, animations } from 'dialogkit';

// Fade animation
const useFadeDialog = createDialog<{}, void>(
  ({ cancel }) => (
    <div className="dialog">
      <button onClick={cancel}>Close</button>
    </div>
  ),
  {
    animation: animations.fade,
  }
);

// Slide animations
const useSlideDialog = createDialog<{}, void>(({ cancel }) => <div>Content</div>, {
  animation: animations.slideUp, // or slideDown, slideLeft, slideRight
});

Custom Animation Configuration

import { createDialog } from 'dialogkit';
import { gsap } from 'gsap';

const useCustomDialog = createDialog<{}, void>(({ cancel }) => <div>Content</div>, {
  animation: {
    duration: 400,
    enter: (element) => {
      gsap.fromTo(element, { opacity: 0, y: -50 }, { opacity: 1, y: 0, duration: 0.4 });
    },
    exit: (element) => {
      return gsap.to(element, {
        opacity: 0,
        y: -50,
        duration: 0.4,
      });
    },
  },
});

Configuration Options

CreateDialogConfig

interface CreateDialogConfig {
  // Backdrop configuration
  backdrop?: {
    backgroundColor?: string;
    backdropFilter?: string;
    zIndex?: number;
    display?: 'flex' | 'block';
    justifyContent?: 'center' | 'flex-start' | 'flex-end' | 'space-between';
    alignItems?: 'center' | 'flex-start' | 'flex-end' | 'stretch';
    customStyles?: React.CSSProperties;
  };

  // Behavior options
  closeOnOutsideClick?: boolean; // Default: true
  closeOnEscape?: boolean; // Default: true
  lockBodyScroll?: boolean; // Default: true

  // Animation configuration
  animation?: AnimationConfig;
}

Custom Backdrop

const useCustomDialog = createDialog<{}, void>(({ cancel }) => <div>Content</div>, {
  backdrop: {
    backgroundColor: 'rgba(0, 0, 0, 0.8)',
    backdropFilter: 'blur(4px)',
    zIndex: 1000,
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    customStyles: {
      padding: '20px',
    },
  },
});

Controller API

The controller passed to your render function provides these methods:

interface OverlayController<TProps, TResult> {
  // The props passed when opening the dialog
  props: TProps;

  // Submit the dialog with a result
  submit: (data: TResult) => void;

  // Cancel the dialog (no result)
  cancel: () => void;

  // Close the dialog (alias for cancel)
  close: () => void;
}

Result Handling

The open() method returns a promise that resolves to a result object:

type DialogResult<TResult> = { status: 'submitted'; data: TResult } | { status: 'cancelled' };

// Example usage
const result = await open({ message: 'Confirm?' });

if (result.status === 'submitted') {
  console.log('Submitted with data:', result.data);
} else {
  console.log('Cancelled');
}

Available Animation Presets

import { animations } from 'dialogkit';

animations.scale; // Scale up/down (default)
animations.fade; // Fade in/out
animations.slideUp; // Slide up from bottom
animations.slideDown; // Slide down from top
animations.slideLeft; // Slide in from right
animations.slideRight; // Slide in from left

Pre-configured Dialog Configs

import { DialogConfigs } from 'dialogkit';

DialogConfigs.default; // Centered, scale animation
DialogConfigs.bottomSheet; // Bottom-aligned, slide up
DialogConfigs.sideSheet; // Right-aligned, slide left
DialogConfigs.prompt; // Top-aligned, slide down

Advanced Examples

Form Dialog with Validation

import { createDialog } from 'dialogkit';
import { useState } from 'react';

interface FormData {
  name: string;
  email: string;
}

const useFormDialog = createDialog<{}, FormData>(({ submit, cancel }) => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = () => {
    if (!name || !email) {
      setError('All fields are required');
      return;
    }
    submit({ name, email });
  };

  return (
    <div className="dialog">
      <h2>User Information</h2>
      {error && <p className="error">{error}</p>}
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        type="email"
      />
      <button onClick={handleSubmit}>Submit</button>
      <button onClick={cancel}>Cancel</button>
    </div>
  );
});

Multi-step Dialog

import { createDialog } from 'dialogkit';
import { useState } from 'react';

const useWizardDialog = createDialog<{}, { step1: string; step2: string }>(({ submit, cancel }) => {
  const [step, setStep] = useState(1);
  const [data, setData] = useState({ step1: '', step2: '' });

  const handleFinish = () => {
    submit(data);
  };

  return (
    <div className="wizard">
      {step === 1 && (
        <div>
          <h2>Step 1</h2>
          <input value={data.step1} onChange={(e) => setData({ ...data, step1: e.target.value })} />
          <button onClick={() => setStep(2)}>Next</button>
          <button onClick={cancel}>Cancel</button>
        </div>
      )}
      {step === 2 && (
        <div>
          <h2>Step 2</h2>
          <input value={data.step2} onChange={(e) => setData({ ...data, step2: e.target.value })} />
          <button onClick={() => setStep(1)}>Back</button>
          <button onClick={handleFinish}>Finish</button>
        </div>
      )}
    </div>
  );
});

Confirmation Dialog with Async Operations

import { createDialog } from 'dialogkit';
import { useState } from 'react';

const useAsyncDialog = createDialog<{ itemId: string }, boolean>(({ props, submit, cancel }) => {
  const [loading, setLoading] = useState(false);

  const handleConfirm = async () => {
    setLoading(true);
    try {
      await fetch(`/api/items/${props.itemId}`, { method: 'DELETE' });
      submit(true);
    } catch (error) {
      console.error(error);
      setLoading(false);
    }
  };

  return (
    <div className="dialog">
      <h2>Delete Item?</h2>
      <p>This action cannot be undone.</p>
      <button onClick={handleConfirm} disabled={loading}>
        {loading ? 'Deleting...' : 'Delete'}
      </button>
      <button onClick={cancel} disabled={loading}>
        Cancel
      </button>
    </div>
  );
});

TypeScript

The library is fully typed and built with TypeScript. Use generics for complete type safety:

Generic Signature

createDialog<TProps, TResult>(
  render: (controller: DialogController<TProps, TResult>) => React.ReactNode,
  config?: CreateDialogConfig
)

Full Type Safety Example

// Define your types
interface EditUserProps {
  userId: number;
  initialName: string;
  initialEmail: string;
}

interface EditUserResult {
  updated: boolean;
  name: string;
  email: string;
}

// Create fully typed dialog with createDialog<TProps, TResult>
const useEditUserDialog = createDialog<EditUserProps, EditUserResult>(
  ({ props, submit, cancel }) => {
    // props is fully typed as EditUserProps
    const [name, setName] = useState(props.initialName);
    const [email, setEmail] = useState(props.initialEmail);

    return (
      <div>
        <h2>Edit User {props.userId}</h2>
        <input value={name} onChange={(e) => setName(e.target.value)} />
        <input value={email} onChange={(e) => setEmail(e.target.value)} />
        <button onClick={() => submit({ updated: true, name, email })}>Save</button>
        <button onClick={cancel}>Cancel</button>
      </div>
    );
  }
);

// Usage with complete type inference
function MyComponent() {
  const { open } = useEditUserDialog();

  const handleEdit = async () => {
    const result = await open({
      userId: 123,
      initialName: 'John',
      initialEmail: '[email protected]',
    });

    // TypeScript knows the exact shape of result
    if (result.status === 'submitted') {
      // result.data is typed as EditUserResult
      console.log(result.data.updated); // boolean
      console.log(result.data.name); // string
      console.log(result.data.email); // string
    }
  };

  return <button onClick={handleEdit}>Edit User</button>;
}

Simple Example with Primitives

// Simple types work great too
const useConfirm = createDialog<
  { message: string }, // Props
  boolean // Result
>(({ props, submit }) => (
  <div>
    <p>{props.message}</p>
    <button onClick={() => submit(true)}>Yes</button>
    <button onClick={() => submit(false)}>No</button>
  </div>
));

Exported Types

import type { DialogController, DialogResult, AnimationConfig } from 'dialogkit';

Peer Dependencies

  • react >= 16.8.0
  • react-dom >= 16.8.0

Dependencies

  • @dialogkit/core - Core animation and overlay system
  • gsap - Animation library (via @dialogkit/core)

License

MIT

Contributing

Contributions are welcome! Please see the main repository for contributing guidelines.

Related Packages

Support

For issues and questions, please visit the GitHub repository.