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

@lerx/promise-modal

v0.13.1

Published

Universal React modal utility that can be used outside React components with promise-based results for alert, confirm, and prompt modals

Readme

@lerx/promise-modal

Typescript Javascript React


Overview

@lerx/promise-modal is a universal modal utility based on React.

Key features include:

  • Can be used even in places not included in React components
  • After opening a modal, you can retrieve the result as a promise
  • Supports various modal types (alert, confirm, prompt)
  • Highly customizable component structure

Installation

yarn add @lerx/promise-modal

or

npm install @lerx/promise-modal

Compatibility

Supported environments:

  • Node.js 16.11.0 or later
  • Modern browsers (Chrome 94+, Firefox 93+, Safari 15+)

For legacy environment support: Please use a transpiler like Babel to transform the code for your target environment.


How to Use

1. Setting up the Modal Provider

Install ModalProvider at the root of your application:

import { ModalProvider } from '@lerx/promise-modal';

function App() {
  return (
    <ModalProvider>
      <YourApp />
    </ModalProvider>
  );
}

You can also apply custom options and components:

import { ModalProvider } from '@lerx/promise-modal';

import {
  CustomBackground,
  CustomContent,
  CustomFooter,
  CustomForeground,
  CustomSubtitle,
  CustomTitle,
} from './components';

function App() {
  return (
    <ModalProvider
      ForegroundComponent={CustomForeground}
      BackgroundComponent={CustomBackground}
      TitleComponent={CustomTitle}
      SubtitleComponent={CustomSubtitle}
      ContentComponent={CustomContent}
      FooterComponent={CustomFooter}
      options={{
        duration: '250ms', // Animation duration
        backdrop: 'rgba(0, 0, 0, 0.35)', // Background overlay color
        manualDestroy: false, // Default auto-destroy behavior
        closeOnBackdropClick: true, // Default backdrop click behavior
      }}
      context={{
        // Context values accessible in all modal components
        theme: 'light',
        locale: 'en-US',
      }}
    >
      <YourApp />
    </ModalProvider>
  );
}

2. Using Basic Modals

Alert Modal

Alert modals provide simple information display with a confirmation button.

import { alert } from '@lerx/promise-modal';

// Basic usage
async function showAlert() {
  await alert({
    title: 'Notification',
    content: 'The task has been completed.',
  });
  console.log('User closed the modal.');
}

// Using various options
async function showDetailedAlert() {
  await alert({
    subtype: 'success', // 'info' | 'success' | 'warning' | 'error'
    title: 'Success',
    subtitle: 'Details',
    content: 'The task has been successfully completed.',
    dimmed: true, // Dim the background
    closeOnBackdropClick: true, // Close on backdrop click
    // If use close animation, you need to set manualDestroy to true
    manualDestroy: false, // Auto-destroy (false: auto, true: manual)
    // Data to pass to the background
    background: {
      data: 'custom-data',
    },
  });
}

Confirm Modal

Confirm modals are used for actions that require user confirmation.

import { confirm } from '@lerx/promise-modal';

async function showConfirm() {
  const result = await confirm({
    title: 'Confirm',
    content: 'Are you sure you want to delete this?',
    // Custom footer text
    footer: {
      confirm: 'Delete',
      cancel: 'Cancel',
    },
  });

  if (result) {
    console.log('User clicked confirm.');
    // Execute delete logic
  } else {
    console.log('User clicked cancel.');
  }
}

Prompt Modal

Prompt modals are used to receive input from users.

import { prompt } from '@lerx/promise-modal';

