All components

Drag Items

Drag Items is a React and Tailwind CSS component from UI Layouts, licensed MIT. Copy it and paste it into your project.

UI Layouts MIT other

What it is

Drag Items is a React and Tailwind CSS 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

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.

Drag Items

./registry/components/drag/drag-items.tsx
'use client';
import {
  type DragControls,
  type MotionValue,
  Reorder,
  animate,
  motion,
  useDragControls,
  useMotionValue,
} from 'motion/react';
import type React from 'react';
import { useEffect, useState } from 'react';

export default function DragItems() {
  const [items, setItems] = useState([
    {
      id: 1,
      title: 'Follow me on X',
      subtitle: 'Get more update follow me on X',
      date: '32.8.2024',
      src: 'https://images.unsplash.com/photo-1724908549265-06972c22ca37?q=80&w=2070&auto=format&fit=crop',
      link: 'https://x.com/naymur_dev',
    },
    {
      id: 2,
      title: 'Star the repo',
      subtitle: 'If you like star the repo',
      date: '32.8.2024',
      src: 'https://images.unsplash.com/photo-1724908549265-06972c22ca37?q=80&w=2070&auto=format&fit=crop',
      link: 'https://github.com/ui-layouts/uilayouts',
    },
    {
      id: 3,
      title: 'Share it with your friend',
      subtitle: 'if you like it, please Share it with your friend ',
      date: '32.8.2024',
      src: 'https://images.unsplash.com/photo-1724908549265-06972c22ca37?q=80&w=2070&auto=format&fit=crop',
      link: 'https://www.ui-layouts.com/',
    },
  ]);

  return (
    <Reorder.Group
      axis='y'
      values={items}
      onReorder={setItems}
      className='space-y-2 w-full max-w-md mx-auto'
    >
      {items.map((item) => (
        <Item key={item.id} item={item}>
          <h1 className='text-xl'>{item.title}</h1>
          <p className='text-sm'>{item.subtitle}</p>
          <span>{item.date}</span>
          <a
            className='block w-fit p-2 px-3 text-xs rounded-md  bg-primary-foreground text-primary'
            href={item.link}
          >
            Click
          </a>
        </Item>
      ))}
    </Reorder.Group>
  );
}

const Item = ({ children, item }: { children: React.ReactNode; item: any }) => {
  const y = useMotionValue(0);
  const boxShadow = useRaisedShadow(y);
  const dragControls = useDragControls();
  const [isDragging, setIsDragging] = useState(false);
  const [pressed, setPressed] = useState(false);

  return (
    <Reorder.Item
      value={item}
      style={{ boxShadow, y }}
      dragListener={false}
      dragControls={dragControls}
      onDragStart={() => setIsDragging(true)}
      onDragEnd={() => {
        setIsDragging(false);
        setPressed(false);
      }}
      onPointerUp={() => setPressed(false)}
      onPointerCancel={() => setPressed(false)}
      className='flex justify-between items-center w-full p-3 text-primary-foreground bg-primary border rounded-md'
    >
      <div>{children}</div>
      <ReorderIcon
        dragControls={dragControls}
        isActive={isDragging || pressed}
        onPress={() => setPressed(true)}
      />
    </Reorder.Item>
  );
};

interface ReorderIconProps {
  dragControls: DragControls;
  isActive: boolean;
  onPress: () => void;
}

export function ReorderIcon({ dragControls, isActive, onPress }: ReorderIconProps) {
  return (
    <motion.button
      type='button'
      aria-label='Reorder'
      animate={{ scale: isActive ? 0.85 : 1 }}
      transition={{ type: 'spring', stiffness: 500, damping: 30 }}
      onPointerDown={(e) => {
        e.preventDefault();
        onPress();
        dragControls.start(e);
      }}
      className='cursor-grab active:cursor-grabbing'
      style={{ touchAction: 'none' }}
    >
      <svg
        xmlns='http://www.w3.org/2000/svg'
        viewBox='0 0 39 39'
        width='39'
        height='39'
        className='fill-primary-foreground'
      >
        <path d='M 5 0 C 7.761 0 10 2.239 10 5 C 10 7.761 7.761 10 5 10 C 2.239 10 0 7.761 0 5 C 0 2.239 2.239 0 5 0 Z'></path>
        <path d='M 19 0 C 21.761 0 24 2.239 24 5 C 24 7.761 21.761 10 19 10 C 16.239 10 14 7.761 14 5 C 14 2.239 16.239 0 19 0 Z'></path>
        <path d='M 33 0 C 35.761 0 38 2.239 38 5 C 38 7.761 35.761 10 33 10 C 30.239 10 28 7.761 28 5 C 28 2.239 30.239 0 33 0 Z'></path>
        <path d='M 33 14 C 35.761 14 38 16.239 38 19 C 38 21.761 35.761 24 33 24 C 30.239 24 28 21.761 28 19 C 28 16.239 30.239 14 33 14 Z'></path>
        <path d='M 19 14 C 21.761 14 24 16.239 24 19 C 24 21.761 21.761 24 19 24 C 16.239 24 14 21.761 14 19 C 14 16.239 16.239 14 19 14 Z'></path>
        <path d='M 5 14 C 7.761 14 10 16.239 10 19 C 10 21.761 7.761 24 5 24 C 2.239 24 0 21.761 0 19 C 0 16.239 2.239 14 5 14 Z'></path>
        <path d='M 5 28 C 7.761 28 10 30.239 10 33 C 10 35.761 7.761 38 5 38 C 2.239 38 0 35.761 0 33 C 0 30.239 2.239 28 5 28 Z'></path>
        <path d='M 19 28 C 21.761 28 24 30.239 24 33 C 24 35.761 21.761 38 19 38 C 16.239 38 14 35.761 14 33 C 14 30.239 16.239 28 19 28 Z'></path>
        <path d='M 33 28 C 35.761 28 38 30.239 38 33 C 38 35.761 35.761 38 33 38 C 30.239 38 28 35.761 28 33 C 28 30.239 30.239 28 33 28 Z'></path>
      </svg>
    </motion.button>
  );
}

const inactiveShadow = '0px 0px 0px rgba(0,0,0,0.8)';

export function useRaisedShadow(value: MotionValue<number>) {
  const boxShadow = useMotionValue(inactiveShadow);

  useEffect(() => {
    let isActive = false;
    value.onChange((latest) => {
      const wasActive = isActive;
      if (latest !== 0) {
        isActive = true;
        if (isActive !== wasActive) {
          animate(boxShadow, '5px 5px 10px rgba(0,0,0,0.3)');
        }
      } else {
        isActive = false;
        if (isActive !== wasActive) {
          animate(boxShadow, inactiveShadow);
        }
      }
    });
  }, [value, boxShadow]);

  return boxShadow;
}

Similar components