From 8a2b0a9954b7565af411a0d62cb5eb9cda8a3207 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Thu, 4 Dec 2025 11:18:44 +0900 Subject: [PATCH 1/2] feat(layer): implement MDC tabs component with Nuxt UI style - Add reference submodules: nuxt/content, nuxt-content/mdc, nuxt/ui - Implement Tabs and TabsItem content components for MDC syntax - Add MDC component mapping in nuxt.config.ts - Update UPageCard to apply padding on default slot - Update index.md to use new ::tabs-item MDC syntax - Update ref-analysis.md with Nuxt Content, MDC, and Nuxt UI analysis - Add MDC component mapping documentation BREAKING CHANGE: Tabs now use ::tabs-item{label="..."} syntax instead of ::tabs-list/::tabs-trigger/::tabs-content --- .gitmodules | 9 + CLAUDE.md | 5 + apps/docs/content/index.md | 50 ++--- bun.lock | 1 + docs/ref-analysis.md | 192 +++++++++++++++++- .../layer/app/components/content/Tabs.vue | 109 +++++++++- .../app/components/content/TabsContent.vue | 14 -- .../layer/app/components/content/TabsItem.vue | 26 +++ .../layer/app/components/content/TabsList.vue | 13 -- .../app/components/content/TabsTrigger.vue | 13 -- .../app/components/content/UPageCard.vue | 4 +- packages/layer/nuxt.config.ts | 13 ++ ref/content | 1 + ref/mdc | 1 + ref/ui | 1 + 15 files changed, 372 insertions(+), 80 deletions(-) delete mode 100644 packages/layer/app/components/content/TabsContent.vue create mode 100644 packages/layer/app/components/content/TabsItem.vue delete mode 100644 packages/layer/app/components/content/TabsList.vue delete mode 100644 packages/layer/app/components/content/TabsTrigger.vue create mode 160000 ref/content create mode 160000 ref/mdc create mode 160000 ref/ui diff --git a/.gitmodules b/.gitmodules index e6220a9c..1afb13ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,12 @@ [submodule "ref/docus"] path = ref/docus url = https://github.com/nuxt-content/docus +[submodule "ref/content"] + path = ref/content + url = https://github.com/nuxt/content +[submodule "ref/mdc"] + path = ref/mdc + url = https://github.com/nuxt-content/mdc +[submodule "ref/ui"] + path = ref/ui + url = https://github.com/nuxt/ui diff --git a/CLAUDE.md b/CLAUDE.md index fff650a6..ecd0334e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,6 +89,11 @@ export default defineNuxtConfig({ }) ``` +## References + +- [Nuxt Content Markdown Documentation](https://content.nuxt.com/raw/docs/files/markdown.md) +- [MDC Prose Components](https://github.com/nuxt-content/mdc/tree/main/src/runtime/components/prose) + ## ESLint Uses `@antfu/eslint-config` with Vue and formatters enabled. diff --git a/apps/docs/content/index.md b/apps/docs/content/index.md index dabe7d9e..a94a112f 100644 --- a/apps/docs/content/index.md +++ b/apps/docs/content/index.md @@ -57,38 +57,30 @@ Ship fast, flexible, and SEO-optimized documentation with beautiful design out o ::::u-page-card --- spotlight: true - class: col-span-1 lg:col-span-2 + class: col-span-2 --- - :::::tabs{default-value="preview"} - ::::::tabs-list - :::::::tabs-trigger{value="preview"} - Preview - ::::::: - :::::::tabs-trigger{value="code"} - Code - ::::::: - :::::: - ::::::tabs-content{value="preview"} - :::::::div{class="flex flex-col gap-4"} - ::::::::note{class="my-0"} - Here's some additional information for you. - :::::::: - - ::::::::tip{class="my-0"} - Here's a helpful suggestion. - :::::::: - - ::::::::warning{class="my-0"} - Be careful with this action as it might have unexpected results. - :::::::: - - ::::::::caution{class="my-0"} - This action cannot be undone. - :::::::: - ::::::: + :::::tabs + ::::::tabs-item{label="Preview" class="mt-5"} + :::::::div{class="flex flex-col gap-4"} + ::::::::note{class="my-0"} + Here's some additional information for you. + :::::::: + + ::::::::tip{class="my-0"} + Here's a helpful suggestion. + :::::::: + + ::::::::warning{class="my-0"} + Be careful with this action as it might have unexpected results. + :::::::: + + ::::::::caution{class="my-0"} + This action cannot be undone. + :::::::: + ::::::: :::::: - ::::::tabs-content{value="code" class="text-sm overflow-x-auto"} + ::::::tabs-item{label="Code" class="mt-5 mb-2 text-xs overflow-x-auto"} ```mdc ::note Here's some additional information. diff --git a/bun.lock b/bun.lock index 204af059..0e3b4785 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "docs-please-workspace", diff --git a/docs/ref-analysis.md b/docs/ref-analysis.md index 16bf7f38..3d13d8c8 100644 --- a/docs/ref-analysis.md +++ b/docs/ref-analysis.md @@ -152,7 +152,175 @@ Override markdown rendering: --- -## 3. Component Mapping +## 3. Nuxt Content Analysis (`ref/content`) + +Core content module for Nuxt - file-based CMS with markdown support. + +### Architecture + +``` +src/ +├── runtime/ +│ ├── components/ # ContentRenderer, ContentPreviewMode +│ └── utils/ # Query helpers, transformers +├── utils/ +│ └── content/ +│ └── transformers/ # CSV, YAML, JSON parsers +└── module.ts # Module setup +``` + +### Key Features + +- File-based content management (Markdown, YAML, JSON, CSV) +- SQLite-powered content database +- Collection-based content organization +- Query builder API + +--- + +## 4. MDC Analysis (`ref/mdc`) + +Markdown Components parser - enables Vue components in Markdown. + +### Architecture + +``` +src/runtime/ +├── components/ +│ └── prose/ # Prose components +│ ├── ProseA.vue +│ ├── ProseCode.vue +│ ├── ProseH1-H6.vue +│ ├── ProsePre.vue +│ ├── ProseTable.vue +│ └── ... +├── parser/ # MDC syntax parser +└── highlighter/ # Code highlighting +``` + +### Prose Components + +Default HTML element overrides for markdown rendering: +- `ProseA`, `ProseP`, `ProseStrong`, `ProseEm` +- `ProseH1` - `ProseH6` +- `ProsePre`, `ProseCode` +- `ProseTable`, `ProseTh`, `ProseTd`, `ProseTr` +- `ProseUl`, `ProseOl`, `ProseLi` +- `ProseBlockquote`, `ProseHr`, `ProseImg` + +--- + +## 5. Nuxt UI Analysis (`ref/ui`) + +UI component library with MDC integration. + +### Architecture + +``` +src/ +├── module.ts # Module setup + MDC mapping +└── runtime/ + ├── components/ + │ ├── prose/ # MDC prose components + │ │ ├── Tabs.vue # ::tabs container + │ │ ├── TabsItem.vue # ::tabs-item + │ │ ├── Callout.vue # ::callout + │ │ ├── Note.vue # ::note + │ │ ├── Tip.vue # ::tip + │ │ ├── Warning.vue # ::warning + │ │ ├── Caution.vue # ::caution + │ │ ├── Steps.vue # ::steps + │ │ ├── CodeGroup.vue # ::code-group + │ │ └── ... + │ ├── content/ # Content-specific components + │ └── Tabs.vue # Base tabs component (UTabs) + ├── composables/ + └── utils/ +``` + +### Prose Tabs Implementation + +**`prose/Tabs.vue`** - Container component: +- Extracts child `tabs-item` components from slots via `transformSlot()` +- Creates `items` array with `{ label, icon, component }` +- Supports `sync` prop for localStorage persistence +- Supports `hash` prop for scroll-to-section + +**`prose/TabsItem.vue`** - Individual tab: +- Exposes `label`, `description`, `icon` props +- Parent reads these props to build tab headers + +--- + +## 6. MDC Component Mapping + +Nuxt UI's `module.ts` (line 188-213) shows how MDC syntax maps to Vue components: + +```typescript +// nuxt.config.ts or module setup +mdc: { + components: { + map: { + 'accordion': 'ProseAccordion', + 'accordion-item': 'ProseAccordionItem', + 'badge': 'ProseBadge', + 'callout': 'ProseCallout', + 'card': 'ProseCard', + 'card-group': 'ProseCardGroup', + 'caution': 'ProseCaution', + 'code-collapse': 'ProseCodeCollapse', + 'code-group': 'ProseCodeGroup', + 'code-icon': 'ProseCodeIcon', + 'code-preview': 'ProseCodePreview', + 'code-tree': 'ProseCodeTree', + 'collapsible': 'ProseCollapsible', + 'field': 'ProseField', + 'field-group': 'ProseFieldGroup', + 'icon': 'ProseIcon', + 'kbd': 'ProseKbd', + 'note': 'ProseNote', + 'steps': 'ProseSteps', + 'tabs': 'ProseTabs', + 'tabs-item': 'ProseTabsItem', + 'tip': 'ProseTip', + 'warning': 'ProseWarning' + } + } +} +``` + +### How It Works + +| MDC Syntax | Vue Component | Location | +|------------|---------------|----------| +| `::tabs` | `ProseTabs.vue` | `components/content/` or global | +| `::tabs-item{label="Tab 1"}` | `ProseTabsItem.vue` | `components/content/` or global | +| `::note` | `ProseNote.vue` | `components/content/` or global | + +### For @pleaseai/docs (without @nuxt/ui) + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + mdc: { + components: { + map: { + 'tabs': 'Tabs', + 'tabs-item': 'TabsItem', + 'note': 'Note', + 'tip': 'Tip', + 'warning': 'Warning', + 'caution': 'Caution', + // custom mappings for components in components/content/ + } + } + } +}) +``` + +--- + +## 7. Component Mapping | Docus (nuxt-ui) | Layer (shadcn-vue) | | -------------------- | --------------------------- | @@ -170,7 +338,7 @@ Override markdown rendering: --- -## 4. Key Files to Reference +## 8. Key Files to Reference ### From Docus @@ -189,9 +357,25 @@ Override markdown rendering: - `components/DocsTableOfContents.vue` - TOC - `composables/useNavigation.ts` - Navigation +### From Nuxt UI (`ref/ui`) + +- `src/module.ts` - MDC component mapping (line 188-213) +- `src/runtime/components/prose/Tabs.vue` - MDC tabs container +- `src/runtime/components/prose/TabsItem.vue` - Tab item wrapper +- `src/runtime/components/Tabs.vue` - Base tabs component (UTabs) + +### From MDC (`ref/mdc`) + +- `src/runtime/components/prose/` - All default prose components + +### From Nuxt Content (`ref/content`) + +- `src/runtime/components/ContentRenderer.vue` - Content rendering +- `docs/content/` - Documentation examples with MDC syntax + --- -## 5. Dependencies +## 9. Dependencies ### Docus @@ -222,7 +406,7 @@ Override markdown rendering: --- -## 6. Design Decisions for @pleaseai/docs +## 10. Design Decisions for @pleaseai/docs 1. **No nuxt-ui** - Use reka-ui primitives via shadcn-vue 2. **Tailwind v4** - oklch colors, @theme inline diff --git a/packages/layer/app/components/content/Tabs.vue b/packages/layer/app/components/content/Tabs.vue index e8e3c7ff..cf749bbd 100644 --- a/packages/layer/app/components/content/Tabs.vue +++ b/packages/layer/app/components/content/Tabs.vue @@ -1,13 +1,112 @@ diff --git a/packages/layer/app/components/content/TabsContent.vue b/packages/layer/app/components/content/TabsContent.vue deleted file mode 100644 index b4bf3b4e..00000000 --- a/packages/layer/app/components/content/TabsContent.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/packages/layer/app/components/content/TabsItem.vue b/packages/layer/app/components/content/TabsItem.vue new file mode 100644 index 00000000..c6753f12 --- /dev/null +++ b/packages/layer/app/components/content/TabsItem.vue @@ -0,0 +1,26 @@ + + + diff --git a/packages/layer/app/components/content/TabsList.vue b/packages/layer/app/components/content/TabsList.vue deleted file mode 100644 index 1815d069..00000000 --- a/packages/layer/app/components/content/TabsList.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/packages/layer/app/components/content/TabsTrigger.vue b/packages/layer/app/components/content/TabsTrigger.vue deleted file mode 100644 index c6aa2763..00000000 --- a/packages/layer/app/components/content/TabsTrigger.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/packages/layer/app/components/content/UPageCard.vue b/packages/layer/app/components/content/UPageCard.vue index 33eb395b..a32bc446 100644 --- a/packages/layer/app/components/content/UPageCard.vue +++ b/packages/layer/app/components/content/UPageCard.vue @@ -69,14 +69,14 @@ const isLink = computed(() => !!props.to) -
-
+ diff --git a/packages/layer/nuxt.config.ts b/packages/layer/nuxt.config.ts index 1e6540c9..166e5dc3 100644 --- a/packages/layer/nuxt.config.ts +++ b/packages/layer/nuxt.config.ts @@ -48,6 +48,19 @@ export default defineNuxtConfig({ }, }, }, + mdc: { + components: { + map: { + 'callout': 'Callout', + 'caution': 'Caution', + 'note': 'Note', + 'tip': 'Tip', + 'warning': 'Warning', + 'tabs': 'Tabs', + 'tabs-item': 'TabsItem', + }, + }, + }, compatibilityDate: '2025-01-01', nitro: { prerender: { diff --git a/ref/content b/ref/content new file mode 160000 index 00000000..fbfd05b3 --- /dev/null +++ b/ref/content @@ -0,0 +1 @@ +Subproject commit fbfd05b3aa1c6c500417d99ddbfe524936cfaa7b diff --git a/ref/mdc b/ref/mdc new file mode 160000 index 00000000..d4483f6d --- /dev/null +++ b/ref/mdc @@ -0,0 +1 @@ +Subproject commit d4483f6d85830232fd647d44daa67ef4f2b0ebc3 diff --git a/ref/ui b/ref/ui new file mode 160000 index 00000000..8dc6b222 --- /dev/null +++ b/ref/ui @@ -0,0 +1 @@ +Subproject commit 8dc6b222c9a3f265073a25e04f105c42c33aacc9 From 1807b1543a09b0141d9b71ca8c2381d812a5e667 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Thu, 4 Dec 2025 11:27:45 +0900 Subject: [PATCH 2/2] feat(layer): enhance MDC tabs with icons, type safety, and accessibility - Fix index bug in slot transformation for fragment handling - Add icon prop support with aria-hidden for accessibility - Replace any types with VNode for type safety - Update documentation for new ::tabs-item API - Add aria-hidden to UPageCard decorative icons --- apps/docs/content/docs/2.components/6.tabs.md | 205 ++++++++++-------- apps/docs/content/index.md | 4 +- .../layer/app/components/app/AppFooter.vue | 2 +- .../layer/app/components/content/Tabs.vue | 51 +++-- .../layer/app/components/content/TabsItem.vue | 4 +- .../app/components/content/UPageCard.vue | 3 +- 6 files changed, 152 insertions(+), 117 deletions(-) diff --git a/apps/docs/content/docs/2.components/6.tabs.md b/apps/docs/content/docs/2.components/6.tabs.md index 32213893..9aa4e290 100644 --- a/apps/docs/content/docs/2.components/6.tabs.md +++ b/apps/docs/content/docs/2.components/6.tabs.md @@ -5,50 +5,32 @@ description: Tabbed content sections for organizing related content # Tabs -A component system for creating tabbed content sections. Useful for showing multiple code examples, alternative approaches, or organizing related content. +A component for creating tabbed content sections. Useful for showing multiple code examples, alternative approaches, or organizing related content. ## Usage -Create tabs using the `::tabs`, `::tabs-list`, `::tabs-trigger`, and `::tabs-content` components: +Create tabs using the `::tabs` and `::tabs-item` components: ```mdc -::tabs{default-value="tab1"} - ::tabs-list - ::tabs-trigger{value="tab1"} - First Tab - :: - ::tabs-trigger{value="tab2"} - Second Tab - :: - :: - - ::tabs-content{value="tab1"} +::tabs + ::tabs-item{label="First Tab"} Content for the first tab. :: - ::tabs-content{value="tab2"} + ::tabs-item{label="Second Tab"} Content for the second tab. :: :: ``` -::tabs{default-value="tab1"} -::tabs-list -::tabs-trigger{value="tab1"} -First Tab -:: -::tabs-trigger{value="tab2"} -Second Tab -:: -:: - -::tabs-content{value="tab1"} -Content for the first tab. -:: +::tabs + ::tabs-item{label="First Tab"} + Content for the first tab. + :: -::tabs-content{value="tab2"} -Content for the second tab. -:: + ::tabs-item{label="Second Tab"} + Content for the second tab. + :: :: ## Code Examples @@ -56,17 +38,8 @@ Content for the second tab. Tabs are useful for showing code in different languages or frameworks: ```mdc -::tabs{default-value="vue"} - ::tabs-list - ::tabs-trigger{value="vue"} - Vue - :: - ::tabs-trigger{value="react"} - React - :: - :: - - ::tabs-content{value="vue"} +::tabs + ::tabs-item{label="Vue"} \`\`\`vue + + + ``` -```vue - + :: - -``` + ::tabs-item{label="React"} + ```tsx + function Counter() { + const [count, setCount] = useState(0) + return + } + ``` + + :: :: -::tabs-content{value="react"} +## Package Manager Example -```tsx -function Counter() { - const [count, setCount] = useState(0) - return -} -``` +Use the `sync` prop to remember the user's choice across tabs: +```mdc +::tabs{sync="pm"} + ::tabs-item{label="npm"} + \`\`\`bash + npm install @pleaseai/docs + \`\`\` + :: + + ::tabs-item{label="pnpm"} + \`\`\`bash + pnpm add @pleaseai/docs + \`\`\` + :: + + ::tabs-item{label="bun"} + \`\`\`bash + bun add @pleaseai/docs + \`\`\` + :: :: -:: +``` -## Components +::tabs{sync="pm"} + ::tabs-item{label="npm"} -### Tabs + ```bash + npm install @pleaseai/docs + ``` -The root container for the tab system. + :: + + ::tabs-item{label="pnpm"} + + ```bash + pnpm add @pleaseai/docs + ``` -| Prop | Type | Default | Description | -| --------------- | -------- | ------- | -------------------------- | -| `default-value` | `string` | - | Initially active tab value | -| `class` | `string` | - | Additional CSS classes | + :: + + ::tabs-item{label="bun"} + + ```bash + bun add @pleaseai/docs + ``` + + :: +:: + +## With Icons + +Add icons to tab triggers using the `icon` prop: -### TabsList +```mdc +::tabs + ::tabs-item{label="Vue" icon="i-logos-vue"} + Vue content here. + :: -Container for tab triggers/buttons. + ::tabs-item{label="React" icon="i-logos-react"} + React content here. + :: +:: +``` -| Prop | Type | Default | Description | -| ------- | -------- | ------- | ---------------------- | -| `class` | `string` | - | Additional CSS classes | +## Components -### TabsTrigger +### Tabs -Individual tab button. +The root container for the tab system. -| Prop | Type | Default | Description | -| ------- | -------- | ------------ | ----------------------------- | -| `value` | `string` | **required** | Unique identifier for the tab | -| `class` | `string` | - | Additional CSS classes | +| Prop | Type | Default | Description | +| -------------- | -------- | ------- | ---------------------------------------------------- | +| `defaultIndex` | `number` | `0` | Initially active tab index (0-based) | +| `sync` | `string` | - | Sync tab selection across tabs with this key via localStorage | +| `class` | `string` | - | Additional CSS classes | -### TabsContent +### TabsItem -Content panel for each tab. +Individual tab content panel with its trigger label. -| Prop | Type | Default | Description | -| ------- | -------- | ------------ | ------------------------------ | -| `value` | `string` | **required** | Must match a TabsTrigger value | -| `class` | `string` | - | Additional CSS classes | +| Prop | Type | Default | Description | +| ------- | -------- | ------------ | ---------------------------------- | +| `label` | `string` | **required** | The label displayed in the tab trigger | +| `icon` | `string` | - | Optional icon name for the tab | +| `class` | `string` | - | Additional CSS classes | ## Styling diff --git a/apps/docs/content/index.md b/apps/docs/content/index.md index a94a112f..627bfe12 100644 --- a/apps/docs/content/index.md +++ b/apps/docs/content/index.md @@ -60,7 +60,7 @@ Ship fast, flexible, and SEO-optimized documentation with beautiful design out o class: col-span-2 --- :::::tabs - ::::::tabs-item{label="Preview" class="mt-5"} + ::::::tabs-item{label="Preview" icon="lucide:eye" class="mt-5"} :::::::div{class="flex flex-col gap-4"} ::::::::note{class="my-0"} Here's some additional information for you. @@ -80,7 +80,7 @@ Ship fast, flexible, and SEO-optimized documentation with beautiful design out o ::::::: :::::: - ::::::tabs-item{label="Code" class="mt-5 mb-2 text-xs overflow-x-auto"} + ::::::tabs-item{label="Code" icon="lucide:code" class="mt-5 mb-2 text-xs overflow-x-auto"} ```mdc ::note Here's some additional information. diff --git a/packages/layer/app/components/app/AppFooter.vue b/packages/layer/app/components/app/AppFooter.vue index 58646cf5..f28593a0 100644 --- a/packages/layer/app/components/app/AppFooter.vue +++ b/packages/layer/app/components/app/AppFooter.vue @@ -13,7 +13,7 @@ const title = computed(() => appConfig.docs?.title || 'Docs') target="_blank" class="font-medium underline underline-offset-4" > - @pleaseai/docs + docs please

diff --git a/packages/layer/app/components/content/Tabs.vue b/packages/layer/app/components/content/Tabs.vue index cf749bbd..e5dcb24c 100644 --- a/packages/layer/app/components/content/Tabs.vue +++ b/packages/layer/app/components/content/Tabs.vue @@ -1,5 +1,5 @@ diff --git a/packages/layer/app/components/content/UPageCard.vue b/packages/layer/app/components/content/UPageCard.vue index a32bc446..665f6fc4 100644 --- a/packages/layer/app/components/content/UPageCard.vue +++ b/packages/layer/app/components/content/UPageCard.vue @@ -37,7 +37,7 @@ const isLink = computed(() => !!props.to) >

- +
!!props.to)