| 1038 | } |
| 1039 | |
| 1040 | function TaskCard({ |
| 1041 | taskDomId, |
| 1042 | task, |
| 1043 | isOverdue, |
| 1044 | isFocused, |
| 1045 | isDragging, |
| 1046 | draggable, |
| 1047 | cardRef, |
| 1048 | onClickRow, |
| 1049 | onOpen, |
| 1050 | shouldSuppressClick, |
| 1051 | onToggle, |
| 1052 | onPointerDown |
| 1053 | }: CardProps): JSX.Element { |
| 1054 | return ( |
| 1055 | <div |
| 1056 | ref={cardRef ?? undefined} |
| 1057 | hidden={isDragging} |
| 1058 | data-kanban-task-id={taskDomId} |
| 1059 | onClick={() => { |
| 1060 | if (shouldSuppressClick()) return |
| 1061 | onClickRow() |
| 1062 | onOpen() |
| 1063 | }} |
| 1064 | onPointerDown={(e) => { |
| 1065 | if (!draggable) return |
| 1066 | onPointerDown(e) |
| 1067 | }} |
| 1068 | className={[ |
| 1069 | 'group rounded-md border-l-2 bg-paper-100/85 px-2.5 py-1.5 transition-colors select-none', |
| 1070 | isOverdue ? 'border-rose-500/70' : 'border-paper-300/60', |
| 1071 | isFocused ? 'ring-1 ring-accent/60' : 'hover:bg-paper-200/60', |
| 1072 | draggable ? 'cursor-grab active:cursor-grabbing' : '' |
| 1073 | ].join(' ')} |
| 1074 | > |
| 1075 | <div className="flex items-start gap-2"> |
| 1076 | {/* Interactive controls stop pointerdown so they do not start a card drag. */} |
| 1077 | <button |
| 1078 | type="button" |
| 1079 | role="checkbox" |
| 1080 | aria-checked={task.checked} |
| 1081 | draggable={false} |
| 1082 | onPointerDown={(e) => e.stopPropagation()} |
| 1083 | onMouseDown={(e) => e.stopPropagation()} |
| 1084 | onClick={(e) => { |
| 1085 | e.stopPropagation() |
| 1086 | onToggle() |
| 1087 | }} |
| 1088 | className={[ |
| 1089 | 'mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded transition-colors', |
| 1090 | task.checked |
| 1091 | ? 'border border-accent bg-accent text-white' |
| 1092 | : 'border border-paper-400/70 hover:bg-paper-200/80' |
| 1093 | ].join(' ')} |
| 1094 | > |
| 1095 | {task.checked && ( |
| 1096 | <svg |
| 1097 | viewBox="0 0 24 24" |