60:00
1import { useEffect, useState } from "react";2
3import { EstimateBadge } from "@/components/features/pxl/tasks/estimate-badge";4
5export default function StatusBadgeDemo() {6 const [value, setValue] = useState<number>(60 * 60);7 const [isRunning, setIsRunning] = useState(false);8
9 const startTimer = () => {10 setValue(60 * 60);11 setIsRunning((value) => !value);12 };13
14 useEffect(() => {15 if (!isRunning || value <= 0) {16 return;17 }18
19 const interval = setInterval(() => {20 setValue((current) => {21 if (current <= 1) {22 setIsRunning(false);23 return 0;24 }25
26 return current - 1;27 });28 }, 1000);29
30 return () => clearInterval(interval);31 }, [isRunning, value]);32
33 return (34 <div className="flex flex-col gap-4">35 <EstimateBadge onClick={startTimer} value={value} />36 </div>37 );38}Installation
Section titled “Installation”pnpm dlx shadcn@latest add pxl-ui/registry/tasks/estimate-badge
Copy and paste the following code into your project.
1import { type ComponentProps, useState } from "react";2
3import { Badge } from "@/components/ui/pxl/badge";4import type { TaskNotes } from "@/lib/schemas/pxl/tasknotes";5import { cn } from "@/lib/utils";6
7function formatEstimate(seconds: number): string {8 const minutes = Math.floor(seconds / 60);9 const secs = seconds % 60;10
11 return `${minutes.toString().padStart(2, "0")}:${secs12 .toString()13 .padStart(2, "0")}`;14}15
16function EstimateBadge({17 className,18 value,19 onClick,20 ...props21}: ComponentProps<typeof Badge> & {22 showLabel?: boolean;23 value: number;24}) {25 const [isFlipping, setIsFlipping] = useState(false);26
27 const handleClick = () => {28 console.log("isFlipping")29 setIsFlipping(true);30 };31
32 const handleAnimationEnd = () => {33 setIsFlipping(false);34 onClick?.(new MouseEvent("click") as never);35 };36
37 return (38 <Badge39 className={cn(className, "perspective-normal")}40 variant="muted"41 {...props}42 onClick={handleClick}43 >44 <span45 onAnimationEnd={handleAnimationEnd}46 className={isFlipping ? "animate-flip" : ""}47 >48 <svg49 xmlns="http://www.w3.org/2000/svg"50 fill="currentColor"51 viewBox="0 0 24 24"52 className="size-3 mr-0.5"53 >54 <path d="M13 18h3v-2h2v4h-2v2H8v-2H6v-4h2v2h3v-4h-1v-4H8V8h8v2h-2v4h-1v4Zm-3-2H8v-2h2v2Zm6 0h-2v-2h2v2ZM8 8H6V4h2v4Zm10 0h-2V4h2v4Zm-2-4H8V2h8v2Z"></path>55 </svg>56 </span>57 {formatEstimate(value)}58 </Badge>59 );60}61
62function TaskNotesEstimateBadge({63 task,64 ...props65}: ComponentProps<typeof Badge> & {66 showLabel?: boolean;67 task: TaskNotes.Task;68}) {69 const value = (task.timeEstimate ?? 0) * 60; // Estimate is in minutes, we need to transform it to seconds.70 return <EstimateBadge {...props} value={value} />;71}72EstimateBadge.TaskNotes = TaskNotesEstimateBadge;73
74export { EstimateBadge };Update the import paths to match your project setup.
import { EstimateBadge } from "@/components/features/pxl/tasks/estimate-badge";<EstimateBadge value={60} />