Download src/components/NoteCard.tsx from jay-hank/Nexova: direct link, hf CLI and curl.
- Browser
- Download file 2.88 kB
-
https://huggingface.co/spaces/jay-hank/Nexova/resolve/main/src/components/NoteCard.tsx
- Command line
-
hf download hf://spaces/jay-hank/Nexova/src/components/NoteCard.tsx
-
curl -L -o NoteCard.tsx https://huggingface.co/spaces/jay-hank/Nexova/resolve/main/src/components/NoteCard.tsx
2.88 kB
| "use client"; | |
| import { useState } from "react"; | |
| type Note = { | |
| id: string; | |
| title: string; | |
| content: string; | |
| color: string; | |
| pinned: boolean; | |
| createdAt: Date; | |
| updatedAt: Date; | |
| }; | |
| const COLORS: Record<string, string> = { | |
| "#fef08a": "bg-yellow-200/10 border-yellow-500/30", | |
| "#86efac": "bg-emerald-200/10 border-emerald-500/30", | |
| "#93c5fd": "bg-blue-200/10 border-blue-500/30", | |
| "#f9a8d4": "bg-pink-200/10 border-pink-500/30", | |
| "#c4b5fd": "bg-violet-200/10 border-violet-500/30", | |
| }; | |
| export default function NoteCard({ | |
| note, | |
| onEdit, | |
| onDelete, | |
| onPin, | |
| }: { | |
| note: Note; | |
| onEdit: () => void; | |
| onDelete: () => void; | |
| onPin: () => void; | |
| }) { | |
| const [showActions, setShowActions] = useState(false); | |
| const cls = COLORS[note.color] ?? "bg-zinc-800/50 border-zinc-700"; | |
| const ago = timeAgo(note.updatedAt); | |
| return ( | |
| <div | |
| className={`border rounded-xl p-4 ${cls} transition hover:scale-[1.02] group`} | |
| onClick={() => setShowActions((v) => !v)} | |
| > | |
| <div className="flex items-start justify-between mb-2"> | |
| <h3 className="font-semibold text-sm leading-tight flex-1 mr-2"> | |
| {note.pinned ? "π " : ""}{note.title} | |
| </h3> | |
| <div className={`flex gap-1 transition ${showActions ? "opacity-100" : "opacity-0 group-hover:opacity-100"}`}> | |
| <Btn onClick={(e) => { e.stopPropagation(); onPin(); }} title={note.pinned ? "Unpin" : "Pin"}> | |
| {note.pinned ? "β" : "β"} | |
| </Btn> | |
| <Btn onClick={(e) => { e.stopPropagation(); onEdit(); }} title="Edit">β</Btn> | |
| <Btn onClick={(e) => { e.stopPropagation(); onDelete(); }} title="Delete">β</Btn> | |
| </div> | |
| </div> | |
| {note.content && ( | |
| <p className="text-xs text-zinc-400 leading-relaxed line-clamp-4 mb-3">{note.content}</p> | |
| )} | |
| <div className="flex items-center justify-between"> | |
| <span | |
| className="w-3 h-3 rounded-full inline-block" | |
| style={{ backgroundColor: note.color }} | |
| /> | |
| <span className="text-[10px] text-zinc-500">{ago}</span> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function Btn({ onClick, title, children }: { onClick: (e: React.MouseEvent) => void; title: string; children: React.ReactNode }) { | |
| return ( | |
| <button | |
| onClick={onClick} | |
| title={title} | |
| className="w-7 h-7 flex items-center justify-center rounded-lg text-sm text-zinc-400 hover:text-white hover:bg-zinc-700 active:bg-zinc-600 transition" | |
| > | |
| {children} | |
| </button> | |
| ); | |
| } | |
| function timeAgo(d: Date | string): string { | |
| const t = new Date(d).getTime(); | |
| if (isNaN(t)) return ""; | |
| const diff = Date.now() - t; | |
| const mins = Math.floor(diff / 60000); | |
| if (mins < 1) return "just now"; | |
| if (mins < 60) return `${mins}m ago`; | |
| const hrs = Math.floor(mins / 60); | |
| if (hrs < 24) return `${hrs}h ago`; | |
| return `${Math.floor(hrs / 24)}d ago`; | |
| } | |