Giter Club home page Giter Club logo

pushmodal's Introduction

hero

Installation

pnpm add pushmodal

We take for granted that you already have @radix-ui/react-dialog installed. If not ➡️ pnpm add @radix-ui/react-dialog

Usage

1. Create a modal

When creating a dialog/sheet/drawer you need to wrap your component with the <(Dialog|Sheet|Drawer)Content> component. But skip the Root since we do that for you.

// file: src/modals/modal-example.tsx
import { DialogContent } from '@/ui/dialog' // shadcn dialog

// or any of the below
// import { SheetContent } from '@/ui/sheet' // shadcn sheet
// import { DrawerContent } from '@/ui/drawer' // shadcn drawer

export default function ModalExample({ foo }: { foo: string }) {
  return (
    <DialogContent>
      Your modal
    </DialogContent>
  )
}

2. Initialize your modals

// file: src/modals/index.tsx (alias '@/modals')
import ModalExample from './modal-example'
import SheetExample from './sheet-example'
import DrawerExample from './drawer-examle'
import { createPushModal } from 'pushmodal'
import { Drawer } from '@/ui/drawer' // shadcn drawer

export const {
  pushModal,
  popModal,
  popAllModals,
  replaceWithModal,
  useOnPushModal,
  onPushModal,
  ModalProvider
} = createPushModal({
  modals: {
    // Short hand
    ModalExample,
    SheetExample,

    // Longer definition where you can choose what wrapper you want
    // Only needed if you don't want `Dialog.Root` from '@radix-ui/react-dialog'
    // shadcn drawer needs a custom Wrapper
    DrawerExample: {
      Wrapper: Drawer,
      Component: DrawerExample
    }
  },
})

How we usually structure things

src
├── ...
├── modals
│   ├── modal-example.tsx
│   ├── sheet-example.tsx
│   ├── drawer-examle.tsx
│   ├── ... more modals here ...
│   └── index.tsx
├── ...
└── ...

3. Add the <ModalProvider /> to your root file.

import { ModalProvider } from '@/modals' 

export default function App({ children }: { children: React.ReactNode }) {
  return (
    <>
      {/* Notice! You should not wrap your children */}
      <ModalProvider />
      {children}
    </>
  )
}

4. Use pushModal

pushModal can have 1-2 arguments

  1. name - name of your modal
  2. props (might be optional) - props for your modal, types are infered from your component!
import { pushModal } from '@/modals' 

export default function RandomComponent() {
  return (
    <div>
      <button onClick={() =>  pushModal('ModalExample', { foo: 'string' })}>
        Open modal
      </button>
      <button onClick={() => pushModal('SheetExample')}>
        Open Sheet
      </button>
      <button onClick={() => pushModal('DrawerExample')}>
        Open Drawer
      </button>
    </div>
  )
}

4. Closing modals

You can close a modal in three different ways:

  • popModal() - will pop the last added modal
  • popModal('Modal1') - will pop the last added modal with name Modal1
  • popAllModals() - will close all your modals

5. Replacing current modal

Replace the last pushed modal. Same interface as pushModal.

replaceWithModal('SheetExample', { /* Props if any */ })

6. Using events

You can listen to events with useOnPushModal (inside react component) or onPushModal (or globally).

The event receive the state of the modal (open/closed), the modals name and props. You can listen to all modal changes with * or provide a name of the modal you want to listen on.

Inside a component

import { useCallback } from 'react'
import { useOnPushModal } from '@/modals'

// file: a-react-component.tsx
export default function ReactComponent() {
  // listen to any modal open/close
  useOnPushModal('*', 
    useCallback((open, props, name) => {
      console.log('is open?', open);
      console.log('props from component', props);
      console.log('name', name);
    }, [])
  )
  
  // listen to `ModalExample` open/close
  useOnPushModal('ModalExample', 
    useCallback((open, props) => {
      console.log('is `ModalExample` open?', open);
      console.log('props for ModalExample', props);
    }, [])
  )
}

Globally

import { onPushModal } from '@/modals'

const unsub = onPushModal('*', (open, props, name) => {
  // do stuff
})

Responsive rendering (mobile/desktop)

In some cases you want to show a drawer on mobile and a dialog on desktop. This is possible and we have created a helper function to get you going faster. createResponsiveWrapper 💪

// path: src/modals/dynamic.tsx
import { createResponsiveWrapper } from 'pushmodal'
import { Dialog, DialogContent } from '@/ui/dialog'; // shadcn dialog
import { Drawer, DrawerContent } from '@/ui/drawer'; // shadcn drawer

