NEW
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
Learn more
Skip to content
Docs

Transaction List

A simple component to list all the recent transaction.

requires interactionClick any recent Transaction
Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/list/transaction-list.json

Manual

Install dependencies

npm install motion lucide-react

Run the following command

It will create a new file transaction-list.tsx inside the components/animata/list directory.

mkdir -p components/animata/list && touch components/animata/list/transaction-list.tsx

Paste the code

Open the newly created file and paste the following code:

import { ArrowRight, CreditCard, X } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import type React from "react";
import { useState } from "react";
 
interface Transaction {
  id: string;
  name: string;
  type: string;
  amount: number;
  date: string;
  time: string;
  icon: React.ReactNode;
  paymentMethod: string;
  cardLastFour: string;
  cardType?: string;
}
 
const spring = {
  type: "spring" as const,
  stiffness: 350,
  damping: 30,
};
 
export default function TransactionList({ transactions }: { transactions: Transaction[] }) {
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const selected = transactions.find((t) => t.id === selectedId) ?? null;
 
  return (
    <div className="mx-auto max-w-md font-sans">
      <motion.div
        layout
        className="w-[300px] overflow-hidden rounded-3xl bg-white shadow-lg"
        transition={spring}
      >
        <AnimatePresence mode="popLayout">
          {!selected ? (
            <motion.div
              key="list"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              transition={{ duration: 0.15 }}
            >
              <h2 className="pl-6 pt-4 text-lg font-semibold text-gray-400">Transactions</h2>
              <div className="space-y-0.5 p-2">
                {transactions.map((transaction) => (
                  <motion.button
                    key={transaction.id}
                    layoutId={`card-${transaction.id}`}
                    className="flex w-full cursor-pointer items-center justify-between rounded-2xl p-2 transition-colors hover:bg-gray-50"
                    onClick={() => setSelectedId(transaction.id)}
                    transition={spring}
                  >
                    <div className="flex items-center gap-3">
                      <motion.div
                        layoutId={`icon-${transaction.id}`}
                        className="flex h-10 w-10 items-center justify-center rounded-full bg-black"
                        transition={spring}
                      >
                        {transaction.icon}
                      </motion.div>
                      <div className="text-left">
                        <motion.p
                          layoutId={`name-${transaction.id}`}
                          className="text-sm font-medium text-gray-800"
                          transition={spring}
                        >
                          {transaction.name}
                        </motion.p>
                        <motion.p
                          layoutId={`type-${transaction.id}`}
                          className="text-xs text-gray-400"
                          transition={spring}
                        >
                          {transaction.type}
                        </motion.p>
                      </div>
                    </div>
                    <motion.p
                      layoutId={`amount-${transaction.id}`}
                      className="text-sm font-semibold text-gray-500"
                      transition={spring}
                    >
                      ${Math.abs(transaction.amount).toFixed(2)}
                    </motion.p>
                  </motion.button>
                ))}
              </div>
              <div className="px-3 pb-3">
                <motion.button
                  whileHover={{ scale: 1.02 }}
                  whileTap={{ scale: 0.98 }}
                  className="flex w-full items-center justify-center gap-2 rounded-xl bg-gray-100 py-2.5 text-sm font-medium text-gray-600"
                  transition={spring}
                >
                  All Transactions
                  <ArrowRight className="h-4 w-4" />
                </motion.button>
              </div>
            </motion.div>
          ) : (
            <motion.div
              key="details"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              transition={{ duration: 0.15 }}
              className="p-4"
            >
              <div className="mb-4 flex items-center justify-between">
                <motion.div
                  layoutId={`card-${selected.id}`}
                  className="flex items-center gap-3"
                  transition={spring}
                >
                  <motion.div
                    layoutId={`icon-${selected.id}`}
                    className="flex h-12 w-12 items-center justify-center rounded-2xl bg-black"
                    transition={spring}
                  >
                    {selected.icon}
                  </motion.div>
                </motion.div>
                <motion.button
                  initial={{ opacity: 0, scale: 0.8 }}
                  animate={{ opacity: 1, scale: 1 }}
                  transition={{ delay: 0.15 }}
                  onClick={() => setSelectedId(null)}
                  className="flex h-7 w-7 items-center justify-center rounded-full bg-gray-200 text-gray-500 transition-colors hover:bg-gray-300"
                >
                  <X className="h-4 w-4" />
                </motion.button>
              </div>
 
              <div className="flex items-start justify-between border-b border-dashed border-gray-200 pb-4">
                <div className="space-y-0.5">
                  <motion.p
                    layoutId={`name-${selected.id}`}
                    className="font-medium text-gray-800"
                    transition={spring}
                  >
                    {selected.name}
                  </motion.p>
                  <motion.p
                    layoutId={`type-${selected.id}`}
                    className="text-sm text-gray-400"
                    transition={spring}
                  >
                    {selected.type}
                  </motion.p>
                </div>
                <motion.p
                  layoutId={`amount-${selected.id}`}
                  className="text-lg font-bold text-gray-700"
                  transition={spring}
                >
                  ${Math.abs(selected.amount).toFixed(2)}
                </motion.p>
              </div>
 
              <motion.div
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ delay: 0.1, duration: 0.25 }}
                className="space-y-4"
              >
                <div className="mt-4 space-y-1.5 text-sm text-gray-400">
                  <p>#{selected.id}</p>
                  <p>{selected.date}</p>
                  <p>{selected.time}</p>
                </div>
                <div className="border-t border-dashed border-gray-200 pt-4 text-sm text-gray-400">
                  <p className="font-medium text-gray-500">Paid Via {selected.paymentMethod}</p>
                  <div className="mt-2 flex items-center gap-2">
                    <CreditCard className="h-5 w-5" />
                    <p>XXXX {selected.cardLastFour}</p>
                    {selected.cardType === "visa" ? <VisaLogo /> : <MasterCardLogo />}
                  </div>
                </div>
              </motion.div>
            </motion.div>
          )}
        </AnimatePresence>
      </motion.div>
    </div>
  );
}
 
