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

88.09% Statements 37/42
60.86% Branches 28/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                                                          275x       275x 275x   275x   275x   275x         81x 25x 25x 1275x                 106x   250x 809x 1025x 381x 275x 593x 250x   39x       314x 250x 275x   250x 1320x       25x 25x                     1025x   525x   775x 1100x      
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>
  );
}