{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cart-note",
  "type": "registry:component",
  "title": "Cart note and gift message",
  "description": "Labeled order note with a live character count and a gift switch that reveals a printed gift message.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "cart",
    "any"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "cart/cart-note.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Order note and gift message. A labeled textarea with a live character\n   count, plus a switch that reveals a separate gift message printed on the\n   packing slip. Counts turn to the sale color only near the limit. */\n\nconst FIELD = \"w-full resize-none rounded-sk-card border border-sk-line bg-sk-card px-3 py-2.5 text-sm leading-[1.5] text-sk-ink placeholder:text-sk-muted/70 transition-colors duration-200 focus-visible:border-sk-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sk-accent/30\"\n\nfunction Count({ value, max, id }: { value: string; max: number; id: string }) {\n  const left = max - value.length\n  return (\n    <span id={id} className={cn(\"text-xs tabular-nums\", left <= 20 ? \"text-sk-sale\" : \"text-sk-muted\")} aria-live={left <= 20 ? \"polite\" : \"off\"}>\n      {value.length} / {max}\n    </span>\n  )\n}\n\nexport function CartNote({\n  noteMax = 250,\n  giftMax = 150,\n  defaultNote = \"\",\n  defaultGift = false,\n  defaultGiftMessage = \"\",\n  onChange,\n  className,\n}: {\n  noteMax?: number\n  giftMax?: number\n  defaultNote?: string\n  defaultGift?: boolean\n  defaultGiftMessage?: string\n  onChange?: (v: { note: string; gift: boolean; giftMessage: string }) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"cart\", \"cart-note\")\n  const [note, setNote] = React.useState(defaultNote)\n  const [gift, setGift] = React.useState(defaultGift)\n  const [giftMessage, setGiftMessage] = React.useState(defaultGiftMessage)\n  const noteId = React.useId()\n  const giftId = React.useId()\n\n  const emit = (v: Partial<{ note: string; gift: boolean; giftMessage: string }>) => onChange?.({ note, gift, giftMessage, ...v })\n\n  return (\n    <div ref={sk.ref as React.Ref<HTMLDivElement>} {...sk.attrs} className={cn(\"grid w-full gap-4\", className)}>\n      <div>\n        <div className=\"mb-1.5 flex items-baseline justify-between gap-3\">\n          <label htmlFor={noteId} className=\"text-[13px] font-medium text-sk-ink\">\n            Order note <span className=\"font-normal text-sk-muted\">(optional)</span>\n          </label>\n          <Count value={note} max={noteMax} id={`${noteId}-count`} />\n        </div>\n        <textarea\n          id={noteId}\n          rows={3}\n          maxLength={noteMax}\n          value={note}\n          aria-describedby={`${noteId}-count ${noteId}-hint`}\n          placeholder=\"Delivery instructions or anything we should know\"\n          onChange={(e) => {\n            setNote(e.target.value)\n            emit({ note: e.target.value })\n          }}\n          className={FIELD}\n        />\n        <p id={`${noteId}-hint`} className=\"mt-1 text-xs text-sk-muted\">Read by the packing team. Not printed.</p>\n      </div>\n\n      <div className=\"rounded-sk-card border border-sk-line\">\n        <button\n          type=\"button\"\n          role=\"switch\"\n          aria-checked={gift}\n          onClick={() => {\n            setGift(!gift)\n            emit({ gift: !gift })\n            sk.emit(\"interact\", gift ? \"gift_message_off\" : \"gift_message_on\")\n          }}\n          className=\"flex min-h-12 w-full items-center gap-3 rounded-sk-card px-4 py-3 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-sk-accent active:scale-[0.99]\"\n        >\n          <span className=\"min-w-0 flex-1\">\n            <span className=\"block text-sm font-medium text-sk-ink\">This is a gift</span>\n            <span className=\"block text-[13px] text-sk-muted\">Add a message. Prices are left off the packing slip.</span>\n          </span>\n          <span aria-hidden className={cn(\"relative flex h-6 w-10 shrink-0 items-center rounded-sk-chip p-0.5 transition-colors duration-200\", gift ? \"bg-sk-accent\" : \"bg-sk-ink/20\")}>\n            <motion.span\n              initial={false}\n              animate={{ x: gift ? 16 : 0 }}\n              transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 600, damping: 34 }}\n              className={cn(\"h-5 w-5 rounded-sk-chip shadow-[0_1px_2px_rgb(0_0_0/0.25)]\", gift ? \"bg-sk-accent-ink\" : \"bg-sk-paper\")}\n            />\n          </span>\n        </button>\n        <AnimatePresence initial={false}>\n          {gift ? (\n            <motion.div\n              initial={{ height: 0, opacity: 0 }}\n              animate={{ height: \"auto\", opacity: 1 }}\n              exit={{ height: 0, opacity: 0 }}\n              transition={{ duration: reduce ? 0 : 0.28, ease: [0.16, 1, 0.3, 1] }}\n              className=\"overflow-hidden\"\n            >\n              <div className=\"border-t border-sk-line px-4 pb-4 pt-3\">\n                <div className=\"mb-1.5 flex items-baseline justify-between gap-3\">\n                  <label htmlFor={giftId} className=\"text-[13px] font-medium text-sk-ink\">\n                    Gift message\n                  </label>\n                  <Count value={giftMessage} max={giftMax} id={`${giftId}-count`} />\n                </div>\n                <textarea\n                  id={giftId}\n                  rows={3}\n                  maxLength={giftMax}\n                  value={giftMessage}\n                  aria-describedby={`${giftId}-count`}\n                  placeholder=\"Printed on a card inside the box\"\n                  onChange={(e) => {\n                    setGiftMessage(e.target.value)\n                    emit({ giftMessage: e.target.value })\n                  }}\n                  className={FIELD}\n                />\n              </div>\n            </motion.div>\n          ) : null}\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/cart/cart-note.tsx"
    }
  ],
  "meta": {
    "section": "cart",
    "variant": "note",
    "niche": [
      "any"
    ],
    "commerce": [
      "gift",
      "note"
    ],
    "previewHeight": 460
  }
}