const MasterCardLogo = () => {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="1.29em" height="1em" viewBox="0 0 256 199">
      <path d="M46.54 198.011V184.84c0-5.05-3.074-8.342-8.343-8.342c-2.634 0-5.488.878-7.464 3.732c-1.536-2.415-3.731-3.732-7.024-3.732c-2.196 0-4.39.658-6.147 3.073v-2.634h-4.61v21.074h4.61v-11.635c0-3.731 1.976-5.488 5.05-5.488c3.072 0 4.61 1.976 4.61 5.488v11.635h4.61v-11.635c0-3.731 2.194-5.488 5.048-5.488c3.074 0 4.61 1.976 4.61 5.488v11.635zm68.271-21.074h-7.463v-6.366h-4.61v6.366h-4.171v4.17h4.17v9.66c0 4.83 1.976 7.683 7.245 7.683c1.976 0 4.17-.658 5.708-1.536l-1.318-3.952c-1.317.878-2.853 1.098-3.951 1.098c-2.195 0-3.073-1.317-3.073-3.513v-9.44h7.463zm39.076-.44c-2.634 0-4.39 1.318-5.488 3.074v-2.634h-4.61v21.074h4.61v-11.854c0-3.512 1.536-5.488 4.39-5.488c.878 0 1.976.22 2.854.439l1.317-4.39c-.878-.22-2.195-.22-3.073-.22m-59.052 2.196c-2.196-1.537-5.269-2.195-8.562-2.195c-5.268 0-8.78 2.634-8.78 6.805c0 3.513 2.634 5.488 7.244 6.147l2.195.22c2.415.438 3.732 1.097 3.732 2.195c0 1.536-1.756 2.634-4.83 2.634s-5.488-1.098-7.025-2.195l-2.195 3.512c2.415 1.756 5.708 2.634 9 2.634c6.147 0 9.66-2.853 9.66-6.805c0-3.732-2.854-5.708-7.245-6.366l-2.195-.22c-1.976-.22-3.512-.658-3.512-1.975c0-1.537 1.536-2.415 3.951-2.415c2.635 0 5.269 1.097 6.586 1.756zm122.495-2.195c-2.635 0-4.391 1.317-5.489 3.073v-2.634h-4.61v21.074h4.61v-11.854c0-3.512 1.537-5.488 4.39-5.488c.879 0 1.977.22 2.855.439l1.317-4.39c-.878-.22-2.195-.22-3.073-.22m-58.833 10.976c0 6.366 4.39 10.976 11.196 10.976c3.073 0 5.268-.658 7.463-2.414l-2.195-3.732c-1.756 1.317-3.512 1.975-5.488 1.975c-3.732 0-6.366-2.634-6.366-6.805c0-3.951 2.634-6.586 6.366-6.805c1.976 0 3.732.658 5.488 1.976l2.195-3.732c-2.195-1.757-4.39-2.415-7.463-2.415c-6.806 0-11.196 4.61-11.196 10.976m42.588 0v-10.537h-4.61v2.634c-1.537-1.975-3.732-3.073-6.586-3.073c-5.927 0-10.537 4.61-10.537 10.976s4.61 10.976 10.537 10.976c3.073 0 5.269-1.097 6.586-3.073v2.634h4.61zm-16.904 0c0-3.732 2.415-6.805 6.366-6.805c3.732 0 6.367 2.854 6.367 6.805c0 3.732-2.635 6.805-6.367 6.805c-3.951-.22-6.366-3.073-6.366-6.805m-55.1-10.976c-6.147 0-10.538 4.39-10.538 10.976s4.39 10.976 10.757 10.976c3.073 0 6.147-.878 8.562-2.853l-2.196-3.293c-1.756 1.317-3.951 2.195-6.146 2.195c-2.854 0-5.708-1.317-6.367-5.05h15.587v-1.755c.22-6.806-3.732-11.196-9.66-11.196m0 3.951c2.853 0 4.83 1.757 5.268 5.05h-10.976c.439-2.854 2.415-5.05  5.708-5.05m114.372 7.025v-18.879h-4.61v10.976c-1.537-1.975-3.732-3.073-6.586-3.073c-5.927 0-10.537 4.61-10.537 10.976s4.61 10.976 10.537 10.976c3.074  0 5.269-1.097 6.586-3.073v2.634h4.61zm-16.903 0c0-3.732 2.414-6.805 6.366-6.805c3.732 0 6.366 2.854 6.366 6.805c0 3.732-2.634 6.805-6.366 6.805c-3.952-.22-6.366-3.073-6.366-6.805m-154.107 0v-10.537h-4.61v2.634c-1.537-1.975-3.732-3.073-6.586-3.073c-5.927 0-10.537 4.61-10.537 10.976s4.61 10.976 10.537 10.976c3.074 0 5.269-1.097 6.586-3.073v2.634h4.61zm-17.123 0c0-3.732 2.415-6.805 6.366-6.805c3.732 0 6.367 2.854 6.367 6.805c0 3.732-2.635 6.805-6.367 6.805c-3.951-.22-6.366-3.073-6.366-6.805"></path>
      <path fill="black" d="M93.298 16.903h69.15v124.251h-69.15z"></path>
      <path
        fill="black"
        d="M97.689 79.029c0-25.245 11.854-47.637 30.074-62.126C114.373 6.366 97.47 0 79.03 0C35.343 0 0 35.343 0 79.029s35.343 79.029 79.029 79.029c18.44 0 35.343-6.366 48.734-16.904c-18.22-14.269-30.074-36.88-30.074-62.125"
      ></path>
      <path
        fill="#5e5e5e"
        d="M255.746 79.029c0 43.685-35.343 79.029-79.029 79.029c-18.44 0-35.343-6.366-48.734-16.904c18.44-14.488 30.075-36.88 30.075-62.125s-11.855-47.637-30.075-62.126C141.373 6.366 158.277 0 176.717 0c43.686 0 79.03 35.563 79.03 79.029"
      ></path>
    </svg>
  );
};
 
