flagon-io/g1t

public

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

g1t/apps/web/app/components/ui/checkbox.tsx

60 lines2,601 bytesCodeBlame
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 id={id} className={className?.includes("items-center") ? undefined : "mt-0.5"} aria-describedby={description ? `${id}-d` : undefined} {...props} />
27 <label htmlFor={id} className={cn("min-w-0 text-sm text-fg", props.disabled ? "cursor-not-allowed" : "cursor-pointer", labelClassName)}>
28 {label}
29 {description && (
30 <span id={`${id}-d`} className="mt-0.5 block text-xs text-faint">
31 {description}
32 </span>
33 )}
34 </label>
35 </div>
36 );
37}
38
39export function Checkbox({ className, ...props }: ComponentProps<typeof Primitive.Root>) {
40 return (
41 <Primitive.Root
42 className={cn(
43 "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",
44 "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",
45 "data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=indeterminate]:border-accent data-[state=indeterminate]:bg-accent",
46 "disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger/70",
47 className,
48 )}
49 {...props}
50 >
51 <Primitive.Indicator className="flex items-center justify-center data-[state=checked]:animate-pop-in motion-reduce:animate-none">
52 {props.checked === "indeterminate" ? (
53 <Minus size={12} strokeWidth={3} />
54 ) : (
55 <Check size={12} strokeWidth={3} />
56 )}
57 </Primitive.Indicator>
58 </Primitive.Root>
59 );
60}