Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

61 linesCodeBlameRaw
1import { Check, Minus } from "lucide-react";
2import { Checkbox as Primitive } from "radix-ui";
3import { type ComponentProps, type ReactNode, useId } from "react";
4
5import { cn } from "../../lib/cn";
6
7// shadcn/ui's checkbox, styled with g1t's tokens.
8//
9// Inside a <Form>, a `name` makes Radix render a hidden checkbox beside it,
10// so it posts `value` (default "on") when checked and nothing when not,
11// exactly as a native one does. Wrap it and its text in a <label>, or point
12// a <Label htmlFor> at its `id`.
13
14/** A checkbox with its label, and optionally a line under it. */
15export function CheckboxOption({
16 label,
17 description,
18 className,
19 labelClassName,
20 ...props
21}: ComponentProps<typeof Primitive.Root> & { label: ReactNode; description?: ReactNode; labelClassName?: string }) {
22 const generated = useId();
23 const id = props.id ?? generated;
24 return (
25 <div className={cn("flex items-start gap-2.5", props.disabled && "opacity-60", className)}>
26 <Checkbox
27 id={id}
28 className={className?.includes("items-center") ? undefined : "mt-0.5"}
29 aria-describedby={description ? `${id}-d` : undefined}
30 {...props}
31 />
32 <label htmlFor={id} className={cn("min-w-0 text-sm text-fg", props.disabled ? "cursor-not-allowed" : "cursor-pointer", labelClassName)}>
33 {label}
34 {description && (
35 <span id={`${id}-d`} className="mt-0.5 block text-xs text-faint">
36 {description}
37 </span>
38 )}
39 </label>
40 </div>
41 );
42}
43
44export function Checkbox({ className, ...props }: ComponentProps<typeof Primitive.Root>) {
45 return (
46 <Primitive.Root
47 className={cn(
48 "peer inline-flex size-4 shrink-0 items-center justify-center rounded-[5px] border border-line-strong bg-bg text-bg outline-none transition-colors",
49 "hover:border-faint focus-visible:ring-2 focus-visible:ring-accent/40 focus-visible:ring-offset-1 focus-visible:ring-offset-bg focus-visible:outline-none",
50 "data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=indeterminate]:border-accent data-[state=indeterminate]:bg-accent",
51 "disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger/70",
52 className,
53 )}
54 {...props}
55 >
56 <Primitive.Indicator className="flex items-center justify-center data-[state=checked]:animate-pop-in motion-reduce:animate-none">
57 {props.checked === "indeterminate" ? <Minus size={12} strokeWidth={3} /> : <Check size={12} strokeWidth={3} />}
58 </Primitive.Indicator>
59 </Primitive.Root>
60 );
61}