mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
229 lines
5.8 KiB
TypeScript
229 lines
5.8 KiB
TypeScript
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>
|
|
);
|
|
};
|