Todos os componentes

Directional Drawer_default

Directional Drawer_default é um componente de sobreposição para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

UI Layouts MIT overlays

O que é

Directional Drawer_default é um componente de sobreposição para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Precisa de

npm i motion lucide-react

Licença

Este componente vem de UI Layouts e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Directional Drawer_default

./components/ui/directional-drawer.tsx
'use client';
import { cn } from '@/lib/utils';
import { X } from 'lucide-react';
import React, { createContext, type ReactNode, useContext, useEffect, useState } from 'react';
import { Drawer as VaulSidebar } from 'vaul';

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

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

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

interface DirectionalDrawerProps {
  children: ReactNode;
  open?: boolean;
  setOpen?: (open: boolean) => void;
  direction?: 'left' | 'right' | 'top' | 'bottom';
  outsideClose?: boolean;
  className?: string;
}

export function DirectionalDrawer({
  children,
  open: controlledOpen,
  setOpen: controlledSetOpen,
  direction = 'left',
  outsideClose = true,
  className,
}: DirectionalDrawerProps) {
  const [internalOpen, setInternalOpen] = useState(false);
  const [isDesktop, setIsDesktop] = useState(false);

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

  useEffect(() => {
    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 === DrawerTrigger
  );
  const content = React.Children.toArray(children).filter(
    (child: any) => child.type !== DrawerTrigger
  );

  // Helper function to get positioning and sizing classes
  const getDirectionClasses = () => {
    switch (direction) {
      case 'right':
        return {
          position: 'right-0 bottom-0',
          size: outsideClose ? 'sm:w-[450px] w-[90%] h-full' : 'w-full h-full',
          border: 'border-l',
          handlePosition: 'top-[40%] left-2',
          handleSize: 'h-16 w-[0.30rem]',
        };
      case 'top':
        return {
          position: 'top-0 left-0',
          size: outsideClose ? 'w-full sm:h-[450px] h-[90%]' : 'w-full h-full',
          border: 'border-b',
          handlePosition: 'bottom-2 left-[40%]',
          handleSize: 'w-16 h-[0.30rem]',
        };
      case 'bottom':
        return {
          position: 'bottom-0 left-0',
          size: outsideClose ? 'w-full sm:h-[450px] h-[90%]' : 'w-full h-full',
          border: 'border-t',
          handlePosition: 'top-2 left-[40%]',
          handleSize: 'w-16 h-[0.30rem]',
        };
      case 'left':
      default:
        return {
          position: 'left-0 bottom-0',
          size: outsideClose ? 'sm:w-[450px] w-[90%] h-full' : 'w-full h-full',
          border: 'border-r',
          handlePosition: 'top-[40%] right-2',
          handleSize: 'h-16 w-[0.30rem]',
        };
    }
  };

  const directionClasses = getDirectionClasses();
  const vaulDirection =
    direction === 'right'
      ? 'right'
      : direction === 'top'
        ? 'top'
        : direction === 'bottom'
          ? 'bottom'
          : 'left';

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

      <VaulSidebar.Root
        open={open}
        direction={vaulDirection}
        onOpenChange={setOpen}
        dismissible={isDesktop ? false : true}
      >
        <VaulSidebar.Portal>
          <VaulSidebar.Overlay
            className='fixed inset-0 dark:bg-black/40 bg-white/50 backdrop-blur-xs z-50'
            onClick={() => setOpen(false)}
          />
          <VaulSidebar.Content
            className={cn(
              `${directionClasses.border} z-50 ${directionClasses.size} fixed ${directionClasses.position} ${
                outsideClose ? 'dark:bg-zinc-950 bg-zinc-100' : ''
              }`,
              className
            )}
          >
            <div
              className={`${
                outsideClose
                  ? 'w-full h-full'
                  : `dark:bg-neutral-900 relative bg-white ${directionClasses.border} ${directionClasses.size}`
              }`}
            >
              {isDesktop ? (
                <button
                  className='flex justify-end w-full absolute right-2 top-2'
                  onClick={() => setOpen(false)}
                >
                  <X />
                </button>
              ) : (
                <div
                  className={`absolute ${directionClasses.handlePosition} mx-auto ${directionClasses.handleSize} shrink-0 rounded-full bg-neutral-600 my-4`}
                />
              )}
              {content}
            </div>
          </VaulSidebar.Content>
        </VaulSidebar.Portal>
      </VaulSidebar.Root>
    </DrawerContext.Provider>
  );
}

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

export function DrawerTrigger({ children }: { children: ReactNode }) {
  const { setOpen } = useDirectionalDrawer();
  return <div onClick={() => setOpen(true)}>{children}</div>;
}
./registry/components/drawer/directional-drawer-default.tsx
'use client';
import {
  DirectionalDrawer,
  DrawerContent,
  DrawerTrigger,
} from '@/components/ui/directional-drawer';
import { Edit } from 'lucide-react';
import { motion } from 'motion/react';
import Image from 'next/image';

export default function DirectionalDrawerDefault() {
  return (
    <>
      <div className='flex justify-center'>
        <figure className='h-96 w-96 relative'>
          <Image
            src={'/myself2.webp'}
            width={600}
            height={600}
            className='h-full w-full object-cover rounded-lg'
            alt='profile_image'
          />
          <DirectionalDrawer direction={'right'} outsideClose={true}>
            <DrawerTrigger>
              <motion.button
                whileTap={{ scale: 0.8 }}
                className='absolute right-2 bottom-2 p-4 dark:bg-black bg-white rounded-lg shadow-black'
              >
                <Edit />
              </motion.button>
            </DrawerTrigger>
            <DrawerContent>
              <figure className='flex flex-col w-full h-full rounded-xl overflow-hidden'>
                {/* Header / Body */}
                <div className='p-6 flex flex-col gap-4 grow'>
                  <div>
                    <h1 className='font-semibold text-xl'>Update Profile Image</h1>
                    <p className='text-sm text-muted-foreground mt-1'>
                      Upload a new profile image or remove the current one.
                    </p>
                  </div>

                  {/* Avatar Preview */}
                  <div className='flex justify-center'>
                    <div className='h-32 w-32 rounded-xl dark:bg-neutral-800 bg-muted grid place-content-center text-xl font-medium'>
                      JP
                    </div>
                  </div>

                  {/* File Input */}
                  <div>
                    <label className='text-sm font-medium mb-1 block'>Profile Picture</label>
                    <input
                      type='file'
                      id='formFile'
                      className='w-full bg-background border border-border rounded-md p-2 file:bg-black file:text-white file:border-none file:px-3 file:py-1 cursor-pointer'
                    />
                  </div>

                  {/* Submit */}
                  <button
                    type='submit'
                    className='w-full rounded-md bg-black text-white dark:bg-white dark:text-black p-2 font-medium'
                  >
                    Save Changes
                  </button>
                </div>
              </figure>
            </DrawerContent>
          </DirectionalDrawer>
        </figure>
      </div>
    </>
  );
}

Componentes parecidos