diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-10-11 16:16:50 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-11 16:16:50 +0100 |
| commit | 87053d2e76a362da9ee417110195ec02673080fd (patch) | |
| tree | 4bf974f693c64a788dc4c92daa239873ba32c38e /apps/web/components/dashboard/lists | |
| parent | dcddda56530b2cedf88d6dacf739ccb506fbfde0 (diff) | |
| download | karakeep-87053d2e76a362da9ee417110195ec02673080fd.tar.zst | |
feat: make list dropdown searchable in Manage Lists modal (#2029)
- Replace simple Select component with searchable Command/Popover component
- Add search input that filters lists as you type (like tags)
- Maintain sidebar ordering using allPaths
- Add visual checkmark for selected list
- Improve UX with keyboard navigation and accessibility
Fixes #2025
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mohamed Bassem <MohamedBassem@users.noreply.github.com>
Diffstat (limited to 'apps/web/components/dashboard/lists')
| -rw-r--r-- | apps/web/components/dashboard/lists/BookmarkListSelector.tsx | 105 |
1 files changed, 75 insertions, 30 deletions
diff --git a/apps/web/components/dashboard/lists/BookmarkListSelector.tsx b/apps/web/components/dashboard/lists/BookmarkListSelector.tsx index 9ce56031..a267c692 100644 --- a/apps/web/components/dashboard/lists/BookmarkListSelector.tsx +++ b/apps/web/components/dashboard/lists/BookmarkListSelector.tsx @@ -1,13 +1,21 @@ +import { useState } from "react"; +import { Button } from "@/components/ui/button"; import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import LoadingSpinner from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; +import { Check, ChevronsUpDown } from "lucide-react"; import { useBookmarkLists } from "@karakeep/shared-react/hooks/lists"; import { ZBookmarkList } from "@karakeep/shared/types/lists"; @@ -29,6 +37,7 @@ export function BookmarkListSelector({ hideBookmarkIds?: string[]; listTypes?: ZBookmarkList["type"][]; }) { + const [open, setOpen] = useState(false); const { data, isPending: isFetchingListsPending } = useBookmarkLists(); let { allPaths } = data ?? {}; @@ -49,29 +58,65 @@ export function BookmarkListSelector({ return !path.map((p) => p.id).includes(hideSubtreeOf); }); + // Find the selected list's display name + const selectedListPath = allPaths?.find( + (path) => path[path.length - 1].id === value, + ); + const selectedListName = selectedListPath + ? selectedListPath.map((p) => `${p.icon} ${p.name}`).join(" / ") + : null; + return ( - <Select onValueChange={onChange} value={value ?? ""}> - <SelectTrigger className={cn("w-full", className)}> - <SelectValue placeholder={placeholder} /> - </SelectTrigger> - <SelectContent> - <SelectGroup> - {allPaths?.map((path) => { - const l = path[path.length - 1]; - const name = path.map((p) => `${p.icon} ${p.name}`).join(" / "); - return ( - <SelectItem key={l.id} value={l.id}> - {name} - </SelectItem> - ); - })} - {allPaths && allPaths.length == 0 && ( - <SelectItem value="nolist" disabled> - You don't currently have any lists. - </SelectItem> - )} - </SelectGroup> - </SelectContent> - </Select> + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <Button + variant="outline" + role="combobox" + aria-expanded={open} + className={cn("w-full justify-between", className)} + > + {selectedListName || placeholder} + <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> + </Button> + </PopoverTrigger> + <PopoverContent className="w-[--radix-popover-trigger-width] p-0"> + <Command> + <CommandInput placeholder="Search lists..." /> + <CommandList> + <CommandEmpty> + {allPaths && allPaths.length === 0 + ? "You don't currently have any lists." + : "No lists found."} + </CommandEmpty> + <CommandGroup className="max-h-60 overflow-y-auto"> + {allPaths?.map((path) => { + const l = path[path.length - 1]; + const name = path.map((p) => `${p.icon} ${p.name}`).join(" / "); + return ( + <CommandItem + key={l.id} + value={l.id} + keywords={[l.name, l.icon]} + onSelect={(currentValue) => { + onChange(currentValue); + setOpen(false); + }} + className="cursor-pointer" + > + <Check + className={cn( + "mr-2 h-4 w-4", + value === l.id ? "opacity-100" : "opacity-0", + )} + /> + {name} + </CommandItem> + ); + })} + </CommandGroup> + </CommandList> + </Command> + </PopoverContent> + </Popover> ); } |
