File size: 2,906 Bytes
5f3e9f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { useId } from 'react'
import type { ReactNode } from 'react'
import { Check } from 'lucide-react'

interface Props {
  checked: boolean
  onChange: (checked: boolean) => void
  /** Visible label rendered next to the box. Pass empty string for an icon-only checkbox. */
  label?: ReactNode
  /** Helper text below the label. */
  description?: ReactNode
  disabled?: boolean
  /** When true, no visible label is rendered — but `aria-label` must be provided. */
  hideLabel?: boolean
  ariaLabel?: string
  /** Extra class on the outer label/wrapper. */
  className?: string
  /** ID for the underlying input — defaults to a generated one. */
  id?: string
}

/**
 * Design-system checkbox. A real `<input type="checkbox">` (visually hidden)
 * with a rendered box + check glyph so styling is consistent across browsers
 * and brand-recoloring works through CSS variables.
 */
export default function Checkbox({
  checked,
  onChange,
  label,
  description,
  disabled,
  hideLabel,
  ariaLabel,
  className,
  id,
}: Props) {
  const generatedId = useId()
  const inputId = id ?? generatedId
  const descId = description ? `${inputId}-desc` : undefined
  const visibleLabel = !hideLabel && label !== undefined && label !== ''

  return (
    <label
      htmlFor={inputId}
      className={
        'group inline-flex items-start gap-2 ' +
        (disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer') +
        (className ? ' ' + className : '')
      }
    >
      <span className="relative mt-0.5 inline-flex">
        <input
          id={inputId}
          type="checkbox"
          checked={checked}
          disabled={disabled}
          aria-describedby={descId}
          aria-label={!visibleLabel ? ariaLabel : undefined}
          onChange={(e) => onChange(e.target.checked)}
          className="peer absolute inset-0 h-full w-full cursor-inherit opacity-0 disabled:cursor-not-allowed"
        />
        <span
          aria-hidden="true"
          className={
            'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border transition-colors ' +
            'peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-brand-500 ' +
            (checked
              ? 'border-brand-500 bg-brand-500 text-white'
              : 'border-[rgb(var(--line))] bg-[rgb(var(--bg-surface))]')
          }
        >
          {checked && <Check size={11} strokeWidth={3} />}
        </span>
      </span>
      {visibleLabel && (
        <span className="min-w-0">
          <span className="block text-sm leading-snug text-[rgb(var(--text-strong))]">
            {label}
          </span>
          {description && (
            <span id={descId} className="block text-xs text-muted">
              {description}
            </span>
          )}
        </span>
      )}
    </label>
  )
}