Vector Motion
Medical

Upcoming Appointments Card - Scheduling

Monitor upcoming patient appointments and schedule. Track appointment reminders and patient notifications.

Upcoming Appointments Card

The Upcoming Appointments Card Display upcoming patient appointments and scheduling.

Preview

Installation

ash npx shadcn@latest add https://vectormotion.vercel.app/registry/upcoming-appointments-card.json

Upcoming Appointments Card
'use client'import React from 'react';import { CalendarClock, Calendar, MoreHorizontal, Video, Stethoscope, FileText, LucideIcon } from 'lucide-react';import { motion } from 'motion/react';import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs));}interface Appointment {  time: string;  patient: string;  type: string;  status: string;  icon: LucideIcon;}interface UpcomingAppointmentsCardProps {  className?: string;  title?: string;  subtitle?: string;  appointments?: Appointment[];}const DEFAULT_APPOINTMENTS: Appointment[] = [  { time: '09:00 AM', patient: 'Sarah Connor', type: 'Check-up', status: 'current', icon: Stethoscope },  { time: '10:30 AM', patient: 'Rick Deckard', type: 'Tele-Health', status: 'upcoming', icon: Video },  { time: '01:00 PM', patient: 'Ellen Ripley', type: 'Lab Review', status: 'upcoming', icon: FileText },];const DEFAULT_TITLE = "Schedule";const DEFAULT_SUBTITLE = "Today's Agenda";export const UpcomingAppointmentsCard: React.FC<UpcomingAppointmentsCardProps> = ({  className = "",  title = DEFAULT_TITLE,  subtitle = DEFAULT_SUBTITLE,  appointments = DEFAULT_APPOINTMENTS,}) => {  const isInteractive = true;  const index = 2;  return (    <motion.div      layoutId={isInteractive ? `card-${index}-${title}` : undefined}      transition={{ duration: 0.4, ease: "easeOut" }}      className={cn(        "relative overflow-hidden rounded-2xl border border-border bg-card text-card-foreground shadow-sm transition-all flex flex-col h-full",        isInteractive ? "hover:border-violet-300 dark:hover:border-violet-700 hover:shadow-md" : "",        className      )}    >      <div className="p-5 flex flex-col h-full relative z-10">        <div className="mb-5 flex items-start justify-between">          <div>            <h3 className="font-bold text-lg text-foreground">              {title}            </h3>            <p className="text-sm text-muted-foreground font-medium mt-1">              {subtitle}            </p>          </div>          <div className="rounded-xl bg-violet-500/10 p-2.5 text-violet-500 flex items-center justify-center ring-1 ring-violet-100 dark:ring-violet-800">            <CalendarClock className="h-5 w-5" />          </div>        </div>        <div className="relative flex-1">          {/* Timeline Line */}          <div className="absolute left-[19px] top-2 bottom-4 w-px bg-zinc-100 dark:bg-zinc-800 z-0" />          <div className="space-y-6 relative z-10">            {appointments.map((appt, i) => {              const Icon = appt.icon;              const isCurrent = appt.status === 'current';              return (                <motion.div                  key={i}                  initial={{ opacity: 0, x: -10 }}                  animate={{ opacity: 1, x: 0 }}                  transition={{ delay: i * 0.15 }}                  className={cn(                    "flex gap-4 relative group",                    isCurrent ? "" : "opacity-80 hover:opacity-100 transition-opacity"                  )}                >                  {/* Timeline Node */}                  <div className={cn(                    "relative z-10 h-10 w-10 rounded-full flex items-center justify-center border-4 border-white dark:border-zinc-900 shadow-sm",                    isCurrent                      ? "bg-violet-500 text-white"                      : "bg-zinc-100 dark:bg-zinc-800 text-muted-foreground dark:text-muted-foreground"                  )}>                    <Icon className="h-4 w-4" />                    {isCurrent && (                      <span className="absolute -right-1 -top-1 flex h-3 w-3">                        <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-violet-400 opacity-75"></span>                        <span className="relative inline-flex rounded-full h-3 w-3 bg-violet-500"></span>                      </span>                    )}                  </div>                  {/* Content */}                  <div className={cn(                    "flex-1 p-3 rounded-xl border transition-all",                    isCurrent                      ? "bg-violet-50/50 dark:bg-violet-900/10 border-violet-100 dark:border-violet-800/50"                      : "bg-white dark:bg-zinc-800/30 border-border group-hover:border-zinc-200 dark:group-hover:border-zinc-700"                  )}>                    <div className="flex justify-between items-start mb-1">                      <span className={cn(                        "text-xs font-bold px-2 py-0.5 rounded-md",                        isCurrent                          ? "bg-white dark:bg-violet-900/40 text-violet-700 dark:text-violet-300 shadow-sm"                          : "bg-zinc-100 dark:bg-zinc-800 text-muted-foreground"                      )}>                        {appt.time}                      </span>                      <button className="text-muted-foreground hover:text-zinc-600">                        <MoreHorizontal className="h-4 w-4" />                      </button>                    </div>                    <h4 className="font-semibold text-sm text-foreground">{appt.patient}</h4>                    <p className="text-xs text-muted-foreground font-medium">{appt.type}</p>                  </div>                </motion.div>              );            })}          </div>        </div>        <button className="mt-4 w-full py-2 flex items-center justify-center gap-2 rounded-lg border border-dashed border-zinc-300 dark:border-zinc-700 text-xs font-medium text-muted-foreground hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-all">          <Calendar className="h-3.5 w-3.5" /> View Full Calendar        </button>      </div>    </motion.div>  );};

Usage

This component is a demo card displaying medical metrics with animated visualizations and dark mode support.

Prop

Type