| 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> | |
| ); | |
| } | |