turash/bugulma/frontend/components/ui/CheckboxGroup.tsx
2025-12-15 10:06:41 +01:00

108 lines
2.8 KiB
TypeScript

import React from 'react';
import { clsx } from 'clsx';
import Checkbox from './Checkbox';
export interface CheckboxOption {
value: string;
label: string;
description?: string;
disabled?: boolean;
}
export interface CheckboxGroupProps {
options: CheckboxOption[];
value?: string[];
onChange?: (value: string[]) => void;
name: string;
className?: string;
orientation?: 'horizontal' | 'vertical';
label?: string;
description?: string;
error?: string;
}
/**
* Checkbox group component
*/
export const CheckboxGroup = ({
options,
value = [],
onChange,
name,
className,
orientation = 'vertical',
label,
description,
error,
}: CheckboxGroupProps) => {
const handleChange = (optionValue: string, checked: boolean) => {
if (!onChange) return;
if (checked) {
onChange([...value, optionValue]);
} else {
onChange(value.filter((v) => v !== optionValue));
}
};
return (
<div className={clsx('flex flex-col gap-2', className)}>
{label && <label className="text-sm font-medium text-foreground">{label}</label>}
{description && <p className="text-xs text-muted-foreground">{description}</p>}
<div
className={clsx('flex gap-4', {
'flex-col': orientation === 'vertical',
'flex-row flex-wrap': orientation === 'horizontal',
})}
role="group"
aria-labelledby={label ? `${name}-label` : undefined}
>
{options.map((option) => (
<label
key={option.value}
className={clsx('flex items-start gap-3 cursor-pointer', 'group', {
'opacity-50 cursor-not-allowed': option.disabled,
})}
>
<Checkbox
name={name}
value={option.value}
checked={value.includes(option.value)}
onChange={(e) => {
if (!option.disabled) {
handleChange(option.value, e.target.checked);
}
}}
disabled={option.disabled}
className="mt-0.5"
/>
<div className="flex flex-col">
<span
className={clsx('text-sm font-medium', {
'text-muted-foreground': option.disabled,
})}
>
{option.label}
</span>
{option.description && (
<span
className={clsx('text-xs text-muted-foreground mt-0.5', {
'opacity-70': option.disabled,
})}
>
{option.description}
</span>
)}
</div>
</label>
))}
</div>
{error && (
<p className="text-sm text-destructive" role="alert">
{error}
</p>
)}
</div>
);
};