Files
local-cal/src/components/event-card.tsx

139 lines
3.9 KiB
TypeScript

"use client";
import { motion } from "framer-motion";
import {
Clock,
ExternalLink,
LucideMapPin,
MoreHorizontal,
Pencil,
Trash2,
} from "lucide-react";
import { RRuleDisplay } from "@/components/rrule-display";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { formatEventRangeLabel } from "@/lib/event-date-format";
import type { CalendarEvent } from "@/lib/types";
interface EventCardProps {
event: CalendarEvent;
onEdit: (event: CalendarEvent) => void;
onDelete: (eventId: string) => void;
}
export const EVENT_CARD_SURFACE_CLASSES =
"glass-card group cursor-pointer p-4 transition-[background-color,border-color,transform] duration-150 hover:-translate-y-0.5 hover:bg-accent/30 hover:border-primary/15";
export const EventCard = ({ event, onEdit, onDelete }: EventCardProps) => {
const handleEdit = () => {
onEdit({
id: event.id,
title: event.title,
description: event.description || "",
location: event.location || "",
url: event.url || "",
start: event.start,
end: event.end || "",
allDay: event.allDay || false,
recurrenceRule: event.recurrenceRule,
});
};
return (
<motion.div
layout
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8, transition: { duration: 0.15 } }}
transition={{ duration: 0.2 }}
>
<div className={EVENT_CARD_SURFACE_CLASSES}>
<div className="flex items-start gap-3">
<div className="min-w-0 flex-1 space-y-1.5">
<h3 className="truncate text-sm font-medium leading-snug">
{event.title}
</h3>
{event.description && (
<p className="line-clamp-2 text-xs leading-relaxed text-muted-foreground">
{event.description}
</p>
)}
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
<span className="inline-flex items-center gap-1">
<Clock className="h-3 w-3 shrink-0" />
{formatEventRangeLabel(event)}
</span>
{event.location && (
<span className="inline-flex items-center gap-1 truncate">
<LucideMapPin className="h-3 w-3 shrink-0" />
<span className="truncate">{event.location}</span>
</span>
)}
{event.url && (
<Button
variant="link"
size="sm"
className="h-auto gap-1 p-0 text-xs text-primary/70 hover:text-primary"
asChild
>
<a
href={event.url}
target="_blank"
rel="noopener noreferrer"
onClick={(currentEvent) => currentEvent.stopPropagation()}
>
<ExternalLink className="h-3 w-3" />
<span className="max-w-[120px] truncate">Link</span>
</a>
</Button>
)}
</div>
{event.recurrenceRule && (
<RRuleDisplay rrule={event.recurrenceRule} start={event.start} />
)}
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 shrink-0 text-muted-foreground/70 hover:text-foreground"
aria-label="Event actions"
>
<MoreHorizontal className="h-3.5 w-3.5" />
<span className="sr-only">Event actions</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-36">
<DropdownMenuItem onClick={handleEdit}>
<Pencil className="h-3.5 w-3.5" />
Edit
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => onDelete(event.id)}
className="text-destructive focus:text-destructive"
>
<Trash2 className="h-3.5 w-3.5" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</motion.div>
);
};