async function showPrompt() {
  // Text input
  const name = await prompt<string>({
    title: 'Enter Name',
    content: 'Please enter your name.',
    defaultValue: '', // Default value
    Input: ({ value, onChange }) => {
      // Important: value is the current value, onChange is the function to update the value
      return (
        <input
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder="Enter name"
        />
      );
    },
    // Validate input to control the confirmation button
    disabled: (value) => value.length < 2,
  });

  console.log('Entered name:', name);

  // Complex data input
  const userInfo = await prompt<{ name: string; age: number }>({
    title: 'User Information',
    defaultValue: { name: '', age: 0 },
    Input: ({ value, onChange }) => (
      <div>
        <input
          value={value.name}
          onChange={(e) => onChange({ ...value, name: e.target.value })}
          placeholder="Name"
        />
        <input
          type="number"
          value={value.age}
          onChange={(e) => onChange({ ...value, age: Number(e.target.value) })}
          placeholder="Age"
        />
      </div>
    ),
  });

  console.log('User info:', userInfo);
}

3. Using Custom Components

You can customize the appearance and behavior of modals:

import { css } from '@emotion/css';
import { ModalProvider, alert, confirm } from '@lerx/promise-modal';

// Custom foreground component
const CustomForegroundComponent = ({ children, type, ...props }) => {
  return (
    <div
      className={css`
        background-color: #ffffff;
        border-radius: 12px;
        box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
        width: 90%;
        max-width: 500px;
        padding: 24px;
        position: relative;
      `}
    >
      {children}
    </div>
  );
};

// Custom background component (utilizing background data)
const CustomBackgroundComponent = ({ children, onClick, background }) => {
  // Apply different styles based on background data
  const getBgColor = () => {
    if (background?.data === 'alert') return 'rgba(0, 0, 0, 0.7)';
    if (background?.data === 'confirm') return 'rgba(0, 0, 0, 0.5)';
    if (background?.data === 'prompt') return 'rgba(0, 0, 0, 0.6)';
    return 'rgba(0, 0, 0, 0.4)';
  };

  return (
    <div
      className={css`
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        background-color: ${getBgColor()};
        z-index: 1000;
      `}
      onClick={onClick}
    >
      {children}
    </div>
  );
};

// Custom title component
const CustomTitleComponent = ({ children, context }) => {
  return (
    <h2
      className={css`
        font-size: 24px;
        margin-bottom: 8px;
        color: ${context?.theme === 'dark' ? '#ffffff' : '#333333'};
      `}
    >
      {children}
    </h2>
  );
};

// Custom subtitle component
const CustomSubtitleComponent = ({ children, context }) => {
  return (
    <h3
      className={css`
        font-size: 16px;
        margin-bottom: 16px;
        color: ${context?.theme === 'dark' ? '#cccccc' : '#666666'};
      `}
    >
      {children}
    </h3>
  );
};

// Custom content component
const CustomContentComponent = ({ children, context }) => {
  return (
    <div
      className={css`
        margin-bottom: 24px;
        color: ${context?.theme === 'dark' ? '#eeeeee' : '#444444'};
      `}
    >
      {children}
    </div>
  );
};

// Custom footer component
const CustomFooterComponent = ({
  onConfirm,
  onClose,
  onCancel,
  type,
  disabled,
}) => {
  return (
    <div
      className={css`
        display: flex;
        justify-content: flex-end;
        gap: 12px;
        margin-top: 24px;
      `}
    >
      {/* Display cancel button for confirm and prompt modals */}
      {(type === 'confirm' || type === 'prompt') && (
        <button
          className={css`
            padding: 8px 16px;
            border: 1px solid #ccc;
            background: none;
            border-radius: 4px;
            cursor: pointer;
            &:hover {
              background-color: #f3f3f3;
            }
          `}
          onClick={type === 'confirm' ? () => onConfirm(false) : onCancel}
        >
          Cancel
        </button>
      )}
      <button
        className={css`
          padding: 8px 16px;
          background-color: #4a90e2;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: ${disabled ? 'not-allowed' : 'pointer'};
          opacity: ${disabled ? 0.6 : 1};
          &:hover {
            background-color: ${disabled ? '#4a90e2' : '#357ac7'};
          }
        `}
        onClick={() => onConfirm(type === 'confirm' ? true : undefined)}
        disabled={disabled}
      >
        Confirm
      </button>
    </div>
  );
};

