{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "variant-picker",
  "type": "registry:block",
  "title": "Product variant picker",
  "description": "Colour swatches and sizes as native radio groups, a quantity stepper and an Add to cart button that confirms with a toast.",
  "author": "Box Kite <https://www.box-kite.dev>",
  "categories": [
    "ecommerce",
    "product",
    "form"
  ],
  "dependencies": [
    "@box-kite/react"
  ],
  "registryDependencies": [
    "https://www.box-kite.dev/r/store-design.json"
  ],
  "files": [
    {
      "path": "registry/designs/store/variant-picker.tsx",
      "content": "'use client';\nimport Button from '@box-kite/react/components/button';\nimport Flex from '@box-kite/react/components/flex';\nimport RadioGroup from '@box-kite/react/components/radioGroup';\nimport { Span } from '@box-kite/react/components/semantics';\nimport { toast } from '@box-kite/react/components/toaster';\nimport VisuallyHidden from '@box-kite/react/components/visuallyHidden';\nimport { useState } from 'react';\nimport { finishSwatch } from './product-art';\nimport QuantityStepper from './quantity-stepper';\nimport { type CartLine, formatPrice, finishes, type Product, productBySlug } from './store-data';\nimport { useStoreLinks } from './store-design';\n\nexport interface VariantPickerProps {\n  product?: Product;\n  /** The finish, when the page owns it — the gallery beside the picker shows the same one. */\n  finish?: string;\n  onFinishChange?: (finish: string) => void;\n  /** Called with the line the reader added. */\n  onAdd?: (line: CartLine) => void;\n}\n\n/**\n * Colour, size and quantity, then Add to cart. The swatches and the sizes are native radios, each set one\n * tab stop with the arrow keys inside it, and only the frame around them is drawn here: the `<label>`\n * finds out which radio is chosen with `:has(:checked)`, so nothing is mirrored into state for the styling.\n */\nexport default function VariantPicker({ product = productBySlug('table-lamp'), finish, onFinishChange, onAdd }: VariantPickerProps) {\n  const links = useStoreLinks();\n  const [ownFinish, setOwnFinish] = useState(product.finishes[0]);\n  const [size, setSize] = useState(product.sizes?.[0]);\n  const [quantity, setQuantity] = useState(1);\n  const current = finish ?? ownFinish;\n\n  const choose = (value: string | undefined) => {\n    if (!value) return;\n    setOwnFinish(value);\n    onFinishChange?.(value);\n  };\n\n  const add = () => {\n    const line = { id: `${product.slug}-${current}-${size ?? ''}`, slug: product.slug, finish: current, size, quantity };\n\n    onAdd?.(line);\n    toast.success('Added to your cart', {\n      description: `${quantity} × ${product.name}, ${finishes[current].name}${size ? `, ${size}` : ''}`,\n      action: { label: 'View cart', onClick: () => window.location.assign(links.cart) },\n    });\n  };\n\n  return (\n    <Flex d=\"column\" gap={6}>\n      <RadioGroup\n        label={\n          <>\n            Finish:{' '}\n            <Span fontWeight={400} color=\"stone-600\" theme={{ dark: { color: 'stone-400' } }}>\n              {finishes[current].name}\n            </Span>\n          </>\n        }\n        value={current}\n        onValueChange={choose}\n        orientation=\"horizontal\"\n        gap={3}\n      >\n        {product.finishes.map((id) => (\n          <RadioGroup.Item\n            key={id}\n            value={id}\n            label={<VisuallyHidden>{finishes[id].name}</VisuallyHidden>}\n            clean\n            appearance=\"none\"\n            display=\"block\"\n            width={8}\n            height={8}\n            m={0}\n            borderRadius={999}\n            cursor=\"pointer\"\n            {...finishSwatch(id)}\n            ring={1}\n            ringColor=\"stone-900/15\"\n            theme={{ dark: { ringColor: 'white/20' } }}\n            labelProps={{\n              p: 1,\n              b: 2,\n              borderRadius: 999,\n              borderColor: 'transparent',\n              has: {\n                ':checked': { borderColor: 'stone-900' },\n                ':focus-visible': { outline: 2, outlineColor: 'stone-900', outlineOffset: 2 },\n              },\n              theme: { dark: { has: { ':checked': { borderColor: 'stone-100' } } } },\n            }}\n          />\n        ))}\n      </RadioGroup>\n\n      {product.sizes && (\n        <RadioGroup label=\"Size\" value={size} onValueChange={setSize} orientation=\"horizontal\" gap={2}>\n          {product.sizes.map((option) => (\n            <RadioGroup.Item\n              key={option}\n              value={option}\n              label={option}\n              clean\n              appearance=\"none\"\n              position=\"absolute\"\n              opacity={0}\n              width={0}\n              height={0}\n              m={0}\n              labelProps={{\n                position: 'relative',\n                px: 5,\n                py: 2,\n                b: 1,\n                borderRadius: 999,\n                fontSize: 14,\n                fontWeight: 500,\n                cursor: 'pointer',\n                borderColor: 'stone-300',\n                hover: { borderColor: 'stone-500' },\n                has: {\n                  ':checked': { bgColor: 'stone-900', borderColor: 'stone-900', color: 'stone-50' },\n                  ':focus-visible': { outline: 2, outlineColor: 'stone-900', outlineOffset: 2 },\n                },\n                theme: {\n                  dark: {\n                    borderColor: 'stone-700',\n                    hover: { borderColor: 'stone-500' },\n                    has: { ':checked': { bgColor: 'stone-100', borderColor: 'stone-100', color: 'stone-900' } },\n                  },\n                },\n              }}\n            />\n          ))}\n        </RadioGroup>\n      )}\n\n      <Flex gap={3} ai=\"center\" flexWrap=\"wrap\">\n        <QuantityStepper value={quantity} onChange={setQuantity} item={product.name} />\n        <Button flex1 minWidth={48} py={3} onClick={add}>\n          Add to cart · {formatPrice(product.price * quantity)}\n        </Button>\n      </Flex>\n    </Flex>\n  );\n}\n",
      "type": "registry:block",
      "target": "@components/box-kite/store/variant-picker.tsx"
    }
  ],
  "docs": "Render <VariantPicker product={…} onAdd={…} />. It calls toast(), so mount <Toaster /> once near the root. Wrap it in <StoreSurface> from store-design.tsx, or render it inside one of the store pages."
}