export default createResponsiveWrapper({
  desktop: {
    Wrapper: Dialog,
    Content: DialogContent,
  },
  mobile: {
    Wrapper: Drawer,
    Content: DrawerContent,
  },
  breakpoint: 640,
});

// path: src/modals/your-modal.tsx
import * as Dynamic from './dynamic'

export default function YourModal() {
  return (
    <Dynamic.Content>
      Drawer in mobile and dialog on desktop 🤘
    </Dynamic.Content>
  )
}

// path: src/modals/index.ts
import * as Dynamic from './dynamic'
import YourModal from './your-modal'
import { createPushModal } from 'pushmodal'

export const {
  pushModal,
  popModal,
  popAllModals,
  replaceWithModal,
  useOnPushModal,
  onPushModal,
  ModalProvider
} = createPushModal({
  modals: {
    YourModal: {
      Wrapper: Dynamic.Wrapper,
      Component: YourModal
    }
  },
})

Issues / Limitations

Issues or limitations will be listed here.

Contributors

pushmodal's People

Contributors

lindesvard avatar nicholascostadev avatar lleifermann avatar erjanmx avatar iswilljr avatar

Stargazers

Joonas Meriläinen avatar Alejandro Gutierrez Barcenilla avatar Jason Rodrigues avatar Luke avatar  avatar Emre avatar Ruie Dela Peña avatar Eric Chernuka avatar Brandin Canfield avatar Mohammed Zeeshan avatar Tobi avatar Lennard Zündorf avatar Simon Betton avatar Puru Dahal avatar Samuel Ribeiro avatar Eliasegg avatar Chetan avatar Pedro H. avatar Andrés Chacón Miranda avatar Dale Inverarity avatar Taylor Facen avatar Fahmi Idris avatar Muhammad Ilham Jaya  avatar Louis avatar Jacob McKenney avatar Paulius Friedt avatar Fred Carlsen avatar Tom Bates avatar Matt Wood avatar Mahmoud Alhussen avatar João Fernandes avatar lizunlong avatar Nahean Fardous avatar Fellipe Pinheiro avatar Venkatesh avatar Tommy Marshall avatar Sumit Gautam avatar  avatar Hemik Vijay avatar Spencer McKenney avatar Daniel Sam avatar  avatar K. Marco BEKOUTARE avatar Jesper van den Munckhof avatar MCode-Team avatar Abhishek Butola avatar An Pham avatar Mehmet Yiğit Yalım avatar Clark Young avatar  avatar  avatar Tanmay Agrawal avatar Anderson Henrique avatar Alex avatar Eric Ruiz avatar Ali Azlan avatar Rychillie avatar Francis Miyoba avatar Marcos Viana avatar  avatar Steven Tey avatar  avatar Vlad Prodan avatar Franklin Martinez  avatar Geovane Santana avatar Mislav avatar Jimin (Aaron) Son avatar Waiyaki avatar Pablo Hernández avatar Lokesh avatar Sunday Ibrahim Ijai avatar Patrick avatar Dustin Karp avatar Johnie Hjelm avatar Pablo Hdez avatar Vince Fulco--Bighire.tools avatar Pontus Abrahamsson avatar antonin caudron avatar Marc Seitz avatar Jon Espen Kvisler avatar Mohammad H. Sattarian avatar Aaron Hodges avatar Axel Talmet avatar LaoHan avatar Elijah avatar Paul Brissaud avatar Jon Insley avatar Digital avatar Dan Conger avatar Marlon Lückert avatar Kjartan Hrafnkelsson avatar Tyler Forest-Hauser avatar Nemanja avatar Hegar Garcia avatar Shashwat avatar 81NARY avatar Evan Fleischman avatar Xecei avatar  avatar Thomas Gak-Deluen avatar

pushmodal's Issues

Prevent dialog from closing

Hi, thanks for this amazing library! I was trying to convert my app to using this library for handling the modal state but inevitable I have hit a barrier.

I was trying to stack a dialog with an alert dialog from radix ui. I wanted to prevent closing the dialog when trying to close it with a dirty form inside it and show the alert dialog instead. But I can't do that since there's no access to the controlled state of the modal (specifically the onOpenChange of the Root)

If someone can find a way to implement this kind of flow without modifications to the library I would love to know. The only thing that comes to my mind would be to manage the "closable" state in the StateItem like it's being done with the closedAt property. And than pass a "setClosable" it in the Component so it can be changed within the modal.

