Todos os componentes

Swapy Handle

Swapy Handle é 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 é

Swapy Handle é 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.

Swapy Handle

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

type Item = {
  id: string;
  title: string;
  bgColor: string;
  iconBgColor: string;
  className?: string;
};

const initialItems: Item[] = [
  {
    id: '1',
    title: '⚡ Turbocharge Everything',
    bgColor: 'bg-blue-400',
    iconBgColor: 'bg-blue-100',
    className: 'lg:col-span-4 sm:col-span-7 col-span-12',
  },
  {
    id: '2',
    title: '🧠 Brainy Decisions',
    bgColor: 'bg-pink-300',
    iconBgColor: 'bg-pink-100',
    className: 'lg:col-span-3 sm:col-span-5 col-span-12',
  },
  {
    id: '3',
    title: '🌈 Magical Moments',
    bgColor: 'bg-green-400',
    iconBgColor: 'bg-green-100',
    className: 'lg:col-span-5 sm:col-span-5 col-span-12',
  },
  {
    id: '4',
    title: '🚀 Innovate Like Crazy',
    bgColor: 'bg-yellow-300',
    iconBgColor: 'bg-yellow-100',
    className: 'lg:col-span-5 sm:col-span-7 col-span-12',
  },
  {
    id: '5',
    title: '💰 Save Big, Smile More',
    bgColor: 'bg-purple-300',
    iconBgColor: 'bg-purple-100',
    className: 'lg:col-span-4 sm:col-span-6 col-span-12',
  },
  {
    id: '6',
    title: '🔮 AI Vibes Only',
    bgColor: 'bg-cyan-300',
    iconBgColor: 'bg-cyan-100',
    className: 'lg:col-span-3 sm:col-span-6 col-span-12',
  },
];

function SwapyHandle() {
  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-4 py-4'>
        {slottedItems.map(({ itemId }) => {
          const item = initialItems.find((i) => i.id === itemId);
          return (
            <SwapySlot
              key={itemId}
              className={`swapyItem rounded-lg h-64 ${item?.className}`}
              id={itemId}
            >
              <SwapyItem
                id={itemId}
                className={`relative rounded-lg w-full h-full flex flex-col justify-center items-center gap-2 p-8 ${item?.bgColor}`}
              >
                <DragHandle />
                <h3 className='text-xl font-bold text-black '>{item?.title}</h3>
              </SwapyItem>
            </SwapySlot>
          );
        })}
      </div>
    </SwapyLayout>
  );
}

export default SwapyHandle;
./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