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:
@@ -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)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ interface CardThumbnailProps {
|
||||
card: Card;
|
||||
boardLabels: Label[];
|
||||
columnId: string;
|
||||
onCardClick?: (cardId: string) => void;
|
||||
}
|
||||
|
||||
export function CardThumbnail({ card, boardLabels, columnId }: CardThumbnailProps) {
|
||||
export function CardThumbnail({ card, boardLabels, columnId, onCardClick }: CardThumbnailProps) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
@@ -35,8 +36,7 @@ export function CardThumbnail({ card, boardLabels, columnId }: CardThumbnailProp
|
||||
const overdue = dueDate != null && isPast(dueDate) && !isToday(dueDate);
|
||||
|
||||
function handleClick() {
|
||||
// Card detail modal will be wired in Task 11
|
||||
console.log("Card clicked:", card.id);
|
||||
onCardClick?.(card.id);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -23,9 +23,10 @@ const WIDTH_MAP = {
|
||||
|
||||
interface KanbanColumnProps {
|
||||
column: Column;
|
||||
onCardClick?: (cardId: string) => void;
|
||||
}
|
||||
|
||||
export function KanbanColumn({ column }: KanbanColumnProps) {
|
||||
export function KanbanColumn({ column, onCardClick }: KanbanColumnProps) {
|
||||
const [showAddCard, setShowAddCard] = useState(false);
|
||||
const board = useBoardStore((s) => s.board);
|
||||
|
||||
@@ -85,6 +86,7 @@ export function KanbanColumn({ column }: KanbanColumnProps) {
|
||||
card={card}
|
||||
boardLabels={board?.labels ?? []}
|
||||
columnId={column.id}
|
||||
onCardClick={onCardClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user