{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "product-qa-community",
  "title": "Product Qa Community",
  "type": "registry:block",
  "description": "Searchable customer and engineering Q&A community forum with staff answer badges, question upvoting, category filters, and live ask modal.",
  "categories": [
    "ecommerce",
    "marketing"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/product-qa-community/product-qa-community.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { HelpCircle, MessageSquare, Search, ThumbsUp, ShieldCheck, CheckCircle, Plus, Send } from 'lucide'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/badge/badge'\nimport '../../components/button/button'\nimport '../../components/card/card'\nimport '../../components/input/input'\nimport type { UipInput } from '../../components/input/input'\nimport '../../components/textarea/textarea'\n\nexport interface AnswerItem {\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: 'compatibility' | 'sound' | 'comfort' | 'cables' | 'shipping'\n  upvotes: number\n  hasVoted?: boolean\n  answers: AnswerItem[]\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 = [\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\ntype QaCategory = 'compatibility' | 'sound' | 'comfort' | 'cables' | 'shipping'\n\n/**\n * <uip-product-qa-community> — the registry ProductQaCommunity block as a web component.\n *\n * Class strings and seed data are React's verbatim. React state becomes\n * { state: true } fields; `questions` is a property (attribute: false — set it\n * from JS). Like React, the block fires no events; search, category filter,\n * voting, and the ask form are internal. The search icon moves from an\n * absolute overlay into <uip-input>'s prefix slot (same visual result, no\n * overlay positioning to maintain).\n */\nexport class UipProductQaCommunity extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    productName: { attribute: 'product-name' },\n    questions: { attribute: false },\n    localQuestions: { state: true },\n    searchQuery: { state: true },\n    selectedCategory: { state: true },\n    isAsking: { state: true },\n    newQuestionText: { state: true },\n    newQuestionCategory: { state: true },\n  }\n\n  productName = 'Apex Pro Reference Studio Monitor Headphones'\n  questions: QuestionItem[] = defaultQuestions\n\n  private localQuestions: QuestionItem[] = []\n  private searchQuery = ''\n  private selectedCategory = 'all'\n  private isAsking = false\n  private newQuestionText = ''\n  private newQuestionCategory: QaCategory = 'compatibility'\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'product-qa-community')\n  }\n\n  willUpdate(changed: Map<string, unknown>) {\n    if (changed.has('questions')) {\n      this.localQuestions = (this.questions ?? []).map((q) => ({ ...q, answers: q.answers.map((a) => ({ ...a })) }))\n    }\n  }\n\n  private get filteredQuestions(): QuestionItem[] {\n    return this.localQuestions.filter((q) => {\n      const matchesCat = this.selectedCategory === 'all' || q.category === this.selectedCategory\n      const matchesSearch =\n        this.searchQuery.trim() === '' ||\n        q.question.toLowerCase().includes(this.searchQuery.toLowerCase()) ||\n        q.answers.some((a) => a.answer.toLowerCase().includes(this.searchQuery.toLowerCase()))\n      return matchesCat && matchesSearch\n    })\n  }\n\n  private toggleVoteQuestion(qId: string) {\n    this.localQuestions = this.localQuestions.map((q) => {\n      if (q.id !== qId) return q\n      return { ...q, upvotes: q.hasVoted ? q.upvotes - 1 : q.upvotes + 1, hasVoted: !q.hasVoted }\n    })\n  }\n\n  private toggleVoteAnswer(qId: string, aId: string) {\n    this.localQuestions = this.localQuestions.map((q) => {\n      if (q.id !== qId) return q\n      return {\n        ...q,\n        answers: q.answers.map((a) => {\n          if (a.id !== aId) return a\n          return {\n            ...a,\n            helpfulCount: a.hasUpvoted ? a.helpfulCount - 1 : a.helpfulCount + 1,\n            hasUpvoted: !a.hasUpvoted,\n          }\n        }),\n      }\n    })\n  }\n\n  private submitNewQuestion() {\n    if (!this.newQuestionText.trim()) return\n    const newQ: QuestionItem = {\n      id: `q-${Date.now()}`,\n      question: this.newQuestionText.trim(),\n      askedBy: 'You (Guest User)',\n      askedDate: 'Just now',\n      category: this.newQuestionCategory,\n      upvotes: 1,\n      hasVoted: true,\n      answers: [],\n    }\n    this.newQuestionText = ''\n    this.isAsking = false\n    this.localQuestions = [newQ, ...this.localQuestions]\n  }\n\n  render() {\n    const filteredQuestions = this.filteredQuestions\n    // data-slot lives on this wrapper: <uip-card> stamps its own\n    // data-slot=\"card\" on connect, which would clobber the block slot.\n    return html`\n      <div data-slot=\"product-qa-community\">\n        <uip-card class=\"border-border w-full shadow-xs\">\n          <uip-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 class=\"border-border bg-background flex size-6 items-center justify-center rounded-md border shadow-2xs\">\n                    ${icon(HelpCircle, 'circle-help', '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                <uip-card-title class=\"text-foreground mt-1 text-lg font-semibold tracking-tight sm:text-xl\">\n                  Questions & Answers\n                </uip-card-title>\n                <uip-card-description class=\"text-xs\">\n                  Have questions about ${this.productName}? Search community inquiries or ask our acoustic engineering team.\n                </uip-card-description>\n              </div>\n              <uip-button\n                size=\"sm\"\n                class=\"gap-1.5 self-start text-xs shadow-xs sm:self-auto\"\n                @click=${() => {\n                  this.isAsking = !this.isAsking\n                }}\n              >\n                ${icon(Plus, 'plus', 'size-3.5')}\n                <span>Ask a Question</span>\n              </uip-button>\n            </div>\n\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                ${categories.map(\n                  (cat) => html`<button\n                    type=\"button\"\n                    class=${`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${\n                      this.selectedCategory === cat.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                    @click=${() => {\n                      this.selectedCategory = cat.id\n                    }}\n                  >\n                    ${cat.label}\n                  </button>`,\n                )}\n              </div>\n              <uip-input\n                .value=${this.searchQuery}\n                placeholder=\"Search answered Q&A...\"\n                class=\"h-8 w-full text-xs sm:w-64\"\n                @input=${(e: Event) => {\n                  this.searchQuery = (e.target as UipInput).value ?? ''\n                }}\n              >\n                <span slot=\"prefix\">${icon(Search, 'search', 'size-3.5')}</span>\n              </uip-input>\n            </div>\n          </uip-card-header>\n\n          <uip-card-content class=\"space-y-6 p-4 sm:p-6\">\n            ${this.isAsking\n              ? html`<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                      ${icon(MessageSquare, 'message-square', '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 text-xs\">Staff answers typically arrive within 4 hours</span>\n                  </div>\n                  <uip-textarea\n                    .value=${this.newQuestionText}\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                    @value-change=${(e: CustomEvent<{ value: string }>) => {\n                      this.newQuestionText = e.detail.value\n                    }}\n                  ></uip-textarea>\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                        class=\"border-border bg-background text-foreground h-7 rounded-md border px-2 text-xs focus:outline-hidden\"\n                        .value=${this.newQuestionCategory}\n                        @change=${(e: Event) => {\n                          this.newQuestionCategory = (e.target as HTMLSelectElement).value as QaCategory\n                        }}\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                    <div class=\"flex items-center gap-2\">\n                      <uip-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"h-7 text-xs\"\n                        @click=${() => {\n                          this.isAsking = false\n                        }}\n                      >\n                        Cancel\n                      </uip-button>\n                      <uip-button size=\"sm\" class=\"h-7 gap-1.5 text-xs shadow-2xs\" @click=${() => this.submitNewQuestion()}>\n                        ${icon(Send, 'send', 'size-3')}\n                        <span>Submit Question</span>\n                      </uip-button>\n                    </div>\n                  </div>\n                </div>`\n              : nothing}\n\n            <div class=\"divide-border space-y-5 divide-y\">\n              ${filteredQuestions.map(\n                (q) => html`<div class=\"space-y-3 pt-5 first:pt-0\">\n                  <div class=\"flex items-start justify-between gap-3\">\n                    <div class=\"flex items-start gap-3\">\n                      <div class=\"bg-muted text-muted-foreground flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-bold\">\n                        Q\n                      </div>\n                      <div class=\"space-y-1\">\n                        <h4 class=\"text-foreground text-sm leading-snug font-semibold\">${q.question}</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                          <uip-badge variant=\"outline\" class=\"h-4.5 font-mono text-xs uppercase\">${q.category}</uip-badge>\n                        </div>\n                      </div>\n                    </div>\n                    <button\n                      type=\"button\"\n                      class=${`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                        q.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                      @click=${() => this.toggleVoteQuestion(q.id)}\n                    >\n                      ${icon(ThumbsUp, 'thumbs-up', 'size-3')}\n                      <span>${q.upvotes}</span>\n                    </button>\n                  </div>\n\n                  <div class=\"ml-9 space-y-3\">\n                    ${q.answers.map(\n                      (ans) => html`<div\n                        class=${`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                        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                          <div class=\"flex items-center gap-2\">\n                            ${ans.role === 'staff'\n                              ? html`<uip-badge variant=\"default\" class=\"bg-success h-4.5 gap-1 px-1.5 text-xs font-semibold text-white\">\n                                  ${icon(ShieldCheck, 'shield-check', 'size-2.5')} Staff Expert\n                                </uip-badge>`\n                              : nothing}\n                            ${ans.role === 'verified_buyer'\n                              ? html`<uip-badge variant=\"secondary\" class=\"text-foreground h-4.5 gap-1 px-1.5 text-xs font-semibold\">\n                                  ${icon(CheckCircle, 'circle-check', 'text-success size-2.5')} Verified Owner\n                                </uip-badge>`\n                              : nothing}\n                            <span class=\"text-foreground font-semibold\">${ans.answeredBy}</span>\n                            <span class=\"text-muted-foreground\">· ${ans.answeredDate}</span>\n                          </div>\n                          <button\n                            type=\"button\"\n                            class=${`flex items-center gap-1 text-xs font-medium transition-colors ${\n                              ans.hasUpvoted ? 'text-primary font-bold' : 'text-muted-foreground hover:text-foreground'\n                            }`}\n                            @click=${() => this.toggleVoteAnswer(q.id, ans.id)}\n                          >\n                            ${icon(ThumbsUp, 'thumbs-up', 'size-3')}\n                            <span>Helpful (${ans.helpfulCount})</span>\n                          </button>\n                        </div>\n                        <p class=\"text-foreground leading-relaxed\">${ans.answer}</p>\n                      </div>`,\n                    )}\n                    ${q.answers.length === 0\n                      ? html`<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                      : nothing}\n                  </div>\n                </div>`,\n              )}\n              ${filteredQuestions.length === 0\n                ? html`<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                : nothing}\n            </div>\n          </uip-card-content>\n        </uip-card>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-product-qa-community') || customElements.define('uip-product-qa-community', UipProductQaCommunity)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-product-qa-community': UipProductQaCommunity\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/product-qa-community/product-qa-community.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/badge.json",
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/card.json",
    "https://uipkge.dev/r/lit/input.json",
    "https://uipkge.dev/r/lit/lit-core.json",
    "https://uipkge.dev/r/lit/textarea.json"
  ]
}
