diff --git a/animata/card/state-action-card.stories.tsx b/animata/card/state-action-card.stories.tsx new file mode 100644 index 00000000..d1787e65 --- /dev/null +++ b/animata/card/state-action-card.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import StateActionCard from "@/animata/card/state-action-card"; + +const meta = { + title: "Card/State Action Card", + component: StateActionCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const TaskManager: Story = { + args: { + useCase: "task", + }, +}; + +export const SocialCard: Story = { + args: { + useCase: "social", + }, +}; + +export const OrderCard: Story = { + args: { + useCase: "order", + }, +}; diff --git a/animata/card/state-action-card.tsx b/animata/card/state-action-card.tsx new file mode 100644 index 00000000..b44475dd --- /dev/null +++ b/animata/card/state-action-card.tsx @@ -0,0 +1,271 @@ +"use client"; + +import { + Check, + CheckCircle2, + ClipboardList, + Heart, + Package, + Share2, + Sparkles, + Users, +} from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; +import { useMemo, useState } from "react"; + +import { cn } from "@/lib/utils"; + +type CardUseCase = "task" | "social" | "order"; + +type ActionType = "favorite" | "complete" | "share"; + +interface CardPreset { + title: string; + description: string; + meta: string; + badge: string; + icon: typeof ClipboardList; +} + +interface StateActionCardProps { + readonly useCase?: CardUseCase; + readonly className?: string; +} + +const cardPresets: Record = { + task: { + title: "Finalize Sprint Notes", + description: "Wrap up pending checklist items and post a summary for the team standup.", + meta: "Due in 3 hours", + badge: "Task Manager", + icon: ClipboardList, + }, + social: { + title: "Design Community Spotlight", + description: "A new behind-the-scenes post is trending. Save it or share it with your team.", + meta: "2.4k interactions", + badge: "Social Card", + icon: Users, + }, + order: { + title: "Order #48291", + description: "Wireless Keyboard and Mouse bundle is packed and ready for final dispatch.", + meta: "Ships today", + badge: "Dashboard Order", + icon: Package, + }, +}; + +const confettiPieces = [ + { id: "c1", x: -48, y: -34, rotate: -35, color: "bg-emerald-400" }, + { id: "c2", x: -26, y: -50, rotate: -10, color: "bg-cyan-400" }, + { id: "c3", x: -6, y: -56, rotate: 6, color: "bg-yellow-400" }, + { id: "c4", x: 18, y: -50, rotate: 22, color: "bg-fuchsia-400" }, + { id: "c5", x: 42, y: -34, rotate: 38, color: "bg-orange-400" }, + { id: "c6", x: -36, y: -18, rotate: -24, color: "bg-lime-400" }, + { id: "c7", x: 32, y: -16, rotate: 30, color: "bg-sky-400" }, + { id: "c8", x: 0, y: -30, rotate: 0, color: "bg-violet-400" }, +]; + +export default function StateActionCard({ + useCase = "task", + className, +}: Readonly) { + const preset = cardPresets[useCase]; + const CardIcon = preset.icon; + + const [isFavorite, setIsFavorite] = useState(false); + const [isCompleted, setIsCompleted] = useState(false); + const [isShared, setIsShared] = useState(false); + const [lastAction, setLastAction] = useState(null); + const [showConfetti, setShowConfetti] = useState(false); + + const statuses = useMemo(() => { + return [ + { label: preset.badge, className: "bg-zinc-900 text-white" }, + isCompleted + ? { label: "Completed", className: "bg-emerald-100 text-emerald-700" } + : { label: "In Progress", className: "bg-amber-100 text-amber-700" }, + isFavorite + ? { label: "Favorited", className: "bg-rose-100 text-rose-700" } + : { label: "Not Favorite", className: "bg-zinc-100 text-zinc-600" }, + isShared + ? { label: "Shared", className: "bg-sky-100 text-sky-700" } + : { label: "Private", className: "bg-zinc-100 text-zinc-600" }, + ]; + }, [isCompleted, isFavorite, isShared, preset.badge]); + + const triggerActionFeedback = (action: ActionType) => { + setLastAction(action); + window.setTimeout(() => { + setLastAction((previous) => (previous === action ? null : previous)); + }, 800); + }; + + const onFavorite = () => { + setIsFavorite((previous) => !previous); + triggerActionFeedback("favorite"); + }; + + const onComplete = () => { + const nextValue = !isCompleted; + setIsCompleted(nextValue); + triggerActionFeedback("complete"); + + if (nextValue) { + setShowConfetti(true); + window.setTimeout(() => { + setShowConfetti(false); + }, 1000); + } + }; + + const onShare = () => { + setIsShared((previous) => !previous); + triggerActionFeedback("share"); + }; + + return ( + +
+ +
+
+ + + +

+ Interactive Card +

+
+ + + Live State + +
+ +

{preset.title}

+

{preset.description}

+ +
+ {statuses.map((status) => ( + + {status.label} + + ))} +
+ +
+

{preset.meta}

+ + + {lastAction && ( + + + Action saved + + )} + +
+ +
+ +
+ + + + + +
+ + + {showConfetti && ( + + {confettiPieces.map((piece) => ( + + ))} + + )} + + + ); +} + +interface ActionButtonProps { + readonly icon: typeof Heart; + readonly label: string; + readonly active: boolean; + readonly onClick: () => void; +} + +function ActionButton({ icon: Icon, label, active, onClick }: Readonly) { + return ( + + ); +} diff --git a/content/docs/card/state-action-card.mdx b/content/docs/card/state-action-card.mdx new file mode 100644 index 00000000..a7a1e9c1 --- /dev/null +++ b/content/docs/card/state-action-card.mdx @@ -0,0 +1,45 @@ +--- +title: State Action Card +description: Cards with state-based action buttons, status badges, hover-reveal actions, and success feedback animations. +labels: ["requires interaction", "hover", "state", "actions"] +author: ujjwalbasnet +published: false +--- + + + +## Installation + + +Install dependencies + +```bash +npm install motion lucide-react +``` + +Run the following command + +It will create a new file `state-action-card.tsx` inside the `components/animata/card` directory. + +```bash +mkdir -p components/animata/card && touch components/animata/card/state-action-card.tsx +``` + +Paste the code + +Open the newly created file and paste the following code: + +```tsx file=/animata/card/state-action-card.tsx +``` + + + +## Use Cases + +- task managers +- social cards +- order cards in a dashboard + +## Credits + +Built by [Ujjwal Basnet](https://github.com/ujjwalbasnet)