Opacity Swapy
Opacity 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 é
Opacity 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.
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.
Opacity Swapy
'use client';
import { DragHandle, SwapyItem, SwapyLayout, SwapySlot } from '@/components/ui/swapy';
import {
BarChart2,
Brain,
DollarSign,
Heart,
PlusCircle,
Sparkles,
Users,
Zap,
} from 'lucide-react';
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { type SlotItemMapArray, Swapy, utils } from 'swapy';
type Item = {
id: string;
title: string;
icon: ReactNode;
bgColor: string;
className?: string;
};
const initialItems: Item[] = [
{
id: '1',
icon: <Zap className='w-7 h-7 text-blue-600' />,
title: 'Streamline Operations',
bgColor: 'bg-blue-50',
},
{
id: '2',
icon: <BarChart2 className='w-7 h-7 text-pink-600' />,
title: 'Decision Making',
bgColor: 'bg-pink-50',
},
{
id: '3',
icon: <Users className='w-7 h-7 text-green-600' />,
title: 'Customer Experiences',
bgColor: 'bg-green-50',
},
{
id: '4',
icon: <Brain className='w-7 h-7 text-yellow-600' />,
title: 'Accelerate Innovation',
bgColor: 'bg-yellow-50',
},
{
id: '5',
icon: <DollarSign className='w-7 h-7 text-purple-600' />,
title: 'Reduce Costs',
bgColor: 'bg-purple-50',
},
{
id: '6',
icon: <Sparkles className='w-7 h-7 text-cyan-600' />,
title: 'Future-Proof',
bgColor: 'bg-cyan-50',
},
];
function SwapyOpacity() {
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) => {
console.log('Swap detected!', event.newSlotItemMap.asArray);
}}
>
<div className='w-96 mx-auto space-y-2'>
{slottedItems.map(({ itemId }) => {
const item = initialItems.find((i) => i.id === itemId);
return (
<SwapySlot
key={itemId}
className={`swapyItem rounded-lg ${item?.className}`}
id={itemId}
>
<SwapyItem
id={itemId}
dragItemOpacity={50}
className={`relative flex p-4 w-full items-center rounded-lg gap-5 h-full border border-dashed border-neutral-300 ${item?.bgColor}`}
>
<div className={`rounded-full flex items-center justify-center`}>{item?.icon}</div>
<h3 className='text-xl text-neutral-900 font-medium '>{item?.title}</h3>
</SwapyItem>
</SwapySlot>
);
})}
</div>
</SwapyLayout>
);
}
export default SwapyOpacity;
'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>
);
};