All components

Responsive Modal Default

Responsive Modal Default is a React and Tailwind CSS overlay component from UI Layouts, licensed MIT. Copy it and paste it into your project.

UI Layouts MIT overlays

What it is

Responsive Modal Default is a React and Tailwind CSS overlay component from UI Layouts, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

Needs

npm i motion lucide-react

Licence

This component comes from UI Layouts and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Responsive Modal Default

./components/ui/responsive-modal.tsx
'use client';

import { cn } from '@/lib/utils';
import { X } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import React, { createContext, type ReactNode, useContext, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { Drawer as VaulDrawer } from 'vaul';

interface DrawerContextProps {
  open: boolean;
  setOpen: (open: boolean) => void;
}

const DrawerContext = createContext<DrawerContextProps | undefined>(undefined);

export const useResponsiveModal = () => {
  const context = useContext(DrawerContext);
  if (!context) {
    throw new Error('useResponsiveModal must be used within a ResponsiveModalProvider');
  }
  return context;
};

interface ResponsiveModalProps {
  children: ReactNode;
  open?: boolean;
  setOpen?: (open: boolean) => void;
  classname?: string;
  clsBtnClassname?: string;
}

export function ResponsiveModal({
  children,
  open: controlledOpen,
  setOpen: controlledSetOpen,
  classname,
  clsBtnClassname,
}: ResponsiveModalProps) {
  const [internalOpen, setInternalOpen] = useState(false);
  const [isDesktop, setIsDesktop] = useState(false);
  const [mounted, setMounted] = useState(false);

  const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
  const setOpen = controlledSetOpen || setInternalOpen;

  useEffect(() => {
    setMounted(true);
    const mediaQuery = window.matchMedia('(min-width: 768px)');
    const handleMediaChange = (event: MediaQueryListEvent) => {
      setIsDesktop(event.matches);
    };

    setIsDesktop(mediaQuery.matches);
    mediaQuery.addEventListener('change', handleMediaChange);
    return () => mediaQuery.removeEventListener('change', handleMediaChange);
  }, []);

  const trigger = React.Children.toArray(children).find(
    (child: any) => child.type === ResponsiveModalTrigger
  );
  const content = React.Children.toArray(children).filter(
    (child: any) => child.type !== ResponsiveModalTrigger
  );

  const desktopModal =
    isDesktop && mounted
      ? createPortal(
          <AnimatePresence>
            {open && (
              <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                className='fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-xs cursor-zoom-out'
                onClick={() => setOpen(false)}
              >
                <motion.div
                  initial={{ scale: 0.9, opacity: 0 }}
                  animate={{ scale: 1, opacity: 1 }}
                  exit={{ scale: 0.9, opacity: 0 }}
                  transition={{ type: 'spring', duration: 0.5 }}
                  onClick={(e) => e.stopPropagation()}
                  className={cn(
                    'relative w-full max-w-md border bg-white dark:bg-neutral-900 rounded-lg cursor-default',
                    classname
                  )}
                >
                  <button
                    className={cn(
                      'absolute top-2 right-2 bg-primary text-background p-2 border z-1 rounded-md',
                      clsBtnClassname
                    )}
                    onClick={() => setOpen(false)}
                    aria-label='Close'
                  >
                    <X />
                  </button>
                  {content}
                </motion.div>
              </motion.div>
            )}
          </AnimatePresence>,
          document.body
        )
      : null;

  return (
    <DrawerContext.Provider value={{ open, setOpen }}>
      {trigger}

      {desktopModal}

      {!isDesktop && (
        <VaulDrawer.Root shouldScaleBackground open={open} onOpenChange={setOpen}>
          <VaulDrawer.Portal>
            <VaulDrawer.Overlay className='fixed inset-0 z-50 bg-white/50 dark:bg-black/50 backdrop-blur-xs' />
            <VaulDrawer.Content className='fixed bottom-0 left-0 z-50 w-full max-h-[96%] bg-white dark:bg-neutral-900'>
              <div className='mx-auto w-16 h-[0.30rem] shrink-0 rounded-full bg-neutral-600 my-4' />
              <div className='w-full mx-auto max-h-[96vh] overflow-auto px-4 pb-2'>{content}</div>
            </VaulDrawer.Content>
          </VaulDrawer.Portal>
        </VaulDrawer.Root>
      )}
    </DrawerContext.Provider>
  );
}

export function ResponsiveModalContent({
  children,
  className,
}: {
  children: ReactNode;
  className?: string;
}) {
  return <div className={cn('', className)}>{children}</div>;
}

interface ResponsiveModalTriggerProps {
  children: ReactNode;
  asChild?: boolean;
}

export function ResponsiveModalTrigger({ children, asChild = false }: ResponsiveModalTriggerProps) {
  const { setOpen } = useResponsiveModal();

  if (asChild) {
    // Clone the child element and add onClick handler
    const child = React.Children.only(children) as React.ReactElement<any>;
    return React.cloneElement(child, {
      ...child.props,
      onClick: (e: React.MouseEvent) => {
        setOpen(true);
        // Call original onClick if it exists
        child.props.onClick?.(e);
      },
    } as any);
  }

  return <div onClick={() => setOpen(true)}>{children}</div>;
}
./registry/components/modal/responsive-modal.tsx
'use client';
import {
  ResponsiveModal,
  ResponsiveModalContent,
  ResponsiveModalTrigger,
} from '@/components/ui/responsive-modal';
import { useMediaQuery } from '@/hooks/use-media-query';
import { motion } from 'motion/react';
import { useState } from 'react';
import { toast } from 'sonner';

export default function MyDrawer() {
  // const [drawerOpen, setDrawerOpen] = useState(false);

  const handleSubmit = () => {
    toast.success('Profile image updated successfully');
    // setDrawerOpen(false);
  };
  return (
    <>
      {/* <div className='flex justify-between'>
        <motion.button
          onClick={() => setDrawerOpen(true)}
          whileTap={{ scale: 0.9 }}
          className='inline-flex h-12 w-fit mx-auto animate-background-shine items-center justify-center rounded-md  border-2 dark:border-[#656fe2] border-[#c0c6fc] dark:bg-[linear-gradient(110deg,#1e2a78,45%,#3749be,55%,#1e2a78)] bg-[linear-gradient(110deg,#3d5af1,45%,#5471ff,55%,#3d5af1)] bg-size-[200%_100%] dark:hover:border-white px-6 font-medium text-white dark:text-white transition-colors focus:outline-hidden focus:ring-2 dark:focus:ring-neutral-400 focus:ring-offset-2 focus:ring-offset-neutral-50'
        >
          Open Dialog
        </motion.button>
      </div> */}
      {/* <ResponsiveModal open={drawerOpen} setOpen={setDrawerOpen}> */}
      <div className='flex justify-center items-center'>
        <ResponsiveModal>
          <ResponsiveModalTrigger asChild>
            <motion.button
              whileTap={{ scale: 0.9 }}
              className='inline-flex h-12 w-fit mx-auto animate-background-shine items-center justify-center rounded-md  border-2 dark:border-[#656fe2] border-[#c0c6fc] dark:bg-[linear-gradient(110deg,#1e2a78,45%,#3749be,55%,#1e2a78)] bg-[linear-gradient(110deg,#3d5af1,45%,#5471ff,55%,#3d5af1)] bg-size-[200%_100%] dark:hover:border-white px-6 font-medium text-white dark:text-white transition-colors focus:outline-hidden focus:ring-2 dark:focus:ring-neutral-400 focus:ring-offset-2 focus:ring-offset-neutral-50'
            >
              Open Dialog
            </motion.button>
          </ResponsiveModalTrigger>
          <ResponsiveModalContent>
            <figure className='flex flex-col space-y-1.5 text-center  h-fit dark:bg-neutral-800 md:p-4 p-6'>
              <h1 className='font-medium text-2xl'>Update Profile Image</h1>
              <p className='text-sm text-muted-foreground w-4/5 mx-auto'>
                Upload a new profile image or remove the current one.
              </p>
              <div data-vaul-no-drag className='py-4 space-y-4'>
                <span className='relative flex justify-center overflow-hidden rounded-xl w-full '>
                  <span className='grid place-content-center h-40 w-40 rounded-xl dark:bg-neutral-950 bg-muted'>
                    JP
                  </span>
                </span>
                <div className='mb-3'>
                  <input
                    className='w-full border file:p-2 file:bg-black  file:border-none  file:text-white rounded-xs overflow-hidden'
                    type='file'
                    id='formFile'
                  />
                </div>
                <button
                  type='submit'
                  onClick={handleSubmit}
                  className='w-full rounded-xs dark:bg-white bg-black  p-2 dark:text-black text-white'
                >
                  Submit
                </button>
              </div>
            </figure>
          </ResponsiveModalContent>
        </ResponsiveModal>
      </div>
    </>
  );
}

Similar components