{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "cart-drawer",
  "title": "Cart Drawer",
  "type": "registry:block",
  "description": "Shopping cart drawer & full cart page view featuring free shipping progress meter, interactive line items with quantity steppers and variant tags, promo code application, order summary with tax & shipping calculation, trust badges, and empty state.",
  "categories": [
    "commerce",
    "ecommerce"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/cart-drawer/cart-drawer.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { Leaf, Lock, Minus, Plus, RotateCcw, ShieldCheck, ShoppingBag, Tag, Trash2, Truck, X } from 'lucide'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/badge/badge'\nimport '../../components/button/button'\nimport '../../components/input/input'\nimport type { UipInput } from '../../components/input/input'\nimport '../../components/progress/progress'\nimport '../../components/separator/separator'\n\nexport interface CartItem {\n  id: string\n  name: string\n  variant: string\n  price: number\n  originalPrice?: number\n  qty: number\n  inStock?: boolean\n  image: string\n}\n\nconst DEFAULT_ITEMS: CartItem[] = [\n  {\n    id: 'item-1',\n    name: 'Aero Minimalist Runner',\n    variant: 'Size: 10.5 · Color: Matte Black',\n    price: 120,\n    originalPrice: 140,\n    qty: 1,\n    inStock: true,\n    image: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=300&auto=format&fit=crop&q=80',\n  },\n  {\n    id: 'item-2',\n    name: 'Technical Shell Parka',\n    variant: 'Size: L · Color: Mineral Gray',\n    price: 95,\n    originalPrice: 110,\n    qty: 1,\n    inStock: true,\n    image: 'https://images.unsplash.com/photo-1591047139829-d91aecb6caea?w=300&auto=format&fit=crop&q=80',\n  },\n  {\n    id: 'item-3',\n    name: 'Minimalist Leather Cardholder',\n    variant: 'Size: Slim · Color: Saddle Brown',\n    price: 70,\n    originalPrice: 85,\n    qty: 1,\n    inStock: true,\n    image: 'https://images.unsplash.com/photo-1627123424574-724758594e93?w=300&auto=format&fit=crop&q=80',\n  },\n]\n\n/**\n * <uip-cart-drawer> — the registry CartDrawer block as a web component.\n *\n * Class strings are React's CartDrawer verbatim. React state (useState) becomes\n * { state: true } fields; `items` is a property (attribute: false — set it from\n * JS, like the demo's empty-cart story does). `onCheckout` (items, total) /\n * `onContinueShopping` / `onClose` / `onUpdateQuantity` (id, qty) /\n * `onRemoveItem` (id) / `onApplyPromo` (code) / `onRemovePromo` become\n * `checkout` / `continue-shopping` / `close` / `update-quantity` /\n * `remove-item` / `apply-promo` / `remove-promo` CustomEvents (bubbles,\n * composed; payloads in `detail`). React's `promoError` state is never set by\n * the source (every non-empty code succeeds) so it is not ported.\n */\nexport class UipCartDrawer extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    variant: { reflect: true },\n    currency: {},\n    freeShippingThreshold: { type: Number, attribute: 'free-shipping-threshold' },\n    taxRate: { type: Number, attribute: 'tax-rate' },\n    initialPromoCode: { attribute: 'initial-promo-code' },\n    items: { attribute: false },\n    cartItems: { state: true },\n    promoCodeInput: { state: true },\n    appliedPromo: { state: true },\n    promoDiscount: { state: true },\n    promoSuccess: { state: true },\n  }\n\n  variant: 'drawer' | 'page' = 'drawer'\n  currency = 'USD'\n  freeShippingThreshold = 300\n  taxRate = 0.08\n  initialPromoCode = ''\n  items: CartItem[] = DEFAULT_ITEMS\n\n  private cartItems: CartItem[] = []\n  private promoCodeInput = ''\n  private appliedPromo = ''\n  private promoDiscount = 0\n  private promoSuccess = ''\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'cart-drawer')\n  }\n\n  willUpdate(changed: Map<string, unknown>) {\n    if (changed.has('items')) {\n      this.cartItems = (this.items ?? []).map((item) => ({ ...item }))\n    }\n    if (changed.has('initialPromoCode')) {\n      this.appliedPromo = this.initialPromoCode\n      this.promoDiscount = this.initialPromoCode ? 20 : 0\n    }\n  }\n\n  private get totalItemsCount() {\n    return this.cartItems.reduce((acc, item) => acc + item.qty, 0)\n  }\n\n  private get subtotal() {\n    return this.cartItems.reduce((acc, item) => acc + item.price * item.qty, 0)\n  }\n\n  private get isFreeShippingUnlocked() {\n    return this.subtotal >= this.freeShippingThreshold && this.cartItems.length > 0\n  }\n\n  private get amountAwayFromFreeShipping() {\n    return Math.max(0, this.freeShippingThreshold - this.subtotal)\n  }\n\n  private get shippingProgress() {\n    if (this.cartItems.length === 0) return 0\n    return Math.min(100, Math.round((this.subtotal / this.freeShippingThreshold) * 100))\n  }\n\n  private get shippingFee() {\n    if (this.cartItems.length === 0) return 0\n    return this.isFreeShippingUnlocked ? 0 : 15\n  }\n\n  private get discountedSubtotal() {\n    return Math.max(0, this.subtotal - this.promoDiscount)\n  }\n\n  private get estimatedTax() {\n    if (this.cartItems.length === 0) return 0\n    return Number((this.discountedSubtotal * this.taxRate).toFixed(2))\n  }\n\n  private get total() {\n    if (this.cartItems.length === 0) return 0\n    return Number((this.discountedSubtotal + this.shippingFee + this.estimatedTax).toFixed(2))\n  }\n\n  private formatCurrency(amount: number) {\n    try {\n      return new Intl.NumberFormat('en-US', {\n        style: 'currency',\n        currency: this.currency,\n        minimumFractionDigits: 2,\n      }).format(amount)\n    } catch {\n      return `$${amount.toFixed(2)}`\n    }\n  }\n\n  private updateQuantity(id: string, delta: number) {\n    const next: CartItem[] = []\n    for (const item of this.cartItems) {\n      if (item.id !== id) {\n        next.push(item)\n        continue\n      }\n      const newQty = item.qty + delta\n      if (newQty <= 0) {\n        this.dispatchEvent(new CustomEvent('remove-item', { detail: { id }, bubbles: true, composed: true }))\n        continue\n      }\n      this.dispatchEvent(new CustomEvent('update-quantity', { detail: { id, qty: newQty }, bubbles: true, composed: true }))\n      next.push({ ...item, qty: newQty })\n    }\n    this.cartItems = next\n  }\n\n  private removeItem(id: string) {\n    this.cartItems = this.cartItems.filter((item) => item.id !== id)\n    this.dispatchEvent(new CustomEvent('remove-item', { detail: { id }, bubbles: true, composed: true }))\n  }\n\n  private handleApplyPromo() {\n    this.promoSuccess = ''\n    const trimmed = this.promoCodeInput.trim().toUpperCase()\n    if (!trimmed) return\n    if (trimmed === 'SUMMER20' || trimmed === 'SAVE20') {\n      this.appliedPromo = trimmed\n      this.promoDiscount = 20\n      this.promoSuccess = '$20.00 coupon discount applied!'\n    } else if (trimmed === 'SAVE10') {\n      this.appliedPromo = trimmed\n      this.promoDiscount = 10\n      this.promoSuccess = '$10.00 coupon discount applied!'\n    } else {\n      this.appliedPromo = trimmed\n      this.promoDiscount = 15\n      this.promoSuccess = 'Promo code applied!'\n    }\n    this.promoCodeInput = ''\n    this.dispatchEvent(new CustomEvent('apply-promo', { detail: { code: trimmed }, bubbles: true, composed: true }))\n  }\n\n  private handleRemovePromo() {\n    this.appliedPromo = ''\n    this.promoDiscount = 0\n    this.promoSuccess = ''\n    this.dispatchEvent(new CustomEvent('remove-promo', { bubbles: true, composed: true }))\n  }\n\n  private handleCheckout() {\n    this.dispatchEvent(\n      new CustomEvent('checkout', {\n        detail: { items: this.cartItems.map((i) => ({ ...i })), total: this.total },\n        bubbles: true,\n        composed: true,\n      }),\n    )\n  }\n\n  private resetCart() {\n    this.cartItems = DEFAULT_ITEMS.map((item) => ({ ...item }))\n  }\n\n  private renderPromoRow(placeholder: string) {\n    if (this.appliedPromo) {\n      return html`<div class=\"bg-primary/5 border-primary/20 flex items-center justify-between rounded-lg border px-3 py-2 text-xs\">\n        <div class=\"flex items-center gap-2\">\n          ${icon(Tag, 'tag', 'text-primary size-3.5')}\n          <span class=\"text-foreground font-medium\">${this.appliedPromo}</span>\n          <span class=\"text-success text-success font-semibold tabular-nums\">-${this.formatCurrency(this.promoDiscount)}</span>\n        </div>\n        <uip-button\n          variant=\"ghost\"\n          size=\"icon-sm\"\n          class=\"text-muted-foreground hover:text-foreground size-6\"\n          aria-label=\"Remove promo code\"\n          @click=${() => this.handleRemovePromo()}\n        >\n          ${icon(X, 'x', 'size-3.5')}\n        </uip-button>\n      </div>`\n    }\n    return html`<div class=\"flex gap-2\">\n      <uip-input\n        .value=${this.promoCodeInput}\n        placeholder=${placeholder}\n        size=\"small\"\n        class=\"text-xs\"\n        @input=${(e: Event) => {\n          this.promoCodeInput = (e.target as UipInput).value ?? ''\n        }}\n        @keydown=${(e: KeyboardEvent) => {\n          if (e.key === 'Enter') {\n            e.preventDefault()\n            this.handleApplyPromo()\n          }\n        }}\n      ></uip-input>\n      <uip-button\n        variant=\"outline\"\n        size=\"sm\"\n        class=\"shrink-0 text-xs\"\n        ?disabled=${!this.promoCodeInput.trim()}\n        @click=${() => this.handleApplyPromo()}\n      >\n        Apply\n      </uip-button>\n    </div>`\n  }\n\n  private renderCostRows(page: boolean) {\n    return html`<div class=\"space-y-1.5 text-xs\">\n      <div class=\"flex justify-between\">\n        <span class=\"text-muted-foreground\">Subtotal</span>\n        <span class=\"text-foreground font-medium tabular-nums\">${this.formatCurrency(this.subtotal)}</span>\n      </div>\n      ${this.promoDiscount > 0\n        ? html`<div class=\"text-success flex justify-between\">\n            <span>Coupon (${this.appliedPromo})</span>\n            <span class=\"font-medium tabular-nums\">-${this.formatCurrency(this.promoDiscount)}</span>\n          </div>`\n        : nothing}\n      <div class=\"flex justify-between\">\n        <span class=\"text-muted-foreground\">Estimated Shipping</span>\n        ${this.shippingFee === 0\n          ? html`<span class=\"text-success font-medium\">Free</span>`\n          : html`<span class=\"text-foreground font-medium tabular-nums\">${this.formatCurrency(this.shippingFee)}</span>`}\n      </div>\n      <div class=\"flex justify-between\">\n        <span class=\"text-muted-foreground\">${page ? 'Estimated Tax (8%)' : 'Estimated Tax'}</span>\n        <span class=\"text-foreground font-medium tabular-nums\">${this.formatCurrency(this.estimatedTax)}</span>\n      </div>\n      <uip-separator class=\"my-2\"></uip-separator>\n      ${page\n        ? html`<div class=\"flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 text-base font-semibold\">\n            <span>Total</span>\n            <span class=\"tabular-nums\">${this.formatCurrency(this.total)}</span>\n          </div>`\n        : html`<div class=\"flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 text-sm\">\n            <span class=\"font-semibold\">Total</span>\n            <span class=\"font-semibold tabular-nums\">${this.formatCurrency(this.total)}</span>\n          </div>`}\n    </div>`\n  }\n\n  render() {\n    return html`\n      <div data-slot=\"cart-drawer\" class=${cn('w-full')}>\n        ${this.variant === 'drawer' ? this.renderDrawer() : this.renderPage()}\n      </div>\n    `\n  }\n\n  private renderDrawer() {\n    return html`<div\n      class=\"bg-card text-card-foreground border-border/80 mx-auto flex h-full max-h-[820px] min-h-[640px] w-full max-w-md flex-col overflow-hidden rounded-2xl border shadow-xs\"\n    >\n      <div class=\"border-border/60 flex shrink-0 items-center justify-between border-b px-5 py-4\">\n        <div class=\"flex items-center gap-2\">\n          <h2 class=\"text-base font-semibold tracking-tight\">Shopping Cart</h2>\n          ${this.totalItemsCount > 0\n            ? html`<uip-badge variant=\"secondary\" class=\"tabular-nums\">${this.totalItemsCount}</uip-badge>`\n            : nothing}\n        </div>\n        <uip-button\n          variant=\"ghost\"\n          size=\"icon-sm\"\n          aria-label=\"Close cart drawer\"\n          @click=${() => this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))}\n        >\n          ${icon(X, 'x', 'size-4')}\n        </uip-button>\n      </div>\n\n      ${this.cartItems.length > 0\n        ? html`<div class=\"bg-muted/30 border-border/40 shrink-0 border-b px-5 py-3.5\">\n            <div class=\"flex items-center justify-between gap-2 text-xs\">\n              ${this.isFreeShippingUnlocked\n                ? html`<span class=\"text-primary flex items-center gap-1.5 font-medium\">\n                    ${icon(Truck, 'truck', 'size-3.5')} You&apos;ve unlocked Free Express Shipping!\n                  </span>`\n                : html`<span class=\"text-muted-foreground\">\n                    🎉 You&apos;re\n                    <strong class=\"text-foreground font-semibold tabular-nums\"\n                      >${this.formatCurrency(this.amountAwayFromFreeShipping)}</strong\n                    >\n                    away from Free Shipping!\n                  </span>`}\n              <span class=\"text-muted-foreground font-medium tabular-nums\">${this.shippingProgress}%</span>\n            </div>\n            <uip-progress value=${this.shippingProgress} class=\"mt-2 h-1.5\"></uip-progress>\n          </div>`\n        : nothing}\n\n      <div class=\"flex-1 overflow-y-auto p-5\">\n        ${this.cartItems.length === 0\n          ? html`<div class=\"flex h-full flex-col items-center justify-center py-12 text-center\">\n              <div class=\"bg-muted text-muted-foreground mb-4 flex size-14 items-center justify-center rounded-full\">\n                ${icon(ShoppingBag, 'shopping-bag', 'size-7 stroke-[1.5]')}\n              </div>\n              <h3 class=\"text-base font-semibold\">Your cart is empty</h3>\n              <p class=\"text-muted-foreground mt-1 max-w-xs text-xs leading-relaxed\">\n                Looks like you haven&apos;t added any products to your cart yet. Explore our latest items!\n              </p>\n              <uip-button class=\"mt-5 gap-2\" size=\"sm\" @click=${() => this.resetCart()}>\n                ${icon(ShoppingBag, 'shopping-bag', 'size-3.5')} Explore Products\n              </uip-button>\n            </div>`\n          : html`<div class=\"space-y-4\">\n              <ul class=\"divide-border/60 divide-y\" role=\"list\">\n                ${this.cartItems.map(\n                  (item) => html`<li class=\"flex gap-3.5 py-4 first:pt-0 last:pb-0\">\n                    <div class=\"border-border/60 bg-muted/40 relative size-20 shrink-0 overflow-hidden rounded-lg border shadow-xs\">\n                      <img src=${item.image} alt=${item.name} class=\"size-full object-cover\" />\n                    </div>\n                    <div class=\"flex min-w-0 flex-1 flex-col justify-between\">\n                      <div>\n                        <div class=\"flex items-start justify-between gap-2\">\n                          <h3 class=\"text-foreground truncate text-sm leading-snug font-medium\">${item.name}</h3>\n                          <uip-button\n                            variant=\"ghost\"\n                            size=\"icon-sm\"\n                            class=\"text-muted-foreground hover:text-destructive -mt-1 -mr-1.5 size-7 shrink-0\"\n                            aria-label=${`Remove ${item.name} from cart`}\n                            @click=${() => this.removeItem(item.id)}\n                          >\n                            ${icon(Trash2, 'trash-2', 'size-3.5')}\n                          </uip-button>\n                        </div>\n                        <p class=\"text-muted-foreground mt-0.5 text-xs\">${item.variant}</p>\n                        <div class=\"mt-1 flex items-center gap-1.5\">\n                          <span class=\"bg-success size-1.5 rounded-full\"></span>\n                          <span class=\"text-success text-xs font-medium\">In stock</span>\n                        </div>\n                      </div>\n                      <div class=\"mt-2.5 flex items-center justify-between\">\n                        <div class=\"border-border/80 bg-background flex items-center rounded-md border shadow-xs\">\n                          <uip-button\n                            variant=\"ghost\"\n                            size=\"icon-sm\"\n                            class=\"size-7 rounded-none rounded-l-md\"\n                            aria-label=${`Decrease quantity for ${item.name}`}\n                            @click=${() => this.updateQuantity(item.id, -1)}\n                          >\n                            ${icon(Minus, 'minus', 'size-3')}\n                          </uip-button>\n                          <span class=\"w-7 text-center text-xs font-medium tabular-nums\">${item.qty}</span>\n                          <uip-button\n                            variant=\"ghost\"\n                            size=\"icon-sm\"\n                            class=\"size-7 rounded-none rounded-r-md\"\n                            aria-label=${`Increase quantity for ${item.name}`}\n                            @click=${() => this.updateQuantity(item.id, 1)}\n                          >\n                            ${icon(Plus, 'plus', 'size-3')}\n                          </uip-button>\n                        </div>\n                        <div class=\"text-right\">\n                          ${item.originalPrice\n                            ? html`<span class=\"text-muted-foreground mr-1.5 text-xs tabular-nums line-through\"\n                                >${this.formatCurrency(item.originalPrice * item.qty)}</span\n                              >`\n                            : nothing}\n                          <span class=\"text-foreground text-sm font-semibold tabular-nums\"\n                            >${this.formatCurrency(item.price * item.qty)}</span\n                          >\n                        </div>\n                      </div>\n                    </div>\n                  </li>`,\n                )}\n              </ul>\n              <uip-separator class=\"my-3\"></uip-separator>\n              <div class=\"space-y-2\">\n                ${this.renderPromoRow('Promo code (e.g. SUMMER20)')}\n                ${this.promoSuccess\n                  ? html`<p class=\"text-success text-xs font-medium\">${this.promoSuccess}</p>`\n                  : nothing}\n              </div>\n            </div>`}\n      </div>\n\n      ${this.cartItems.length > 0\n        ? html`<div class=\"border-border/60 bg-card shrink-0 space-y-3 border-t p-5\">\n            ${this.renderCostRows(false)}\n            <div class=\"space-y-2 pt-1\">\n              <uip-button class=\"w-full justify-center gap-2 font-medium shadow-xs\" size=\"default\" @click=${() => this.handleCheckout()}>\n                ${icon(Lock, 'lock', 'size-3.5')} Proceed to Checkout\n              </uip-button>\n              <uip-button\n                variant=\"ghost\"\n                class=\"text-muted-foreground hover:text-foreground w-full text-xs\"\n                size=\"sm\"\n                @click=${() => this.dispatchEvent(new CustomEvent('continue-shopping', { bubbles: true, composed: true }))}\n              >\n                Continue Shopping\n              </uip-button>\n            </div>\n            <div class=\"border-border/40 text-muted-foreground grid grid-cols-1 gap-2 border-t pt-3 text-center text-xs sm:grid-cols-3\">\n              <div class=\"flex flex-col items-center gap-1\">\n                ${icon(RotateCcw, 'rotate-ccw', 'size-3.5')}\n                <span>30-day returns</span>\n              </div>\n              <div class=\"flex flex-col items-center gap-1\">\n                ${icon(ShieldCheck, 'shield-check', 'size-3.5')}\n                <span>256-bit secure</span>\n              </div>\n              <div class=\"flex flex-col items-center gap-1\">\n                ${icon(Leaf, 'leaf', 'size-3.5')}\n                <span>Carbon neutral</span>\n              </div>\n            </div>\n          </div>`\n        : nothing}\n    </div>`\n  }\n\n  private renderPage() {\n    return html`<div class=\"mx-auto w-full max-w-6xl space-y-6\">\n      <div class=\"flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between\">\n        <div>\n          <h1 class=\"text-2xl font-bold tracking-tight\">Shopping Cart</h1>\n          <p class=\"text-muted-foreground mt-0.5 text-xs\">Review your selected items and calculate shipping before checkout.</p>\n        </div>\n        ${this.totalItemsCount > 0\n          ? html`<uip-badge variant=\"outline\" class=\"w-fit text-xs tabular-nums\">${this.totalItemsCount} items in cart</uip-badge>`\n          : nothing}\n      </div>\n\n      ${this.cartItems.length > 0\n        ? html`<div class=\"bg-card border-border/80 rounded-xl border p-4 shadow-xs\">\n            <div class=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n              <div class=\"flex items-center gap-2 text-xs\">\n                ${this.isFreeShippingUnlocked\n                  ? html`${icon(Truck, 'truck', 'text-primary size-4')}\n                      <span class=\"text-primary font-semibold\">You&apos;ve unlocked Free Express Shipping on this order!</span>`\n                  : html`<span class=\"text-muted-foreground\">\n                      🎉 You&apos;re\n                      <strong class=\"text-foreground font-semibold tabular-nums\"\n                        >${this.formatCurrency(this.amountAwayFromFreeShipping)}</strong\n                      >\n                      away from Free Shipping!\n                    </span>`}\n              </div>\n              <span class=\"text-muted-foreground text-xs font-medium tabular-nums\">${this.shippingProgress}% completed</span>\n            </div>\n            <uip-progress value=${this.shippingProgress} class=\"mt-2.5 h-2\"></uip-progress>\n          </div>`\n        : nothing}\n\n      ${this.cartItems.length === 0\n        ? html`<div class=\"bg-card border-border/80 flex flex-col items-center justify-center rounded-2xl border py-16 text-center shadow-xs\">\n            <div class=\"bg-muted text-muted-foreground mb-4 flex size-16 items-center justify-center rounded-full\">\n              ${icon(ShoppingBag, 'shopping-bag', 'size-8 stroke-[1.5]')}\n            </div>\n            <h2 class=\"text-lg font-semibold\">Your cart is currently empty</h2>\n            <p class=\"text-muted-foreground mt-1 max-w-sm text-sm\">\n              Before you proceed to checkout you must add some products to your shopping cart.\n            </p>\n            <uip-button class=\"mt-6 gap-2\" size=\"default\" @click=${() => this.resetCart()}>\n              ${icon(ShoppingBag, 'shopping-bag', 'size-4')} Explore Products\n            </uip-button>\n          </div>`\n        : html`<div class=\"grid grid-cols-1 items-start gap-8 lg:grid-cols-12\">\n            <div class=\"space-y-4 lg:col-span-8\">\n              <div class=\"bg-card border-border/80 divide-border/60 divide-y rounded-2xl border shadow-xs\">\n                ${this.cartItems.map(\n                  (item) => html`<div class=\"flex flex-col gap-4 p-5 sm:flex-row sm:items-center sm:justify-between\">\n                    <div class=\"flex items-center gap-4\">\n                      <div class=\"border-border/60 bg-muted/40 relative size-20 shrink-0 overflow-hidden rounded-lg border shadow-xs\">\n                        <img src=${item.image} alt=${item.name} class=\"size-full object-cover\" />\n                      </div>\n                      <div>\n                        <h3 class=\"text-foreground text-sm font-semibold\">${item.name}</h3>\n                        <p class=\"text-muted-foreground mt-0.5 text-xs\">${item.variant}</p>\n                        <div class=\"mt-1.5 flex items-center gap-2\">\n                          <span class=\"bg-success size-1.5 rounded-full\"></span>\n                          <span class=\"text-success text-xs font-medium\">In stock</span>\n                          <span class=\"text-muted-foreground text-xs font-normal tabular-nums\">\n                            · ${this.formatCurrency(item.price)} each\n                          </span>\n                        </div>\n                      </div>\n                    </div>\n                    <div class=\"flex items-center justify-between gap-6 sm:justify-end\">\n                      <div class=\"border-border/80 bg-background flex items-center rounded-md border shadow-xs\">\n                        <uip-button\n                          variant=\"ghost\"\n                          size=\"icon-sm\"\n                          class=\"size-8 rounded-none rounded-l-md\"\n                          aria-label=${`Decrease quantity for ${item.name}`}\n                          @click=${() => this.updateQuantity(item.id, -1)}\n                        >\n                          ${icon(Minus, 'minus', 'size-3.5')}\n                        </uip-button>\n                        <span class=\"w-8 text-center text-xs font-medium tabular-nums\">${item.qty}</span>\n                        <uip-button\n                          variant=\"ghost\"\n                          size=\"icon-sm\"\n                          class=\"size-8 rounded-none rounded-r-md\"\n                          aria-label=${`Increase quantity for ${item.name}`}\n                          @click=${() => this.updateQuantity(item.id, 1)}\n                        >\n                          ${icon(Plus, 'plus', 'size-3.5')}\n                        </uip-button>\n                      </div>\n                      <div class=\"w-24 text-right\">\n                        ${item.originalPrice\n                          ? html`<span class=\"text-muted-foreground block text-xs tabular-nums line-through\"\n                              >${this.formatCurrency(item.originalPrice * item.qty)}</span\n                            >`\n                          : nothing}\n                        <span class=\"text-foreground text-base font-semibold tabular-nums\"\n                          >${this.formatCurrency(item.price * item.qty)}</span\n                        >\n                      </div>\n                      <uip-button\n                        variant=\"ghost\"\n                        size=\"icon-sm\"\n                        class=\"text-muted-foreground hover:text-destructive size-8\"\n                        aria-label=${`Remove ${item.name} from cart`}\n                        @click=${() => this.removeItem(item.id)}\n                      >\n                        ${icon(Trash2, 'trash-2', 'size-4')}\n                      </uip-button>\n                    </div>\n                  </div>`,\n                )}\n              </div>\n\n              <div class=\"bg-card border-border/80 grid grid-cols-1 gap-4 rounded-xl border p-4 shadow-xs sm:grid-cols-3\">\n                <div class=\"flex items-center gap-3 text-xs\">\n                  <div class=\"bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-md\">\n                    ${icon(RotateCcw, 'rotate-ccw', 'size-4')}\n                  </div>\n                  <div>\n                    <p class=\"font-medium\">Free 30-Day Returns</p>\n                    <p class=\"text-muted-foreground\">Hassle-free return policy</p>\n                  </div>\n                </div>\n                <div class=\"flex items-center gap-3 text-xs\">\n                  <div class=\"bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-md\">\n                    ${icon(ShieldCheck, 'shield-check', 'size-4')}\n                  </div>\n                  <div>\n                    <p class=\"font-medium\">256-Bit SSL Security</p>\n                    <p class=\"text-muted-foreground\">Bank-grade data encryption</p>\n                  </div>\n                </div>\n                <div class=\"flex items-center gap-3 text-xs\">\n                  <div class=\"bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-md\">\n                    ${icon(Leaf, 'leaf', 'size-4')}\n                  </div>\n                  <div>\n                    <p class=\"font-medium\">Carbon Neutral</p>\n                    <p class=\"text-muted-foreground\">Offset delivery emissions</p>\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            <div class=\"space-y-4 lg:sticky lg:top-6 lg:col-span-4\">\n              <div class=\"bg-card border-border/80 space-y-4 rounded-2xl border p-5 shadow-xs\">\n                <h2 class=\"text-base font-semibold\">Order Summary</h2>\n                <div class=\"space-y-2\">\n                  <label class=\"text-muted-foreground text-xs font-medium\">Have a coupon code?</label>\n                  ${this.renderPromoRow('Enter code (SUMMER20)')}\n                </div>\n                <uip-separator></uip-separator>\n                ${this.renderCostRows(true)}\n                <uip-button class=\"w-full justify-center gap-2 font-medium shadow-xs\" size=\"lg\" @click=${() => this.handleCheckout()}>\n                  ${icon(Lock, 'lock', 'size-4')} Proceed to Checkout\n                </uip-button>\n                <uip-button\n                  variant=\"ghost\"\n                  class=\"text-muted-foreground hover:text-foreground w-full text-xs\"\n                  size=\"sm\"\n                  @click=${() => this.dispatchEvent(new CustomEvent('continue-shopping', { bubbles: true, composed: true }))}\n                >\n                  Continue Shopping\n                </uip-button>\n              </div>\n            </div>\n          </div>`}\n    </div>`\n  }\n}\n\ncustomElements.get('uip-cart-drawer') || customElements.define('uip-cart-drawer', UipCartDrawer)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-cart-drawer': UipCartDrawer\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/cart-drawer/cart-drawer.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/badge.json",
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/input.json",
    "https://uipkge.dev/r/lit/lit-core.json",
    "https://uipkge.dev/r/lit/progress.json",
    "https://uipkge.dev/r/lit/separator.json"
  ]
}
