Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 58 additions & 40 deletions src/components/bookmarks/BookmarkItem.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { forwardRef } from 'react'
import { Pencil, Trash, Square, CheckSquare } from '../ui/Icons'
import { Pencil, Trash, Check } from '../ui/Icons'

export const BookmarkItem = forwardRef(function BookmarkItem(
{ bookmark, isSelected, isChecked, selectionMode, keyboardNavActive, onEdit, onDelete, onTagClick, onToggleSelect, onMouseEnter },
Expand All @@ -26,6 +26,20 @@ export const BookmarkItem = forwardRef(function BookmarkItem(
}
}

const handleCheckboxClick = (e) => {
e.stopPropagation()
e.preventDefault()
// If not in selection mode, clicking checkbox initiates selection
if (!selectionMode) {
onToggleSelect?.(bookmark._id, true)
} else {
onToggleSelect?.(bookmark._id)
}
}

// Keyboard selection should be visible on top of checked state
const showKeyboardSelection = isSelected && keyboardNavActive

return (
<div
ref={ref}
Expand All @@ -35,29 +49,31 @@ export const BookmarkItem = forwardRef(function BookmarkItem(
selectionMode ? 'cursor-pointer' : 'cursor-default'
} ${
isChecked
? 'bg-primary/10 ring-1 ring-primary/30'
: isSelected
? 'bg-accent ring-1 ring-ring'
: keyboardNavActive
? ''
: 'hover:bg-accent/50'
? 'bg-primary/15'
: keyboardNavActive
? ''
: 'hover:bg-accent/50'
} ${
showKeyboardSelection
? 'ring-2 ring-ring ring-offset-1 ring-offset-background'
: ''
}`}
>
{selectionMode && (
<button
onClick={(e) => {
e.stopPropagation()
onToggleSelect?.(bookmark._id)
}}
className="flex-shrink-0 text-muted-foreground hover:text-foreground transition-colors"
>
{isChecked ? (
<CheckSquare className="w-4 h-4 text-primary" strokeWidth={1.5} />
) : (
<Square className="w-4 h-4" strokeWidth={1.5} />
)}
</button>
)}
{/* Always render checkbox area for consistent alignment */}
<button
onClick={handleCheckboxClick}
className={`flex-shrink-0 w-4 h-4 rounded border transition-all duration-150 flex items-center justify-center ${
isChecked
? 'bg-primary border-primary'
: selectionMode
? 'border-muted-foreground/40 hover:border-muted-foreground bg-transparent'
: 'border-transparent bg-transparent opacity-0 group-hover:opacity-100 group-hover:border-muted-foreground/30 hover:!border-muted-foreground/50'
}`}
>
{isChecked && (
<Check className="w-3 h-3 text-primary-foreground" strokeWidth={2.5} />
)}
</button>

<img
src={faviconUrl}
Expand Down Expand Up @@ -99,24 +115,26 @@ export const BookmarkItem = forwardRef(function BookmarkItem(
</div>
</div>

{!selectionMode && (
<div className="flex items-center gap-0.5 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity">
<button
onClick={() => onEdit(bookmark)}
className="p-1.5 text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors"
title="Edit"
>
<Pencil className="w-3.5 h-3.5" strokeWidth={1.5} />
</button>
<button
onClick={() => onDelete(bookmark._id)}
className="p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-md transition-colors"
title="Delete"
>
<Trash className="w-3.5 h-3.5" strokeWidth={1.5} />
</button>
</div>
)}
<div className={`flex items-center gap-0.5 transition-opacity ${
selectionMode
? 'opacity-0 pointer-events-none'
: 'opacity-100 md:opacity-0 md:group-hover:opacity-100'
}`}>
<button
onClick={() => onEdit(bookmark)}
className="p-1.5 text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors"
title="Edit"
>
<Pencil className="w-3.5 h-3.5" strokeWidth={1.5} />
</button>
<button
onClick={() => onDelete(bookmark._id)}
className="p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-md transition-colors"
title="Delete"
>
<Trash className="w-3.5 h-3.5" strokeWidth={1.5} />
</button>
</div>
</div>
)
})
4 changes: 4 additions & 0 deletions src/components/bookmarks/BookmarkList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ export function BookmarkList() {
const next = new Set(prev)
if (next.has(id)) {
next.delete(id)
// Exit selection mode if no items remain selected
if (next.size === 0) {
setSelectionMode(false)
}
} else {
next.add(id)
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/bookmarks/FilterBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export function FilterBar({
<Menu className="w-5 h-5" strokeWidth={1.5} />
</button>

{/* Spacer to align search with bookmark titles (accounts for checkbox + gap) */}
<div className="hidden lg:block w-7 flex-shrink-0" />

<div className="flex-1 relative">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none">
<Search className="w-4 h-4" strokeWidth={1.5} />
Expand Down
9 changes: 8 additions & 1 deletion src/hooks/useHotkeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,16 @@ export function useHotkeys(hotkeyMap, options = {}) {
if (!enableOnInputs && isInputElement(e.target)) return

const normalizedKey = normalizeKey(e)


// Handle escape specially - reset sequence and check for escape handler
if (normalizedKey === 'esc') {
resetSequence()
// Check for escape/esc handler and call it
const escapeHandler = hotkeyMap['escape'] || hotkeyMap['esc']
if (escapeHandler) {
e.preventDefault()
escapeHandler(e)
}
return
}

Expand Down