{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pdp-specs",
  "type": "registry:component",
  "title": "Spec tabs",
  "description": "Overview, details, ingredients and shipping tabs with a sliding underline and a mono spec ledger.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "pdp",
    "supplements",
    "wellness"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json"
  ],
  "files": [
    {
      "path": "pdp/pdp-specs.tsx",
      "content": "\"use client\"\n\nimport { useId, useRef, useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\n\n/* Tabbed product information. The underline glides between tabs with a\n   shared layoutId; Specifications renders as a two column mono ledger. */\n\ntype Spec = { label: string; value: string }\n\nconst EASE = [0.23, 1, 0.32, 1] as const\nconst RING = \"outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper\"\n\nconst DEFAULT_SPECS: Spec[] = [\n  { label: \"Servings\", value: \"30 per pouch\" },\n  { label: \"Serving size\", value: \"1 scoop (10g)\" },\n  { label: \"Per serving\", value: \"$1.63\" },\n  { label: \"Form\", value: \"Powder\" },\n  { label: \"Diet\", value: \"Vegan, gluten free\" },\n  { label: \"Made in\", value: \"USA, third-party tested\" },\n]\n\nexport function PdpSpecs({\n  overview = \"One scoop of greens, adaptogens and probiotics to start the day. Mixes clean in cold water and tastes like a bright, lightly sweet green juice.\",\n  specs = DEFAULT_SPECS,\n  ingredients = \"Organic spirulina, wheatgrass and spinach, ashwagandha, a 10 billion CFU probiotic blend and monk fruit. No fillers, no artificial sweeteners, nothing we would not take ourselves.\",\n  shipping = \"Ships same day before 2pm ET. Free shipping on subscriptions, and tracking is emailed as soon as the label prints.\",\n  className,\n}: {\n  overview?: React.ReactNode\n  specs?: Spec[]\n  ingredients?: React.ReactNode\n  shipping?: React.ReactNode\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const uid = useId()\n  const tabs = [\"Overview\", \"Details\", \"Ingredients\", \"Shipping\"]\n  const [active, setActive] = useState(1)\n  const refs = useRef<(HTMLButtonElement | null)[]>([])\n\n  const onKeyDown = (e: React.KeyboardEvent) => {\n    let next = -1\n    if (e.key === \"ArrowRight\") next = (active + 1) % tabs.length\n    else if (e.key === \"ArrowLeft\") next = (active - 1 + tabs.length) % tabs.length\n    else if (e.key === \"Home\") next = 0\n    else if (e.key === \"End\") next = tabs.length - 1\n    if (next < 0) return\n    e.preventDefault()\n    setActive(next)\n    refs.current[next]?.focus()\n  }\n\n  const panel = [\n    <p key=\"o\" className=\"max-w-prose text-[15px] leading-[1.5] text-sk-muted\">{overview}</p>,\n    <dl key=\"s\" className=\"grid gap-x-8 sm:grid-cols-2\">\n      {specs.map((s) => (\n        <div key={s.label} className=\"flex items-baseline justify-between gap-4 border-b border-sk-line py-3\">\n          <dt className=\"shrink-0 text-sm text-sk-muted\">{s.label}</dt>\n          <dd className=\"min-w-0 break-all text-right font-sk-mono text-[13px] text-sk-ink\">{s.value}</dd>\n        </div>\n      ))}\n    </dl>,\n    <p key=\"r\" className=\"max-w-prose text-[15px] leading-[1.5] text-sk-muted\">{ingredients}</p>,\n    <p key=\"sh\" className=\"max-w-prose text-[15px] leading-[1.5] text-sk-muted\">{shipping}</p>,\n  ]\n\n  return (\n    <div data-sk-section=\"pdp\" data-sk-variant=\"pdp-specs\" className={cn(\"w-full\", className)}>\n      <div role=\"tablist\" aria-label=\"Product information\" className=\"flex overflow-x-auto border-b border-sk-line [scrollbar-width:none] sm:gap-1\">\n        {tabs.map((t, i) => {\n          const selected = i === active\n          return (\n            <button\n              key={t}\n              ref={(el) => {\n                refs.current[i] = el\n              }}\n              id={`${uid}-tab-${i}`}\n              role=\"tab\"\n              type=\"button\"\n              aria-selected={selected}\n              aria-controls={`${uid}-panel`}\n              tabIndex={selected ? 0 : -1}\n              onClick={() => setActive(i)}\n              onKeyDown={onKeyDown}\n              className={cn(\n                \"relative h-11 shrink-0 rounded-t-sk-btn px-2.5 text-[13px] font-medium transition-colors duration-200 sm:px-3 sm:text-sm\",\n                selected ? \"text-sk-ink\" : \"text-sk-muted hover:text-sk-ink\",\n                RING\n              )}\n            >\n              {t}\n              {selected ? (\n                <motion.span\n                  layoutId={`${uid}-underline`}\n                  transition={{ duration: reduce ? 0 : 0.4, ease: EASE }}\n                  className=\"absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-sk-accent\"\n                />\n              ) : null}\n            </button>\n          )\n        })}\n      </div>\n      <div id={`${uid}-panel`} role=\"tabpanel\" aria-labelledby={`${uid}-tab-${active}`} tabIndex={0} className={cn(\"pt-6\", \"outline-none focus-visible:ring-2 focus-visible:ring-sk-accent\")}>\n        <AnimatePresence mode=\"wait\" initial={false}>\n          <motion.div\n            key={active}\n            initial={{ opacity: 0, y: reduce ? 0 : 8 }}\n            animate={{ opacity: 1, y: 0 }}\n            exit={{ opacity: 0, y: reduce ? 0 : -4 }}\n            transition={{ duration: reduce ? 0 : 0.3, ease: EASE }}\n          >\n            {panel[active]}\n          </motion.div>\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/pdp/pdp-specs.tsx"
    }
  ],
  "meta": {
    "section": "pdp",
    "variant": "tabs",
    "niche": [
      "supplements",
      "wellness"
    ],
    "commerce": [],
    "previewHeight": 360
  }
}