Files
Academia-Front/src/components/ui/Checkbox.tsx
T

112 lines
3.7 KiB
TypeScript

import React, { useEffect, useRef } from 'react';
import { FiCheck, FiMinus } from 'react-icons/fi';
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
label?: React.ReactNode;
description?: string;
errorMessage?: string;
indeterminate?: boolean;
}
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
(
{
label,
description,
errorMessage,
indeterminate = false,
checked,
disabled,
className = '',
id,
name,
...props
},
forwardedRef
) => {
const internalRef = useRef<HTMLInputElement>(null);
const resolvedRef = (forwardedRef as React.RefObject<HTMLInputElement>) || internalRef;
const checkboxId = id || name || (typeof label === 'string' ? label.toLowerCase().replace(/\s+/g, '-') : undefined);
useEffect(() => {
if (resolvedRef.current) {
resolvedRef.current.indeterminate = indeterminate;
}
}, [indeterminate, resolvedRef]);
const isChecked = Boolean(checked);
return (
<div className={`flex flex-col space-y-1 ${className}`}>
<label
htmlFor={checkboxId}
className={`inline-flex items-start gap-2.5 select-none cursor-pointer group ${
disabled ? 'opacity-50 !cursor-not-allowed' : ''
}`}
>
{/* Custom Styled Box */}
<div className="relative flex items-center justify-center mt-0.5 shrink-0">
<input
ref={resolvedRef}
id={checkboxId}
name={name}
type="checkbox"
checked={checked}
disabled={disabled}
className="peer sr-only"
{...props}
/>
<div
className={`
w-4 h-4 rounded-md flex items-center justify-center transition-all duration-200
border
peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2
peer-focus-visible:ring-cyan-500 peer-focus-visible:ring-offset-white dark:peer-focus-visible:ring-offset-slate-950
${
isChecked || indeterminate
? 'bg-cyan-500 border-cyan-500 text-slate-950 shadow-sm shadow-cyan-500/20'
: 'bg-white dark:bg-slate-900 border-slate-300 dark:border-slate-700 text-transparent group-hover:border-slate-400 dark:group-hover:border-slate-500'
}
${
errorMessage
? '!border-rose-500 !bg-rose-50 dark:!bg-rose-950/30'
: ''
}
`}
>
{indeterminate ? (
<FiMinus className="text-xs stroke-3" />
) : isChecked ? (
<FiCheck className="text-xs stroke-3" />
) : null}
</div>
</div>
{/* Label and Sub-description */}
{(label || description) && (
<div className="flex flex-col text-left">
{label && (
<span className="text-xs sm:text-sm font-medium text-slate-800 dark:text-slate-200 group-hover:text-slate-950 dark:group-hover:text-white transition-colors">
{label}
</span>
)}
{description && (
<span className="text-xs text-slate-500 dark:text-slate-400">
{description}
</span>
)}
</div>
)}
</label>
{errorMessage && (
<p className="text-xs text-rose-600 dark:text-rose-400 pl-6.5">{errorMessage}</p>
)}
</div>
);
}
);
Checkbox.displayName = 'Checkbox';