aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/lists
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-10-11 16:16:50 +0100
committerGitHub <noreply@github.com>2025-10-11 16:16:50 +0100
commit87053d2e76a362da9ee417110195ec02673080fd (patch)
tree4bf974f693c64a788dc4c92daa239873ba32c38e /apps/web/components/dashboard/lists
parentdcddda56530b2cedf88d6dacf739ccb506fbfde0 (diff)
downloadkarakeep-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.tsx105
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&apos;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>
);
}