diff --git a/src/components/road-combobox.tsx b/src/components/road-combobox.tsx index da9801d..a985ce2 100644 --- a/src/components/road-combobox.tsx +++ b/src/components/road-combobox.tsx @@ -1,21 +1,15 @@ "use client"; -import { useState } from "react"; import type { Road } from "@/lib/types"; import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { ChevronsUpDown, Loader2 } from "lucide-react"; + Combobox, + ComboboxContent, + ComboboxEmpty, + ComboboxInput, + ComboboxItem, + ComboboxList, +} from "@/components/ui/combobox"; +import { Loader2 } from "lucide-react"; interface RoadComboboxProps { roads: Road[]; @@ -32,54 +26,45 @@ export function RoadCombobox({ disabled, loading, }: RoadComboboxProps) { - const [open, setOpen] = useState(false); - return (
- - + + {"載入中..."} +
+ ) : ( + { + onChange(val ?? null); + }} + itemToStringLabel={(road) => road.name} + filter={(road, query) => { + const q = query.toLowerCase(); + return ( + road.name.toLowerCase().includes(q) || + road.en.toLowerCase().includes(q) + ); + }} > - {loading ? ( - - - {"載入中..."} - - ) : value ? ( - {value.name} - ) : ( - {"請選擇路/街..."} - )} - - - - - - - {"查無結果"} - - {roads.map((road) => ( - { - onChange(road); - setOpen(false); - }} - data-checked={value?.name === road.name ? true : undefined} - > - {road.name} - - ))} - - - - - + + + {"查無結果"} + + {(road: Road) => ( + + {road.name} + + )} + + + + )} ); } diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx new file mode 100644 index 0000000..3767a85 --- /dev/null +++ b/src/components/ui/combobox.tsx @@ -0,0 +1,300 @@ +"use client"; + +import * as React from "react"; +import { Combobox as ComboboxPrimitive } from "@base-ui/react"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, +} from "@/components/ui/input-group"; +import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"; + +const Combobox = ComboboxPrimitive.Root; + +function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { + return ; +} + +function ComboboxTrigger({ + className, + children, + ...props +}: ComboboxPrimitive.Trigger.Props) { + return ( + + {children} + + + ); +} + +function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { + return ( + } + className={cn(className)} + {...props} + > + + + ); +} + +function ComboboxInput({ + className, + children, + disabled = false, + showTrigger = true, + showClear = false, + ...props +}: ComboboxPrimitive.Input.Props & { + showTrigger?: boolean; + showClear?: boolean; +}) { + return ( + + } + {...props} + /> + + {showTrigger && ( + } + data-slot="input-group-button" + className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent" + disabled={disabled} + /> + )} + {showClear && } + + {children} + + ); +} + +function ComboboxContent({ + className, + side = "bottom", + sideOffset = 6, + align = "start", + alignOffset = 0, + anchor, + ...props +}: ComboboxPrimitive.Popup.Props & + Pick< + ComboboxPrimitive.Positioner.Props, + "side" | "align" | "sideOffset" | "alignOffset" | "anchor" + >) { + return ( + + + + + + ); +} + +function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { + return ( + + ); +} + +function ComboboxItem({ + className, + children, + ...props +}: ComboboxPrimitive.Item.Props) { + return ( + + {children} + + } + > + + + + ); +} + +function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { + return ( + + ); +} + +function ComboboxLabel({ + className, + ...props +}: ComboboxPrimitive.GroupLabel.Props) { + return ( + + ); +} + +function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { + return ( + + ); +} + +function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { + return ( +