From d4b805ea699bd33234c817a7519ff9efbd4753fd Mon Sep 17 00:00:00 2001 From: Darius Cepulis Date: Tue, 14 Apr 2026 17:19:01 -0500 Subject: [PATCH] feat(site): feature and preset reference UI + docs integration (#1258) Co-authored-by: Claude Opus 4.6 (1M context) --- .gitignore | 2 + packages/html/src/presets/audio.ts | 1 + packages/html/src/presets/background.ts | 1 + packages/html/src/presets/video.ts | 1 + packages/react/src/presets/audio/index.ts | 1 + .../react/src/presets/background/index.ts | 1 + packages/react/src/presets/video/index.ts | 1 + site/scripts/api-docs-builder/src/index.ts | 78 +++++- .../api-docs-builder/src/tests/e2e.test.ts | 5 + site/scripts/api-docs-builder/src/types.ts | 6 + site/src/components/docs/DocsLink.astro | 4 +- .../docs/api-reference/ApiActionsTable.astro | 42 ++++ .../docs/api-reference/DetailRow.astro | 16 +- .../docs/api-reference/FeatureReference.astro | 48 ++++ .../docs/api-reference/PresetReference.astro | 228 ++++++++++++++++++ .../docs/api-reference/StateRow.astro | 5 +- site/src/content.config.ts | 29 ++- site/src/content/docs/concepts/overview.mdx | 8 +- site/src/content/docs/concepts/presets.mdx | 26 +- site/src/content/docs/concepts/skins.mdx | 27 +-- .../content/docs/reference/feature-buffer.mdx | 13 +- .../docs/reference/feature-controls.mdx | 13 +- .../content/docs/reference/feature-error.mdx | 18 +- .../docs/reference/feature-fullscreen.mdx | 20 +- .../content/docs/reference/feature-pip.mdx | 20 +- .../docs/reference/feature-playback-rate.mdx | 19 +- .../docs/reference/feature-playback.mdx | 22 +- .../content/docs/reference/feature-source.mdx | 19 +- .../docs/reference/feature-text-tracks.mdx | 22 +- .../content/docs/reference/feature-time.mdx | 20 +- .../content/docs/reference/feature-volume.mdx | 21 +- site/src/types/feature-reference.ts | 24 ++ site/src/types/preset-reference.ts | 27 +++ site/src/utils/featureReferenceModel.js | 59 +++++ site/src/utils/remarkConditionalHeadings.js | 30 +++ 35 files changed, 643 insertions(+), 234 deletions(-) create mode 100644 site/src/components/docs/api-reference/ApiActionsTable.astro create mode 100644 site/src/components/docs/api-reference/FeatureReference.astro create mode 100644 site/src/components/docs/api-reference/PresetReference.astro create mode 100644 site/src/types/feature-reference.ts create mode 100644 site/src/types/preset-reference.ts create mode 100644 site/src/utils/featureReferenceModel.js diff --git a/.gitignore b/.gitignore index 6f890eb5..d6723a32 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ __tests__/coverage/ site/src/content/generated-api-reference/ site/src/content/generated-component-reference/ site/src/content/generated-util-reference/ +site/src/content/generated-feature-reference/ +site/src/content/generated-preset-reference/ site/src/content/ejected-skins.json packages/cli/docs/ diff --git a/packages/html/src/presets/audio.ts b/packages/html/src/presets/audio.ts index 12498901..0255d995 100644 --- a/packages/html/src/presets/audio.ts +++ b/packages/html/src/presets/audio.ts @@ -1,3 +1,4 @@ +/** Audio-only player preset with playback and volume controls. */ export { audioFeatures } from '@videojs/core/dom'; export { MinimalAudioSkinElement } from '../define/audio/minimal-skin'; export { MinimalAudioSkinTailwindElement } from '../define/audio/minimal-skin.tailwind'; diff --git a/packages/html/src/presets/background.ts b/packages/html/src/presets/background.ts index 928fd911..9a6ad5a9 100644 --- a/packages/html/src/presets/background.ts +++ b/packages/html/src/presets/background.ts @@ -1 +1,2 @@ +/** Ambient background video preset with no user controls. */ export { backgroundFeatures } from '@videojs/core/dom'; diff --git a/packages/html/src/presets/video.ts b/packages/html/src/presets/video.ts index befde046..143c74fd 100644 --- a/packages/html/src/presets/video.ts +++ b/packages/html/src/presets/video.ts @@ -1,3 +1,4 @@ +/** General-purpose video player preset with full playback controls. */ export { videoFeatures } from '@videojs/core/dom'; export { MinimalVideoSkinElement } from '../define/video/minimal-skin'; export { MinimalVideoSkinTailwindElement } from '../define/video/minimal-skin.tailwind'; diff --git a/packages/react/src/presets/audio/index.ts b/packages/react/src/presets/audio/index.ts index 49f2a3f8..e96c79c4 100644 --- a/packages/react/src/presets/audio/index.ts +++ b/packages/react/src/presets/audio/index.ts @@ -1,3 +1,4 @@ +/** Audio-only player preset with playback and volume controls. */ export { audioFeatures } from '@videojs/core/dom'; export { Audio, type AudioProps } from '@/media/audio'; export * from './minimal-skin'; diff --git a/packages/react/src/presets/background/index.ts b/packages/react/src/presets/background/index.ts index 6a85b963..75d7dee6 100644 --- a/packages/react/src/presets/background/index.ts +++ b/packages/react/src/presets/background/index.ts @@ -1,3 +1,4 @@ +/** Ambient background video preset with no user controls. */ export { backgroundFeatures } from '@videojs/core/dom'; export { BackgroundVideo, type BackgroundVideoProps } from '@/media/background-video'; export * from './skin'; diff --git a/packages/react/src/presets/video/index.ts b/packages/react/src/presets/video/index.ts index 3e2c1ca5..1158b11a 100644 --- a/packages/react/src/presets/video/index.ts +++ b/packages/react/src/presets/video/index.ts @@ -1,3 +1,4 @@ +/** General-purpose video player preset with full playback controls. */ export { videoFeatures } from '@videojs/core/dom'; export { Video, type VideoProps } from '@/media/video'; export * from './minimal-skin'; diff --git a/site/scripts/api-docs-builder/src/index.ts b/site/scripts/api-docs-builder/src/index.ts index 757bc085..715d0c16 100644 --- a/site/scripts/api-docs-builder/src/index.ts +++ b/site/scripts/api-docs-builder/src/index.ts @@ -1,7 +1,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { generateComponentReferences } from './pipeline.js'; -import { ComponentReferenceSchema } from './types.js'; +import { generateComponentReferences, generateFeatureReferences, generatePresetReferences } from './pipeline.js'; +import { ComponentReferenceSchema, FeatureReferenceSchema, PresetReferenceSchema } from './types.js'; import { generateUtilReferences } from './util-handler.js'; // Magenta prefix - visible on both light and dark terminals @@ -18,6 +18,8 @@ const log = { const MONOREPO_ROOT = path.resolve(import.meta.dirname, '../../../../'); const COMPONENT_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-component-reference'); const UTIL_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-util-reference'); +const FEATURE_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-feature-reference'); +const PRESET_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-preset-reference'); /** * Main entry point. @@ -33,9 +35,11 @@ function main() { originalWarn.apply(console, args); }; - // Ensure output directory exists - if (!fs.existsSync(COMPONENT_OUTPUT_PATH)) { - fs.mkdirSync(COMPONENT_OUTPUT_PATH, { recursive: true }); + // Ensure output directories exist + for (const dir of [COMPONENT_OUTPUT_PATH, UTIL_OUTPUT_PATH, FEATURE_OUTPUT_PATH, PRESET_OUTPUT_PATH]) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } } // Generate component references via pipeline @@ -80,6 +84,70 @@ function main() { log.info(`Done! Generated ${utilResult.success} util files.`); + // Generate feature references + const featureResults = generateFeatureReferences(MONOREPO_ROOT); + + if (featureResults.length === 0) { + log.info('No features found.'); + } else { + log.info(`Found ${featureResults.length} features. Processing...`); + } + + let featureSuccessCount = 0; + for (const result of featureResults) { + const validated = FeatureReferenceSchema.safeParse(result.reference); + if (!validated.success) { + log.error(`Schema validation failed for feature ${result.name}:`); + for (const issue of validated.error.issues) { + log.error(` - ${issue.path.join('.')}: ${issue.message}`); + } + errorCount++; + continue; + } + + const outputFile = path.join(FEATURE_OUTPUT_PATH, `${result.slug}.json`); + const json = `${JSON.stringify(validated.data, null, 2)}\n`; + fs.writeFileSync(outputFile, json); + + log.success(`✅ Generated ${path.basename(outputFile)}`); + featureSuccessCount++; + successCount++; + } + + log.info(`Done! Generated ${featureSuccessCount} feature files.`); + + // Generate preset references + const presetResults = generatePresetReferences(MONOREPO_ROOT); + + if (presetResults.length === 0) { + log.info('No presets found.'); + } else { + log.info(`Found ${presetResults.length} presets. Processing...`); + } + + let presetSuccessCount = 0; + for (const result of presetResults) { + const validated = PresetReferenceSchema.safeParse(result.reference); + if (!validated.success) { + log.error(`Schema validation failed for preset ${result.name}:`); + for (const issue of validated.error.issues) { + log.error(` - ${issue.path.join('.')}: ${issue.message}`); + } + errorCount++; + continue; + } + + const outputFile = path.join(PRESET_OUTPUT_PATH, `${result.name}.json`); + const json = `${JSON.stringify(validated.data, null, 2)}\n`; + fs.writeFileSync(outputFile, json); + + log.success(`✅ Generated ${path.basename(outputFile)}`); + presetSuccessCount++; + successCount++; + } + + log.info(`Done! Generated ${presetSuccessCount} preset files.`); + console.warn = originalWarn; if (errorCount > 0) { diff --git a/site/scripts/api-docs-builder/src/tests/e2e.test.ts b/site/scripts/api-docs-builder/src/tests/e2e.test.ts index 997f8191..b8d6b03e 100644 --- a/site/scripts/api-docs-builder/src/tests/e2e.test.ts +++ b/site/scripts/api-docs-builder/src/tests/e2e.test.ts @@ -894,6 +894,11 @@ describe('Preset pipeline (end-to-end)', () => { // ───────────────────────────────────────────────────────────────── describe('video preset', () => { + it('extracts description from file-level JSDoc', () => { + const ref = findPreset('video')!.reference; + expect(ref.description).toContain('Mock React video preset'); + }); + it('identifies the feature bundle', () => { const ref = findPreset('video')!.reference; expect(ref.featureBundle).toBe('videoFeatures'); diff --git a/site/scripts/api-docs-builder/src/types.ts b/site/scripts/api-docs-builder/src/types.ts index c734399d..41c51a1b 100644 --- a/site/scripts/api-docs-builder/src/types.ts +++ b/site/scripts/api-docs-builder/src/types.ts @@ -13,6 +13,12 @@ export type { export { ComponentReferenceSchema, PartReferenceSchema } from '../../../src/types/component-reference.js'; +export type { FeatureActionDef, FeatureReference, FeatureStateDef } from '../../../src/types/feature-reference.js'; +export { FeatureReferenceSchema } from '../../../src/types/feature-reference.js'; + +export type { PresetReference, PresetSkinDef } from '../../../src/types/preset-reference.js'; +export { PresetReferenceSchema } from '../../../src/types/preset-reference.js'; + /** * Discovered part within a multi-part component. */ diff --git a/site/src/components/docs/DocsLink.astro b/site/src/components/docs/DocsLink.astro index 1bbc91c8..248a64f2 100644 --- a/site/src/components/docs/DocsLink.astro +++ b/site/src/components/docs/DocsLink.astro @@ -7,7 +7,7 @@ import A from '../typography/A.astro'; interface Props extends Omit, 'href'> { slug: string; } -const { slug } = Astro.props; +const { slug, class: className } = Astro.props; const { framework: paramFramework } = Astro.params; if (!paramFramework || !isValidFramework(paramFramework)) { @@ -20,4 +20,4 @@ const { url: href } = resolveDocsLinkUrl({ }); --- - + diff --git a/site/src/components/docs/api-reference/ApiActionsTable.astro b/site/src/components/docs/api-reference/ApiActionsTable.astro new file mode 100644 index 00000000..b409da4f --- /dev/null +++ b/site/src/components/docs/api-reference/ApiActionsTable.astro @@ -0,0 +1,42 @@ +--- +import Table from '@/components/typography/Table.astro'; +import Tbody from '@/components/typography/Tbody.astro'; +import Th from '@/components/typography/Th.astro'; +import Thead from '@/components/typography/Thead.astro'; +import Tr from '@/components/typography/Tr.astro'; +import type { FeatureActionDef } from '@/types/feature-reference'; +import StateRow from './StateRow.astro'; + +interface Props { + actions: Record; + featureName: string; +} + +const { actions, featureName } = Astro.props; + +const actionEntries = Object.entries(actions); +--- + + + + + + + + + + + { + actionEntries.map(([name, def]) => ( + + )) + } + +
ActionTypeDetails
diff --git a/site/src/components/docs/api-reference/DetailRow.astro b/site/src/components/docs/api-reference/DetailRow.astro index bab8d8c0..b5a0295a 100644 --- a/site/src/components/docs/api-reference/DetailRow.astro +++ b/site/src/components/docs/api-reference/DetailRow.astro @@ -32,7 +32,7 @@ const hasDetail = Boolean(attributeName || detailedType || description);