File size: 3,178 Bytes
9b72f0d |
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 |
import cn from "@utils/classnames.ts";
import { type ReactNode } from "react";
import { FormError } from "../index.ts";
import LabelTooltip from "./LabelTooltip.tsx";
interface CheckboxOption {
name: string;
label: string;
description?: string;
}
interface InputCheckboxListProps {
label: string;
options: CheckboxOption[];
value: string[];
onChange: (value: string[]) => void;
error?: string;
required?: boolean;
className?: string;
id?: string;
tooltip?: string | ReactNode;
}
export default function InputCheckboxList({
label,
options,
value,
onChange,
error,
required,
className = "",
id,
tooltip = "",
}: InputCheckboxListProps) {
const handleCheckboxChange = (optionName: string, checked: boolean) => {
if (checked) {
onChange([...value, optionName]);
} else {
onChange(value.filter((name) => name !== optionName));
}
};
return (
<div className={cn("flex flex-col gap-2", className)}>
<div className="relative text-sm font-medium text-gray-900 dark:text-gray-100">
{label}
{required && (
<span className="ml-1 text-blue-500 dark:text-blue-400">*</span>
)}
{tooltip !== "" && <LabelTooltip text={<>{tooltip}</>} />}
</div>
<div className="flex flex-col gap-3">
{options.map((option) => {
const checkboxId = `${id || "checkbox-list"}-${option.name}`;
const isChecked = value.includes(option.name);
return (
<label
key={option.name}
htmlFor={checkboxId}
className="flex cursor-pointer items-start gap-3"
>
<input
type="checkbox"
id={checkboxId}
checked={isChecked}
onChange={(e) =>
handleCheckboxChange(option.name, e.target.checked)
}
className={cn(
"mt-0.5 h-4 w-4 cursor-pointer rounded border transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none",
"bg-white dark:bg-gray-800",
"focus:ring-offset-white dark:focus:ring-offset-gray-900",
error
? "border-red-300 text-red-600 focus:border-red-500 focus:ring-red-500 dark:border-red-700 dark:text-red-500 dark:focus:border-red-600 dark:focus:ring-red-600"
: "border-gray-300 text-yellow-600 focus:border-yellow-500 focus:ring-yellow-500 dark:border-gray-600 dark:text-yellow-500 dark:focus:border-yellow-400 dark:focus:ring-yellow-400"
)}
/>
<div className="flex flex-col gap-1">
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
{option.label}
</span>
{option.description && (
<span className="text-xs text-gray-600 dark:text-gray-400">
{option.description}
</span>
)}
</div>
</label>
);
})}
</div>
{error && <FormError>{error}</FormError>}
</div>
);
}
|