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:
Your Name
2026-02-15 19:05:02 +02:00
parent 86de747bc4
commit b527d441e3
9 changed files with 796 additions and 5 deletions

View File

@@ -23,6 +23,7 @@ import {
CardOverlay,
ColumnOverlay,
} from "@/components/board/DragOverlayContent";
import { CardDetailModal } from "@/components/card-detail/CardDetailModal";
import type { Board } from "@/types/board";
function findColumnByCardId(board: Board, cardId: string) {
@@ -35,6 +36,7 @@ export function BoardView() {
const moveCard = useBoardStore((s) => s.moveCard);
const moveColumn = useBoardStore((s) => s.moveColumn);
const [selectedCardId, setSelectedCardId] = useState<string | null>(null);
const [addingColumn, setAddingColumn] = useState(false);
const [newColumnTitle, setNewColumnTitle] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
@@ -233,6 +235,7 @@ export function BoardView() {
const columnIds = board.columns.map((c) => c.id);
return (
<>
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
@@ -246,7 +249,11 @@ export function BoardView() {
>
<div className="flex h-full gap-6 overflow-x-auto p-6">
{board.columns.map((column) => (
<KanbanColumn key={column.id} column={column} />
<KanbanColumn
key={column.id}
column={column}
onCardClick={setSelectedCardId}
/>
))}
{/* Add column button / inline input */}
@@ -307,5 +314,11 @@ export function BoardView() {
) : null}
</DragOverlay>
</DndContext>
<CardDetailModal
cardId={selectedCardId}
onClose={() => setSelectedCardId(null)}
/>
</>
);
}