Todos os componentes

Default Swapy

Default Swapy é um componente para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

UI Layouts MIT other

O que é

Default Swapy é um componente 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 swapy

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.

Default Swapy

./registry/components/swapy/default-swapy.tsx
'use client';
import { DragHandle, SwapyItem, SwapyLayout, SwapySlot } from '@/components/ui/swapy';
import { Heart, PlusCircle } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { type SlotItemMapArray, utils } from 'swapy';

export function ProjectViewsCard() {
  return (
    <div className='bg-emerald-600 rounded-xl h-full p-6 flex flex-col justify-center items-center text-center shadow-md'>
      <div className='flex gap-2'>
        <h2 className='text-yellow-200 2xl:text-5xl text-3xl font-bold mb-2'>4.875</h2>
        <div className='text-yellow-200 flex items-center gap-1 mb-1'>
          <span className='text-xl'>
            <Heart className='fill-yellow-200' size={24} />
          </span>
        </div>
      </div>
      <p className='text-yellow-200 font-medium'>Project Views</p>
      <p className='text-yellow-200/80 text-sm'>last year</p>
    </div>
  );
}
export function NewUsersCard() {
  return (
    <div className='bg-neutral-600 rounded-xl h-full p-6 flex flex-col justify-center shadow-md'>
      <p className='text-yellow-200 mb-1 font-medium'>New Users</p>
      <h2 className='text-yellow-200 2xl:text-6xl text-4xl font-bold leading-none'>57K</h2>
      <p className='text-green-400 font-medium mt-2'>+10%</p>
    </div>
  );
}
export function TeamCard() {
  return (
    <div className='bg-blue-100 rounded-xl p-6 h-full  flex flex-col justify-between relative overflow-hidden shadow-md'>
      <div className='bg-blue-300 text-black font-medium px-4 py-2 rounded-xl inline-block mb-4 max-w-fit'>
        Team of passionate designers and developers
      </div>
      <div>
        <p className='font-bold text-neutral-800'>Daily New clients</p>
        <div className='flex items-end gap-2'>
          <span className='text-6xl font-bold text-neutral-900'>54</span>
          <span className='text-green-500 font-medium mb-1'>+40%</span>
        </div>
      </div>
    </div>
  );
}
export function AgencyCard() {
  return (
    <div className='bg-purple-300 rounded-xl h-full p-4 relative overflow-hidden shadow-md'>
      <div className='bg-neutral-900 text-yellow-200 text-lg font-medium px-4 py-2 rounded-lg inline-block mb-4 w-full'>
        <p>Smart Digital</p>
        <p>Agency For Your</p>
        <p>Business</p>
      </div>
      <div className='flex  gap-2 h-20'>
        <div className='w-full rounded-xl bg-purple-400  overflow-hidden'></div>
        <div className='w-full rounded-xl bg-yellow-200  overflow-hidden ml-4'></div>
      </div>
    </div>
  );
}
export function LogoCard() {
  return (
    <div className='bg-pink-200 rounded-xl h-full p-6 flex flex-col items-center justify-center shadow-md'>
      <div className='w-16 h-16 mb-4'>
        <svg viewBox='0 0 100 100' className='w-full h-full'>
          <circle cx='33' cy='33' r='25' fill='rgb(27, 13, 221)' />
          <circle cx='67' cy='33' r='25' fill='rgb(9, 4, 255)' />
          <circle cx='50' cy='67' r='25' fill='rgb(1, 61, 226)' />
        </svg>
      </div>
      <h2 className='2xl:text-3xl text-xl font-bold text-neutral-900'>UI-Layouts</h2>
    </div>
  );
}
export function UserTrustCard() {
  return (
    <div className='bg-blue-600 rounded-xl h-full p-4 flex flex-col justify-center items-center text-white shadow-lg'>
      <h3 className='text-2xl font-bold mb-2'>Trusted By</h3>
      <p className='text-3xl font-bold mb-4'>500+ Users</p>

      <div className='flex -space-x-2 mb-4'>
        <div className='w-10 h-10 rounded-xl overflow-hidden border-2 border-blue-600 bg-neutral-200'></div>
        <div className='w-10 h-10 rounded-xl overflow-hidden border-2 border-blue-600 bg-neutral-200'></div>
        <div className='w-10 h-10 rounded-xl overflow-hidden border-2 border-blue-600 bg-neutral-200'></div>
        <div className='w-10 h-10 rounded-xl overflow-hidden border-2 border-blue-600 bg-neutral-200'></div>
        <div className='w-10 h-10 rounded-xl bg-yellow-500 border-2 border-blue-600 flex items-center justify-center'>
          <PlusCircle className='w-5 h-5 text-white' />
        </div>
      </div>

      <p className='text-sm'>Don't Take Our Words For It...</p>
    </div>
  );
}
export function FontCard() {
  return (
    <div className='bg-yellow-200 rounded-xl h-full p-6 col-span-1 shadow-md'>
      <h2 className='text-3xl font-bold mb-1 text-neutral-900'>Font</h2>
      <p className='mb-6 text-neutral-700'>SK-Modernist</p>

      <div className='flex gap-3 mt-4'>
        <div className='w-12 h-12 bg-neutral-800 rounded-md'></div>
        <div className='w-12 h-12 bg-neutral-400 rounded-md'></div>
        <div className='w-12 h-12 bg-red-400 rounded-md'></div>
        <div className='w-12 h-12 bg-pink-300 rounded-md'></div>
      </div>
    </div>
  );
}
export function DesignIndustryCard() {
  return (
    <div className='bg-emerald-600 text-yellow-200 rounded-xl h-full p-6 flex flex-col justify-between relative shadow-md'>
      <p className='text-2xl font-bold'>We Build Future of</p>
      <p className='text-2xl font-bold'>Design Industry</p>
    </div>
  );
}
export function CardBalanceCard() {
  return (
    <div className='bg-yellow-200 rounded-xl h-full p-6 shadow-lg'>
      <h3 className='text-xl font-bold mb-4 text-neutral-950'>Cards balance</h3>
      <h2 className='text-3xl font-bold mb-6 text-neutral-800'>$ 12,457</h2>

      <div className='bg-black text-white rounded-lg p-4 shadow-xs'>
        <div className='flex justify-between text-sm mb-2'>
          <span>Card Holder</span>
          <span>Expires</span>
        </div>
        <div className='flex justify-between font-medium'>
          <span>Robert Fox</span>
          <span>07/22</span>
        </div>
      </div>
    </div>
  );
}