const VisaLogo = () => {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width="3.09em" height="1em" viewBox="0 0 256 83">
      <defs>
        <linearGradient id="logosVisa0" x1="45.974%" x2="54.877%" y1="-2.006%" y2="100%">
          <stop offset="0%" stopColor="gray"></stop>
          <stop offset="100%" stopColor="gray"></stop>
        </linearGradient>
      </defs>
      <path
        fill="url(#logosVisa0)"
        d="M132.397 56.24c-.146-11.516 10.263-17.942 18.104-21.763c8.056-3.92 10.762-6.434 10.73-9.94c-.06-5.365-6.426-7.733-12.383-7.825c-10.393-.161-16.436 2.806-21.24 5.05l-3.744-17.519c4.82-2.221 13.745-4.158 23-4.243c21.725 0 35.938 10.724 36.015 27.351c.085 21.102-29.188 22.27-28.988 31.702c.069 2.86 2.798 5.912 8.778 6.688c2.96.392 11.131.692 20.395-3.574l3.636 16.95c-4.982 1.814-11.385 3.551-19.357 3.551c-20.448 0-34.83-10.87-34.946-26.428m89.241 24.968c-3.967 0-7.31-2.314-8.802-5.865L181.803 1.245h21.709l4.32 11.939h26.528l2.506-11.939H256l-16.697 79.963zm3.037-21.601l6.265-30.027h-17.158zm-118.599 21.6L88.964 1.246h20.687l17.104 79.963zm-30.603 0L53.941 26.782l-8.71 46.277c-1.022 5.166-5.058 8.149-9.54 8.149H.493L0 78.886c7.226-1.568 15.436-4.097 20.41-6.803c3.044-1.653 3.912-3.098 4.912-7.026L41.819 1.245H63.68l33.516 79.963z"
        transform="matrix(1 0 0 -1 0 82.668)"
      ></path>
    </svg>
  );
};

Credits

Built by Prince Yadav