{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "cart-drawer",
  "title": "Cart Drawer",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/cart-drawer/cart-drawer.component.ts",
      "content": "import { Component, EventEmitter, Input, Output, type OnInit, signal, ChangeDetectionStrategy } from '@angular/core'\nimport {\n  Leaf,\n  Lock,\n  LucideAngularModule,\n  Minus,\n  Plus,\n  RotateCcw,\n  ShieldCheck,\n  ShoppingBag,\n  Tag,\n  Trash2,\n  Truck,\n  X,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\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\nexport type CartDrawerVariant = 'drawer' | 'page'\n\nexport interface CartCheckoutEvent {\n  items: CartItem[]\n  total: number\n}\n\nexport interface CartQuantityEvent {\n  id: string\n  qty: number\n}\n\nexport const DEFAULT_CART_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\nconst copyItems = (items: CartItem[]): CartItem[] => items.map((item) => ({ ...item }))\n\n/**\n * CartDrawer -- the React block, 1:1: a slide-over cart (`variant=\"drawer\"`) or a full cart\n * page (`variant=\"page\"`) with a free-shipping meter, quantity steppers, promo codes\n * (SUMMER20 / SAVE20 = $20, SAVE10 = $10, anything else = $15), an order summary with shipping\n * and tax, trust badges and an empty state whose button restores the sample items.\n * Emits `checkout`, `continueShopping`, `close`, `updateQuantity`, `removeItem`,\n * `applyPromo` and `removePromo` where React calls the matching `on*` prop.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-cart-drawer, [ui-cart-drawer]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiInputComponent,\n    UiProgressComponent,\n    UiSeparatorComponent,\n  ],\n  template: `\n    <div data-slot=\"cart-drawer\" [class]=\"rootClass\">\n      @if (variant === 'drawer') {\n        <!-- DRAWER VARIANT -->\n        <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          <!-- Drawer Header -->\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              @if (totalItemsCount > 0) {\n                <span ui-badge variant=\"secondary\" class=\"tabular-nums\">{{ totalItemsCount }}</span>\n              }\n            </div>\n            <button ui-button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Close cart drawer\" (click)=\"close.emit()\">\n              <lucide-icon [img]=\"X\" class=\"size-4\" />\n            </button>\n          </div>\n\n          <!-- Free Shipping Meter -->\n          @if (cartItems().length > 0) {\n            <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                @if (isFreeShippingUnlocked) {\n                  <span class=\"text-primary flex items-center gap-1.5 font-medium\">\n                    <lucide-icon [img]=\"Truck\" class=\"size-3.5\" />\n                    You've unlocked Free Express Shipping!\n                  </span>\n                } @else {\n                  <span class=\"text-muted-foreground\">\n                    🎉 You're\n                    <strong class=\"text-foreground font-semibold tabular-nums\">{{\n                      formatCurrency(amountAwayFromFreeShipping)\n                    }}</strong>\n                    away from Free Shipping!\n                  </span>\n                }\n                <span class=\"text-muted-foreground font-medium tabular-nums\">{{ shippingProgress }}%</span>\n              </div>\n              <ui-progress [value]=\"shippingProgress\" class=\"mt-2 h-1.5\" />\n            </div>\n          }\n\n          <!-- Drawer Body: Line Items or Empty State -->\n          <div class=\"flex-1 overflow-y-auto p-5\">\n            @if (cartItems().length === 0) {\n              <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                  <lucide-icon [img]=\"ShoppingBag\" class=\"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't added any products to your cart yet. Explore our latest items!\n                </p>\n                <button ui-button class=\"mt-5 gap-2\" size=\"sm\" (click)=\"resetCart()\">\n                  <lucide-icon [img]=\"ShoppingBag\" class=\"size-3.5\" />\n                  Explore Products\n                </button>\n              </div>\n            } @else {\n              <div class=\"space-y-4\">\n                <ul class=\"divide-border/60 divide-y\" role=\"list\">\n                  @for (item of cartItems(); track item.id) {\n                    <li class=\"flex gap-3.5 py-4 first:pt-0 last:pb-0\">\n                      <div\n                        class=\"border-border/60 bg-muted/40 relative size-20 shrink-0 overflow-hidden rounded-lg border shadow-xs\"\n                      >\n                        <img [src]=\"item.image\" [alt]=\"item.name\" class=\"size-full object-cover\" />\n                      </div>\n\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                            <button\n                              ui-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                              [attr.aria-label]=\"'Remove ' + item.name + ' from cart'\"\n                              (click)=\"dropItem(item.id)\"\n                            >\n                              <lucide-icon [img]=\"Trash2\" class=\"size-3.5\" />\n                            </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\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                            <button\n                              ui-button\n                              variant=\"ghost\"\n                              size=\"icon-sm\"\n                              class=\"size-7 rounded-none rounded-l-md\"\n                              [attr.aria-label]=\"'Decrease quantity for ' + item.name\"\n                              (click)=\"stepQuantity(item.id, -1)\"\n                            >\n                              <lucide-icon [img]=\"Minus\" class=\"size-3\" />\n                            </button>\n                            <span class=\"w-7 text-center text-xs font-medium tabular-nums\">{{ item.qty }}</span>\n                            <button\n                              ui-button\n                              variant=\"ghost\"\n                              size=\"icon-sm\"\n                              class=\"size-7 rounded-none rounded-r-md\"\n                              [attr.aria-label]=\"'Increase quantity for ' + item.name\"\n                              (click)=\"stepQuantity(item.id, 1)\"\n                            >\n                              <lucide-icon [img]=\"Plus\" class=\"size-3\" />\n                            </button>\n                          </div>\n\n                          <div class=\"text-right\">\n                            @if (item.originalPrice) {\n                              <span class=\"text-muted-foreground mr-1.5 text-xs tabular-nums line-through\">{{\n                                formatCurrency(item.originalPrice * item.qty)\n                              }}</span>\n                            }\n                            <span class=\"text-foreground text-sm font-semibold tabular-nums\">{{\n                              formatCurrency(item.price * item.qty)\n                            }}</span>\n                          </div>\n                        </div>\n                      </div>\n                    </li>\n                  }\n                </ul>\n\n                <ui-separator class=\"my-3\" />\n\n                <!-- Promo Code Section -->\n                <div class=\"space-y-2\">\n                  @if (appliedPromo()) {\n                    <div\n                      class=\"bg-primary/5 border-primary/20 flex items-center justify-between rounded-lg border px-3 py-2 text-xs\"\n                    >\n                      <div class=\"flex items-center gap-2\">\n                        <lucide-icon [img]=\"Tag\" class=\"text-primary size-3.5\" />\n                        <span class=\"text-foreground font-medium\">{{ appliedPromo() }}</span>\n                        <span class=\"text-success text-success font-semibold tabular-nums\"\n                          >-{{ formatCurrency(promoDiscount()) }}</span\n                        >\n                      </div>\n                      <button\n                        ui-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)=\"handleRemovePromo()\"\n                      >\n                        <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n                      </button>\n                    </div>\n                  } @else {\n                    <div class=\"flex gap-2\">\n                      <ui-input\n                        [value]=\"promoCodeInput()\"\n                        placeholder=\"Promo code (e.g. SUMMER20)\"\n                        size=\"small\"\n                        class=\"text-xs\"\n                        (valueChange)=\"promoCodeInput.set($event)\"\n                        (keydown.enter)=\"onPromoEnter($event)\"\n                      />\n                      <button\n                        ui-button\n                        variant=\"outline\"\n                        size=\"sm\"\n                        class=\"shrink-0 text-xs\"\n                        [disabled]=\"!promoCodeInput().trim()\"\n                        (click)=\"handleApplyPromo()\"\n                      >\n                        Apply\n                      </button>\n                    </div>\n                  }\n                  @if (promoSuccess()) {\n                    <p class=\"text-success text-xs font-medium\">{{ promoSuccess() }}</p>\n                  }\n                  @if (promoError()) {\n                    <p class=\"text-destructive text-xs\">{{ promoError() }}</p>\n                  }\n                </div>\n              </div>\n            }\n          </div>\n\n          <!-- Drawer Footer: Summary & Actions -->\n          @if (cartItems().length > 0) {\n            <div class=\"border-border/60 bg-card shrink-0 space-y-3 border-t p-5\">\n              <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\">{{ formatCurrency(subtotal) }}</span>\n                </div>\n\n                @if (promoDiscount() > 0) {\n                  <div class=\"text-success flex justify-between\">\n                    <span>Coupon ({{ appliedPromo() }})</span>\n                    <span class=\"font-medium tabular-nums\">-{{ formatCurrency(promoDiscount()) }}</span>\n                  </div>\n                }\n\n                <div class=\"flex justify-between\">\n                  <span class=\"text-muted-foreground\">Estimated Shipping</span>\n                  @if (shippingFee === 0) {\n                    <span class=\"text-success font-medium\">Free</span>\n                  } @else {\n                    <span class=\"text-foreground font-medium tabular-nums\">{{ formatCurrency(shippingFee) }}</span>\n                  }\n                </div>\n\n                <div class=\"flex justify-between\">\n                  <span class=\"text-muted-foreground\">Estimated Tax</span>\n                  <span class=\"text-foreground font-medium tabular-nums\">{{ formatCurrency(estimatedTax) }}</span>\n                </div>\n\n                <ui-separator class=\"my-2\" />\n\n                <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\">{{ formatCurrency(total) }}</span>\n                </div>\n              </div>\n\n              <div class=\"space-y-2 pt-1\">\n                <button\n                  ui-button\n                  class=\"w-full justify-center gap-2 font-medium shadow-xs\"\n                  size=\"default\"\n                  (click)=\"handleCheckout()\"\n                >\n                  <lucide-icon [img]=\"Lock\" class=\"size-3.5\" />\n                  Proceed to Checkout\n                </button>\n\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  class=\"text-muted-foreground hover:text-foreground w-full text-xs\"\n                  size=\"sm\"\n                  (click)=\"continueShopping.emit()\"\n                >\n                  Continue Shopping\n                </button>\n              </div>\n\n              <!-- Guarantee badges -->\n              <div\n                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              >\n                <div class=\"flex flex-col items-center gap-1\">\n                  <lucide-icon [img]=\"RotateCcw\" class=\"size-3.5\" />\n                  <span>30-day returns</span>\n                </div>\n                <div class=\"flex flex-col items-center gap-1\">\n                  <lucide-icon [img]=\"ShieldCheck\" class=\"size-3.5\" />\n                  <span>256-bit secure</span>\n                </div>\n                <div class=\"flex flex-col items-center gap-1\">\n                  <lucide-icon [img]=\"Leaf\" class=\"size-3.5\" />\n                  <span>Carbon neutral</span>\n                </div>\n              </div>\n            </div>\n          }\n        </div>\n      } @else {\n        <!-- FULL PAGE VARIANT -->\n        <div class=\"mx-auto w-full max-w-6xl space-y-6\">\n          <!-- Page Header -->\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\">\n                Review your selected items and calculate shipping before checkout.\n              </p>\n            </div>\n            @if (totalItemsCount > 0) {\n              <span ui-badge variant=\"outline\" class=\"w-fit text-xs tabular-nums\"\n                >{{ totalItemsCount }} items in cart</span\n              >\n            }\n          </div>\n\n          <!-- Free Shipping Banner -->\n          @if (cartItems().length > 0) {\n            <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                  @if (isFreeShippingUnlocked) {\n                    <lucide-icon [img]=\"Truck\" class=\"text-primary size-4\" />\n                    <span class=\"text-primary font-semibold\">\n                      You've unlocked Free Express Shipping on this order!\n                    </span>\n                  } @else {\n                    <span class=\"text-muted-foreground\">\n                      🎉 You're\n                      <strong class=\"text-foreground font-semibold tabular-nums\">{{\n                        formatCurrency(amountAwayFromFreeShipping)\n                      }}</strong>\n                      away from Free Shipping!\n                    </span>\n                  }\n                </div>\n                <span class=\"text-muted-foreground text-xs font-medium tabular-nums\"\n                  >{{ shippingProgress }}% completed</span\n                >\n              </div>\n              <ui-progress [value]=\"shippingProgress\" class=\"mt-2.5 h-2\" />\n            </div>\n          }\n\n          <!-- Empty State for Page -->\n          @if (cartItems().length === 0) {\n            <div\n              class=\"bg-card border-border/80 flex flex-col items-center justify-center rounded-2xl border py-16 text-center shadow-xs\"\n            >\n              <div class=\"bg-muted text-muted-foreground mb-4 flex size-16 items-center justify-center rounded-full\">\n                <lucide-icon [img]=\"ShoppingBag\" class=\"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              <button ui-button class=\"mt-6 gap-2\" size=\"default\" (click)=\"resetCart()\">\n                <lucide-icon [img]=\"ShoppingBag\" class=\"size-4\" />\n                Explore Products\n              </button>\n            </div>\n          } @else {\n            <!-- Page Grid: Items (Left) & Order Summary (Right) -->\n            <div class=\"grid grid-cols-1 items-start gap-8 lg:grid-cols-12\">\n              <!-- Left: Line Items list -->\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                  @for (item of cartItems(); track item.id) {\n                    <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\n                          class=\"border-border/60 bg-muted/40 relative size-20 shrink-0 overflow-hidden rounded-lg border shadow-xs\"\n                        >\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                              >· {{ formatCurrency(item.price) }} each</span\n                            >\n                          </div>\n                        </div>\n                      </div>\n\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                          <button\n                            ui-button\n                            variant=\"ghost\"\n                            size=\"icon-sm\"\n                            class=\"size-8 rounded-none rounded-l-md\"\n                            [attr.aria-label]=\"'Decrease quantity for ' + item.name\"\n                            (click)=\"stepQuantity(item.id, -1)\"\n                          >\n                            <lucide-icon [img]=\"Minus\" class=\"size-3.5\" />\n                          </button>\n                          <span class=\"w-8 text-center text-xs font-medium tabular-nums\">{{ item.qty }}</span>\n                          <button\n                            ui-button\n                            variant=\"ghost\"\n                            size=\"icon-sm\"\n                            class=\"size-8 rounded-none rounded-r-md\"\n                            [attr.aria-label]=\"'Increase quantity for ' + item.name\"\n                            (click)=\"stepQuantity(item.id, 1)\"\n                          >\n                            <lucide-icon [img]=\"Plus\" class=\"size-3.5\" />\n                          </button>\n                        </div>\n\n                        <div class=\"w-24 text-right\">\n                          @if (item.originalPrice) {\n                            <span class=\"text-muted-foreground block text-xs tabular-nums line-through\">{{\n                              formatCurrency(item.originalPrice * item.qty)\n                            }}</span>\n                          }\n                          <span class=\"text-foreground text-base font-semibold tabular-nums\">{{\n                            formatCurrency(item.price * item.qty)\n                          }}</span>\n                        </div>\n\n                        <button\n                          ui-button\n                          variant=\"ghost\"\n                          size=\"icon-sm\"\n                          class=\"text-muted-foreground hover:text-destructive size-8\"\n                          [attr.aria-label]=\"'Remove ' + item.name + ' from cart'\"\n                          (click)=\"dropItem(item.id)\"\n                        >\n                          <lucide-icon [img]=\"Trash2\" class=\"size-4\" />\n                        </button>\n                      </div>\n                    </div>\n                  }\n                </div>\n\n                <!-- Bottom bar: Guarantees -->\n                <div\n                  class=\"bg-card border-border/80 grid grid-cols-1 gap-4 rounded-xl border p-4 shadow-xs sm:grid-cols-3\"\n                >\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                      <lucide-icon [img]=\"RotateCcw\" class=\"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                      <lucide-icon [img]=\"ShieldCheck\" class=\"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                      <lucide-icon [img]=\"Leaf\" class=\"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              <!-- Right: Sticky Order Summary Card -->\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\n                  <!-- Promo Input -->\n                  <div class=\"space-y-2\">\n                    <label class=\"text-muted-foreground text-xs font-medium\">Have a coupon code?</label>\n                    @if (appliedPromo()) {\n                      <div\n                        class=\"bg-primary/5 border-primary/20 flex items-center justify-between rounded-lg border px-3 py-2 text-xs\"\n                      >\n                        <div class=\"flex items-center gap-2\">\n                          <lucide-icon [img]=\"Tag\" class=\"text-primary size-3.5\" />\n                          <span class=\"text-foreground font-medium\">{{ appliedPromo() }}</span>\n                          <span class=\"text-success text-success font-semibold tabular-nums\"\n                            >-{{ formatCurrency(promoDiscount()) }}</span\n                          >\n                        </div>\n                        <button\n                          ui-button\n                          variant=\"ghost\"\n                          size=\"icon-sm\"\n                          class=\"text-muted-foreground hover:text-foreground size-6\"\n                          aria-label=\"Remove coupon\"\n                          (click)=\"handleRemovePromo()\"\n                        >\n                          <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n                        </button>\n                      </div>\n                    } @else {\n                      <div class=\"flex gap-2\">\n                        <ui-input\n                          [value]=\"promoCodeInput()\"\n                          placeholder=\"Enter code (SUMMER20)\"\n                          size=\"small\"\n                          class=\"text-xs\"\n                          (valueChange)=\"promoCodeInput.set($event)\"\n                          (keydown.enter)=\"onPromoEnter($event)\"\n                        />\n                        <button\n                          ui-button\n                          variant=\"outline\"\n                          size=\"sm\"\n                          class=\"shrink-0 text-xs\"\n                          [disabled]=\"!promoCodeInput().trim()\"\n                          (click)=\"handleApplyPromo()\"\n                        >\n                          Apply\n                        </button>\n                      </div>\n                    }\n                  </div>\n\n                  <ui-separator />\n\n                  <!-- Cost Breakdown -->\n                  <div class=\"space-y-2 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\">{{ formatCurrency(subtotal) }}</span>\n                    </div>\n\n                    @if (promoDiscount() > 0) {\n                      <div class=\"text-success flex justify-between\">\n                        <span>Coupon ({{ appliedPromo() }})</span>\n                        <span class=\"font-medium tabular-nums\">-{{ formatCurrency(promoDiscount()) }}</span>\n                      </div>\n                    }\n\n                    <div class=\"flex justify-between\">\n                      <span class=\"text-muted-foreground\">Estimated Shipping</span>\n                      @if (shippingFee === 0) {\n                        <span class=\"text-success font-medium\">Free</span>\n                      } @else {\n                        <span class=\"text-foreground font-medium tabular-nums\">{{ formatCurrency(shippingFee) }}</span>\n                      }\n                    </div>\n\n                    <div class=\"flex justify-between\">\n                      <span class=\"text-muted-foreground\">Estimated Tax (8%)</span>\n                      <span class=\"text-foreground font-medium tabular-nums\">{{ formatCurrency(estimatedTax) }}</span>\n                    </div>\n\n                    <ui-separator class=\"my-2\" />\n\n                    <div\n                      class=\"flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 text-base font-semibold\"\n                    >\n                      <span>Total</span>\n                      <span class=\"tabular-nums\">{{ formatCurrency(total) }}</span>\n                    </div>\n                  </div>\n\n                  <button\n                    ui-button\n                    class=\"w-full justify-center gap-2 font-medium shadow-xs\"\n                    size=\"lg\"\n                    (click)=\"handleCheckout()\"\n                  >\n                    <lucide-icon [img]=\"Lock\" class=\"size-4\" />\n                    Proceed to Checkout\n                  </button>\n\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    class=\"text-muted-foreground hover:text-foreground w-full text-xs\"\n                    size=\"sm\"\n                    (click)=\"continueShopping.emit()\"\n                  >\n                    Continue Shopping\n                  </button>\n                </div>\n              </div>\n            </div>\n          }\n        </div>\n      }\n    </div>\n  `,\n})\nexport class UiCartDrawerComponent implements OnInit {\n  @Input() variant: CartDrawerVariant = 'drawer'\n  /** Line items. A new array resets the cart to it (React re-seeds on an `items` change). */\n  @Input() set items(value: CartItem[] | null | undefined) {\n    this.cartItems.set(copyItems(value ?? DEFAULT_CART_ITEMS))\n  }\n  @Input() currency = 'USD'\n  @Input() freeShippingThreshold = 300\n  @Input() taxRate = 0.08\n  /** Seeds the applied coupon once (a $20 discount), like React's initial state. */\n  @Input() initialPromoCode = ''\n  @Input('class') className?: string\n\n  @Output() readonly checkout = new EventEmitter<CartCheckoutEvent>()\n  @Output() readonly continueShopping = new EventEmitter<void>()\n  @Output() readonly close = new EventEmitter<void>()\n  @Output() readonly updateQuantity = new EventEmitter<CartQuantityEvent>()\n  @Output() readonly removeItem = new EventEmitter<string>()\n  @Output() readonly applyPromo = new EventEmitter<string>()\n  @Output() readonly removePromo = new EventEmitter<void>()\n\n  readonly cartItems = signal<CartItem[]>(copyItems(DEFAULT_CART_ITEMS))\n  readonly promoCodeInput = signal('')\n  readonly appliedPromo = signal('')\n  readonly promoDiscount = signal(0)\n  readonly promoError = signal('')\n  readonly promoSuccess = signal('')\n\n  protected readonly Leaf = Leaf\n  protected readonly Lock = Lock\n  protected readonly Minus = Minus\n  protected readonly Plus = Plus\n  protected readonly RotateCcw = RotateCcw\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly ShoppingBag = ShoppingBag\n  protected readonly Tag = Tag\n  protected readonly Trash2 = Trash2\n  protected readonly Truck = Truck\n  protected readonly X = X\n\n  ngOnInit(): void {\n    // React seeds its useState once from `initialPromoCode`; inputs land after construction.\n    this.appliedPromo.set(this.initialPromoCode)\n    this.promoDiscount.set(this.initialPromoCode ? 20 : 0)\n  }\n\n  protected get rootClass(): string {\n    return cn('w-full', this.className)\n  }\n\n  get totalItemsCount(): number {\n    return this.cartItems().reduce((acc, item) => acc + item.qty, 0)\n  }\n\n  get subtotal(): number {\n    return this.cartItems().reduce((acc, item) => acc + item.price * item.qty, 0)\n  }\n\n  get isFreeShippingUnlocked(): boolean {\n    return this.subtotal >= this.freeShippingThreshold && this.cartItems().length > 0\n  }\n\n  get amountAwayFromFreeShipping(): number {\n    return Math.max(0, this.freeShippingThreshold - this.subtotal)\n  }\n\n  get shippingProgress(): number {\n    if (this.cartItems().length === 0) return 0\n    return Math.min(100, Math.round((this.subtotal / this.freeShippingThreshold) * 100))\n  }\n\n  get shippingFee(): number {\n    if (this.cartItems().length === 0) return 0\n    return this.isFreeShippingUnlocked ? 0 : 15\n  }\n\n  get discountedSubtotal(): number {\n    return Math.max(0, this.subtotal - this.promoDiscount())\n  }\n\n  get estimatedTax(): number {\n    if (this.cartItems().length === 0) return 0\n    return Number((this.discountedSubtotal * this.taxRate).toFixed(2))\n  }\n\n  get total(): number {\n    if (this.cartItems().length === 0) return 0\n    return Number((this.discountedSubtotal + this.shippingFee + this.estimatedTax).toFixed(2))\n  }\n\n  formatCurrency(amount: number): string {\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  stepQuantity(id: string, delta: number): void {\n    const item = this.cartItems().find((i) => i.id === id)\n    if (!item) return\n    const newQty = item.qty + delta\n    if (newQty <= 0) {\n      this.cartItems.update((prev) => prev.filter((i) => i.id !== id))\n      this.removeItem.emit(id)\n      return\n    }\n    this.cartItems.update((prev) => prev.map((i) => (i.id === id ? { ...i, qty: newQty } : i)))\n    this.updateQuantity.emit({ id, qty: newQty })\n  }\n\n  dropItem(id: string): void {\n    this.cartItems.update((prev) => prev.filter((item) => item.id !== id))\n    this.removeItem.emit(id)\n  }\n\n  onPromoEnter(event: Event): void {\n    event.preventDefault()\n    this.handleApplyPromo()\n  }\n\n  handleApplyPromo(): void {\n    this.promoError.set('')\n    this.promoSuccess.set('')\n    const trimmed = this.promoCodeInput().trim().toUpperCase()\n    if (!trimmed) return\n\n    if (trimmed === 'SUMMER20' || trimmed === 'SAVE20') {\n      this.appliedPromo.set(trimmed)\n      this.promoDiscount.set(20)\n      this.promoSuccess.set('$20.00 coupon discount applied!')\n    } else if (trimmed === 'SAVE10') {\n      this.appliedPromo.set(trimmed)\n      this.promoDiscount.set(10)\n      this.promoSuccess.set('$10.00 coupon discount applied!')\n    } else {\n      this.appliedPromo.set(trimmed)\n      this.promoDiscount.set(15)\n      this.promoSuccess.set('Promo code applied!')\n    }\n    this.promoCodeInput.set('')\n    this.applyPromo.emit(trimmed)\n  }\n\n  handleRemovePromo(): void {\n    this.appliedPromo.set('')\n    this.promoDiscount.set(0)\n    this.promoSuccess.set('')\n    this.promoError.set('')\n    this.removePromo.emit()\n  }\n\n  handleCheckout(): void {\n    this.checkout.emit({ items: this.cartItems(), total: this.total })\n  }\n\n  resetCart(): void {\n    this.cartItems.set(copyItems(DEFAULT_CART_ITEMS))\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/cart-drawer/cart-drawer.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/cart-drawer/index.ts",
      "content": "export {\n  UiCartDrawerComponent,\n  DEFAULT_CART_ITEMS,\n  type CartItem,\n  type CartDrawerVariant,\n  type CartCheckoutEvent,\n  type CartQuantityEvent,\n} from './cart-drawer.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/cart-drawer/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "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"
  ]
}