pankod/refine

View on GitHub
packages/devtools-ui/src/components/select.tsx

Summary

Maintainability
A
35 mins
Test Coverage
import React from "react";
import clsx from "clsx";
import { ChevronDownIcon } from "./icons/chevron-down";
import { CloseIcon } from "./icons/close";

type SelectCommonProps = {
  placeholder?: string;
  hint?: string;
  className?: string;
  options: {
    label: string;
    value: string;
  }[];
};

type SelectSingleProps = {
  type: "single";
  value?: string;
  onChange?: (value: string | undefined) => void;
};

type SelectMultiProps = {
  type: "multiple";
  value?: string[];
  onChange?: (value: string[]) => void;
};

type SelectProps<TType extends "multiple" | "single"> = SelectCommonProps &
  (TType extends "multiple" ? SelectMultiProps : SelectSingleProps);

export const Select = <TType extends "multiple" | "single">({
  type,
  options,
  placeholder,
  hint,
  value,
  onChange,
  className,
}: SelectProps<TType>) => {
  return (
    <div
      className={clsx(
        "re-relative",
        "re-flex",
        "re-flex-1",
        "re-rounded",
        "re-border",
        "re-border-gray-700",
        "re-bg-gray-900",
        className,
      )}
    >
      <div
        className={clsx(
          "re-z-[2]",
          "re-min-h-[32px]",
          "re-h-full",
          "re-flex-1",
          "re-py-1.5",
          "re-pl-1.5",
          "re-pr-8",
          "re-pointer-events-none",
          "re-flex",
          "re-flex-wrap",
          "re-items-center",
          "re-gap-2",
        )}
      >
        {((Array.isArray(value) && value.length === 0) || !value) && (
          <span
            className={clsx("re-text-gray-500", "re-text-xs", "re-leading-4")}
          >
            {placeholder}
          </span>
        )}
        {type === "multiple" && (
          <>
            {(value as string[])?.map((item) => (
              <div
                key={item}
                className={clsx(
                  "re-py-0.5",
                  "re-pl-2",
                  "re-pr-1",
                  "re-flex",
                  "re-items-center",
                  "re-justify-center",
                  "re-flex-nowrap",
                  "re-gap-2",
                  "re-rounded-sm",
                  "re-bg-gray-800",
                )}
              >
                <span
                  className={clsx(
                    "re-text-gray-300",
                    "re-text-xs",
                    "re-whitespace-nowrap",
                    "re-break-keep",
                  )}
                >
                  {options?.find((i) => i.value === item)?.label}
                </span>
                <button
                  type="button"
                  onClick={(event) => {
                    event.preventDefault();

                    if (type === "multiple") {
                      const newValue = [...((value as string[]) ?? [])].filter(
                        (v) => v !== item,
                      );
                      onChange?.(newValue as any);
                    }
                  }}
                  className={clsx(
                    "re-pointer-events-auto",
                    "re-text-gray-500",
                    "re-flex-shrink-0",
                    "re-appearance-none",
                    "re-outline-none",
                    "re-border-none",
                    "re-bg-none",
                    "re-px-0",
                    "re-py-0.5",
                  )}
                >
                  <CloseIcon />
                </button>
              </div>
            ))}
          </>
        )}
        {type === "single" && value && (
          <span
            className={clsx("re-text-gray-300", "re-text-xs", "re-leading-4")}
          >
            {options?.find((i) => i.value === value)?.label}
          </span>
        )}
      </div>
      {type === "single" && value ? (
        <button
          type="button"
          onClick={(event) => {
            event.preventDefault();
            onChange?.(undefined as any);
          }}
          className={clsx(
            "re-z-[2]",
            "re-appearance-none",
            "re-border-none",
            "re-bg-none",
            "re-pointer-events-auto",
            "re-absolute",
            "re-right-0",
            "re-h-full",
            "re-flex",
            "re-items-center",
            "re-justify-center",
            "re-p-2",
            "re-text-gray-500",
          )}
        >
          <CloseIcon />
        </button>
      ) : (
        <div
          className={clsx(
            "re-pointer-events-none",
            "re-absolute",
            "re-right-0",
            "re-h-full",
            "re-flex",
            "re-items-center",
            "re-justify-center",
            "re-p-2",
            "re-text-gray-700",
          )}
        >
          <ChevronDownIcon />
        </div>
      )}
      <select
        id="filter-1"
        className={clsx(
          "re-opacity-0",
          "re-w-full",
          "re-h-full",
          "re-absolute",
          "re-left-0",
          "re-top-0",
          "re-bg-none",
          "re-bg-transparent",
          "re-appearance-none",
          "re-border-none",
          "re-outline-none",
        )}
        value=""
        onChange={(event) => {
          if (!event.target.value) return;

          if (type === "multiple") {
            const newValue = [...((value as any) ?? []), event.target.value];

            onChange?.(newValue as any);
          } else {
            const newValue = event.target.value;

            onChange?.(newValue as any);
          }
        }}
      >
        <option value="" selected disabled>
          {hint ?? placeholder ?? ""}
        </option>
        {(type === "multiple"
          ? options.filter((i) => !value?.includes(i.value))
          : options
        ).map((option) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
    </div>
  );
};