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 (
+
+ );
+}
+
+function ComboboxSeparator({
+ className,
+ ...props
+}: ComboboxPrimitive.Separator.Props) {
+ return (
+
+ );
+}
+
+function ComboboxChips({
+ className,
+ ...props
+}: React.ComponentPropsWithRef &
+ ComboboxPrimitive.Chips.Props) {
+ return (
+
+ );
+}
+
+function ComboboxChip({
+ className,
+ children,
+ showRemove = true,
+ ...props
+}: ComboboxPrimitive.Chip.Props & {
+ showRemove?: boolean;
+}) {
+ return (
+
+ {children}
+ {showRemove && (
+ }
+ className="-ml-1 opacity-50 hover:opacity-100"
+ data-slot="combobox-chip-remove"
+ >
+
+
+ )}
+
+ );
+}
+
+function ComboboxChipsInput({
+ className,
+ ...props
+}: ComboboxPrimitive.Input.Props) {
+ return (
+
+ );
+}
+
+function useComboboxAnchor() {
+ return React.useRef(null);
+}
+
+export {
+ Combobox,
+ ComboboxInput,
+ ComboboxContent,
+ ComboboxList,
+ ComboboxItem,
+ ComboboxGroup,
+ ComboboxLabel,
+ ComboboxCollection,
+ ComboboxEmpty,
+ ComboboxSeparator,
+ ComboboxChips,
+ ComboboxChip,
+ ComboboxChipsInput,
+ ComboboxTrigger,
+ ComboboxValue,
+ useComboboxAnchor,
+};