{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "returns-portal",
  "title": "Returns Portal",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/returns-portal/returns-portal.component.ts",
      "content": "import {\n  Component,\n  Input,\n  OnChanges,\n  OnInit,\n  SimpleChanges,\n  computed,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport {\n  Check,\n  CheckCircle2,\n  ChevronLeft,\n  ChevronRight,\n  CreditCard,\n  Download,\n  Gift,\n  LucideAngularModule,\n  Printer,\n  RefreshCw,\n  Shirt,\n  ShieldCheck,\n  Truck,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiRadioGroupComponent, UiRadioGroupItemComponent } from '@/ui/radio-group/radio-group.component'\nimport {\n  UiSelectComponent,\n  UiSelectContentComponent,\n  UiSelectItemComponent,\n  UiSelectTriggerComponent,\n  UiSelectValueComponent,\n} from '@/ui/select/select.component'\nimport { UiStepperComponent, type StepperStep } from '@/ui/stepper/stepper.component'\n\nexport interface OrderItem {\n  id: string\n  name: string\n  variant: string\n  sku: string\n  price: number\n  maxQty: number\n}\n\nexport type ReturnsPortalResolution = 'exchange' | 'store-credit' | 'original-payment'\n\nconst steps: StepperStep[] = [\n  { id: 1, title: 'Find Order' },\n  { id: 2, title: 'Select Items' },\n  { id: 3, title: 'Choose Resolution' },\n  { id: 4, title: 'Confirm & Print' },\n]\n\nconst orderItems: OrderItem[] = [\n  {\n    id: 'item-1',\n    name: 'Merino Wool Crewneck Sweater',\n    variant: 'Midnight Navy / Size L',\n    sku: 'MWC-NAV-L',\n    price: 120,\n    maxQty: 1,\n  },\n  {\n    id: 'item-2',\n    name: 'Classic Canvas High-Tops',\n    variant: 'Off-White / US 10.5',\n    sku: 'CCHT-OW-105',\n    price: 85,\n    maxQty: 1,\n  },\n  {\n    id: 'item-3',\n    name: 'Tailored Chino Trousers',\n    variant: 'Olive / 32x32',\n    sku: 'TCT-OLV-32',\n    price: 95,\n    maxQty: 2,\n  },\n]\n\nconst returnReasonsList = ['Wrong size', 'Item defective', 'Not as described', 'Changed mind', 'Arrived late']\n\nconst exchangeSizesList = ['Small (S)', 'Medium (M)', 'Large (L)', 'Extra Large (XL)']\n\nconst defaultSelectedItems = (): Record<string, boolean> => ({\n  'item-1': true,\n  'item-2': false,\n  'item-3': false,\n})\n\nconst defaultReturnQuantities = (): Record<string, number> => ({\n  'item-1': 1,\n  'item-2': 1,\n  'item-3': 1,\n})\n\nconst defaultReturnReasons = (): Record<string, string> => ({\n  'item-1': 'Wrong size',\n  'item-2': 'Changed mind',\n  'item-3': 'Item defective',\n})\n\nconst currency = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })\n\nconst OPTION_BASE = 'flex cursor-pointer flex-col gap-3 rounded-lg border p-4 transition-colors'\nconst OPTION_ACTIVE = 'border-primary bg-primary/[0.02] dark:bg-primary/10 shadow-xs'\nconst OPTION_IDLE = 'border-border hover:bg-muted/40'\n\n/**\n * Angular port of the React ReturnsPortal block: a Card wizard with a four-step Stepper\n * (Find Order, Select Items, Choose Resolution, Confirm & Print), an order lookup form, item\n * selection with per-item quantity and reason Selects, a resolution RadioGroup (exchange,\n * store credit with a +10% bonus, original payment) with a live summary, and a confirmation\n * screen with label download, drop-off steps and a QR pass. Backward navigation through the\n * stepper is free until the return is confirmed; forward goes through the footer button only.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-returns-portal, [ui-returns-portal]',\n  standalone: true,\n  // React renders the Card itself: the host stays out of layout and `class` goes to the card.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiCheckboxComponent,\n    UiInputComponent,\n    UiRadioGroupComponent,\n    UiRadioGroupItemComponent,\n    UiSelectComponent,\n    UiSelectContentComponent,\n    UiSelectItemComponent,\n    UiSelectTriggerComponent,\n    UiSelectValueComponent,\n    UiStepperComponent,\n  ],\n  template: `\n    <div ui-card data-slot=\"returns-portal\" [class]=\"cardClass\">\n      <div ui-card-header class=\"pb-4\">\n        <div class=\"flex items-center justify-between gap-4\">\n          <div>\n            <h3 ui-card-title class=\"text-base font-semibold\">Returns &amp; Exchange Portal</h3>\n            <p ui-card-description class=\"text-xs\">Self-serve return, exchange, or refund for your recent order.</p>\n          </div>\n          <span ui-badge variant=\"outline\" class=\"shrink-0 text-xs\">\n            <lucide-icon [img]=\"Truck\" [class]=\"'text-muted-foreground mr-1 size-3'\" />\n            Prepaid Shipping\n          </span>\n        </div>\n      </div>\n\n      <div ui-card-content class=\"space-y-6\">\n        <ui-stepper [steps]=\"steps\" [value]=\"step()\" (valueChange)=\"onStepperInput($event)\" class=\"mb-6\" />\n\n        <!-- Step 1: Find Order -->\n        @if (step() === 1) {\n          <div class=\"space-y-5\">\n            <div class=\"space-y-1\">\n              <h3 class=\"text-foreground text-sm font-semibold\">Find your order</h3>\n              <p class=\"text-muted-foreground text-xs\">\n                Enter your order number and email address to start a return or exchange.\n              </p>\n            </div>\n\n            <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n              <div class=\"space-y-1.5\">\n                <label for=\"returns-order-number\" class=\"text-foreground text-xs font-medium\">Order number</label>\n                <ui-input\n                  id=\"returns-order-number\"\n                  [value]=\"orderNumber()\"\n                  (valueChange)=\"orderNumber.set($event)\"\n                  placeholder=\"#ORD-84920\"\n                  autoComplete=\"off\"\n                />\n                <p class=\"text-muted-foreground text-xs\">Found on your order confirmation email.</p>\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <label for=\"returns-email\" class=\"text-foreground text-xs font-medium\">Email address</label>\n                <ui-input\n                  id=\"returns-email\"\n                  [value]=\"email()\"\n                  (valueChange)=\"email.set($event)\"\n                  type=\"email\"\n                  placeholder=\"sarah.connor@example.com\"\n                  autoComplete=\"email\"\n                />\n                <p class=\"text-muted-foreground text-xs\">The email address used at checkout.</p>\n              </div>\n            </div>\n\n            <div class=\"border-border bg-muted/40 flex items-start gap-3 rounded-lg border p-3.5\">\n              <lucide-icon [img]=\"ShieldCheck\" [class]=\"'text-primary mt-0.5 size-4 shrink-0'\" />\n              <div class=\"space-y-0.5 text-xs\">\n                <p class=\"text-foreground font-medium\">30-Day Hassle-Free Return Policy</p>\n                <p class=\"text-muted-foreground\">\n                  Eligible items can be returned within 30 days of delivery. Free shipping on all exchanges and store\n                  credit requests.\n                </p>\n              </div>\n            </div>\n          </div>\n        }\n\n        <!-- Step 2: Select Items -->\n        @if (step() === 2) {\n          <div class=\"space-y-5\">\n            <div class=\"flex flex-wrap items-center justify-between gap-2\">\n              <div class=\"min-w-[12rem]\">\n                <h3 class=\"text-foreground text-sm font-semibold\">Select items to return</h3>\n                <p class=\"text-muted-foreground text-xs\">\n                  Order {{ orderNumber() || '#ORD-84920' }} • Placed Oct 14, 2026 • 3 items eligible\n                </p>\n              </div>\n              <span ui-badge variant=\"secondary\" class=\"text-xs\">{{ selectedItemsCount() }} of 3 selected</span>\n            </div>\n\n            <div class=\"space-y-3\">\n              @for (item of orderItems; track item.id) {\n                <div [class]=\"itemClass(item.id)\">\n                  <div class=\"flex items-start gap-3.5\">\n                    <div class=\"pt-0.5\">\n                      <ui-checkbox\n                        [id]=\"'check-' + item.id\"\n                        [checked]=\"isSelected(item.id)\"\n                        (checkedChange)=\"toggleItemSelection(item.id)\"\n                      />\n                    </div>\n\n                    <div\n                      class=\"border-border bg-muted/60 text-muted-foreground flex size-12 shrink-0 items-center justify-center rounded-md border\"\n                    >\n                      <lucide-icon [img]=\"Shirt\" class=\"size-6\" />\n                    </div>\n\n                    <div class=\"min-w-0 flex-1\">\n                      <div class=\"flex items-start justify-between gap-2\">\n                        <div>\n                          <label\n                            [attr.for]=\"'check-' + item.id\"\n                            class=\"text-foreground cursor-pointer text-sm font-medium select-none\"\n                            >{{ item.name }}</label\n                          >\n                          <p class=\"text-muted-foreground text-xs\">{{ item.variant }} • SKU: {{ item.sku }}</p>\n                        </div>\n                        <span class=\"text-foreground shrink-0 text-sm font-semibold\">{{\n                          formatCurrency(item.price)\n                        }}</span>\n                      </div>\n                    </div>\n                  </div>\n\n                  @if (isSelected(item.id)) {\n                    <div class=\"border-border/60 mt-3.5 grid grid-cols-1 gap-3 border-t pt-3.5 sm:grid-cols-2\">\n                      <div class=\"space-y-1.5\">\n                        <label class=\"text-foreground text-xs font-medium\">Qty to return</label>\n                        <ui-select [value]=\"quantityValue(item.id)\" (valueChange)=\"setQuantity(item.id, $event)\">\n                          <button ui-select-trigger class=\"h-8 w-full text-xs\">\n                            <ui-select-value placeholder=\"Qty\" />\n                          </button>\n                          <ui-select-content>\n                            @for (q of quantityOptions(item.maxQty); track q) {\n                              <ui-select-item [value]=\"'' + q\"\n                                >{{ q }} {{ q === 1 ? 'item' : 'items' }} (of {{ item.maxQty }})</ui-select-item\n                              >\n                            }\n                          </ui-select-content>\n                        </ui-select>\n                      </div>\n\n                      <div class=\"space-y-1.5\">\n                        <label class=\"text-foreground text-xs font-medium\">Return reason</label>\n                        <ui-select [value]=\"reasonFor(item.id)\" (valueChange)=\"setReason(item.id, $event)\">\n                          <button ui-select-trigger class=\"h-8 w-full text-xs\">\n                            <ui-select-value placeholder=\"Select reason\" />\n                          </button>\n                          <ui-select-content>\n                            @for (reason of returnReasonsList; track reason) {\n                              <ui-select-item [value]=\"reason\">{{ reason }}</ui-select-item>\n                            }\n                          </ui-select-content>\n                        </ui-select>\n                      </div>\n                    </div>\n                  }\n                </div>\n              }\n            </div>\n\n            <div class=\"border-border bg-muted/40 flex items-center justify-between rounded-lg border p-3 text-xs\">\n              <span class=\"text-muted-foreground\">\n                @if (selectedItemsCount() > 0) {\n                  <span class=\"text-foreground font-medium\">{{ selectedItemsCount() }}</span> item{{\n                    selectedItemsCount() === 1 ? '' : 's'\n                  }}\n                  selected\n                } @else {\n                  No items selected yet\n                }\n              </span>\n              <span class=\"text-foreground font-medium\">Estimated Value: {{ formatCurrency(itemsSubtotal()) }}</span>\n            </div>\n          </div>\n        }\n\n        <!-- Step 3: Choose Resolution -->\n        @if (step() === 3) {\n          <div class=\"space-y-5\">\n            <div class=\"space-y-1\">\n              <h3 class=\"text-foreground text-sm font-semibold\">Choose return resolution</h3>\n              <p class=\"text-muted-foreground text-xs\">Select how you would like your return or refund handled.</p>\n            </div>\n\n            <ui-radio-group [value]=\"resolution()\" (valueChange)=\"setResolution($event)\" class=\"gap-3\">\n              <!-- Option A: Exchange -->\n              <div [class]=\"optionClass('exchange')\" (click)=\"resolution.set('exchange')\">\n                <div class=\"flex items-start gap-3\">\n                  <ui-radio-group-item id=\"res-exchange\" value=\"exchange\" class=\"mt-0.5\" />\n                  <div class=\"flex-1 space-y-1\">\n                    <div class=\"flex items-center gap-2\">\n                      <!-- The React svg is a shrinkable flex item of this row (no shrink-0): a display:contents\n                           host makes the svg the flex item here too, so narrow widths wrap the label the same way. -->\n                      <lucide-icon [img]=\"RefreshCw\" [class]=\"'text-primary size-4'\" />\n                      <label for=\"res-exchange\" class=\"text-foreground cursor-pointer text-sm font-medium\">\n                        Exchange for different size / color\n                      </label>\n                      <span ui-badge variant=\"outline\" class=\"text-xs\">Free shipping</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">\n                      Reserve replacement item immediately. Ships as soon as carrier scans the return package.\n                    </p>\n                  </div>\n                </div>\n\n                @if (resolution() === 'exchange') {\n                  <div class=\"border-border/60 mt-1 space-y-1.5 border-t pt-3 pl-7\" (click)=\"$event.stopPropagation()\">\n                    <label class=\"text-foreground text-xs font-medium\">Select replacement size</label>\n                    <ui-select [value]=\"exchangeSize()\" (valueChange)=\"exchangeSize.set($event)\">\n                      <button ui-select-trigger class=\"h-8 w-full max-w-xs text-xs\">\n                        <ui-select-value placeholder=\"Select replacement size\" />\n                      </button>\n                      <ui-select-content>\n                        @for (size of exchangeSizesList; track size) {\n                          <ui-select-item [value]=\"size\">{{ size }}</ui-select-item>\n                        }\n                      </ui-select-content>\n                    </ui-select>\n                  </div>\n                }\n              </div>\n\n              <!-- Option B: Store Credit (+10% Bonus) -->\n              <div [class]=\"optionClass('store-credit')\" (click)=\"resolution.set('store-credit')\">\n                <div class=\"flex items-start gap-3\">\n                  <ui-radio-group-item id=\"res-store-credit\" value=\"store-credit\" class=\"mt-0.5\" />\n                  <div class=\"flex-1 space-y-1\">\n                    <div class=\"flex flex-wrap items-center gap-2\">\n                      <lucide-icon [img]=\"Gift\" class=\"text-success size-4\" />\n                      <label for=\"res-store-credit\" class=\"text-foreground cursor-pointer text-sm font-medium\">\n                        Store Credit (+10% Bonus value)\n                      </label>\n                      <span ui-badge variant=\"success\" class=\"text-xs\">+10% Bonus</span>\n                      <span ui-badge variant=\"secondary\" class=\"text-xs\">Fastest refund</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">\n                      Get <span class=\"text-foreground font-medium\">{{ formatCurrency(totalStoreCredit()) }}</span> in\n                      store credit ({{ formatCurrency(itemsSubtotal()) }} + {{ formatCurrency(storeCreditBonus()) }}\n                      bonus). Digital card delivered by email instantly upon drop-off scan.\n                    </p>\n                  </div>\n                </div>\n              </div>\n\n              <!-- Option C: Original Payment Method -->\n              <div [class]=\"optionClass('original-payment')\" (click)=\"resolution.set('original-payment')\">\n                <div class=\"flex items-start gap-3\">\n                  <ui-radio-group-item id=\"res-original\" value=\"original-payment\" class=\"mt-0.5\" />\n                  <div class=\"flex-1 space-y-1\">\n                    <div class=\"flex flex-wrap items-center gap-2\">\n                      <lucide-icon [img]=\"CreditCard\" class=\"text-muted-foreground size-4\" />\n                      <label for=\"res-original\" class=\"text-foreground cursor-pointer text-sm font-medium\">\n                        Original Payment Method\n                      </label>\n                      <span ui-badge variant=\"outline\" class=\"text-xs\">Visa •••• 4242</span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">\n                      Refund of {{ formatCurrency(itemsSubtotal()) }} back to your card. Processed in 3–5 business days\n                      after carrier drop-off.\n                    </p>\n                  </div>\n                </div>\n              </div>\n            </ui-radio-group>\n\n            <!-- Summary calculation box -->\n            <div class=\"border-border bg-muted/40 space-y-2 rounded-lg border p-4 text-xs\">\n              <div class=\"text-muted-foreground flex items-center justify-between\">\n                <span>Selected items subtotal</span>\n                <span class=\"text-foreground font-medium\">{{ formatCurrency(itemsSubtotal()) }}</span>\n              </div>\n              @if (resolution() === 'store-credit') {\n                <div class=\"text-success flex items-center justify-between\">\n                  <span>Bonus store credit (+10%)</span>\n                  <span class=\"font-medium\">+{{ formatCurrency(storeCreditBonus()) }}</span>\n                </div>\n              }\n              <div class=\"text-muted-foreground flex items-center justify-between\">\n                <span>Prepaid return shipping</span>\n                <span class=\"text-success font-medium\">FREE</span>\n              </div>\n              <div\n                class=\"border-border text-foreground flex items-center justify-between border-t pt-2 text-sm font-semibold\"\n              >\n                <span>{{ totalLabel() }}</span>\n                <span [class]=\"resolution() === 'store-credit' ? 'text-success' : ''\">{{ totalValue() }}</span>\n              </div>\n            </div>\n          </div>\n        }\n\n        <!-- Step 4: Confirmation & Label -->\n        @if (step() === 4) {\n          <div class=\"space-y-6\">\n            <div class=\"space-y-2 py-2 text-center\">\n              <div\n                class=\"border-success/20 bg-success/10 text-success relative mx-auto flex size-12 items-center justify-center rounded-full border\"\n              >\n                <lucide-icon [img]=\"CheckCircle2\" class=\"size-6\" />\n              </div>\n              <h3 class=\"text-foreground text-base font-semibold\">Return Authorized &amp; Confirmed</h3>\n              <p class=\"text-muted-foreground mx-auto max-w-md text-xs\">\n                Your return request has been submitted. A prepaid shipping label and confirmation receipt have been sent\n                to <span class=\"text-foreground font-medium\">{{ email() || 'sarah.connor@example.com' }}</span\n                >.\n              </p>\n            </div>\n\n            <!-- Return Details Overview -->\n            <div class=\"border-border bg-muted/20 space-y-3 rounded-lg border p-4\">\n              <div class=\"border-border flex flex-wrap items-center justify-between gap-2 border-b pb-3\">\n                <div class=\"space-y-0.5\">\n                  <span class=\"text-muted-foreground text-xs\">Return ID</span>\n                  <p class=\"text-foreground font-mono text-sm font-semibold\">#RET-2026-849</p>\n                </div>\n                <span ui-badge variant=\"success\" class=\"gap-1 text-xs\">\n                  <lucide-icon [img]=\"Check\" class=\"size-3\" /> Authorized\n                </span>\n              </div>\n\n              <div class=\"grid grid-cols-1 gap-3 text-xs sm:grid-cols-3\">\n                <div class=\"space-y-0.5\">\n                  <span class=\"text-muted-foreground\">Carrier</span>\n                  <p class=\"text-foreground font-medium\">USPS Ground Advantage™</p>\n                </div>\n                <div class=\"space-y-0.5\">\n                  <span class=\"text-muted-foreground\">Tracking Number</span>\n                  <p class=\"text-foreground font-mono font-medium\">9400 1118 9956 2849</p>\n                </div>\n                <div class=\"space-y-0.5\">\n                  <span class=\"text-muted-foreground\">Selected Resolution</span>\n                  <p class=\"text-foreground font-medium\">{{ resolutionSummary() }}</p>\n                </div>\n              </div>\n            </div>\n\n            <!-- Action Download Buttons -->\n            <div class=\"flex flex-col gap-3 sm:flex-row\">\n              <button\n                ui-button\n                aria-label=\"Download attachment\"\n                class=\"flex-1 gap-2\"\n                size=\"default\"\n                (click)=\"isDownloaded.set(true)\"\n              >\n                <lucide-icon [img]=\"Download\" class=\"size-4\" />\n                {{ isDownloaded() ? 'Label Downloaded (PDF)' : 'Download Prepaid Shipping Label (PDF)' }}\n              </button>\n              <button ui-button variant=\"outline\" class=\"gap-2\" size=\"default\">\n                <lucide-icon [img]=\"Printer\" class=\"size-4\" />\n                Print Return Slip\n              </button>\n            </div>\n\n            <!-- Drop-off instructions & QR code card -->\n            <div class=\"border-border bg-card space-y-4 rounded-lg border p-4\">\n              <div class=\"flex items-center justify-between\">\n                <h4 class=\"text-foreground text-xs font-semibold tracking-wide uppercase\">\n                  Carrier Drop-off Pass &amp; Instructions\n                </h4>\n                <span ui-badge variant=\"outline\" class=\"text-xs\">No printer required</span>\n              </div>\n\n              <div class=\"grid grid-cols-1 items-center gap-4 sm:grid-cols-3\">\n                <div class=\"space-y-3 text-xs sm:col-span-2\">\n                  <div class=\"flex items-start gap-2.5\">\n                    <div\n                      class=\"bg-muted text-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-xs font-medium\"\n                    >\n                      1\n                    </div>\n                    <div class=\"space-y-0.5\">\n                      <p class=\"text-foreground font-medium\">Pack your items</p>\n                      <p class=\"text-muted-foreground\">\n                        Place returned items with tags inside the original shipping bag or any sturdy box.\n                      </p>\n                    </div>\n                  </div>\n\n                  <div class=\"flex items-start gap-2.5\">\n                    <div\n                      class=\"bg-muted text-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-xs font-medium\"\n                    >\n                      2\n                    </div>\n                    <div class=\"space-y-0.5\">\n                      <p class=\"text-foreground font-medium\">Attach label or show QR code</p>\n                      <p class=\"text-muted-foreground\">\n                        Tape the downloaded shipping label to the box, or present the digital QR pass at the drop-off\n                        counter.\n                      </p>\n                    </div>\n                  </div>\n\n                  <div class=\"flex items-start gap-2.5\">\n                    <div\n                      class=\"bg-muted text-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-xs font-medium\"\n                    >\n                      3\n                    </div>\n                    <div class=\"space-y-0.5\">\n                      <p class=\"text-foreground font-medium\">Drop off before Nov 15, 2026</p>\n                      <p class=\"text-muted-foreground\">\n                        Bring to any USPS Post Office, USPS drop box, or authorized FedEx shipping point.\n                      </p>\n                    </div>\n                  </div>\n                </div>\n\n                <!-- QR code visual -->\n                <div\n                  class=\"border-border bg-muted/40 flex flex-col items-center justify-center space-y-2 rounded-lg border p-3 text-center sm:col-span-1\"\n                >\n                  <svg\n                    class=\"text-foreground size-24\"\n                    viewBox=\"0 0 100 100\"\n                    fill=\"currentColor\"\n                    aria-label=\"Prepaid return shipping QR dropoff code\"\n                  >\n                    <!-- Top-left finder -->\n                    <rect\n                      x=\"10\"\n                      y=\"10\"\n                      width=\"24\"\n                      height=\"24\"\n                      rx=\"2\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      stroke-width=\"4\"\n                    />\n                    <rect x=\"17\" y=\"17\" width=\"10\" height=\"10\" rx=\"1\" fill=\"currentColor\" />\n                    <!-- Top-right finder -->\n                    <rect\n                      x=\"66\"\n                      y=\"10\"\n                      width=\"24\"\n                      height=\"24\"\n                      rx=\"2\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      stroke-width=\"4\"\n                    />\n                    <rect x=\"73\" y=\"17\" width=\"10\" height=\"10\" rx=\"1\" fill=\"currentColor\" />\n                    <!-- Bottom-left finder -->\n                    <rect\n                      x=\"10\"\n                      y=\"66\"\n                      width=\"24\"\n                      height=\"24\"\n                      rx=\"2\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      stroke-width=\"4\"\n                    />\n                    <rect x=\"17\" y=\"73\" width=\"10\" height=\"10\" rx=\"1\" fill=\"currentColor\" />\n                    <!-- Data dots -->\n                    @for (dot of qrDots; track $index) {\n                      <rect [attr.x]=\"dot[0]\" [attr.y]=\"dot[1]\" width=\"6\" height=\"6\" rx=\"1\" fill=\"currentColor\" />\n                    }\n                  </svg>\n                  <span class=\"text-foreground font-mono text-xs font-semibold\">RET-2026-849</span>\n                </div>\n              </div>\n            </div>\n\n            <div class=\"pt-2 text-center\">\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"sm\"\n                class=\"text-muted-foreground hover:text-foreground text-xs\"\n                (click)=\"reset()\"\n              >\n                Start another return\n              </button>\n            </div>\n          </div>\n        }\n      </div>\n\n      @if (step() < 4) {\n        <div ui-card-footer class=\"border-border flex flex-wrap items-center justify-between gap-2 border-t pt-2\">\n          @if (step() > 1) {\n            <button ui-button variant=\"ghost\" size=\"sm\" (click)=\"goToPrevious()\">\n              <lucide-icon [img]=\"ChevronLeft\" [class]=\"'mr-1 size-4'\" />\n              Back\n            </button>\n          } @else {\n            <div></div>\n          }\n\n          <span class=\"text-muted-foreground text-xs\">Step {{ step() }} of 4</span>\n\n          @if (step() === 1) {\n            <button ui-button size=\"sm\" [disabled]=\"!orderNumber().trim() || !email().trim()\" (click)=\"goToNext()\">\n              Find Order\n              <lucide-icon [img]=\"ChevronRight\" [class]=\"'ml-1 size-4'\" />\n            </button>\n          }\n\n          @if (step() === 2) {\n            <button ui-button size=\"sm\" [disabled]=\"selectedItemsCount() === 0\" (click)=\"goToNext()\">\n              Choose Resolution\n              <lucide-icon [img]=\"ChevronRight\" [class]=\"'ml-1 size-4'\" />\n            </button>\n          }\n\n          @if (step() === 3) {\n            <button ui-button size=\"sm\" (click)=\"goToNext()\">\n              Confirm &amp; Print Label\n              <lucide-icon [img]=\"Check\" [class]=\"'ml-1 size-4'\" />\n            </button>\n          }\n        </div>\n      }\n    </div>\n  `,\n})\nexport class UiReturnsPortalComponent implements OnChanges, OnInit {\n  /** Step to open on (1 Find Order · 2 Select Items · 3 Choose Resolution · 4 Confirm & Print). */\n  @Input() initialStep = 1\n  @Input() initialOrderNumber = '#ORD-84920'\n  @Input() initialEmail = 'sarah.connor@example.com'\n  @Input('class') className?: string\n\n  protected readonly steps = steps\n  protected readonly orderItems = orderItems\n  protected readonly returnReasonsList = returnReasonsList\n  protected readonly exchangeSizesList = exchangeSizesList\n  /** Data-dot modules of the QR pass, [x, y] in the 100×100 viewBox (React order). */\n  protected readonly qrDots: ReadonlyArray<readonly [number, number]> = [\n    [42, 12],\n    [52, 12],\n    [42, 24],\n    [52, 24],\n    [12, 42],\n    [24, 42],\n    [36, 42],\n    [48, 42],\n    [60, 42],\n    [72, 42],\n    [84, 42],\n    [42, 54],\n    [54, 54],\n    [66, 54],\n    [78, 54],\n    [42, 66],\n    [54, 66],\n    [66, 78],\n    [78, 66],\n    [42, 78],\n    [54, 78],\n    [84, 78],\n  ]\n\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly ChevronLeft = ChevronLeft\n  protected readonly ChevronRight = ChevronRight\n  protected readonly CreditCard = CreditCard\n  protected readonly Download = Download\n  protected readonly Gift = Gift\n  protected readonly Printer = Printer\n  protected readonly RefreshCw = RefreshCw\n  protected readonly Shirt = Shirt\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Truck = Truck\n\n  readonly step = signal(1)\n  readonly orderNumber = signal('#ORD-84920')\n  readonly email = signal('sarah.connor@example.com')\n  readonly selectedItems = signal<Record<string, boolean>>(defaultSelectedItems())\n  readonly returnQuantities = signal<Record<string, number>>(defaultReturnQuantities())\n  readonly returnReasons = signal<Record<string, string>>(defaultReturnReasons())\n  readonly resolution = signal<ReturnsPortalResolution>('store-credit')\n  readonly exchangeSize = signal('Medium (M)')\n  readonly isDownloaded = signal(false)\n\n  readonly selectedItemsCount = computed(() =>\n    orderItems.reduce((acc, item) => (this.isSelected(item.id) ? acc + this.quantityFor(item.id) : acc), 0),\n  )\n\n  readonly itemsSubtotal = computed(() =>\n    orderItems.reduce(\n      (acc, item) => (this.isSelected(item.id) ? acc + item.price * this.quantityFor(item.id) : acc),\n      0,\n    ),\n  )\n\n  readonly storeCreditBonus = computed(() => this.itemsSubtotal() * 0.1)\n  readonly totalStoreCredit = computed(() => this.itemsSubtotal() + this.storeCreditBonus())\n\n  readonly totalLabel = computed(() => {\n    const r = this.resolution()\n    return r === 'store-credit' ? 'Total Store Credit' : r === 'exchange' ? 'Exchange Value' : 'Total Refund'\n  })\n\n  readonly totalValue = computed(() =>\n    this.resolution() === 'store-credit'\n      ? this.formatCurrency(this.totalStoreCredit())\n      : this.formatCurrency(this.itemsSubtotal()),\n  )\n\n  readonly resolutionSummary = computed(() => {\n    const r = this.resolution()\n    return r === 'store-credit'\n      ? `Store Credit (${this.formatCurrency(this.totalStoreCredit())})`\n      : r === 'exchange'\n        ? `Exchange (${this.exchangeSize()})`\n        : 'Refund to Visa •••• 4242'\n  })\n\n  get cardClass(): string {\n    return cn('border-border mx-auto w-full max-w-3xl shadow-xs', this.className)\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    // React re-syncs the step whenever `initialStep` changes (useEffect on initialStep).\n    if (changes['initialStep']) this.step.set(this.initialStep)\n  }\n\n  ngOnInit(): void {\n    this.orderNumber.set(this.initialOrderNumber)\n    this.email.set(this.initialEmail)\n  }\n\n  formatCurrency(val: number): string {\n    return currency.format(val)\n  }\n\n  isSelected(id: string): boolean {\n    return this.selectedItems()[id] ?? false\n  }\n\n  quantityFor(id: string): number {\n    return this.returnQuantities()[id] || 1\n  }\n\n  quantityValue(id: string): string {\n    return String(this.quantityFor(id))\n  }\n\n  reasonFor(id: string): string | undefined {\n    return this.returnReasons()[id]\n  }\n\n  quantityOptions(maxQty: number): number[] {\n    return Array.from({ length: maxQty }, (_, idx) => idx + 1)\n  }\n\n  itemClass(id: string): string {\n    return cn(\n      'rounded-lg border p-4 transition-colors',\n      this.isSelected(id)\n        ? 'border-primary/50 bg-primary/[0.02] dark:bg-primary/10 shadow-xs'\n        : 'border-border bg-card hover:bg-muted/20',\n    )\n  }\n\n  optionClass(value: ReturnsPortalResolution): string {\n    return cn(OPTION_BASE, this.resolution() === value ? OPTION_ACTIVE : OPTION_IDLE)\n  }\n\n  setQuantity(id: string, value: string): void {\n    this.returnQuantities.update((prev) => ({ ...prev, [id]: Number(value) }))\n  }\n\n  setReason(id: string, value: string): void {\n    this.returnReasons.update((prev) => ({ ...prev, [id]: value }))\n  }\n\n  setResolution(value: string): void {\n    this.resolution.set(value as ReturnsPortalResolution)\n  }\n\n  onStepperInput(value: number): void {\n    // Free backward navigation until the return is confirmed; forward goes through the footer.\n    const current = this.step()\n    if (value < current && current !== 4) this.step.set(value)\n  }\n\n  goToNext(): void {\n    if (this.step() < 4) this.step.update((s) => s + 1)\n  }\n\n  goToPrevious(): void {\n    const current = this.step()\n    if (current > 1 && current < 4) this.step.set(current - 1)\n  }\n\n  toggleItemSelection(id: string): void {\n    this.selectedItems.update((prev) => ({ ...prev, [id]: !prev[id] }))\n  }\n\n  reset(): void {\n    this.step.set(1)\n    this.isDownloaded.set(false)\n    this.selectedItems.set(defaultSelectedItems())\n    this.returnQuantities.set(defaultReturnQuantities())\n    this.returnReasons.set(defaultReturnReasons())\n    this.resolution.set('store-credit')\n    this.exchangeSize.set('Medium (M)')\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/returns-portal/returns-portal.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/returns-portal/index.ts",
      "content": "export { UiReturnsPortalComponent, type OrderItem, type ReturnsPortalResolution } from './returns-portal.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/returns-portal/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/card.json",
    "https://uipkge.dev/r/angular/checkbox.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/radio-group.json",
    "https://uipkge.dev/r/angular/select.json",
    "https://uipkge.dev/r/angular/stepper.json"
  ],
  "description": "Self-serve e-commerce returns & exchange portal wizard: Stepper header (Find Order → Select Items → Choose Resolution → Confirm & Print Label), order lookup with email and order number, item selection list with return reasons and quantities, resolution selector (size exchange, store credit with +10% bonus, or original payment refund), and confirmation screen with printable prepaid shipping label, QR code drop-off instructions, and return summary.",
  "categories": [
    "commerce",
    "ecommerce",
    "app"
  ]
}