pushmodal

repository·main·Indexed 42 days ago

https://github.com/lindesvard/pushmodal

A lightweight React library for managing a stack of modals, sheets, and drawers, specifically designed to handle shadcn dialog, sheet, and drawer components. It provides a centralized API to programmatically push, pop, and replace modals via createPushModal, and includes utilities like createResponsiveWrapper for breakpoint-based UI switching. Version 1.0.3 requires @radix-ui/react-dialog as a peer dependency.

Tokens
2.8K
Snippets
10
Records
10
Agent score
3%

What's inside pushmodal

  1. Setup the ModalProvider

    main

    Add the <ModalProvider /> to your application's root file. Note that you should not wrap your children with the provider; it should be a sibling to your main application content.

    import { ModalProvider } from '@/modals' 
    
    export default function App({ children }: { children: React.ReactNode }) {
      return (
        <>
          <ModalProvider />
          {children}
        </>
      )
    }
  2. Create a modal component

    main

    When defining a modal component, wrap your content with the Content component from your UI library (e.g., DialogContent, SheetContent, or DrawerContent). Do not include the Root component, as pushmodal handles the root lifecycle for you.

    import { DialogContent } from '@/ui/dialog' // shadcn dialog
    
    export default function ModalExample({ foo }: { foo: string }) {
      return (
        <DialogContent>
          Your modal
        </DialogContent>
      )
    }
  3. Install pushmodal

    main

    Install pushmodal using pnpm. Note that this project requires @radix-ui/react-dialog to be installed as a peer dependency.

    pnpm add pushmodal
    # If you don't have radix-ui dialog installed:
    pnpm add @radix-ui/react-dialog
  4. Listen to modal events with useOnPushModal and onPushModal

    main

    You can subscribe to modal lifecycle events (open/close). You can listen to all modals using the '*' wildcard or target a specific modal by name. The callback receives (open, props, name).

    • useOnPushModal(name, callback): A React hook for listening to events inside a component.
    • onPushModal(name, callback): A global function for listening to events outside of the React lifecycle.
    // Inside a component
    import { useOnPushModal } from '@/modals'
    
    useOnPushModal('*', (open, props, name) => {
      console.log('Modal state changed:', open, name);
    });
    
    // Globally
    import { onPushModal } from '@/modals'
    
    const unsub = onPushModal('ModalExample', (open, props) => {
      console.log('ModalExample is open?', open);
    });
  5. Initialize pushmodal with createPushModal

    main

    Use createPushModal to register your modal components and generate the API surface. You can provide modals using a shorthand (name mapping to component) or a long-form definition if you need to specify a custom Wrapper (useful for components like shadcn Drawer that require a specific root).

    import ModalExample from './modal-example'
    import SheetExample from './sheet-example'
    import DrawerExample from './drawer-examle'
    import { createPushModal } from 'pushmodal'
    import { Drawer } from '@/ui/drawer'
    
    export const {
      pushModal,
      popModal,
      popAllModals,
      replaceWithModal,
      useOnPushModal,
      onPushModal,
      ModalProvider
    } = createPushModal({
      modals: {
        // Shorthand
        ModalExample,
        SheetExample,
    
        // Long-form for custom wrappers
        DrawerExample: {
          Wrapper: Drawer,
          Component: DrawerExample
        }
      },
    })
  6. Push, Pop, and Replace modals

    main

    Control the modal stack using the following methods:

    • pushModal(name, props?): Opens a modal by name. Props are automatically typed based on the component definition.
    • popModal(name?): Closes the last added modal. If a name is provided, it pops the last added modal with that specific name.
    • popAllModals(): Closes all currently open modals.
    • replaceWithModal(name, props?): Replaces the current top-most modal with a new one.
    import { pushModal, popModal, popAllModals, replaceWithModal } from '@/modals' 
    
    // Open
    pushModal('ModalExample', { foo: 'string' })
    
    // Close
    popModal()
    popModal('Modal1')
    popAllModals()
    
    // Replace
    replaceWithModal('SheetExample', { /* Props */ })
  7. Create responsive modals with createResponsiveWrapper

    main

    Use createResponsiveWrapper to render different UI components (e.g., a Dialog on desktop and a Drawer on mobile) based on a breakpoint. This helper returns a Wrapper and a Content component.

    import { createResponsiveWrapper } from 'pushmodal'
    import { Dialog, DialogContent } from '@/ui/dialog'
    import { Drawer, DrawerContent } from '@/ui/drawer'
    
    const Dynamic = createResponsiveWrapper({
      desktop: {
        Wrapper: Dialog,
        Content: DialogContent,
      },
      mobile: {
        Wrapper: Drawer,
        Content: DrawerContent,
      },
      breakpoint: 640,
    });
    
    // Usage in a modal component
    export default function YourModal() {
      return (
        <Dynamic.Content>
          Responsive Content
        </Dynamic.Content>
      )
    }
  8. Initialize the modal system with createPushModal

    main

    Use createPushModal to define your modal registry and generate the necessary components and functions to manage them. You must provide a modals object where each key represents a modal name and the value is either the component itself or a configuration object containing a Wrapper and the Component.

    Modal Configuration Options:

    • Component: The React component to render inside the modal.
    • Wrapper (optional): A component that handles the modal container/overlay logic. It must accept open, onOpenChange, children, and an optional defaultOpen prop. If not provided, the system defaults to @radix-ui/react-dialog's Root.

    To use the system, you must render the returned ModalProvider at the root of your application (or a high-level component) to enable the modal stack.

    import { createPushModal } from './path-to-factory';
    
    interface MyModalProps {
      title: string;
    }
    
    const { ModalProvider, pushModal, popModal } = createPushModal({
      modals: {
        // Simple component usage
        alert: ({ title }: MyModalProps) => <div>{title}</div>,
        
        // Advanced usage with custom Wrapper
        settings: {
          Wrapper: ({ children, open, onOpenChange }) => (
            <div className={open ? 'visible' : 'hidden'}>
              <button onClick={() => onOpenChange(false)}>Close</button>
              {children}
            </div>
          ),
          Component: ({ user }: { user: string }) => <div>Settings for {user}</div>
        }
      }
    });
    
    // In your App root:
    function App() {
      return (
        <ModalProvider>
          <YourContent />
        </ModalProvider>
      );
    }
  9. Manage modals using push, pop, and replace

    main

    Once initialized via createPushModal, you can control the modal stack using the following functions:

    • pushModal(name, props?): Adds a new modal to the top of the stack. If props are provided, they are passed to the component.
    • popModal(name?): Closes a specific modal by name. If no name is provided, it closes the most recent (top-most) modal.
    • replaceWithModal(name, props?): Closes the current top-most modal and immediately pushes a new one in its place.
    • popAllModals(): Closes all currently open modals in the stack.
    // Push a modal
    pushModal('alert', { title: 'Hello World' });
    
    // Close the top modal
    popModal();
    
    // Close a specific modal by name
    popModal('settings');
    
    // Replace the current modal with a new one
    replaceWithModal('settings', { user: 'Alice' });
    
    // Clear the entire stack
    popAllModals();
  10. Listen to modal changes with useOnPushModal

    main

    You can react to modals opening or closing using the useOnPushModal hook. This is useful for triggering side effects when a specific modal enters or leaves the stack.

    Arguments:

    • name: The name of the modal to watch, or '*' to watch all modal changes.
    • callback: A function called whenever the state changes. It receives (open, props, name).

    Note: The hook returns a cleanup function via useEffect, so it should be used inside a React component.

    // Inside a component:
    useOnPushModal('alert', (open, props, name) => {
      if (open) {
        console.log(`Modal ${name} opened with props:`, props);
      } else {
        console.log(`Modal ${name} closed`);
      }
    });
    
    // Watch all modals:
    useOnPushModal('*', (open, props, name) => {
      console.log(`Global change: ${name} is now ${open ? 'open' : 'closed'}`);
    });