feat: add two-panel card detail modal with markdown, checklist, labels, dates, attachments
- CardDetailModal: two-panel layout (60/40) with inline title editing - MarkdownEditor: edit/preview toggle with react-markdown + remark-gfm - ChecklistSection: add/toggle/edit/delete items with progress counter - LabelPicker: toggle labels + create new labels with color swatches - DueDatePicker: date input with relative time and overdue styling - AttachmentSection: list with remove, placeholder add button - Wired into BoardView via selectedCardId state
This commit is contained in:
69
src/components/card-detail/AttachmentSection.tsx
Normal file
69
src/components/card-detail/AttachmentSection.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { FileIcon, X, Plus } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useBoardStore } from "@/stores/board-store";
|
||||
import type { Attachment } from "@/types/board";
|
||||
|
||||
interface AttachmentSectionProps {
|
||||
cardId: string;
|
||||
attachments: Attachment[];
|
||||
attachmentMode: "link" | "copy";
|
||||
}
|
||||
|
||||
export function AttachmentSection({
|
||||
cardId,
|
||||
attachments,
|
||||
}: AttachmentSectionProps) {
|
||||
const removeAttachment = useBoardStore((s) => s.removeAttachment);
|
||||
|
||||
function handleAdd() {
|
||||
// Placeholder: Tauri file dialog will be wired in a later task
|
||||
console.log("Add attachment (file dialog not yet wired)");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-mono text-xs uppercase tracking-widest text-pylon-text-secondary">
|
||||
Attachments
|
||||
</h4>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={handleAdd}
|
||||
className="text-pylon-text-secondary hover:text-pylon-text"
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Attachment list */}
|
||||
{attachments.length > 0 ? (
|
||||
<div className="flex flex-col gap-1">
|
||||
{attachments.map((att) => (
|
||||
<div
|
||||
key={att.id}
|
||||
className="group/att flex items-center gap-2 rounded px-1 py-1 hover:bg-pylon-column/60"
|
||||
>
|
||||
<FileIcon className="size-3.5 shrink-0 text-pylon-text-secondary" />
|
||||
<span className="flex-1 truncate text-sm text-pylon-text">
|
||||
{att.name}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => removeAttachment(cardId, att.id)}
|
||||
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/att:opacity-100"
|
||||
aria-label="Remove attachment"
|
||||
>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs italic text-pylon-text-secondary/60">
|
||||
No attachments
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user