packages/devtools-ui/src/components/select.tsx
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>
);
};