{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "variant-selector",
  "title": "Variant Selector",
  "description": "Renders every variant dimension as pills or swatches with three tiers of emphasis (purchasable, out-of-stock, not-offered).",
  "dependencies": [
    "@channel3/sdk"
  ],
  "registryDependencies": [
    "https://ui.trychannel3.com/r/variants.json"
  ],
  "files": [
    {
      "path": "registry/default/components/variant-selector.tsx",
      "content": "import * as React from \"react\";\nimport type { OptionValue, VariantOption, Variants } from \"@channel3/sdk/resources\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  type ValueState,\n  isSwatchOption,\n  selectionFromVariants,\n  valueState,\n} from \"@/registry/default/lib/variants\";\n\nconst PILL_STATE: Record<ValueState, string> = {\n  selected: \"border-primary bg-primary text-primary-foreground\",\n  available: \"border-input hover:bg-accent hover:text-accent-foreground\",\n  outOfStock: \"border-input text-muted-foreground line-through\",\n  notOffered: \"border-dashed text-muted-foreground/70 hover:text-foreground\",\n};\n\nconst SWATCH_STATE: Record<ValueState, string> = {\n  selected: \"ring-2 ring-ring ring-offset-2 ring-offset-background\",\n  available: \"ring-1 ring-border hover:ring-ring\",\n  outOfStock: \"opacity-60 ring-1 ring-border\",\n  notOffered: \"opacity-40 ring-1 ring-border\",\n};\n\nexport interface VariantSelectorProps extends Omit<React.ComponentProps<\"div\">, \"onSelect\"> {\n  variants: Variants;\n  value?: Record<string, string>;\n  onSelect?: (optionName: string, value: OptionValue) => void;\n  onValuePreview?: (value: OptionValue | null) => void;\n}\n\nexport function VariantSelector({\n  variants,\n  value,\n  onSelect,\n  onValuePreview,\n  className,\n  ...props\n}: VariantSelectorProps) {\n  const selection = value ?? selectionFromVariants(variants);\n\n  return (\n    <div data-slot=\"variant-selector\" className={cn(\"flex flex-col gap-5\", className)} {...props}>\n      {variants.options.map((option) => (\n        <OptionGroup\n          key={option.name}\n          option={option}\n          selectedLabel={selection[option.name]}\n          onSelect={onSelect}\n          onValuePreview={onValuePreview}\n        />\n      ))}\n    </div>\n  );\n}\n\nfunction OptionGroup({\n  option,\n  selectedLabel,\n  onSelect,\n  onValuePreview,\n}: {\n  option: VariantOption;\n  selectedLabel: string | undefined;\n  onSelect: VariantSelectorProps[\"onSelect\"];\n  onValuePreview: VariantSelectorProps[\"onValuePreview\"];\n}) {\n  const swatches = isSwatchOption(option);\n\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <div className=\"flex items-center gap-2 text-sm\">\n        <span className=\"font-medium\">{option.name}</span>\n        {selectedLabel ? <span className=\"text-muted-foreground\">{selectedLabel}</span> : null}\n      </div>\n      <div className=\"flex flex-wrap gap-2\">\n        {option.values.map((optionValue) => {\n          const state = valueState(optionValue, optionValue.label === selectedLabel);\n          return swatches && optionValue.thumbnail_url ? (\n            <Swatch\n              key={optionValue.label}\n              value={optionValue}\n              state={state}\n              onClick={() => onSelect?.(option.name, optionValue)}\n              onPreview={onValuePreview}\n            />\n          ) : (\n            <Pill\n              key={optionValue.label}\n              value={optionValue}\n              state={state}\n              onClick={() => onSelect?.(option.name, optionValue)}\n            />\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nfunction Pill({\n  value,\n  state,\n  onClick,\n}: {\n  value: OptionValue;\n  state: ValueState;\n  onClick: () => void;\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      aria-pressed={state === \"selected\"}\n      className={cn(\n        \"inline-flex min-w-9 cursor-pointer items-center justify-center rounded-md border px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n        PILL_STATE[state],\n      )}\n    >\n      {value.label}\n    </button>\n  );\n}\n\nfunction Swatch({\n  value,\n  state,\n  onClick,\n  onPreview,\n}: {\n  value: OptionValue;\n  state: ValueState;\n  onClick: () => void;\n  onPreview: VariantSelectorProps[\"onValuePreview\"];\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      onMouseEnter={() => onPreview?.(value)}\n      onMouseLeave={() => onPreview?.(null)}\n      onFocus={() => onPreview?.(value)}\n      onBlur={() => onPreview?.(null)}\n      title={value.label}\n      aria-label={value.label}\n      aria-pressed={state === \"selected\"}\n      className={cn(\n        \"relative size-10 cursor-pointer overflow-hidden rounded-full bg-muted transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n        SWATCH_STATE[state],\n      )}\n    >\n      {value.thumbnail_url ? (\n        <img src={value.thumbnail_url} alt=\"\" className=\"size-full object-cover\" />\n      ) : null}\n    </button>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}
