{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "product-qa-community",
  "title": "Product Qa Community",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/product-qa-community/product-qa-community.component.ts",
      "content": "// Searchable customer + engineering Q&A community forum: category chips, keyword search,\n// question upvoting, staff / verified-owner answer badges, and an inline \"ask a question\" form.\n// Port of the Vue block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  CheckCircle2,\n  HelpCircle,\n  LucideAngularModule,\n  MessageSquare,\n  Plus,\n  Search,\n  Send,\n  ShieldCheck,\n  ThumbsUp,\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  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport type QaCategory = 'compatibility' | 'sound' | 'comfort' | 'cables' | 'shipping'\nexport type QaCategoryFilter = 'all' | QaCategory\n\nexport interface QaAnswer {\n  id: string\n  answer: string\n  answeredBy: string\n  answeredDate: string\n  role: 'staff' | 'verified_buyer' | 'community'\n  helpfulCount: number\n  hasUpvoted?: boolean\n}\n\nexport interface QuestionItem {\n  id: string\n  question: string\n  askedBy: string\n  askedDate: string\n  category: QaCategory\n  upvotes: number\n  hasVoted?: boolean\n  answers: QaAnswer[]\n}\n\nconst defaultQuestions: QuestionItem[] = [\n  {\n    id: 'q1',\n    question:\n      'Can these 32-ohm planar drivers be driven directly from a MacBook Pro headphone jack without an external DAC/AMP?',\n    askedBy: 'Julian R. (Audio Engineer)',\n    askedDate: '3 days ago',\n    category: 'compatibility',\n    upvotes: 42,\n    answers: [\n      {\n        id: 'a1',\n        answer:\n          'Yes! Thanks to our high-sensitivity 106 dB/mW planar trace design and flat 32Ω impedance curve, modern high-power laptops (such as Apple Silicon M-series MacBook Pros) drive them with pristine headroom. However, for 192kHz/24-bit studio mastering, pairing with a dedicated balanced 4.4mm DAC/AMP will unlock their full dynamic staging.',\n        answeredBy: 'Marcus Sterling · Lead Acoustic Engineer (Apex)',\n        answeredDate: '2 days ago',\n        role: 'staff',\n        helpfulCount: 38,\n      },\n    ],\n  },\n  {\n    id: 'q2',\n    question: 'Are the lambskin earpads user-replaceable if they wear down over time?',\n    askedBy: 'Elena K.',\n    askedDate: '1 week ago',\n    category: 'comfort',\n    upvotes: 19,\n    answers: [\n      {\n        id: 'a2',\n        answer:\n          'The earpads utilize our magnetic snap-lock mounting system. You can easily remove and swap them in seconds without adhesive. We also offer perforated vegan suede and cooling velour pads in our accessories catalog.',\n        answeredBy: 'Apex Support Team',\n        answeredDate: '6 days ago',\n        role: 'staff',\n        helpfulCount: 22,\n      },\n      {\n        id: 'a3',\n        answer:\n          'Verified buyer here — swapped mine to the cooling velour for long 6-hour mixing sessions. Magnetic snaps are rock solid!',\n        answeredBy: 'Devon T. (Verified Studio Buyer)',\n        answeredDate: '5 days ago',\n        role: 'verified_buyer',\n        helpfulCount: 14,\n      },\n    ],\n  },\n  {\n    id: 'q3',\n    question:\n      'Does the included balanced 4.4mm cable work with Sony DAP walkmans and standard desktop audio interfaces?',\n    askedBy: 'Kenji S.',\n    askedDate: '2 weeks ago',\n    category: 'cables',\n    upvotes: 15,\n    answers: [\n      {\n        id: 'a4',\n        answer:\n          'Yes, the native 4.4mm TRRRS Pentaconn termination matches Sony, FiiO, Astell&Kern, and modern studio DAC balanced outputs. The package also includes a gold-plated 6.35mm (1/4\") TRS screw-on adapter for standard rack equipment.',\n        answeredBy: 'David L. · Product Specialist',\n        answeredDate: '2 weeks ago',\n        role: 'staff',\n        helpfulCount: 16,\n      },\n    ],\n  },\n]\n\nconst categories: { id: QaCategoryFilter; label: string }[] = [\n  { id: 'all', label: 'All Questions' },\n  { id: 'compatibility', label: 'Compatibility & DACs' },\n  { id: 'sound', label: 'Sound Signature' },\n  { id: 'comfort', label: 'Fit & Ergonomics' },\n  { id: 'cables', label: 'Cables & Hardware' },\n  { id: 'shipping', label: 'Warranty & Shipping' },\n]\n\nfunction cloneQuestions(items: QuestionItem[]): QuestionItem[] {\n  return items.map((q) => ({ ...q, answers: q.answers.map((a) => ({ ...a })) }))\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-product-qa-community, [ui-product-qa-community]',\n  standalone: true,\n  // Vue renders the Card root 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    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiInputComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div ui-card data-slot=\"product-qa-community\" [class]=\"rootClass\">\n      <div ui-card-header class=\"border-border bg-muted/20 border-b pb-4\">\n        <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n          <div>\n            <div class=\"flex items-center gap-2\">\n              <span\n                class=\"border-border bg-background flex size-6 items-center justify-center rounded-md border shadow-2xs\"\n              >\n                <lucide-icon [img]=\"HelpCircle\" class=\"text-primary size-3.5\" />\n              </span>\n              <span class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                Customer & Engineering Community\n              </span>\n            </div>\n            <h3 ui-card-title class=\"text-foreground mt-1 text-lg font-semibold tracking-tight sm:text-xl\">\n              Questions & Answers\n            </h3>\n            <p ui-card-description class=\"text-xs\">\n              Have questions about {{ productName }}? Search community inquiries or ask our acoustic engineering team.\n            </p>\n          </div>\n\n          <button ui-button size=\"sm\" class=\"gap-1.5 self-start text-xs shadow-xs sm:self-auto\" (click)=\"toggleAsk()\">\n            <lucide-icon [img]=\"Plus\" class=\"size-3.5\" />\n            <span>Ask a Question</span>\n          </button>\n        </div>\n\n        <!-- Controls: Category Chips & Search Input -->\n        <div class=\"mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n          <div class=\"flex flex-wrap items-center gap-1.5\">\n            @for (cat of categoryList; track cat.id) {\n              <button type=\"button\" [class]=\"chipClass(cat.id)\" (click)=\"selectedCategory.set(cat.id)\">\n                {{ cat.label }}\n              </button>\n            }\n          </div>\n\n          <div class=\"relative w-full sm:w-64\">\n            <lucide-icon\n              [img]=\"Search\"\n              class=\"text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2\"\n            />\n            <ui-input\n              [value]=\"searchQuery()\"\n              (valueChange)=\"searchQuery.set($event)\"\n              placeholder=\"Search answered Q&A...\"\n              class=\"h-8 pl-8 text-xs\"\n            />\n          </div>\n        </div>\n      </div>\n\n      <div ui-card-content class=\"space-y-6 p-4 sm:p-6\">\n        <!-- Ask Question Form Accordion -->\n        @if (isAsking()) {\n          <div class=\"border-primary/40 bg-primary/5 space-y-3 rounded-lg border p-4 shadow-xs\">\n            <div class=\"flex items-center justify-between\">\n              <div class=\"flex items-center gap-2\">\n                <lucide-icon [img]=\"MessageSquare\" class=\"text-primary size-4\" />\n                <span class=\"text-foreground text-xs font-semibold\">Post a Question to Engineering & Community</span>\n              </div>\n              <span class=\"text-muted-foreground hidden text-xs sm:inline\">\n                Staff answers typically arrive within 4 hours\n              </span>\n            </div>\n\n            <ui-textarea\n              [value]=\"newQuestionText()\"\n              (valueChange)=\"newQuestionText.set($event)\"\n              [rows]=\"3\"\n              placeholder=\"e.g. Does the headband clamping force loosen up after 20 hours of break-in?\"\n              class=\"text-xs\"\n            />\n\n            <div class=\"flex flex-wrap items-center justify-between gap-2\">\n              <div class=\"flex items-center gap-2 text-xs\">\n                <span class=\"text-muted-foreground\">Topic:</span>\n                <select\n                  [value]=\"newQuestionCategory()\"\n                  (change)=\"newQuestionCategory.set($any($event.target).value)\"\n                  class=\"border-border bg-background text-foreground h-7 rounded-md border px-2 text-xs focus:outline-hidden\"\n                >\n                  <option value=\"compatibility\">Compatibility & DACs</option>\n                  <option value=\"sound\">Sound Signature</option>\n                  <option value=\"comfort\">Fit & Ergonomics</option>\n                  <option value=\"cables\">Cables & Hardware</option>\n                  <option value=\"shipping\">Warranty & Shipping</option>\n                </select>\n              </div>\n\n              <div class=\"flex items-center gap-2\">\n                <button ui-button variant=\"ghost\" size=\"sm\" class=\"h-7 text-xs\" (click)=\"isAsking.set(false)\">\n                  Cancel\n                </button>\n                <button ui-button size=\"sm\" class=\"h-7 gap-1.5 text-xs shadow-2xs\" (click)=\"submitNewQuestion()\">\n                  <lucide-icon [img]=\"Send\" class=\"size-3\" />\n                  <span>Submit Question</span>\n                </button>\n              </div>\n            </div>\n          </div>\n        }\n\n        <!-- Question List -->\n        <div class=\"divide-border space-y-5 divide-y\">\n          @for (q of filteredQuestions(); track q.id) {\n            <div class=\"space-y-3 pt-5 first:pt-0\">\n              <!-- Question Header Row -->\n              <div class=\"flex items-start justify-between gap-3\">\n                <div class=\"flex items-start gap-3\">\n                  <div\n                    class=\"bg-muted text-muted-foreground flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-bold\"\n                  >\n                    Q\n                  </div>\n                  <div class=\"space-y-1\">\n                    <h4 class=\"text-foreground text-sm leading-snug font-semibold\">\n                      {{ q.question }}\n                    </h4>\n                    <div class=\"text-muted-foreground flex flex-wrap items-center gap-2 text-xs\">\n                      <span>Asked by {{ q.askedBy }}</span>\n                      <span>·</span>\n                      <span>{{ q.askedDate }}</span>\n                      <span ui-badge variant=\"outline\" class=\"h-4.5 font-mono text-xs uppercase\">\n                        {{ q.category }}\n                      </span>\n                    </div>\n                  </div>\n                </div>\n\n                <!-- Upvote Question Button -->\n                <button type=\"button\" [class]=\"voteClass(q.hasVoted)\" (click)=\"toggleVoteQuestion(q.id)\">\n                  <lucide-icon [img]=\"ThumbsUp\" class=\"size-3\" />\n                  <span>{{ q.upvotes }}</span>\n                </button>\n              </div>\n\n              <!-- Answers Section -->\n              <div class=\"ml-9 space-y-3\">\n                @for (ans of q.answers; track ans.id) {\n                  <div\n                    [class]=\"\n                      cn(\n                        'space-y-2 rounded-lg border p-3.5 text-xs',\n                        ans.role === 'staff' ? 'border-success/30 bg-success/5' : 'border-border bg-muted/30'\n                      )\n                    \"\n                  >\n                    <!-- Answer Meta Bar -->\n                    <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                      <div class=\"flex items-center gap-2\">\n                        @if (ans.role === 'staff') {\n                          <span\n                            ui-badge\n                            variant=\"default\"\n                            class=\"bg-success h-4.5 gap-1 px-1.5 text-xs font-semibold text-white\"\n                          >\n                            <lucide-icon [img]=\"ShieldCheck\" class=\"size-2.5\" />\n                            Staff Expert\n                          </span>\n                        } @else if (ans.role === 'verified_buyer') {\n                          <span\n                            ui-badge\n                            variant=\"secondary\"\n                            class=\"text-foreground h-4.5 gap-1 px-1.5 text-xs font-semibold\"\n                          >\n                            <lucide-icon [img]=\"CheckCircle2\" class=\"text-success size-2.5\" />\n                            Verified Owner\n                          </span>\n                        }\n\n                        <span class=\"text-foreground font-semibold\">{{ ans.answeredBy }}</span>\n                        <span class=\"text-muted-foreground\">· {{ ans.answeredDate }}</span>\n                      </div>\n\n                      <button type=\"button\" [class]=\"helpfulClass(ans.hasUpvoted)\" (click)=\"toggleVoteAnswer(ans.id)\">\n                        <lucide-icon [img]=\"ThumbsUp\" class=\"size-3\" />\n                        <span>Helpful ({{ ans.helpfulCount }})</span>\n                      </button>\n                    </div>\n\n                    <!-- Answer Body Text -->\n                    <p class=\"text-foreground leading-relaxed\">\n                      {{ ans.answer }}\n                    </p>\n                  </div>\n                }\n\n                @if (q.answers.length === 0) {\n                  <div class=\"border-border text-muted-foreground rounded-lg border border-dashed p-3 text-xs\">\n                    Awaiting answer from engineering team. You will be notified when responded.\n                  </div>\n                }\n              </div>\n            </div>\n          }\n\n          @if (filteredQuestions().length === 0) {\n            <div class=\"text-muted-foreground py-12 text-center text-xs\">\n              No questions found matching your search. Be the first to ask!\n            </div>\n          }\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiProductQaCommunityComponent {\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly HelpCircle = HelpCircle\n  protected readonly MessageSquare = MessageSquare\n  protected readonly Plus = Plus\n  protected readonly Search = Search\n  protected readonly Send = Send\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly ThumbsUp = ThumbsUp\n  protected readonly cn = cn\n\n  protected readonly categoryList = categories\n\n  @Input() productName = 'Apex Pro Reference Studio Monitor Headphones'\n  @Input() set questions(v: QuestionItem[] | undefined) {\n    this.localQuestions.set(cloneQuestions(v ?? defaultQuestions))\n  }\n  @Input('class') className?: string\n\n  readonly localQuestions = signal<QuestionItem[]>(cloneQuestions(defaultQuestions))\n  readonly searchQuery = signal('')\n  readonly selectedCategory = signal<QaCategoryFilter>('all')\n\n  readonly isAsking = signal(false)\n  readonly newQuestionText = signal('')\n  readonly newQuestionCategory = signal<QaCategory>('compatibility')\n\n  readonly filteredQuestions = computed(() => {\n    const cat = this.selectedCategory()\n    const q = this.searchQuery().trim().toLowerCase()\n    return this.localQuestions().filter((item) => {\n      const matchesCat = cat === 'all' || item.category === cat\n      const matchesSearch =\n        q === '' ||\n        item.question.toLowerCase().includes(q) ||\n        item.answers.some((a) => a.answer.toLowerCase().includes(q))\n      return matchesCat && matchesSearch\n    })\n  })\n\n  get rootClass(): string {\n    return cn('border-border w-full shadow-xs', this.className)\n  }\n\n  chipClass(id: QaCategoryFilter): string {\n    return cn(\n      'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',\n      this.selectedCategory() === id\n        ? 'bg-foreground text-background shadow-2xs'\n        : 'bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground border border-transparent',\n    )\n  }\n\n  voteClass(hasVoted?: boolean): string {\n    return cn(\n      'flex shrink-0 items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium shadow-2xs transition-colors',\n      hasVoted\n        ? 'border-primary bg-primary/10 text-primary'\n        : 'border-border bg-card text-muted-foreground hover:bg-muted hover:text-foreground',\n    )\n  }\n\n  helpfulClass(hasUpvoted?: boolean): string {\n    return cn(\n      'flex items-center gap-1 text-xs font-medium transition-colors',\n      hasUpvoted ? 'text-primary font-bold' : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  toggleAsk(): void {\n    this.isAsking.set(!this.isAsking())\n  }\n\n  toggleVoteQuestion(id: string): void {\n    this.localQuestions.update((qs) =>\n      qs.map((q) => (q.id === id ? { ...q, upvotes: q.upvotes + (q.hasVoted ? -1 : 1), hasVoted: !q.hasVoted } : q)),\n    )\n  }\n\n  toggleVoteAnswer(answerId: string): void {\n    this.localQuestions.update((qs) =>\n      qs.map((q) => ({\n        ...q,\n        answers: q.answers.map((a) =>\n          a.id === answerId\n            ? { ...a, helpfulCount: a.helpfulCount + (a.hasUpvoted ? -1 : 1), hasUpvoted: !a.hasUpvoted }\n            : a,\n        ),\n      })),\n    )\n  }\n\n  submitNewQuestion(): void {\n    const text = this.newQuestionText().trim()\n    if (!text) return\n    this.localQuestions.update((qs) => [\n      {\n        id: `q-${Date.now()}`,\n        question: text,\n        askedBy: 'You (Guest User)',\n        askedDate: 'Just now',\n        category: this.newQuestionCategory(),\n        upvotes: 1,\n        hasVoted: true,\n        answers: [],\n      },\n      ...qs,\n    ])\n    this.newQuestionText.set('')\n    this.isAsking.set(false)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/product-qa-community/product-qa-community.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/product-qa-community/index.ts",
      "content": "export {\n  UiProductQaCommunityComponent,\n  type QaAnswer,\n  type QaCategory,\n  type QaCategoryFilter,\n  type QuestionItem,\n} from './product-qa-community.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/product-qa-community/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/input.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "Searchable customer and engineering Q&A community forum with staff answer badges, question upvoting, category filters, and live ask modal.",
  "categories": [
    "ecommerce",
    "marketing"
  ]
}