// Global setup in provider
function App() {
  return (
    <ModalProvider
      ForegroundComponent={CustomForegroundComponent}
      BackgroundComponent={CustomBackgroundComponent}
      TitleComponent={CustomTitleComponent}
      SubtitleComponent={CustomSubtitleComponent}
      ContentComponent={CustomContentComponent}
      FooterComponent={CustomFooterComponent}
      options={{
        duration: 300, // Animation duration (ms)
      }}
      context={{
        // Context accessible in all modals
        theme: 'light',
        locale: 'en-US',
      }}
    >
      <YourApp />
    </ModalProvider>
  );
}

// Applying custom components to specific modals
async function showCustomAlert() {
  await alert({
    title: 'Notification',
    content: 'Content',
    // Custom component only for this modal
    ForegroundComponent: ({ children }) => (
      <div
        className={css`
          background-color: #f0f8ff;
          padding: 30px;
          border-radius: 16px;
          box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
        `}
      >
        {children}
      </div>
    ),
    // Pass background data
    background: {
      data: 'custom-alert',
    },
  });
}

4. Using Custom Anchors and Initialization

You can specify the DOM element where modals will be rendered:

import { useEffect, useRef } from 'react';

import {
  ModalProvider,
  ModalProviderHandle,
  alert,
  useInitializeModal,
} from '@lerx/promise-modal';

// Using refs
function CustomAnchorExample() {
  // Modal provider handle reference
  const modalProviderRef = useRef<ModalProviderHandle>(null);
  // Container reference for modal display
  const modalContainerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // Initialize modal when container is ready
    if (modalContainerRef.current && modalProviderRef.current) {
      modalProviderRef.current.initialize(modalContainerRef.current);
    }
  }, []);

  return (
    <ModalProvider ref={modalProviderRef}>
      <div>
        {/* Modals will render inside this div */}
        <div
          ref={modalContainerRef}
          style={{
            backgroundColor: '#f0f0f0',
            width: '100%',
            height: '500px',
            position: 'relative',
            overflow: 'hidden',
          }}
        />

        <button
          onClick={() =>
            alert({ title: 'Notice', content: 'Displayed in custom anchor.' })
          }
        >
          Show Modal
        </button>
      </div>
    </ModalProvider>
  );
}

// Using the useInitializeModal hook
function CustomAnchorWithHookExample() {
  // useInitializeModal hook (manual mode: manual initialization)
  const { initialize, portal } = useInitializeModal({ mode: 'manual' });
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (containerRef.current) {
      // Initialize modal with container element
      initialize(containerRef.current);
    }
  }, [initialize]);

  return (
    <div>
      {/* Container for modal rendering */}
      <div
        ref={containerRef}
        style={{
          backgroundColor: '#f0f0f0',
          width: '100%',
          height: '500px',
        }}
      />

      <button
        onClick={() =>
          alert({ title: 'Notice', content: 'Displayed in custom anchor.' })
        }
      >
        Show Modal
      </button>

      {/* Portal rendering in another location (optional) */}
      <div id="another-container">{portal}</div>
    </div>
  );
}

5. Implementing Toast Messages

You can implement toast message functionality using promise-modal. This example is based on actual implementation in the project:

import React, { type ReactNode, useEffect, useRef } from 'react';

import { css } from '@emotion/css';
import {
  ModalFrameProps,
  alert,
  useDestroyAfter,
  useModalAnimation,
  useModalDuration,
} from '@lerx/promise-modal';

