All files / src/ahuora-design-system/ui search-select-input.tsx

88.09% Statements 37/42
63.04% Branches 29/46
57.14% Functions 4/7
93.75% Lines 30/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101                                                          235x       235x 235x   235x   235x   235x         71x 21x 21x 1091x                 93x   214x 685x 877x 328x 235x 514x 214x   35x       270x 214x 235x   214x 1183x       21x 21x                     877x   449x   663x 940x      
import { CaretSortIcon } from "@radix-ui/react-icons";
import { Check } from "lucide-react";
import * as React from "react";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/ahuora-design-system/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/ahuora-design-system/ui/popover";
import { cn } from "@/lib/utils";
 
interface SearchSelectInputProps {
  options: { value: string; label: string }[];
  value: string;
  onValueChange: (value: string) => void;
  placeholder?: string;
  searchPlaceholder?: string;
  ariaLabel?: string;
  disabled?: boolean;
}
 
// This is a more friendly searchable select input component compared to combobox.tsx
export function SearchSelectInput({
  options,
  value,
  onValueChange,
  placeholder = "Select an option...",
  searchPlaceholder = "Search...",
  ariaLabel,
  disabled = false,
}: SearchSelectInputProps) {
  const [open, setOpen] = React.useState(false);
 
  function handleChange(currentLabel: string) {
    if (disabled) {
      return;
    }
    const currentValue =
      options.find((option) => option.label === currentLabel)?.value || "";
    const nextValue = currentValue === value ? "" : currentValue;
    onValueChange(nextValue);
  }
 
  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger
        aria-label={ariaLabel}
        disabled={disabled}
        className="h-9 px-3 flex justify-between items-center w-full border-b-[1px] border-input shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:border-b-2 focus:border-b-primary"
      >
        <p>
          {" "}
          {value
            ? options.find((option) => option.value === value)?.label
            : placeholder}{" "}
        </p>
        <CaretSortIcon className="h-4 w-4 opacity-50" />
      </PopoverTrigger>
      <PopoverContent className="w-full p-0" side="bottom" align="start">
        <Command>
          <CommandInput
            placeholder={searchPlaceholder}
            className="h-9"
            aria-label="search-select-input"
          />
          <CommandList>
            <CommandEmpty>No option found.</CommandEmpty>
            <CommandGroup>
              {options.map((option) => (
                <CommandItem
                  key={option.value}
                  value={option.label}
                  onSelect={(currentLabel) => {
                    handleChange(currentLabel);
                    setOpen(false);
                  }}
                >
                  {option.label}
                  <Check
                    className={cn(
                      "ml-auto",
                      value === option.value ? "opacity-100" : "opacity-0",
                    )}
                  />
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}