Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Reference for Nuxt UI v4 (125+ components built on Reka UI + Tailwind CSS v4) including forms, overlays, and theming.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/generate-components.ts
1#!/usr/bin/env npx tsx2/**3* Generates nuxt-ui component docs from Nuxt UI repo (cloned to /tmp)4* Run: npx tsx skills/nuxt-ui/scripts/generate-components.ts5*6* Creates:7* - references/components.md (index with version column for Other category)8* - components/<name>.md (per-component details)9*/1011import { execSync } from 'node:child_process'12import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'13import { tmpdir } from 'node:os'14import { basename, dirname, join } from 'node:path'15import { fileURLToPath } from 'node:url'1617const TMP_DIR = join(tmpdir(), 'nuxt-ui-docs-gen')18const REPO_URL = 'https://github.com/nuxt/ui.git'19const DOCS_PATH = 'docs/content/docs/2.components'2021interface ComponentMeta {22name: string23description: string24category: string25rekaLink?: string26version?: string27}2829// Category groupings for better organization30const CATEGORIES: Record<string, string> = {31element: 'Element',32form: 'Form',33data: 'Data',34navigation: 'Navigation',35overlay: 'Overlay',36layout: 'Layout',37}3839// Version mapping for components introduced after v4.040const VERSION_MAP: Record<string, string> = {41'empty': 'v4.1+',42'scroll-area': 'v4.3+',43'input-date': 'v4.2+',44'input-time': 'v4.2+',45'editor': 'v4.3+',46'editor-drag-handle': 'v4.3+',47'editor-emoji-menu': 'v4.3+',48'editor-mention-menu': 'v4.3+',49'editor-suggestion-menu': 'v4.3+',50'editor-toolbar': 'v4.3+',51}5253function parseYamlFrontmatter(content: string): { frontmatter: Record<string, any>, body: string } {54const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)55if (!match)56return { frontmatter: {}, body: content }5758const frontmatter: Record<string, any> = {}59const yamlContent = match[1]6061// Simple YAML parsing for our needs62for (const line of yamlContent.split('\n')) {63const colonIdx = line.indexOf(':')64if (colonIdx > 0 && !line.startsWith(' ') && !line.startsWith('-')) {65const key = line.slice(0, colonIdx).trim()66const value = line.slice(colonIdx + 1).trim()67frontmatter[key] = value.replace(/^['"]|['"]$/g, '')68}69}7071// Parse links for Reka UI reference72if (yamlContent.includes('reka-ui.com')) {73const rekaMatch = yamlContent.match(/to:\s*(https:\/\/reka-ui\.com[^\n]+)/)74if (rekaMatch)75frontmatter.rekaLink = rekaMatch[1]76}7778return { frontmatter, body: match[2] }79}8081function generateComponentFile(name: string, meta: ComponentMeta, body: string): string {82const lines: string[] = []83const displayName = name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')8485lines.push(`# ${displayName}`)86lines.push('')87lines.push(meta.description)88lines.push('')8990if (meta.rekaLink) {91lines.push(`> Based on [Reka UI ${displayName}](${meta.rekaLink})`)92lines.push('')93}9495// Extract key props from body text96const propMentions = body.match(/Use the `(\w+)` prop/g)97if (propMentions && propMentions.length > 0) {98lines.push('## Key Props')99lines.push('')100const uniqueProps = [...new Set(propMentions.map(m => m.match(/`(\w+)`/)?.[1]).filter(Boolean))]101for (const prop of uniqueProps.slice(0, 10)) {102// Find the description after the prop mention103const propRegex = new RegExp(`Use the \`${prop}\` prop ([^.]+\\.?)`)104const desc = body.match(propRegex)?.[1] || ''105lines.push(`- \`${prop}\`: ${desc.replace(/to\s+$/, '').trim()}`)106}107lines.push('')108}109110// Add basic usage111lines.push('## Usage')112lines.push('')113lines.push('```vue')114lines.push(`<U${displayName}`)115lines.push(` <!-- props here -->`)116lines.push(`/>`)117lines.push('```')118lines.push('')119120// Add slot info if present - look for slot mentions in text121const slotPattern = /`#(\w+)`\{?/g122const slotMatches = [...body.matchAll(slotPattern)]123if (slotMatches.length > 0) {124const validSlots = ['default', 'content', 'header', 'body', 'footer', 'title', 'description', 'leading', 'trailing', 'icon', 'label', 'close', 'trigger', 'actions', 'item', 'empty']125const uniqueSlots = [...new Set(slotMatches.map(m => m[1]))]126.filter(s => validSlots.includes(s))127if (uniqueSlots.length > 0) {128lines.push('## Slots')129lines.push('')130for (const slot of uniqueSlots.slice(0, 8)) {131lines.push(`- \`#${slot}\``)132}133lines.push('')134}135}136137return lines.join('\n')138}139140async function main() {141const __dirname = dirname(fileURLToPath(import.meta.url))142const baseDir = join(__dirname, '..')143const componentsDir = join(baseDir, 'components')144145// Clean previous run and clone fresh146rmSync(TMP_DIR, { recursive: true, force: true })147console.log('Cloning nuxt/ui (sparse checkout)...')148try {149execSync(`git clone --depth 1 --filter=blob:none --sparse ${REPO_URL} ${TMP_DIR}`, { stdio: 'inherit' })150execSync(`git sparse-checkout set ${DOCS_PATH}`, { cwd: TMP_DIR, stdio: 'inherit' })151}152catch {153console.error(`\nFailed to clone ${REPO_URL}. Check network/GitHub status.`)154process.exit(1)155}156157const NUXT_UI_DOCS = join(TMP_DIR, DOCS_PATH)158159mkdirSync(componentsDir, { recursive: true })160161console.log('Generating Nuxt UI component docs...')162163const files = readdirSync(NUXT_UI_DOCS).filter(f => f.endsWith('.md') && f !== '0.index.md')164const components: ComponentMeta[] = []165166for (const file of files) {167const name = basename(file, '.md')168const content = readFileSync(join(NUXT_UI_DOCS, file), 'utf-8')169const { frontmatter, body } = parseYamlFrontmatter(content)170171const meta: ComponentMeta = {172name,173description: frontmatter.description || '',174category: frontmatter.category || 'other',175rekaLink: frontmatter.rekaLink,176version: VERSION_MAP[name],177}178components.push(meta)179180// Generate component file181const componentContent = generateComponentFile(name, meta, body)182writeFileSync(join(componentsDir, `${name}.md`), componentContent)183console.log(`✓ Generated components/${name}.md`)184}185186// Generate index187const index: string[] = []188index.push('# Components')189index.push('')190index.push('> Auto-generated from Nuxt UI docs. Run `npx tsx skills/nuxt-ui/scripts/generate-components.ts` to update.')191index.push('')192index.push('> **For headless primitives (API, accessibility):** see `reka-ui` skill')193index.push('')194195// Group by category196const byCategory: Record<string, ComponentMeta[]> = {}197for (const comp of components) {198const cat = CATEGORIES[comp.category] || 'Other'199if (!byCategory[cat])200byCategory[cat] = []201byCategory[cat].push(comp)202}203204// Calculate column widths for alignment205function getMaxLengths(comps: ComponentMeta[], hasVersionCol: boolean) {206let maxComp = 'Component'.length207let maxDesc = 'Description'.length208for (const comp of comps) {209const displayName = comp.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')210const link = `[${displayName}](components/${comp.name}.md)`211if (link.length > maxComp)212maxComp = link.length213const desc = hasVersionCol ? comp.description : (comp.version ? `${comp.description} (${comp.version})` : comp.description)214if (desc.length > maxDesc)215maxDesc = desc.length216}217return { maxComp, maxDesc }218}219220for (const [cat, comps] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {221index.push(`## ${cat}`)222index.push('')223const hasVersionCol = cat === 'Other'224const { maxComp, maxDesc } = getMaxLengths(comps, hasVersionCol)225226if (hasVersionCol) {227index.push(`| ${'Component'.padEnd(maxComp)} | ${'Description'.padEnd(maxDesc)} | Version |`)228index.push(`| ${'-'.repeat(maxComp)} | ${'-'.repeat(maxDesc)} | ------- |`)229}230else {231index.push(`| ${'Component'.padEnd(maxComp)} | ${'Description'.padEnd(maxDesc)} |`)232index.push(`| ${'-'.repeat(maxComp)} | ${'-'.repeat(maxDesc)} |`)233}234235for (const comp of comps.sort((a, b) => a.name.localeCompare(b.name))) {236const displayName = comp.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')237const link = `[${displayName}](components/${comp.name}.md)`238if (hasVersionCol) {239const desc = comp.version ? `${comp.description} (${comp.version})` : comp.description240index.push(`| ${link.padEnd(maxComp)} | ${desc.padEnd(maxDesc)} | ${(comp.version || '').padEnd(7)} |`)241}242else {243const desc = comp.version ? `${comp.description} (${comp.version})` : comp.description244index.push(`| ${link.padEnd(maxComp)} | ${desc.padEnd(maxDesc)} |`)245}246}247index.push('')248}249250writeFileSync(join(baseDir, 'references', 'components.md'), index.join('\n'))251console.log('✓ Generated references/components.md (index)')252253console.log(`\nDone! Generated ${components.length + 1} files.`)254}255256main().catch(console.error)257