{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkbox-group",
  "type": "registry:ui",
  "title": "Checkbox Group",
  "description": "A group of checkboxes for multi-selection with horizontal or vertical layout.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/ruixenui/checkbox-group.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\ninterface CheckboxOption {\n  value: string;\n  label: string;\n  description?: string;\n  disabled?: boolean;\n}\n\ninterface CheckboxGroupProps {\n  options: CheckboxOption[];\n  value?: string[];\n  defaultValue?: string[];\n  onValueChange?: (value: string[]) => void;\n  label?: string;\n  orientation?: \"horizontal\" | \"vertical\";\n  className?: string;\n}\n\nexport function CheckboxGroup({\n  options,\n  value,\n  defaultValue = [],\n  onValueChange,\n  label,\n  orientation = \"vertical\",\n  className,\n}: CheckboxGroupProps) {\n  const [internalValue, setInternalValue] =\n    React.useState<string[]>(defaultValue);\n  const isControlled = value !== undefined;\n  const selectedValues = isControlled ? value : internalValue;\n\n  const handleChange = (optionValue: string, checked: boolean) => {\n    const newValue = checked\n      ? [...selectedValues, optionValue]\n      : selectedValues.filter((v) => v !== optionValue);\n\n    if (!isControlled) {\n      setInternalValue(newValue);\n    }\n    onValueChange?.(newValue);\n  };\n\n  return (\n    <div className={cn(\"flex flex-col gap-3\", className)}>\n      {label && (\n        <span className=\"text-[14px] font-medium tracking-[-0.01em] text-neutral-900 dark:text-neutral-100\">\n          {label}\n        </span>\n      )}\n      <div\n        className={cn(\n          \"flex gap-3\",\n          orientation === \"vertical\" ? \"flex-col\" : \"flex-row flex-wrap\",\n        )}\n      >\n        {options.map((option) => {\n          const isChecked = selectedValues.includes(option.value);\n          const inputId = `checkbox-group-${option.value}`;\n\n          return (\n            <label\n              key={option.value}\n              htmlFor={inputId}\n              className={cn(\n                \"group inline-flex cursor-pointer items-start gap-3\",\n                option.disabled && \"pointer-events-none opacity-40\",\n              )}\n            >\n              <div className=\"relative flex items-center justify-center pt-px\">\n                <input\n                  type=\"checkbox\"\n                  id={inputId}\n                  checked={isChecked}\n                  onChange={(e) => handleChange(option.value, e.target.checked)}\n                  disabled={option.disabled}\n                  className=\"peer sr-only\"\n                />\n                <motion.div\n                  className={cn(\n                    \"flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors duration-150\",\n                    isChecked\n                      ? \"border-neutral-900 bg-neutral-900 text-white dark:border-neutral-100 dark:bg-neutral-100 dark:text-neutral-950\"\n                      : \"border-neutral-300 bg-transparent dark:border-neutral-700\",\n                    !option.disabled &&\n                      !isChecked &&\n                      \"group-hover:border-neutral-400 dark:group-hover:border-neutral-500\",\n                    \"peer-focus-visible:ring-2 peer-focus-visible:ring-neutral-900/20 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-white dark:peer-focus-visible:ring-neutral-100/20 dark:peer-focus-visible:ring-offset-neutral-950\",\n                  )}\n                  whileTap={option.disabled ? undefined : { scale: 0.92 }}\n                  transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n                >\n                  <AnimatePresence initial={false}>\n                    {isChecked && (\n                      <motion.svg\n                        key=\"check\"\n                        viewBox=\"0 0 12 12\"\n                        className=\"h-3 w-3\"\n                        fill=\"none\"\n                      >\n                        <motion.path\n                          d=\"M2.5 6.5L5 9L9.5 3.5\"\n                          stroke=\"currentColor\"\n                          strokeWidth={2}\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                          initial={{ pathLength: 0 }}\n                          animate={{ pathLength: 1 }}\n                          exit={{ pathLength: 0 }}\n                          transition={{ duration: 0.2, ease: \"easeOut\" }}\n                        />\n                      </motion.svg>\n                    )}\n                  </AnimatePresence>\n                </motion.div>\n              </div>\n              <div className=\"flex flex-col gap-1\">\n                <span className=\"text-[14px] font-medium leading-none tracking-[-0.01em] text-neutral-900 dark:text-neutral-100\">\n                  {option.label}\n                </span>\n                {option.description && (\n                  <span className=\"text-[13px] leading-snug text-neutral-500 dark:text-neutral-400\">\n                    {option.description}\n                  </span>\n                )}\n              </div>\n            </label>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ruixen/checkbox-group.tsx"
    }
  ]
}