// Toast foreground component definition
const ToastForeground = ({
  id,
  visible,
  children,
  onClose,
  hideAfterMs = 3000,
}) => {
  const modalRef = useRef(null);
  const { duration } = useModalDuration();

  // Auto-close after specified time
  useEffect(() => {
    const timer = setTimeout(onClose, hideAfterMs);
    return () => clearTimeout(timer);
  }, [onClose, hideAfterMs]);

  // Animation handling
  useModalAnimation(visible, {
    onVisible: () => {
      modalRef.current?.classList.add('visible');
    },
    onHidden: () => {
      modalRef.current?.classList.remove('visible');
    },
  });

  // Destroy after closing
  useDestroyAfter(id, duration);

  return (
    <div
      ref={modalRef}
      className={css`
        position: fixed;
        bottom: 20px;
        left: 50%;
        transform: translateX(-50%) translateY(100px);
        opacity: 0;
        transition:
          transform ${duration}ms,
          opacity ${duration}ms;
        &.visible {
          transform: translateX(-50%) translateY(0);
          opacity: 1;
        }
      `}
    >
      {children}
    </div>
  );
};

// Toast message interface
interface ToastProps {
  message: ReactNode;
  duration?: number;
}

// Handler to remove previous toast
let onDestroyPrevToast: () => void;

// Toast display function
export const toast = ({ message, duration = 1250 }: ToastProps) => {
  // Remove previous toast if exists
  onDestroyPrevToast?.();

  return alert({
    content: message,
    ForegroundComponent: (props: ModalFrameProps) => {
      // Store destroy function of new toast
      onDestroyPrevToast = props.onDestroy;
      return <ToastForeground {...props} hideAfterMs={duration} />;
    },
    footer: false, // Hide footer
    dimmed: false, // Disable background dim
    closeOnBackdropClick: false, // Disable closing on backdrop click
  });
};

// Usage example
function showToastExample() {
  toast({
    message: (
      <div
        className={css`
          background-color: #333;
          color: white;
          padding: 12px 24px;
          border-radius: 8px;
          box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        `}
      >
        Task completed!
      </div>
    ),
    duration: 2000, // Auto-close after 2 seconds
  });
}

6. Various Modal Configuration Options

Examples utilizing various modal configuration options:

import { alert, confirm } from '@lerx/promise-modal';

// Basic alert modal
async function showBasicAlert() {
  await alert({
    title: 'Basic Notification',
    content: 'This is an alert modal with default settings.',
  });
}

// Modal settings by type
async function showModalByType() {
  // Success notification
  await alert({
    title: 'Success',
    content: 'Task completed successfully.',
    subtype: 'success',
    dimmed: true,
  });

  // Warning confirmation
  const result = await confirm({
    title: 'Warning',
    content: 'This action cannot be undone. Continue?',
    subtype: 'warning',
    closeOnBackdropClick: false, // Prevent closing by backdrop click
  });

  // Error notification
  await alert({
    title: 'Error',
    content: 'Unable to complete the task.',
    subtype: 'error',
    // Custom footer text
    footer: {
      confirm: 'I understand',
    },
  });
}

// Using manual destroy mode
async function showManualDestroyModal() {
  await alert({
    title: 'Notification',
    content:
      "This modal won't close on backdrop click and requires confirmation button to close.",
    manualDestroy: true, // Enable manual destroy mode
    closeOnBackdropClick: false, // Prevent closing by backdrop click
  });
}

// Using background data
async function showModalWithBackground() {
  await alert({
    title: 'Background Data Usage',
    content: 'This modal passes data to the background component.',
    background: {
      data: 'custom-background-data',
      opacity: 0.8,
      blur: '5px',
    },
  });
}

API Reference

Core Functions

alert(options)

Opens a simple alert modal to display information to the user.

Parameters:

  • options: Alert modal configuration object
    • title?: Modal title (ReactNode)
    • subtitle?: Subtitle (ReactNode)
    • content?: Modal content (ReactNode or component)
    • subtype?: Modal type ('info' | 'success' | 'warning' | 'error')
    • background?: Background settings (ModalBackground object)
    • footer?: Footer settings
      • Function: (props: FooterComponentProps) => ReactNode
      • Object: { confirm?: string; hideConfirm?: boolean }
      • false: Hide footer
    • dimmed?: Whether to dim the background (boolean)
    • manualDestroy?: Enable manual destroy mode (boolean)
    • closeOnBackdropClick?: Whether to close on backdrop click (boolean)
    • ForegroundComponent?: Custom foreground component
    • BackgroundComponent?: Custom background component