State hook

It would be nice to be able to subscribe to the state of a modal by key. My use-case is to reset a state whenever a modal is opened. I see you're using state: 'open' | 'close' but it would probably best to reuse open: boolean Radix's API. Something like:

// no key for any opened modal, or could export another hook useAllModalsState
// useModalState or useModalOpenState or useModalOpen
const useModalState = (key?: T) => {
  // return the open state of the modal with that key
  return open
}

That may require the provider to have children, which would be a more intuitive API since most providers are designed like that. You can prevent the re-rendering by nesting the ModalProvider content one level down so only that child will be re-rendered on modal state change:

const ModalContext = React.createContext(...)

const Modal = () => {} // previous ModalProvider implementation

const ModalProvider = () => {  
  return (
    <ModalContext.Provider>
      <Modal />
      {children}
    </ModalContext.Provider>
  )
}

popModal doesn't animate dialog & drawer (with my setup)

Hi,

I implemented pushmodal in the following way.

Responsive modal

export function ModalResponsive({
  trigger,
  title,
  description,
  children = "Modal content",
  open: openDefault = false,
  onClose,
}) {
  const [open, setOpen] = React.useState(openDefault);
  const isDesktop = useIsDesktop();

  React.useEffect(() => {
    if (!open) {
      onClose && onClose();
    }
  }, [open]);
  return isDesktop ? (
    <Dialog open={open} onOpenChange={setOpen}>
      {trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
      <DialogContent>
        <DialogHeader className={css({ mb: "3" })}>
          <DialogTitle className={css({ textAlign: "left" })}>
            {title}
          </DialogTitle>
          <DialogDescription className={css({ textAlign: "left" })}>
            {description}
          </DialogDescription>
        </DialogHeader>
        {children}
      </DialogContent>
    </Dialog>
  ) : (
    <Drawer open={open} onOpenChange={setOpen}>
      {trigger && <DrawerTrigger asChild>{trigger}</DrawerTrigger>}
      <DrawerContent
        className={css({
          px: "4",
        })}>
        <DrawerHeader className={css({ textAlign: "left" })}>
          <DrawerTitle>{title}</DrawerTitle>
          <DrawerDescription>{description}</DrawerDescription>
        </DrawerHeader>
        {children}
        <DrawerFooter className={css({ pt: "2" })}>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  );
}

Modal

export default function ModalExample({
  title,
  text,
  customButton,
  onConfirm,
  onCancel,
}) {
  return (
    <>
      <ModalResponsive
        open
        title={title}>
        {text && <div>{text}</div>}
        <div>
          <Button
            variant="outline"
            onClick={() => {
              popModal("Modal");
              onCancel();
            }}>
            Cancel
          </Button>
          {customButton}
          <Button
            variant="destructive"
            onClick={() => {
              popModal("Modal");
              onConfirm();
            }}>
            Yes
          </Button>
        </div>
      </ModalResponsive>
    </>
  );
}

The problem I have is that I declare the buttons in the ModalExample that control the open/close state of the modal.
When I first pushmodal to show the ModalExample, it correctly animates. But when I click on the Cancel button and popModal("Modal") triggers, it closes the modal without the nice effect (both with dialog and drawer).
If in ModalResponsive I add the DrawerClose and click on it, it animates correctly:

<DrawerClose asChild>
    <Button variant="default">Cancel</Button>
</DrawerClose>

Do you know what could I change so that it animates correctly? I could pass the buttons to the ModalResponsive and wrap them with DrawerClose/DialogClose instead of Button but, in that case I would not leverage popModal at all to close my modals (which defeats the purpose of using pushmodal in the first place).

Turbopack error - "It might have been deleted in an HMR update"

Hello,

Your lib is awesome and I've found myself unable to work without it!
I found an issue using pushmodal with nextjs and turbopack.

Minimal reproduction link:
https://github.com/louisthomaspro/pushmodal-turbopack-error

  1. pnpm i
  2. pnpm dev --turbo
  3. When navigating to http://localhost:3000/, you will see the below error:
Error: Module [project]/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/image.js [app-client] (ecmascript) was instantiated because it was required from module [project]/app/page.tsx [app-client] (ecmascript), but the module factory is not available. It might have been deleted in an HMR update.

image

I have no issue using webpack instead of turbopack. But turbopack is so much faster and nearly stable!
Anyone has this issue ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.