All files / src/components checkbox.tsx

94.44% Statements 17/18
95% Branches 19/20
100% Functions 5/5
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 101 102 103 104 105 106 107 108 109                                          5x     91x   91x             91x 52x   52x 50x     2x     91x                                                           5x     5x     86x 4x               10x       3x                   82x     5x      
import type { InputHTMLAttributes, ReactNode } from 'react';
import { forwardRef } from 'react';
import type { Control, FieldPath, FieldValues } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { cn } from '@/lib/utils';
import { Text } from '@/components/text';
 
export interface BaseCheckboxProps
  extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
  label?: string | ReactNode;
  error?: string;
  description?: string;
}
 
// Enhanced Checkbox props that can work with React Hook Form
export interface CheckboxProps<T extends FieldValues = FieldValues>
  extends BaseCheckboxProps {
  name?: FieldPath<T>;
  control?: Control<T>;
}
 
const BaseCheckbox = forwardRef<HTMLInputElement, BaseCheckboxProps>(
  ({ className, label, error, description, id, ...props }, ref) => {
    const checkboxId =
      id || `checkbox-${Math.random().toString(36).substr(2, 9)}`;
 
    const baseStyles = [
      'rounded border-gray-300 text-primary focus:ring-primary',
      'focus:ring-2 focus:ring-offset-2',
      'disabled:cursor-not-allowed disabled:opacity-50',
      error ? 'border-red-500' : 'border-gray-300',
    ];
 
    const renderLabel = () => {
      Iif (!label) return null;
 
      if (typeof label === 'string') {
        return <span className="text-sm text-muted-foreground">{label}</span>;
      }
 
      return label;
    };
 
    return (
      <div className="flex flex-col space-y-1">
        <div className="flex items-center space-x-2">
          <input
            type="checkbox"
            id={checkboxId}
            className={cn(baseStyles.join(' '), className)}
            ref={ref}
            {...props}
          />
          {label && (
            <label htmlFor={checkboxId} className="text-sm cursor-pointer">
              {renderLabel()}
            </label>
          )}
        </div>
        {description && (
          <Text size="xs" variant="muted" className="ml-6">
            {description}
          </Text>
        )}
        {error && (
          <Text size="sm" variant="destructive" className="ml-6" as="span">
            {error}
          </Text>
        )}
      </div>
    );
  }
);
BaseCheckbox.displayName = 'BaseCheckbox';
 
// Smart Checkbox component that works with or without React Hook Form
const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
  ({ name, control, ...props }, ref) => {
    // If control and name are provided, use Controller
    if (control && name) {
      return (
        <Controller
          name={name as any}
          control={control as any}
          render={({
            field: { value, onChange, ...field },
            fieldState: { error },
          }) => (
            <BaseCheckbox
              {...field}
              {...props}
              checked={value}
              onChange={e => onChange(e.target.checked)}
              error={error?.message || props.error}
              ref={ref}
            />
          )}
        />
      );
    }
 
    // Otherwise, use as regular checkbox
    return <BaseCheckbox {...props} ref={ref} />;
  }
);
Checkbox.displayName = 'Checkbox';
 
export { Checkbox, BaseCheckbox };