{
  "name": "add-on-selector",
  "title": "AddOnSelector",
  "description": "Modifier groups with single-select or multi-select options.",
  "type": "component",
  "registryDependencies": [
    "price"
  ],
  "files": [
    {
      "path": "add-on-selector.tsx",
      "content": "\"use client\";\n\nimport React, { useCallback, useMemo } from \"react\";\nimport { Checkbox } from \"@base-ui/react/checkbox\";\nimport type { AddOnWithOptions } from \"@cimplify/sdk\";\nimport type { CurrencyCode } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface AddOnSelectorClassNames {\n  root?: string;\n  group?: string;\n  header?: string;\n  name?: string;\n  required?: string;\n  constraint?: string;\n  validation?: string;\n  options?: string;\n  option?: string;\n  optionSelected?: string;\n  optionName?: string;\n  optionDescription?: string;\n}\n\nexport interface AddOnSelectorProps {\n  addOns: AddOnWithOptions[];\n  selectedOptions: string[];\n  onOptionsChange: (optionIds: string[]) => void;\n  currency?: CurrencyCode;\n  className?: string;\n  classNames?: AddOnSelectorClassNames;\n}\n\nexport function AddOnSelector({\n  addOns,\n  selectedOptions,\n  onOptionsChange,\n  currency,\n  className,\n  classNames,\n}: AddOnSelectorProps): React.ReactElement | null {\n  const selectedSet = useMemo(() => new Set(selectedOptions), [selectedOptions]);\n\n  const isOptionSelected = useCallback(\n    (optionId: string) => selectedSet.has(optionId),\n    [selectedSet],\n  );\n\n  const handleCheckedChange = useCallback(\n    (addOn: AddOnWithOptions, optionId: string, checked: boolean) => {\n      const isSelected = selectedSet.has(optionId);\n\n      if (addOn.is_mutually_exclusive || !addOn.is_multiple_allowed) {\n        const groupOptionIds = new Set(addOn.options.map((o) => o.id));\n        const withoutGroup = selectedOptions.filter((id) => !groupOptionIds.has(id));\n\n        if (isSelected) {\n          if (!addOn.is_required) {\n            onOptionsChange(withoutGroup);\n          }\n        } else {\n          onOptionsChange([...withoutGroup, optionId]);\n        }\n      } else {\n        if (isSelected) {\n          onOptionsChange(selectedOptions.filter((id) => id !== optionId));\n        } else {\n          let currentCount = 0;\n          for (const option of addOn.options) {\n            if (selectedSet.has(option.id)) {\n              currentCount += 1;\n            }\n          }\n\n          if (addOn.max_selections && currentCount >= addOn.max_selections) {\n            return;\n          }\n\n          onOptionsChange([...selectedOptions, optionId]);\n        }\n      }\n    },\n    [selectedOptions, selectedSet, onOptionsChange],\n  );\n\n  if (!addOns || addOns.length === 0) {\n    return null;\n  }\n\n  return (\n    <div data-cimplify-addon-selector className={cn(\"space-y-6\", className, classNames?.root)}>\n      {addOns.map((addOn) => {\n        let currentSelections = 0;\n        for (const option of addOn.options) {\n          if (selectedSet.has(option.id)) {\n            currentSelections += 1;\n          }\n        }\n        const minMet = !addOn.min_selections || currentSelections >= addOn.min_selections;\n        const isSingleSelect = addOn.is_mutually_exclusive || !addOn.is_multiple_allowed;\n\n        return (\n          <div\n            key={addOn.id}\n            data-cimplify-addon-group\n            className={cn(classNames?.group)}\n          >\n            <div\n              data-cimplify-addon-header\n              className={cn(\"flex items-center justify-between py-3\", classNames?.header)}\n            >\n              <div>\n                <span\n                  data-cimplify-addon-name\n                  className={cn(\"text-base font-bold\", classNames?.name)}\n                >\n                  {addOn.name}\n                </span>\n                {(addOn.min_selections || addOn.max_selections) && (\n                  <span\n                    data-cimplify-addon-constraint\n                    className={cn(\"block text-xs text-muted-foreground mt-0.5\", classNames?.constraint)}\n                  >\n                    {addOn.min_selections && addOn.max_selections\n                      ? `Choose ${addOn.min_selections}\\u2013${addOn.max_selections}`\n                      : addOn.min_selections\n                        ? `Choose at least ${addOn.min_selections}`\n                        : `Choose up to ${addOn.max_selections}`}\n                  </span>\n                )}\n              </div>\n              {(addOn.is_required || !minMet) && (\n                <span\n                  data-cimplify-addon-required\n                  className={cn(\n                    \"text-xs font-semibold px-2.5 py-1 rounded\",\n                    !minMet\n                      ? \"text-destructive bg-destructive/10\"\n                      : \"text-destructive bg-destructive/10\",\n                    classNames?.required,\n                  )}\n                >\n                  Required\n                </span>\n              )}\n            </div>\n\n            <div\n              data-cimplify-addon-options\n              className={cn(\"divide-y divide-border\", classNames?.options)}\n            >\n              {addOn.options.map((option) => {\n                const isSelected = isOptionSelected(option.id);\n\n                return (\n                  <Checkbox.Root\n                    key={option.id}\n                    checked={isSelected}\n                    onCheckedChange={(checked) =>\n                      handleCheckedChange(addOn, option.id, checked)\n                    }\n                    value={option.id}\n                    data-cimplify-addon-option\n                    data-selected={isSelected || undefined}\n                    className={cn(\n                      \"w-full flex items-center gap-3 py-4 transition-colors text-left cursor-pointer\",\n                      isSelected ? classNames?.optionSelected : classNames?.option,\n                    )}\n                  >\n                    <Checkbox.Indicator\n                      className=\"hidden\"\n                      keepMounted={false}\n                    />\n\n                    {/* Visual indicator: radio circle for single-select, checkbox square for multi-select */}\n                    {isSingleSelect ? (\n                      <span\n                        data-cimplify-addon-radio\n                        className={cn(\n                          \"w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors\",\n                          isSelected ? \"border-primary\" : \"border-muted-foreground/30\",\n                        )}\n                      >\n                        {isSelected && <span className=\"w-2.5 h-2.5 rounded-full bg-primary\" />}\n                      </span>\n                    ) : (\n                      <span\n                        data-cimplify-addon-checkbox\n                        className={cn(\n                          \"w-5 h-5 rounded-sm border-2 flex items-center justify-center shrink-0 transition-colors\",\n                          isSelected ? \"border-primary bg-primary\" : \"border-muted-foreground/30\",\n                        )}\n                      >\n                        {isSelected && (\n                          <svg viewBox=\"0 0 12 12\" className=\"w-3 h-3 text-primary-foreground\" fill=\"none\">\n                            <path d=\"M2 6l3 3 5-5\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n                          </svg>\n                        )}\n                      </span>\n                    )}\n\n                    <span\n                      data-cimplify-addon-option-name\n                      className={cn(\n                        \"flex-1 min-w-0 text-sm\",\n                        classNames?.optionName,\n                      )}\n                    >\n                      {option.name}\n                    </span>\n\n                    {option.default_price != null && (\n                      <span className=\"text-sm text-muted-foreground\">\n                        +<Price amount={option.default_price} currency={currency} />\n                      </span>\n                    )}\n                  </Checkbox.Root>\n                );\n              })}\n            </div>\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n"
    }
  ]
}