type Item = {
  id: string;
  title: string;
  widgets: React.ReactNode;
  className?: string;
};

const initialItems: Item[] = [
  {
    id: '1',
    title: '1',
    widgets: <ProjectViewsCard />,
    className: 'lg:col-span-4 sm:col-span-7 col-span-12',
  },
  {
    id: '2',
    title: '2',
    widgets: <NewUsersCard />,
    className: 'lg:col-span-3 sm:col-span-5 col-span-12',
  },
  {
    id: '3',
    title: '3',
    widgets: <DesignIndustryCard />,
    className: 'lg:col-span-5 sm:col-span-5 col-span-12',
  },
  {
    id: '4',
    title: '4',
    widgets: <TeamCard />,
    className: 'lg:col-span-5 sm:col-span-7 col-span-12',
  },
  {
    id: '5',
    title: '5',
    widgets: <LogoCard />,
    className: 'lg:col-span-4 sm:col-span-6 col-span-12',
  },
  {
    id: '6',
    title: '6',
    widgets: <FontCard />,
    className: 'lg:col-span-3 sm:col-span-6 col-span-12',
  },
  {
    id: '7',
    title: '7',
    widgets: <AgencyCard />,
    className: 'lg:col-span-4 sm:col-span-5 col-span-12',
  },
  {
    id: '8',
    title: '8',
    widgets: <UserTrustCard />,
    className: 'lg:col-span-4 sm:col-span-7 col-span-12',
  },
  {
    id: '9',
    title: '9',
    widgets: <CardBalanceCard />,
    className: 'lg:col-span-4 sm:col-span-12 col-span-12',
  },
];

function DefaultSwapy() {
  const [slotItemMap, setSlotItemMap] = useState<SlotItemMapArray>(
    utils.initSlotItemMap(initialItems, 'id')
  );

  const slottedItems = useMemo(
    () => utils.toSlottedItems(initialItems, 'id', slotItemMap),
    [initialItems, slotItemMap]
  );

  return (
    <SwapyLayout
      id='swapy'
      className='w-full'
      config={{
        swapMode: 'hover',
      }}
      onSwap={(event: { newSlotItemMap: { asArray: any } }) => {
        console.log('Swap detected!', event.newSlotItemMap.asArray);
      }}
    >
      <div className='grid w-full  grid-cols-12 gap-2 md:gap-6 py-4'>
        {slottedItems.map(({ slotId, itemId }) => {
          const item = initialItems.find((i) => i.id === itemId);

          return (
            <SwapySlot
              key={slotId}
              className={`swapyItem rounded-lg h-64 ${item?.className}`}
              id={slotId}
            >
              <SwapyItem
                id={itemId}
                className='relative rounded-lg w-full h-full 2xl:text-xl text-sm'
                key={itemId}
              >
                {item?.widgets}
              </SwapyItem>
            </SwapySlot>
          );
        })}
      </div>
    </SwapyLayout>
  );
}

