Mobile-responsive: task cards, detail panel, sticky header
- TaskCard: hide action buttons on mobile (tap to open detail panel), smaller text/badges, word-break titles - TaskDetailPanel: full-screen on mobile with Back button, responsive padding, stacked timeline, hidden UUID on small screens - QueuePage: sticky header offset for mobile nav bar (top-14) - Priority/source grid stacks vertically on mobile
This commit is contained in:
@@ -72,29 +72,30 @@ export function TaskCard({
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`rounded-xl border p-4 transition-all cursor-pointer group ${
|
||||
className={`rounded-xl border p-3 sm:p-4 transition-all cursor-pointer group ${
|
||||
isActive
|
||||
? "border-amber-300 bg-gradient-to-r from-amber-50 to-orange-50 shadow-lg shadow-amber-100/50 hover:shadow-xl hover:shadow-amber-200/50"
|
||||
: "border-gray-200 bg-white shadow-sm hover:shadow-md hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
{/* Top row: title + expand chevron */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap mb-1.5">
|
||||
{isActive && (
|
||||
<span className="relative flex h-2.5 w-2.5 mr-0.5">
|
||||
<span className="relative flex h-2.5 w-2.5 mr-0.5 shrink-0">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-amber-500"></span>
|
||||
</span>
|
||||
)}
|
||||
<h3 className={`font-semibold truncate ${isActive ? "text-amber-900" : "text-gray-900"}`}>
|
||||
<h3 className={`font-semibold text-sm sm:text-base leading-snug ${isActive ? "text-amber-900" : "text-gray-900"}`} style={{ wordBreak: "break-word" }}>
|
||||
{task.title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
||||
<div className="flex items-center gap-1.5 sm:gap-2 mb-1.5 flex-wrap">
|
||||
<span
|
||||
className="text-xs px-1.5 py-0.5 rounded font-mono font-bold bg-amber-100 text-amber-700 cursor-pointer hover:bg-amber-200 transition"
|
||||
className="text-[10px] sm:text-xs px-1.5 py-0.5 rounded font-mono font-bold bg-amber-100 text-amber-700 cursor-pointer hover:bg-amber-200 transition"
|
||||
title={`Click to copy: ${displayId}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -103,68 +104,69 @@ export function TaskCard({
|
||||
>
|
||||
{displayId}
|
||||
</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full font-medium ${priorityColors[task.priority]}`}>
|
||||
<span className={`text-[10px] sm:text-xs px-1.5 sm:px-2 py-0.5 rounded-full font-medium ${priorityColors[task.priority]}`}>
|
||||
{task.priority}
|
||||
</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full font-medium ${sourceColors[task.source] || sourceColors.other}`}>
|
||||
<span className={`text-[10px] sm:text-xs px-1.5 sm:px-2 py-0.5 rounded-full font-medium ${sourceColors[task.source] || sourceColors.other}`}>
|
||||
{task.source}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-[10px] sm:text-xs text-gray-400">
|
||||
{timeAgo(task.createdAt)}
|
||||
</span>
|
||||
{noteCount > 0 && (
|
||||
<span className="text-xs text-gray-400 flex items-center gap-0.5">
|
||||
<span className="text-[10px] sm:text-xs text-gray-400 flex items-center gap-0.5">
|
||||
💬 {noteCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{task.description && (
|
||||
<p className="text-sm text-gray-500 line-clamp-1">{task.description}</p>
|
||||
<p className="text-xs sm:text-sm text-gray-500 line-clamp-1">{task.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 shrink-0" onClick={(e) => e.stopPropagation()}>
|
||||
{/* Reorder buttons for queued tasks */}
|
||||
{task.status === "queued" && (
|
||||
<div className="flex gap-1 mr-1">
|
||||
<button
|
||||
onClick={onMoveUp}
|
||||
disabled={isFirst}
|
||||
className="text-xs px-1.5 py-0.5 rounded-md bg-gray-100 hover:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition"
|
||||
title="Move up"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
onClick={onMoveDown}
|
||||
disabled={isLast}
|
||||
className="text-xs px-1.5 py-0.5 rounded-md bg-gray-100 hover:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition"
|
||||
title="Move down"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* Expand chevron - always visible */}
|
||||
<div className="shrink-0 mt-1 text-gray-300 group-hover:text-gray-500 transition">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick status actions - show fewer on card, full set in detail */}
|
||||
{actions.slice(0, 2).map((action) => (
|
||||
{/* Action buttons row - hidden on mobile, shown on sm+ */}
|
||||
<div className="hidden sm:flex items-center gap-1 mt-2 pt-2 border-t border-gray-100" onClick={(e) => e.stopPropagation()}>
|
||||
{/* Reorder buttons for queued tasks */}
|
||||
{task.status === "queued" && (
|
||||
<div className="flex gap-1 mr-1">
|
||||
<button
|
||||
key={action.next}
|
||||
onClick={() => onStatusChange(task.id, action.next)}
|
||||
className="text-xs px-2.5 py-1.5 rounded-lg border border-gray-200 hover:bg-gray-50 whitespace-nowrap text-gray-600 hover:text-gray-800 transition font-medium"
|
||||
onClick={onMoveUp}
|
||||
disabled={isFirst}
|
||||
className="text-xs px-1.5 py-0.5 rounded-md bg-gray-100 hover:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition"
|
||||
title="Move up"
|
||||
>
|
||||
{action.label}
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
onClick={onMoveDown}
|
||||
disabled={isLast}
|
||||
className="text-xs px-1.5 py-0.5 rounded-md bg-gray-100 hover:bg-gray-200 disabled:opacity-30 disabled:cursor-not-allowed transition"
|
||||
title="Move down"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
))}
|
||||
|
||||
{/* Expand indicator */}
|
||||
<div className="ml-1 text-gray-300 group-hover:text-gray-500 transition">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick status actions */}
|
||||
{actions.slice(0, 2).map((action) => (
|
||||
<button
|
||||
key={action.next}
|
||||
onClick={() => onStatusChange(task.id, action.next)}
|
||||
className="text-xs px-2.5 py-1.5 rounded-lg border border-gray-200 hover:bg-gray-50 whitespace-nowrap text-gray-600 hover:text-gray-800 transition font-medium"
|
||||
>
|
||||
{action.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user