Returns: Promise<void> - Resolved when the modal is closed

// Example
await alert({
  title: 'Notification',
  content: 'Content',
  subtype: 'info',
  closeOnBackdropClick: true,
});

confirm(options)

Opens a confirmation modal that requests user confirmation.

Parameters:

  • options: Confirm modal configuration
    • Same options as alert, plus:
    • footer?: Footer settings
      • Function: (props: FooterComponentProps) => ReactNode
      • Object: { confirm?: string; cancel?: string; hideConfirm?: boolean; hideCancel?: boolean }
      • false: Hide footer

Returns: Promise<boolean> - Resolves to true if confirmed, false if canceled

// Example
const result = await confirm({
  title: 'Confirm',
  content: 'Do you want to proceed?',
  footer: {
    confirm: 'Confirm',
    cancel: 'Cancel',
  },
});

prompt<T>(options)

Opens a prompt modal to receive input from the user.

Parameters:

  • options: Prompt modal configuration object
    • Same options as alert, plus:
    • Input: Function to render input field
      • (props: PromptInputProps<T>) => ReactNode
      • props: { value: T; onChange: (value: T) => void }
    • defaultValue?: Default value (T)
    • disabled?: Function to determine if confirm button should be disabled
      • (value: T | undefined) => boolean
    • returnOnCancel?: If true, cancel resolves with the current input value instead of null (boolean)
    • footer?: Footer settings (similar to confirm)

Returns: Promise<T | null> - Resolves to the input value on confirm, or null on cancel

Upgrade note: prompt() now types cancellation explicitly — the return is Promise<T | null> and the disabled callback receives T | undefined. Consumers that previously annotated these as non-null may hit new TypeScript errors on upgrade; the runtime behaviour (cancel resolves null) is unchanged.

// Example
const value = await prompt<string>({
  title: 'Input',
  defaultValue: '',
  Input: ({ value, onChange }) => (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  ),
  disabled: (value) => value.trim() === '',
});

Components

ModalProvider (or BootstrapProvider)

Component that initializes and provides the modal service.

Props:

  • ForegroundComponent?: Custom foreground component
    • (props: WrapperComponentProps) => ReactNode
  • BackgroundComponent?: Custom background component
    • (props: WrapperComponentProps) => ReactNode
  • TitleComponent?: Custom title component
  • SubtitleComponent?: Custom subtitle component
  • ContentComponent?: Custom content component
  • FooterComponent?: Custom footer component
    • (props: FooterComponentProps) => ReactNode
  • options?: Global modal options
    • duration?: Animation duration (milliseconds)
    • backdrop?: Backdrop click handling
    • Other options...
  • context?: Context object to pass to modal components
  • usePathname?: Custom pathname hook function

Handle:

  • initialize: Method to manually initialize modal service
// Example using ref for control
import { useRef } from 'react';
import { ModalProvider, ModalProviderHandle } from '@lerx/promise-modal';

function App() {
  const modalProviderRef = useRef<ModalProviderHandle>(null);

  const handleInitialize = () => {
    modalProviderRef.current?.initialize();
  };

  return (
    <ModalProvider ref={modalProviderRef}>
      <YourApp />
    </ModalProvider>
  );
}

Hooks

useModal

Returns modal handler functions that share the component's lifecycle.

Key Features:

Unlike static handlers (alert, confirm, prompt), modals created with useModal automatically clean up when the component unmounts. This is useful when you want to ensure that modals opened within a specific component are automatically closed when that component is removed.

Returns:

  • alert: Alert modal handler function
  • confirm: Confirm modal handler function
  • prompt: Prompt modal handler function
