feat: Phase 3 - filter bar, keyboard navigation, notifications, comments

- FilterBar component with text search, label chips, due date and priority dropdowns
- "/" keyboard shortcut and toolbar button to toggle filter bar
- Keyboard card navigation with J/K/H/L keys, Enter to open, Escape to clear
- Focus ring on keyboard-selected cards with auto-scroll
- Desktop notifications for due/overdue cards via tauri-plugin-notification
- CommentsSection component with add/delete and relative timestamps
- Filtered card count display in column headers
This commit is contained in:
Your Name
2026-02-16 14:52:08 +02:00
parent a17c8b6b62
commit fc4310a30f
17 changed files with 791 additions and 140 deletions

View File

@@ -25,11 +25,13 @@ const WIDTH_MAP = {
interface KanbanColumnProps {
column: Column;
filteredCardIds?: string[];
focusedCardId?: string | null;
onCardClick?: (cardId: string) => void;
isNew?: boolean;
}
export function KanbanColumn({ column, onCardClick, isNew }: KanbanColumnProps) {
export function KanbanColumn({ column, filteredCardIds, focusedCardId, onCardClick, isNew }: KanbanColumnProps) {
const [showAddCard, setShowAddCard] = useState(false);
const board = useBoardStore((s) => s.board);
const toggleColumnCollapse = useBoardStore((s) => s.toggleColumnCollapse);
@@ -62,6 +64,8 @@ export function KanbanColumn({ column, onCardClick, isNew }: KanbanColumnProps)
? `3px solid ${board.color}30`
: undefined;
const displayCardIds = filteredCardIds ?? column.cardIds;
const isFiltering = filteredCardIds != null;
const cardCount = column.cardIds.length;
const wipTint = column.wipLimit != null
@@ -117,7 +121,7 @@ export function KanbanColumn({ column, onCardClick, isNew }: KanbanColumnProps)
>
{/* The column header is the drag handle for column reordering */}
<div {...listeners}>
<ColumnHeader column={column} cardCount={column.cardIds.length} />
<ColumnHeader column={column} cardCount={cardCount} filteredCount={isFiltering ? displayCardIds.length : undefined} />
</div>
{/* Card list - wrapped in SortableContext for within-column sorting */}
@@ -138,7 +142,7 @@ export function KanbanColumn({ column, onCardClick, isNew }: KanbanColumnProps)
initial="hidden"
animate="visible"
>
{column.cardIds.map((cardId) => {
{displayCardIds.map((cardId) => {
const card = board?.cards[cardId];
if (!card) return null;
return (
@@ -148,13 +152,14 @@ export function KanbanColumn({ column, onCardClick, isNew }: KanbanColumnProps)
boardLabels={board?.labels ?? []}
columnId={column.id}
onCardClick={onCardClick}
isFocused={focusedCardId === cardId}
/>
</li>
);
})}
{column.cardIds.length === 0 && (
{displayCardIds.length === 0 && (
<li className="flex min-h-[60px] items-center justify-center rounded-md border border-dashed border-pylon-text-secondary/20 text-xs text-pylon-text-secondary/50">
Drop or add a card
{isFiltering ? "No matching cards" : "Drop or add a card"}
</li>
)}
</motion.ul>