export default DefaultSwapy;
./components/ui/swapy.tsx
'use client';

import { cn } from '@/lib/utils';
import type React from 'react';
import { useEffect, useRef } from 'react';
import { type SlotItemMapArray, createSwapy } from 'swapy';

type AnimationType = 'dynamic' | 'spring' | 'none';
type SwapMode = 'hover' | 'drop';

type Config = {
  animation: AnimationType;
  continuousMode: boolean;
  manualSwap: boolean;
  swapMode: SwapMode;
  autoScrollOnDrag: boolean;
};

type SwapyLayoutProps = {
  id: string;
  enable?: boolean;
  onSwap?: (event: { newSlotItemMap: { asArray: SlotItemMapArray } }) => void;
  config?: Partial<Config>;
  className?: string;
  children: React.ReactNode;
};

export const SwapyLayout = ({ id, onSwap, config = {}, className, children }: SwapyLayoutProps) => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const swapyRef = useRef<ReturnType<typeof createSwapy> | null>(null);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    swapyRef.current = createSwapy(container, config);

    if (onSwap) {
      swapyRef.current.onSwap(onSwap);
    }

    return () => {
      swapyRef.current?.destroy();
    };
  }, [config, onSwap]);

  return (
    <div id={id} ref={containerRef} className={className}>
      {children}
    </div>
  );
};

export const DragHandle = ({ className }: { className?: string }) => {
  return (
    <div
      data-swapy-handle
      className={cn(
        'absolute top-2 left-2 cursor-grab  text-neutral-500  rounded-md bg-transparent  active:cursor-grabbing  ',
        className
      )}
    >
      <svg
        xmlns='http://www.w3.org/2000/svg'
        width='24'
        height='24'
        viewBox='0 0 24 24'
        fill='none'
        stroke='currentColor'
        strokeWidth='2'
        strokeLinecap='round'
        strokeLinejoin='round'
        className='lucide lucide-grip-vertical-icon lucide-grip-vertical opacity-80'
      >
        <circle cx='9' cy='12' r='1' />
        <circle cx='9' cy='5' r='1' />
        <circle cx='9' cy='19' r='1' />
        <circle cx='15' cy='12' r='1' />
        <circle cx='15' cy='5' r='1' />
        <circle cx='15' cy='19' r='1' />
      </svg>
    </div>
  );
};

export const SwapySlot = ({
  id,
  className,
  children,
}: {
  id: string;
  className?: string;
  children: React.ReactNode;
}) => {
  return (
    <div
      className={cn(
        'data-swapy-highlighted:bg-neutral-200 dark:data-swapy-highlighted:bg-neutral-800',
        className
      )}
      data-swapy-slot={id}
    >
      {children}
    </div>
  );
};

const dragOpacityClassMap: Record<number, string> = {
  10: 'data-swapy-dragging:opacity-10',
  20: 'data-swapy-dragging:opacity-20',
  30: 'data-swapy-dragging:opacity-30',
  40: 'data-swapy-dragging:opacity-40',
  50: 'data-swapy-dragging:opacity-50',
  60: 'data-swapy-dragging:opacity-60',
  70: 'data-swapy-dragging:opacity-70',
  80: 'data-swapy-dragging:opacity-80',
  90: 'data-swapy-dragging:opacity-90',
  100: 'data-swapy-dragging:opacity-100',
};

export const SwapyItem = ({
  id,
  className,
  children,
  dragItemOpacity = 100, // default to 100
}: {
  id: string;
  className?: string;
  children: React.ReactNode;
  dragItemOpacity?: number;
}) => {
  const opacityClass = dragOpacityClassMap[dragItemOpacity] ?? 'data-swapy-dragging:opacity-50';
  return (
    <div className={cn(opacityClass, className)} data-swapy-item={id}>
      {children}
    </div>
  );
};

Componentes parecidos