import { useModal } from '@lerx/promise-modal';

function MyComponent() {
  const modal = useModal();

  const handleShowAlert = async () => {
    // Modal will be automatically cleaned up when MyComponent unmounts
    await modal.alert({
      title: 'Notice',
      content: 'This modal is tied to the component lifecycle.',
    });
  };

  const handleShowConfirm = async () => {
    const result = await modal.confirm({
      title: 'Confirm',
      content: 'Do you want to proceed?',
    });
    console.log('Result:', result);
  };

  return (
    <div>
      <button onClick={handleShowAlert}>Show Alert</button>
      <button onClick={handleShowConfirm}>Show Confirm</button>
    </div>
  );
}

Comparison with Static Handlers:

| Feature | Static Handlers | useModal Hook | | -------------- | ----------------------------- | ---------------------------- | | Lifecycle | Independent of components | Tied to component lifecycle | | Cleanup | Manual cleanup required | Automatic cleanup on unmount | | Usage Location | Anywhere (even outside React) | Inside React components only |

useModalOptions

Read global options for modals.

import { useModalOptions } from '@lerx/promise-modal';

function Component() {
  // ConfigurationContextProps
  const options = useModalOptions();

  // ...
}

useModalDuration

Read modal animation duration.

import { useModalDuration } from '@lerx/promise-modal';

function Component() {
  // duration is 300ms, milliseconds is 300
  const { duration, milliseconds } = useModalDuration();
  // ...
}

useModalBackdrop

Read modal backdrop settings.

import { useModalBackdrop } from '@lerx/promise-modal';

function Component() {
  // backdrop is Color(#000000~#ffffff or rgba(0,0,0,0.5))
  const backdrop = useModalBackdrop();
  // ...
}

useInitializeModal

Initializes the modal service. Usually called automatically by ModalProvider.

import { useInitializeModal } from '@lerx/promise-modal';

function Component() {
  const { initialize } = useInitializeModal();

  // Manually initialize if needed
  useEffect(() => {
    initialize();
  }, [initialize]);

  // ...
}

useSubscribeModal

Subscribes to modal state changes.

import { useSubscribeModal } from '@lerx/promise-modal';

function Component({ modal }) {
  // Component rerenders when modal state changes
  const version = useSubscribeModal(modal);

  // ...
}

useDestroyAfter

Automatically destroys a modal after specified time.

import { useDestroyAfter } from '@lerx/promise-modal';

function Component({ modalId }) {
  // Auto-close modal after 3 seconds
  useDestroyAfter(modalId, 3000);

  // ...
}

useActiveModalCount

Returns the number of currently active modals.

import { useActiveModalCount } from '@lerx/promise-modal';

function Component() {
  const count = useActiveModalCount();

  return (
    <div>
      Currently open modals: {count}
    </div>
  );
}

useModalAnimation

Provides modal animation state and control.

import { useModalAnimation } from '@lerx/promise-modal';

export const Foreground = ({
  id,
  visible,
  children,
  onClose,
  hideAfterMs,
}: PropsWithChildren<ForegroundProps>) => {
  const modalRef = useRef<HTMLDivElement>(null);
  useModalAnimation(visible, {
    onVisible: () => {
      modalRef.current?.classList.add(styles.visible);
    },
    onHidden: () => {
      modalRef.current?.classList.remove(styles.visible);
    },
  });
  ...
}

Type Definitions

The library provides various types for TypeScript compatibility:

ModalFrameProps

Properties passed to the modal frame component.

interface ModalFrameProps<Context = any, B = any> {
  id: number;
  type: 'alert' | 'confirm' | 'prompt';
  alive: boolean;
  visible: boolean;
  initiator: string;
  manualDestroy: boolean;
  closeOnBackdropClick: boolean;
  background?: ModalBackground<B>;
  onConfirm: () => void;
  onClose: () => void;
  onChange: (value: any) => void;
  onDestroy: () => void;
  onChangeOrder: Function;
  context: Context;
}

