From 1cabafd7ed1dad904799b4ce2e4a3a3cbaca3efc Mon Sep 17 00:00:00 2001
From: Hirotaka Mizutani <52546+hirotaka@users.noreply.github.com>
Date: Sat, 27 Jun 2026 23:33:22 +0900
Subject: [PATCH] feat(registry): Add faq tabs block
---
app/components/demo/FaqTabsDemo.vue | 52 ++++++
app/components/ui/accordion/Accordion.vue | 19 ++
.../ui/accordion/AccordionContent.vue | 23 +++
app/components/ui/accordion/AccordionItem.vue | 23 +++
.../ui/accordion/AccordionTrigger.vue | 30 +++
app/components/ui/accordion/index.ts | 4 +
app/registry/blocks/faq-tabs/FaqTabs.vue | 174 ++++++++++++++++++
app/registry/blocks/faq-tabs/index.ts | 1 +
content/docs/2.components/15.faq-tabs.md | 122 ++++++++++++
scripts/registry-verify.ts | 4 +
10 files changed, 452 insertions(+)
create mode 100644 app/components/demo/FaqTabsDemo.vue
create mode 100644 app/components/ui/accordion/Accordion.vue
create mode 100644 app/components/ui/accordion/AccordionContent.vue
create mode 100644 app/components/ui/accordion/AccordionItem.vue
create mode 100644 app/components/ui/accordion/AccordionTrigger.vue
create mode 100644 app/components/ui/accordion/index.ts
create mode 100644 app/registry/blocks/faq-tabs/FaqTabs.vue
create mode 100644 app/registry/blocks/faq-tabs/index.ts
create mode 100644 content/docs/2.components/15.faq-tabs.md
diff --git a/app/components/demo/FaqTabsDemo.vue b/app/components/demo/FaqTabsDemo.vue
new file mode 100644
index 0000000..b702acc
--- /dev/null
+++ b/app/components/demo/FaqTabsDemo.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/accordion/Accordion.vue b/app/components/ui/accordion/Accordion.vue
new file mode 100644
index 0000000..50b5c9d
--- /dev/null
+++ b/app/components/ui/accordion/Accordion.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/accordion/AccordionContent.vue b/app/components/ui/accordion/AccordionContent.vue
new file mode 100644
index 0000000..8460e64
--- /dev/null
+++ b/app/components/ui/accordion/AccordionContent.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/components/ui/accordion/AccordionItem.vue b/app/components/ui/accordion/AccordionItem.vue
new file mode 100644
index 0000000..22c7b9b
--- /dev/null
+++ b/app/components/ui/accordion/AccordionItem.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/accordion/AccordionTrigger.vue b/app/components/ui/accordion/AccordionTrigger.vue
new file mode 100644
index 0000000..c3954df
--- /dev/null
+++ b/app/components/ui/accordion/AccordionTrigger.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/ui/accordion/index.ts b/app/components/ui/accordion/index.ts
new file mode 100644
index 0000000..92efaba
--- /dev/null
+++ b/app/components/ui/accordion/index.ts
@@ -0,0 +1,4 @@
+export { default as Accordion } from "./Accordion.vue";
+export { default as AccordionContent } from "./AccordionContent.vue";
+export { default as AccordionItem } from "./AccordionItem.vue";
+export { default as AccordionTrigger } from "./AccordionTrigger.vue";
diff --git a/app/registry/blocks/faq-tabs/FaqTabs.vue b/app/registry/blocks/faq-tabs/FaqTabs.vue
new file mode 100644
index 0000000..c17ac85
--- /dev/null
+++ b/app/registry/blocks/faq-tabs/FaqTabs.vue
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+ {{ title }}
+
+
+ {{ description }}
+
+
+
+
+
+
+ {{ category.label }}
+
+
+
+
+
+
+
+ {{ item.question }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/registry/blocks/faq-tabs/index.ts b/app/registry/blocks/faq-tabs/index.ts
new file mode 100644
index 0000000..7e878a4
--- /dev/null
+++ b/app/registry/blocks/faq-tabs/index.ts
@@ -0,0 +1 @@
+export { default as FaqTabs, type FaqTabsCategory, type FaqTabsItem, type FaqTabsProps } from "./FaqTabs.vue";
diff --git a/content/docs/2.components/15.faq-tabs.md b/content/docs/2.components/15.faq-tabs.md
new file mode 100644
index 0000000..91abc4a
--- /dev/null
+++ b/content/docs/2.components/15.faq-tabs.md
@@ -0,0 +1,122 @@
+---
+title: FaqTabs
+description: Render categorized FAQ sections while keeping answer content in your app.
+category: marketing
+---
+
+::component-preview
+---
+name: FaqTabsDemo
+---
+::
+
+## Installation
+
+```bash
+npx shadcn-vue@latest add "https://ui.stackhacker.io/r/faq-tabs.json"
+```
+
+## Usage
+
+```vue
+
+
+
+
+
+```
+
+## App-Owned Answers
+
+`FaqTabs` renders plain answer strings by default. Use `answer` for a single paragraph or `answers` when paragraph boundaries matter.
+
+The component intentionally does not render markdown, import Nuxt MDC, accept `answerHtml`, or use `v-html`. If your app needs rich answer content, render it through the scoped `answer` slot so markdown parsing and sanitization stay app-owned.
+
+```vue
+
+
+
+
+
+```
+
+## Examples
+
+### Categorized FAQ
+
+::component-preview
+---
+name: FaqTabsDemo
+---
+::
+
+## API Reference
+
+### Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `title` | `string` | — | Optional section title. |
+| `description` | `string` | — | Optional supporting section copy. |
+| `categories` | `FaqTabsCategory[] \| null` | `[]` | Categorized FAQ data supplied by your app. |
+| `defaultCategoryValue` | `string` | first category with items | Initial selected category. |
+| `type` | `'single' \| 'multiple'` | `'multiple'` | Accordion expansion behavior inside each category. |
+| `collapsible` | `boolean` | `false` | Allows closing the open item when `type` is `'single'`. |
+| `class` | `string` | — | Additional CSS classes for the section. |
+
+### Types
+
+```ts
+interface FaqTabsCategory {
+ value: string
+ label: string
+ items?: FaqTabsItem[]
+}
+
+interface FaqTabsItem {
+ value: string
+ question: string
+ answer?: string
+ answers?: string[]
+}
+```
+
+### Slots
+
+| Slot | Props | Description |
+|------|-------|-------------|
+| `answer` | `{ category: FaqTabsCategory, item: FaqTabsItem, categoryIndex: number, itemIndex: number }` | Render rich answer content owned by your app. |
diff --git a/scripts/registry-verify.ts b/scripts/registry-verify.ts
index 6a47a3a..8a3a26d 100644
--- a/scripts/registry-verify.ts
+++ b/scripts/registry-verify.ts
@@ -139,6 +139,10 @@ const expectedItems: Record = {
dependencies: [],
registryDependencies: [],
},
+ "faq-tabs": {
+ dependencies: [],
+ registryDependencies: ["accordion", "tabs"],
+ },
};
if (registryIndex) {