Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/ui/input/InputSelect.tsx
T

171 lines
4.9 KiB
TypeScript

import { ReactNode, useRef, useState } from "react";
import clsx from "~/utils/shared/ClassesUtils";
import HintTooltip from "../tooltips/HintTooltip";
import ColorBadge from "../badges/ColorBadge";
import { useTranslation } from "react-i18next";
import { Input } from "../input";
import { Select } from "~/ui/components/Select";
interface Props {
name?: string;
title?: string;
withLabel?: boolean;
options: {
name: string | ReactNode;
value: string | number | undefined;
disabled?: boolean;
component?: ReactNode;
color?: number;
}[];
value?: string | number | undefined;
setValue?: React.Dispatch<React.SetStateAction<string | number | undefined>>;
defaultValue?: string | number | undefined;
className?: string;
required?: boolean;
disabled?: boolean;
help?: string;
hint?: ReactNode;
borderless?: boolean;
autoFocus?: boolean;
placeholder?: string;
withColors?: boolean;
withSearch?: boolean;
selectClassName?: string;
canClear?: boolean;
dirty?: boolean;
size?: "sm" | "default";
style?: React.CSSProperties;
hideIcon?: boolean;
icon?: React.ReactNode;
}
export default function InputSelect({
name,
title,
withLabel = true,
value,
defaultValue,
options,
setValue,
className,
required,
disabled,
help,
hint,
borderless,
// autoFocus,
placeholder,
withColors,
withSearch = false,
selectClassName,
canClear = true,
size = "default",
style,
icon,
hideIcon = false,
}: Props) {
const { t } = useTranslation();
const refSearch = useRef<HTMLInputElement>(null);
const [inputSearch, setInputSearch] = useState("");
const filteredOptions = () => {
let filteredItems = options.filter((f) => f.value);
if (inputSearch.trim()) {
filteredItems = options.filter((item) => {
return (
item.name?.toString().toLowerCase().includes(inputSearch.toLowerCase()) || item.value?.toString().toLowerCase().includes(inputSearch.toLowerCase())
);
});
}
if (canClear) {
return [
{
name: getPlaceholder(),
value: "{null}",
disabled: false,
},
...filteredItems,
];
}
return filteredItems;
};
const getPlaceholder = () => {
return withLabel ? placeholder || `${t("shared.select")}...` : title || `${t("shared.select")}...`;
};
const showLabel = Boolean(withLabel && title);
// Rich label content rendered into Subframe's label slot (spans only → inline-safe).
const labelContent: ReactNode = showLabel ? (
<span className="flex w-full items-center justify-between gap-2">
<span className="flex items-center space-x-1 truncate">
<span className="flex space-x-1 truncate">
<span className="truncate">{title}</span>
{required && title && <span className="ml-1 text-red-600">*</span>}
</span>
{help && <HintTooltip text={help} />}
</span>
{hint}
</span>
) : null;
return (
<div className={clsx(className, "")} style={style}>
<Select
className={clsx("w-full max-w-none", selectClassName)}
name={name}
value={value !== undefined ? value.toString() : undefined}
defaultValue={defaultValue !== undefined ? defaultValue.toString() : undefined}
onValueChange={(e) => {
if (e === "{null}") {
setValue?.("");
} else {
setValue?.(e);
}
}}
disabled={disabled}
required={required}
borderless={borderless}
size={size === "sm" ? "small" : "medium"}
showLabel={showLabel}
label={labelContent}
icon={!hideIcon ? icon : undefined}
placeholder={getPlaceholder()}
>
{withSearch && options.length > 30 && (
<Input
ref={refSearch}
autoFocus
type="text"
className="mb-1 px-2 py-1.5"
placeholder={t("shared.search")}
value={inputSearch}
onChange={(e) => setInputSearch(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
onKeyDownCapture={(e) => e.stopPropagation()}
onKeyUp={(e) => e.stopPropagation()}
/>
)}
{filteredOptions().length === 0 && (
<Select.Item disabled value={"{empty}"}>
<div className="text-muted-foreground w-full text-center">{t("shared.noRecords")}</div>
</Select.Item>
)}
{filteredOptions().map((item, idx) => {
return (
<Select.Item key={idx} disabled={item.disabled} value={item.value?.toString() ?? ""}>
{("component" in item && item.component) || (
<div className="flex items-center space-x-2">
{withColors && "color" in item && <ColorBadge color={item.color} />}
<div>{item.name}</div>
</div>
)}
</Select.Item>
);
})}
</Select>
</div>
);
}