FooterComponentProps

Properties passed to the footer component.

interface FooterComponentProps {
  onConfirm: () => void;
  onClose: () => void;
  // Other props...
}

PromptInputProps<T>

Properties passed to the input component in prompt modals.

interface PromptInputProps<T> {
  value: T;
  onChange: (value: T) => void;
}

Additional types provided:

  • ModalBackground: Modal background settings type
  • AlertContentProps: Alert modal content component props
  • ConfirmContentProps: Confirm modal content component props
  • PromptContentProps: Prompt modal content component props
  • WrapperComponentProps: Modal wrapper component props

Advanced Usage Examples

1. Nested Modals (Opening Modals inside Other Modals)

You can open modals inside other modals to create complex user interactions.

import { alert, confirm, prompt } from '@lerx/promise-modal';

// Multi-step modal workflow example
async function multiStepProcess() {
  // First modal: confirm to proceed
  const shouldProceed = await confirm({
    title: 'Start Task',
    content: 'This task involves multiple steps. Do you want to continue?',
    footer: {
      confirm: 'Proceed',
      cancel: 'Cancel',
    },
  });

  if (!shouldProceed) return;

  // Second modal: get user input
  const userName = await prompt<string>({
    title: 'User Information',
    content: 'Please enter your name.',
    defaultValue: '',
    Input: ({ value, onChange }) => (
      <input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder="Name"
      />
    ),
  });

  if (!userName) return;

  // Third modal: final confirmation
  const confirmed = await confirm({
    title: 'Final Confirmation',
    content: `${userName}, are you sure you want to proceed?`,
    subtype: 'warning',
  });

  if (confirmed) {
    // Last modal: completion notification
    await alert({
      title: 'Complete',
      content: `${userName}, the task has been completed successfully.`,
      subtype: 'success',
    });
  }
}

// Opening modal from within a footer
async function nestedModalInFooter() {
  const result = await confirm({
    title: 'Confirmation Required',
    content: 'Do you want to proceed with this task?',
    // Custom footer that opens another modal
    footer: ({ onConfirm, onClose }) => {
      const handleConfirm = async () => {
        // Open another modal when confirm button is clicked
        const isConfirmed = await confirm({
          title: 'Final Confirmation',
          content: 'Are you really sure? This action cannot be undone.',
          closeOnBackdropClick: false,
          // Apply different design to second modal
          ForegroundComponent: CustomForegroundComponent,
        });

        // Process first modal result based on second modal result
        if (isConfirmed) onConfirm();
        else onClose();
      };

      return (
        <div>
          <button onClick={onClose}>Cancel</button>
          <button onClick={handleConfirm}>Next Step</button>
        </div>
      );
    },
  });

  if (result) {
    console.log('Proceeding with the task.');
  }
}

// Opening modal from within a prompt modal
async function promptWithNestedModal() {
  const value = await prompt<{ title: string; description: string }>({
    title: 'Create Content',
    defaultValue: { title: '', description: '' },
    Input: ({ value, onChange }) => {
      // Show help modal when help button is clicked
      const showHelp = () => {
        alert({
          title: 'Help',
          content: 'Enter a title and description. Title is required.',
        });
      };

      return (
        <div>
          <div>
            <label>Title</label>
            <input
              value={value.title}
              onChange={(e) => onChange({ ...value, title: e.target.value })}
            />
          </div>
          <div>
            <label>Description</label>
            <textarea
              value={value.description}
              onChange={(e) =>
                onChange({ ...value, description: e.target.value })
              }
            />
          </div>
          <button type="button" onClick={showHelp}>
            Help
          </button>
        </div>
      );
    },
    disabled: (value) => !value.title,
  });

  console.log('Input value:', value);
}

License

This repository is licensed under the MIT License. Please refer to the LICENSE file for details.


Contact

For inquiries or suggestions related to the project, please create an issue.