Files

72 lines
2.0 KiB
TypeScript

"use client";
/**
* Checkbox — themed replacement for the native `<input type="checkbox" />`.
*
* The native control is rendered visually hidden (sr-only) so screen
* readers and keyboard focus still work the way browsers expect, while
* we paint a small rounded box next to it that fills with the primary
* color when checked. Fully controlled — caller owns `checked` state.
*
* Usage:
*
* <Checkbox checked={ok} onCheckedChange={setOk} />
*
* <label className="flex items-center gap-2">
* <Checkbox checked={ok} onCheckedChange={setOk} />
* Accept terms
* </label>
*/
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
export function Checkbox({
checked,
onCheckedChange,
disabled,
className,
ariaLabel,
}: {
checked: boolean;
onCheckedChange: (next: boolean) => void;
disabled?: boolean;
className?: string;
ariaLabel?: string;
}) {
return (
<span
className={cn(
"relative inline-flex items-center justify-center shrink-0",
className,
)}
>
<input
type="checkbox"
checked={checked}
disabled={disabled}
aria-label={ariaLabel}
onChange={(e) => onCheckedChange(e.target.checked)}
className="peer absolute inset-0 size-full appearance-none m-0 cursor-pointer disabled:cursor-not-allowed rounded-md"
/>
<span
aria-hidden
className={cn(
"pointer-events-none size-5 rounded-md border transition-colors",
"flex items-center justify-center",
checked
? "bg-primary border-primary"
: "bg-background border-border",
"peer-hover:border-foreground/40",
"peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-1 peer-focus-visible:ring-offset-background",
disabled && "opacity-50",
)}
>
{checked ? (
<Check className="size-3.5 text-primary-foreground" strokeWidth={3} />
) : null}
</span>
</span>
);
}