{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "order-summary",
  "type": "registry:block",
  "title": "Order summary",
  "description": "Subtotal, discount, shipping, tax and total from the cart lines, with a discount code field and a free-shipping progress bar.",
  "author": "Box Kite <https://www.box-kite.dev>",
  "categories": [
    "ecommerce",
    "cart",
    "checkout"
  ],
  "dependencies": [
    "@box-kite/react"
  ],
  "registryDependencies": [
    "https://www.box-kite.dev/r/store-design.json"
  ],
  "files": [
    {
      "path": "registry/designs/store/order-summary.tsx",
      "content": "'use client';\nimport Box from '@box-kite/react';\nimport Button from '@box-kite/react/components/button';\nimport Flex from '@box-kite/react/components/flex';\nimport Progress from '@box-kite/react/components/progress';\nimport { Aside, H2, Label, Li, Span, Ul } from '@box-kite/react/components/semantics';\nimport Textbox from '@box-kite/react/components/textbox';\nimport { type FormEvent, type ReactNode, useId, useState } from 'react';\nimport ProductArt from './product-art';\nimport { type CartLine, FREE_SHIPPING_FROM, formatPrice, finishes, productBySlug, subtotalOf, TAX_RATE } from './store-data';\nimport { heading } from './store-design';\n\n/** The one code the demo accepts. Check codes on your server; this is only the shape of the round trip. */\nconst CODES: Record<string, number> = { STILL10: 0.1 };\n\nexport interface OrderSummaryProps {\n  lines: readonly CartLine[];\n  /** The shipping price once a method is chosen. Left out, the summary says it is worked out at checkout. */\n  shipping?: number;\n  /** List the objects themselves — what a checkout page shows, where the cart is no longer on screen. */\n  showLines?: boolean;\n  /** The button underneath: \"Checkout\" on the cart page, nothing on the checkout. */\n  action?: ReactNode;\n  /** Told the discount once a code is accepted, for a page that shows the total somewhere else too. */\n  onDiscountChange?: (amount: number) => void;\n}\n\n/**\n * Subtotal, discount, shipping, tax and the total, with a discount-code field and how far the cart is from\n * free shipping. Every figure is computed from `lines`, so it can never disagree with the cart beside it.\n */\nexport default function OrderSummary({ lines, shipping, showLines, action, onDiscountChange }: OrderSummaryProps) {\n  const [code, setCode] = useState('');\n  const [applied, setApplied] = useState<string>();\n  const [error, setError] = useState<string>();\n  const ids = { heading: useId(), code: useId(), error: useId() };\n\n  const subtotal = subtotalOf(lines);\n  const discount = applied ? subtotal * CODES[applied] : 0;\n  const tax = (subtotal - discount) * TAX_RATE;\n  const total = subtotal - discount + (shipping ?? 0) + tax;\n  const toFree = Math.max(FREE_SHIPPING_FROM - subtotal, 0);\n\n  const apply = (event: FormEvent) => {\n    event.preventDefault();\n    const normalised = code.trim().toUpperCase();\n\n    if (CODES[normalised]) {\n      setApplied(normalised);\n      onDiscountChange?.(subtotal * CODES[normalised]);\n      setError(undefined);\n    } else {\n      setError(normalised ? `“${code.trim()}” is not a code we recognise.` : 'Enter a code first.');\n    }\n  };\n\n  return (\n    <Aside props={{ 'aria-labelledby': ids.heading }} p={6} borderRadius={4} bgColor=\"stone-100\" theme={{ dark: { bgColor: 'stone-900' } }}>\n      <H2 id={ids.heading} fontSize={18} {...heading}>\n        Order summary\n      </H2>\n\n      {showLines && (\n        <Ul listStyle=\"none\" m={0} p={0} mt={5} display=\"flex\" d=\"column\" gap={4}>\n          {lines.map((line) => {\n            const product = productBySlug(line.slug);\n\n            return (\n              <Li key={line.id} display=\"flex\" ai=\"center\" gap={3}>\n                <Box position=\"relative\" flexShrink={0} width={14} height={14}>\n                  <ProductArt shape={product.shape} finish={line.finish} stroke={1.5} radius={2} />\n                  <Span\n                    position=\"absolute\"\n                    top={-1.5}\n                    insetEnd={-1.5}\n                    minWidth={5}\n                    height={5}\n                    px={1}\n                    display=\"flex\"\n                    ai=\"center\"\n                    jc=\"center\"\n                    borderRadius={999}\n                    fontSize={11}\n                    fontWeight={700}\n                    bgColor=\"stone-700\"\n                    color=\"white\"\n                    props={{ 'aria-hidden': true }}\n                  >\n                    {line.quantity}\n                  </Span>\n                </Box>\n                <Flex d=\"column\" flex1 minWidth={0} fontSize={14}>\n                  <Span fontWeight={500}>\n                    {product.name}\n                    {line.quantity > 1 && <Span display=\"inline\" fontWeight={400}>{` × ${line.quantity}`}</Span>}\n                  </Span>\n                  <Span color=\"stone-600\" theme={{ dark: { color: 'stone-400' } }}>\n                    {[finishes[line.finish].name, line.size].filter(Boolean).join(' · ')}\n                  </Span>\n                </Flex>\n                <Span fontSize={14} fontWeight={500}>\n                  {formatPrice(product.price * line.quantity)}\n                </Span>\n              </Li>\n            );\n          })}\n        </Ul>\n      )}\n\n      <Box tag=\"form\" mt={5} props={{ onSubmit: apply, noValidate: true }}>\n        <Label display=\"block\" fontSize={14} fontWeight={500} mb={2} props={{ htmlFor: ids.code }}>\n          Discount code\n        </Label>\n        <Flex gap={2}>\n          <Textbox\n            id={ids.code}\n            value={code}\n            onChange={(event) => setCode(event.target.value)}\n            placeholder=\"Try STILL10\"\n            props={{ autoComplete: 'off', 'aria-invalid': error ? true : undefined, 'aria-describedby': error ? ids.error : undefined }}\n          />\n          <Button type=\"submit\" variant=\"secondary\" flexShrink={0}>\n            Apply\n          </Button>\n        </Flex>\n        {error && (\n          <Span id={ids.error} display=\"block\" mt={2} fontSize={13} color=\"red-700\" theme={{ dark: { color: 'red-400' } }}>\n            {error}\n          </Span>\n        )}\n      </Box>\n\n      <Box tag=\"dl\" mt={5} display=\"flex\" d=\"column\" gap={3} fontSize={15}>\n        <Row term=\"Subtotal\" value={formatPrice(subtotal)} />\n        {applied && <Row term={`Discount (${applied})`} value={`−${formatPrice(discount)}`} accent />}\n        <Row term=\"Shipping\" value={shipping === undefined ? 'At checkout' : shipping === 0 ? 'Free' : formatPrice(shipping)} />\n        <Row term=\"Estimated tax\" value={formatPrice(tax)} />\n        <Flex jc=\"space-between\" ai=\"baseline\" pt={4} mt={1} bt={1} borderColor=\"stone-200\" theme={{ dark: { borderColor: 'stone-800' } }}>\n          <Box tag=\"dt\" fontWeight={500}>\n            Total\n          </Box>\n          <Box tag=\"dd\" m={0} fontSize={22} fontWeight={500}>\n            {formatPrice(total)}\n          </Box>\n        </Flex>\n      </Box>\n\n      {shipping === undefined && (\n        <Box mt={5}>\n          <Progress label=\"Progress towards free shipping\" value={Math.min(subtotal / FREE_SHIPPING_FROM, 1) * 100} />\n          <Span display=\"block\" mt={2} fontSize={13} color=\"stone-600\" theme={{ dark: { color: 'stone-400' } }}>\n            {toFree ? `${formatPrice(toFree)} more and standard shipping is free.` : 'Your order ships free.'}\n          </Span>\n        </Box>\n      )}\n\n      {action && <Box mt={6}>{action}</Box>}\n    </Aside>\n  );\n}\n\nfunction Row({ term, value, accent }: { term: string; value: string; accent?: boolean }) {\n  return (\n    <Flex jc=\"space-between\" gap={4} color={accent ? 'green-700' : undefined} theme={accent ? { dark: { color: 'green-400' } } : undefined}>\n      <Box tag=\"dt\" color={accent ? undefined : 'stone-600'} theme={accent ? undefined : { dark: { color: 'stone-400' } }}>\n        {term}\n      </Box>\n      <Box tag=\"dd\" m={0} fontWeight={500}>\n        {value}\n      </Box>\n    </Flex>\n  );\n}\n",
      "type": "registry:block",
      "target": "@components/box-kite/store/order-summary.tsx"
    }
  ],
  "docs": "Render <OrderSummary lines={lines} action={…} />. Check discount codes on your server. Wrap it in <StoreSurface> from store-design.tsx, or render it inside one of the store pages."
}
