mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(site): feature and preset reference UI + docs integration (#1258)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5301202a18
commit
d4b805ea69
@@ -7,7 +7,7 @@ import A from '../typography/A.astro';
|
||||
interface Props extends Omit<HTMLAttributes<'a'>, '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({
|
||||
});
|
||||
---
|
||||
|
||||
<A href={href}><slot /></A>
|
||||
<A href={href} class={className}><slot /></A>
|
||||
|
||||
@@ -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<string, FeatureActionDef>;
|
||||
featureName: string;
|
||||
}
|
||||
|
||||
const { actions, featureName } = Astro.props;
|
||||
|
||||
const actionEntries = Object.entries(actions);
|
||||
---
|
||||
|
||||
<Table maxWidth={false} outerClass="my-6">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Action</Th>
|
||||
<Th>Type</Th>
|
||||
<Th>Details</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{
|
||||
actionEntries.map(([name, def]) => (
|
||||
<StateRow
|
||||
name={name}
|
||||
type={def.type}
|
||||
detailedType={def.detailedType}
|
||||
description={def.description}
|
||||
componentName={featureName}
|
||||
kind="action"
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Tbody>
|
||||
</Table>
|
||||
@@ -32,7 +32,7 @@ const hasDetail = Boolean(attributeName || detailedType || description);
|
||||
|
||||
<Tr
|
||||
id={id}
|
||||
data-detail-row
|
||||
data-apiref-row
|
||||
class={clsx(
|
||||
hasDetail &&
|
||||
"group cursor-pointer data-expanded:border-transparent dark:data-expanded:border-transparent intent:bg-manila-75 dark:intent:bg-soot",
|
||||
@@ -46,7 +46,7 @@ const hasDetail = Boolean(attributeName || detailedType || description);
|
||||
aria-expanded="false"
|
||||
aria-controls={`${id}-detail`}
|
||||
aria-label={`Details for ${name}`}
|
||||
data-detail-toggle
|
||||
data-apiref-toggle
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -66,8 +66,8 @@ const hasDetail = Boolean(attributeName || detailedType || description);
|
||||
<Td colspan={String(colspan)} class="p-0">
|
||||
<div class="px-6 py-4">
|
||||
<dl
|
||||
class="grid grid-cols-(--cols) gap-x-6 gap-y-3"
|
||||
style="--cols: auto 1fr"
|
||||
class="grid grid-cols-(--grid-cols) gap-x-6 gap-y-3"
|
||||
style="--grid-cols: auto 1fr"
|
||||
>
|
||||
{attributeName && (
|
||||
<>
|
||||
@@ -104,13 +104,13 @@ const hasDetail = Boolean(attributeName || detailedType || description);
|
||||
|
||||
<script>
|
||||
document
|
||||
.querySelectorAll<HTMLButtonElement>("[data-detail-toggle]")
|
||||
.querySelectorAll<HTMLButtonElement>("[data-apiref-toggle]")
|
||||
.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const expanded = button.getAttribute("aria-expanded") === "true";
|
||||
button.setAttribute("aria-expanded", String(!expanded));
|
||||
|
||||
const row = button.closest<HTMLTableRowElement>("[data-detail-row]");
|
||||
const row = button.closest<HTMLTableRowElement>("[data-apiref-row]");
|
||||
if (row) row.toggleAttribute("data-expanded", !expanded);
|
||||
|
||||
const detail = document.getElementById(
|
||||
@@ -121,11 +121,11 @@ const hasDetail = Boolean(attributeName || detailedType || description);
|
||||
});
|
||||
|
||||
document
|
||||
.querySelectorAll<HTMLTableRowElement>("[data-detail-row]")
|
||||
.querySelectorAll<HTMLTableRowElement>("[data-apiref-row]")
|
||||
.forEach((row) => {
|
||||
row.addEventListener("click", (e) => {
|
||||
if ((e.target as Element).closest("a, button")) return;
|
||||
row.querySelector<HTMLButtonElement>("[data-detail-toggle]")?.click();
|
||||
row.querySelector<HTMLButtonElement>("[data-apiref-toggle]")?.click();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
import { getEntry } from 'astro:content';
|
||||
import ContentWidth from '@/components/frames/ContentWidth.astro';
|
||||
import H2 from '@/components/typography/H2Markdown.astro';
|
||||
import H3 from '@/components/typography/H3Markdown.astro';
|
||||
import type { FeatureReference } from '@/types/feature-reference';
|
||||
import { createFeatureReferenceModel } from '@/utils/featureReferenceModel';
|
||||
import ApiActionsTable from './ApiActionsTable.astro';
|
||||
import ApiStateTable from './ApiStateTable.astro';
|
||||
|
||||
interface Props {
|
||||
feature: string;
|
||||
}
|
||||
|
||||
const { feature } = Astro.props;
|
||||
|
||||
const entry = await getEntry('featureReference', feature);
|
||||
const ref: FeatureReference | null = entry?.data ?? null;
|
||||
if (!ref) return;
|
||||
|
||||
const model = createFeatureReferenceModel(feature, ref);
|
||||
if (!model) return;
|
||||
|
||||
const stateSection = model.sections.find((s) => s.key === 'state');
|
||||
const actionsSection = model.sections.find((s) => s.key === 'actions');
|
||||
---
|
||||
|
||||
<ContentWidth>
|
||||
<H2 id={model.heading.id}>{model.heading.text}</H2>
|
||||
|
||||
{
|
||||
stateSection && (
|
||||
<>
|
||||
<H3 id={stateSection.id}>{stateSection.title}</H3>
|
||||
<ApiStateTable state={model.data.state} componentName={feature} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
actionsSection && (
|
||||
<>
|
||||
<H3 id={actionsSection.id}>{actionsSection.title}</H3>
|
||||
<ApiActionsTable actions={model.data.actions} featureName={feature} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
</ContentWidth>
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import ContentWidth from '@/components/frames/ContentWidth.astro';
|
||||
import MarkdownCode from '@/components/typography/MarkdownCode.astro';
|
||||
import Table from '@/components/typography/Table.astro';
|
||||
import Tbody from '@/components/typography/Tbody.astro';
|
||||
import Td from '@/components/typography/Td.astro';
|
||||
import Th from '@/components/typography/Th.astro';
|
||||
import Thead from '@/components/typography/Thead.astro';
|
||||
import Tr from '@/components/typography/Tr.astro';
|
||||
import { isValidFramework } from '@/types/docs';
|
||||
import type { PresetReference as PresetReferenceType } from '@/types/preset-reference';
|
||||
import DocsLink from '../DocsLink.astro';
|
||||
import FrameworkCase from '../FrameworkCase.astro';
|
||||
import InlineMarkdown from './InlineMarkdown.astro';
|
||||
|
||||
const entries = await getCollection('presetReference');
|
||||
const PINNED_PRESETS = ['video', 'audio'];
|
||||
const presets: PresetReferenceType[] = entries
|
||||
.map((e) => e.data)
|
||||
.sort((a, b) => {
|
||||
const aPin = PINNED_PRESETS.indexOf(a.name);
|
||||
const bPin = PINNED_PRESETS.indexOf(b.name);
|
||||
if (aPin !== -1 && bPin !== -1) return aPin - bPin;
|
||||
if (aPin !== -1) return -1;
|
||||
if (bPin !== -1) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
const { framework } = Astro.params;
|
||||
const pkg = framework && isValidFramework(framework) ? `@videojs/${framework}` : '@videojs/html';
|
||||
|
||||
/**
|
||||
* Feature slug overrides for cases where kebabCase(featureName) doesn't
|
||||
* match the docs page slug.
|
||||
*/
|
||||
const FEATURE_SLUG_OVERRIDES: Record<string, string> = {
|
||||
textTrack: 'text-tracks',
|
||||
};
|
||||
|
||||
/**
|
||||
* Native media elements used by presets that don't have a custom element.
|
||||
* The pipeline can't detect these since they're not classes with `static tagName`.
|
||||
*/
|
||||
const NATIVE_MEDIA_ELEMENTS: Record<string, string> = {
|
||||
video: 'video',
|
||||
audio: 'audio',
|
||||
};
|
||||
|
||||
function featureDocsSlug(featureName: string): string {
|
||||
const override = FEATURE_SLUG_OVERRIDES[featureName];
|
||||
if (override) return `reference/feature-${override}`;
|
||||
const kebab = featureName.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
||||
return `reference/feature-${kebab}`;
|
||||
}
|
||||
|
||||
function htmlMediaElement(preset: PresetReferenceType): string | undefined {
|
||||
return preset.html.mediaElement ?? NATIVE_MEDIA_ELEMENTS[preset.name];
|
||||
}
|
||||
|
||||
const listFormat = new Intl.ListFormat('en', { style: 'long', type: 'unit' });
|
||||
---
|
||||
|
||||
<ContentWidth>
|
||||
<Table maxWidth={false} outerClass="my-6">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Import</Th>
|
||||
<Th>Description</Th>
|
||||
<Th><span class="sr-only">Details</span></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{
|
||||
presets.map((preset) => {
|
||||
const id = `preset-${preset.name}`;
|
||||
const htmlMedia = htmlMediaElement(preset);
|
||||
return (
|
||||
<>
|
||||
<Tr
|
||||
id={id}
|
||||
data-preset-row
|
||||
class="group cursor-pointer data-expanded:border-transparent dark:data-expanded:border-transparent intent:bg-manila-75 dark:intent:bg-soot"
|
||||
>
|
||||
<Td class="align-top">
|
||||
<MarkdownCode>{`${pkg}/${preset.name}`}</MarkdownCode>
|
||||
</Td>
|
||||
<Td class="align-top">
|
||||
{preset.description && (
|
||||
<InlineMarkdown content={preset.description} />
|
||||
)}
|
||||
</Td>
|
||||
<Td class="align-top">
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-controls={`${id}-detail`}
|
||||
aria-label={`Details for ${preset.name} preset`}
|
||||
data-preset-toggle
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="inline-block group-data-expanded:rotate-90"
|
||||
>
|
||||
▸
|
||||
</span>
|
||||
</button>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr id={`${id}-detail`} hidden>
|
||||
<Td colspan="3" class="p-0">
|
||||
<div class="px-6 py-4">
|
||||
<dl
|
||||
class="grid grid-cols-(--grid-cols) gap-x-6 gap-y-3"
|
||||
style="--grid-cols: auto 1fr"
|
||||
>
|
||||
<dt class="font-bold">Feature bundle</dt>
|
||||
<dd>
|
||||
<MarkdownCode>{preset.featureBundle}</MarkdownCode>
|
||||
</dd>
|
||||
|
||||
<dt class="font-bold">Features</dt>
|
||||
<dd>
|
||||
{preset.features.length > 0
|
||||
? listFormat
|
||||
.formatToParts(preset.features)
|
||||
.map((part) =>
|
||||
part.type === "element" ? (
|
||||
<DocsLink
|
||||
class="inline-block whitespace-nowrap"
|
||||
slug={featureDocsSlug(part.value)}
|
||||
>
|
||||
{part.value}
|
||||
</DocsLink>
|
||||
) : (
|
||||
<span>{part.value}</span>
|
||||
),
|
||||
)
|
||||
: "–"}
|
||||
</dd>
|
||||
|
||||
<dt class="font-bold">Skins</dt>
|
||||
<dd>
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
{preset.react.skins.length > 0
|
||||
? listFormat
|
||||
.formatToParts(
|
||||
preset.react.skins.map((s) => s.name),
|
||||
)
|
||||
.map((part) =>
|
||||
part.type === "element" ? (
|
||||
<MarkdownCode class="inline-block whitespace-nowrap">{`<${part.value}>`}</MarkdownCode>
|
||||
) : (
|
||||
<span>{part.value}</span>
|
||||
),
|
||||
)
|
||||
: "–"}
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
{preset.html.skins.length > 0
|
||||
? listFormat
|
||||
.formatToParts(
|
||||
preset.html.skins.map(
|
||||
(s) => s.tagName ?? s.name,
|
||||
),
|
||||
)
|
||||
.map((part) =>
|
||||
part.type === "element" ? (
|
||||
<MarkdownCode class="inline-block whitespace-nowrap">{`<${part.value}>`}</MarkdownCode>
|
||||
) : (
|
||||
<span>{part.value}</span>
|
||||
),
|
||||
)
|
||||
: "–"}
|
||||
</FrameworkCase>
|
||||
</dd>
|
||||
|
||||
<dt class="font-bold">Default media element</dt>
|
||||
<dd>
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<MarkdownCode>{`<${preset.react.mediaElement}>`}</MarkdownCode>
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
{htmlMedia ? (
|
||||
<MarkdownCode>{`<${htmlMedia}>`}</MarkdownCode>
|
||||
) : (
|
||||
"–"
|
||||
)}
|
||||
</FrameworkCase>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</ContentWidth>
|
||||
|
||||
<script>
|
||||
document
|
||||
.querySelectorAll<HTMLButtonElement>("[data-preset-toggle]")
|
||||
.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const expanded = button.getAttribute("aria-expanded") === "true";
|
||||
button.setAttribute("aria-expanded", String(!expanded));
|
||||
|
||||
const row = button.closest<HTMLTableRowElement>("[data-preset-row]");
|
||||
if (row) row.toggleAttribute("data-expanded", !expanded);
|
||||
|
||||
const detail = document.getElementById(
|
||||
button.getAttribute("aria-controls")!,
|
||||
);
|
||||
if (detail) detail.hidden = expanded;
|
||||
});
|
||||
});
|
||||
|
||||
document
|
||||
.querySelectorAll<HTMLTableRowElement>("[data-preset-row]")
|
||||
.forEach((row) => {
|
||||
row.addEventListener("click", (e) => {
|
||||
if ((e.target as Element).closest("a, button")) return;
|
||||
row.querySelector<HTMLButtonElement>("[data-preset-toggle]")?.click();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -9,11 +9,12 @@ interface Props {
|
||||
detailedType?: string;
|
||||
description?: string;
|
||||
componentName: string;
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
const { name, type, detailedType, description, componentName } = Astro.props;
|
||||
const { name, type, detailedType, description, componentName, kind = 'state' } = Astro.props;
|
||||
|
||||
const id = `${componentName}-state-${name}`;
|
||||
const id = `${componentName}-${kind}-${name}`;
|
||||
---
|
||||
|
||||
<DetailRow
|
||||
|
||||
@@ -3,6 +3,8 @@ import { file, glob } from 'astro/loaders';
|
||||
import { z } from 'astro/zod';
|
||||
import { ComponentReferenceSchema } from './types/component-reference';
|
||||
import { SUPPORTED_FRAMEWORKS } from './types/docs';
|
||||
import { FeatureReferenceSchema } from './types/feature-reference';
|
||||
import { PresetReferenceSchema } from './types/preset-reference';
|
||||
import { UtilReferenceSchema } from './types/util-reference';
|
||||
import { defaultGitService } from './utils/gitService';
|
||||
import { globWithParser } from './utils/globWithParser';
|
||||
@@ -127,6 +129,22 @@ const utilReference = defineCollection({
|
||||
schema: UtilReferenceSchema,
|
||||
});
|
||||
|
||||
const featureReference = defineCollection({
|
||||
loader: glob({
|
||||
pattern: '*.json',
|
||||
base: './src/content/generated-feature-reference',
|
||||
}),
|
||||
schema: FeatureReferenceSchema,
|
||||
});
|
||||
|
||||
const presetReference = defineCollection({
|
||||
loader: glob({
|
||||
pattern: '*.json',
|
||||
base: './src/content/generated-preset-reference',
|
||||
}),
|
||||
schema: PresetReferenceSchema,
|
||||
});
|
||||
|
||||
const ejectedSkins = defineCollection({
|
||||
loader: file('./src/content/ejected-skins.json'),
|
||||
schema: z.object({
|
||||
@@ -141,4 +159,13 @@ const ejectedSkins = defineCollection({
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog, docs, authors, componentReference, utilReference, ejectedSkins };
|
||||
export const collections = {
|
||||
blog,
|
||||
docs,
|
||||
authors,
|
||||
componentReference,
|
||||
utilReference,
|
||||
featureReference,
|
||||
presetReference,
|
||||
ejectedSkins,
|
||||
};
|
||||
|
||||
@@ -144,7 +144,13 @@ DASH, YouTube, Vimeo, Mux, and more media elements are currently under developme
|
||||
|
||||
**Presets** preconfigure these parts for a specific use case.
|
||||
|
||||
The default presets are `/video` and `/audio`, covering the baseline set of controls you'd expect from the HTML `<video>` and `<audio>` tags.
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
The default presets are `@videojs/react/video` and `@videojs/react/audio`, covering the baseline set of controls you'd expect from the HTML `<video>` and `<audio>` tags.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
The default presets are `@videojs/html/video` and `@videojs/html/audio`, covering the baseline set of controls you'd expect from the HTML `<video>` and `<audio>` tags.
|
||||
</FrameworkCase>
|
||||
|
||||
Beyond the defaults, presets target more specific use cases. For example, `/background` includes a media element with autoplay, mute, and loop built in, a skin with no controls, and just the features needed to power it:
|
||||
|
||||
|
||||
@@ -3,15 +3,16 @@ title: Presets
|
||||
description: Pre-packaged player configurations that bundle state management, skins, and media elements for specific use cases.
|
||||
---
|
||||
|
||||
import PresetReference from '@/components/docs/api-reference/PresetReference.astro';
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
import Aside from '@/components/Aside.astro';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
|
||||
A **preset** packages what you need for a specific player use case. It can include special <DocsLink slug="concepts/features">state management</DocsLink>, one or more <DocsLink slug="concepts/skins">skins</DocsLink> for UI, and specific media elements. Instead of assembling these pieces individually, you pick a preset that matches what you're building.
|
||||
|
||||
For example, the `/background` preset includes a media element with autoplay, mute, and loop built in, a skin with no controls, and just the features needed to power them:
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
For example, the `@videojs/react/background` preset includes a media element with autoplay, mute, and loop built in, a skin with no controls, and just the features needed to power them:
|
||||
|
||||
```tsx title="App.tsx"
|
||||
import { createPlayer } from '@videojs/react';
|
||||
import { backgroundFeatures, BackgroundVideo, BackgroundVideoSkin } from '@videojs/react/background'; // [!code focus]
|
||||
@@ -30,6 +31,8 @@ function Hero() {
|
||||
```
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
For example, the `@videojs/html/background` preset includes a media element with autoplay, mute, and loop built in, a skin with no controls, and just the features needed to power them:
|
||||
|
||||
```html title="index.html"
|
||||
<script type="module">
|
||||
import '@videojs/html/background/player';
|
||||
@@ -49,24 +52,7 @@ function Hero() {
|
||||
|
||||
The default presets are `/video` and `/audio`. These cover the baseline controls you'd expect from the HTML `<video>` and `<audio>` tags. Beyond the defaults, presets target more specific use cases — the `/background` preset, for example, needs layout but not controls. Over time we'll add more: short-form players, podcast players, TV streaming players, and others.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
| Preset | Feature bundle | Skins | Default media element |
|
||||
|--------|---------------|-------|-----------------------|
|
||||
| `/video` | `videoFeatures` | `<VideoSkin>`, `<MinimalVideoSkin>` | `<Video>` |
|
||||
| `/audio` | `audioFeatures` | `<AudioSkin>`, `<MinimalAudioSkin>` | `<Audio>` |
|
||||
| `/background` | `backgroundFeatures` | `<BackgroundVideoSkin>` | `<BackgroundVideo>` |
|
||||
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
|
||||
| Preset | Skins | Default media element |
|
||||
|--------|-------|-----------------------|
|
||||
| `/video` | `<video-skin>`, `<video-minimal-skin>` | `<video>` |
|
||||
| `/audio` | `<audio-skin>`, `<audio-minimal-skin>` | `<audio>` |
|
||||
| `/background` | `<background-video-skin>` | `<background-video>` |
|
||||
|
||||
</FrameworkCase>
|
||||
<PresetReference />
|
||||
|
||||
## What's in a preset
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Skins
|
||||
description: Packaged player designs that include both UI components and their styles.
|
||||
---
|
||||
|
||||
import PresetReference from '@/components/docs/api-reference/PresetReference.astro';
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
import { FrostedSkinDemo } from '@/examples/react/FrostedSkin/FrostedSkinDemo';
|
||||
import { MinimalSkinDemo } from '@/examples/react/MinimalSkin/MinimalSkinDemo';
|
||||
@@ -88,31 +89,15 @@ When you choose a skin you have two options for how you use it: **packaged** or
|
||||
|
||||
## Skins, features, and presets
|
||||
|
||||
Each skin is built with specific <DocsLink slug="concepts/features">features</DocsLink> in mind. For example, a video skin renders fullscreen and picture-in-picture controls. An audio skin doesn't. The features associated with a skin are called a **feature bundle**.
|
||||
Each skin is built with specific <DocsLink slug="concepts/features">features</DocsLink> in mind. For example, a video skin renders fullscreen and picture-in-picture controls. An audio skin doesn't.
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
You'll find both a skin and the feature bundle it expects exported from the same path. We call these paths **presets**.
|
||||
|
||||
| Player with feature bundle | Available skins | Import |
|
||||
| ---------- | ----------------- | -------- |
|
||||
| `<video-player>` | `<video-skin>`, `<video-minimal-skin>` | `@videojs/html/video/*` |
|
||||
| `<audio-player>` | `<audio-skin>`, `<audio-minimal-skin>` | `@videojs/html/audio/*` |
|
||||
| `<background-video-player>` | `<background-video-skin>` | `@videojs/html/background/*` |
|
||||
<PresetReference />
|
||||
|
||||
</FrameworkCase>
|
||||
Presets are a topic quite a bit bigger than just this guide. To learn more, check out the guide:
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
| Feature bundle | Available skins | Import |
|
||||
| ---------- | ----------------- | -------- |
|
||||
| `videoFeatures` | `<VideoSkin>`, `<MinimalVideoSkin>` | `@videojs/react/video` |
|
||||
| `audioFeatures` | `<AudioSkin>`, `<MinimalAudioSkin>` | `@videojs/react/audio` |
|
||||
| `backgroundFeatures` | `<BackgroundVideoSkin>` | `@videojs/react/background` |
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
Want to learn more about skins and feature bundles? Check out the presets guide:
|
||||
|
||||
<DocsLinkCard slug="concepts/presets">Learn more about presets</DocsLinkCard>
|
||||
<DocsLinkCard slug="concepts/presets">Read about presets</DocsLinkCard>
|
||||
|
||||
## Styling
|
||||
|
||||
|
||||
@@ -3,20 +3,15 @@ title: Buffer
|
||||
description: Buffered and seekable time range state for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Read-only — tracks buffered and seekable time ranges.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="buffer" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `buffered` | `[number, number][]` | Buffered time ranges as `[start, end]` tuples |
|
||||
| `seekable` | `[number, number][]` | Seekable time ranges as `[start, end]` tuples |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectBuffer` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to buffer state. Returns `undefined` if the buffer feature is not configured.
|
||||
@@ -53,5 +48,3 @@ class BufferBar extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectBuffer" />
|
||||
|
||||
@@ -3,20 +3,15 @@ title: Controls
|
||||
description: User activity and controls visibility state for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Read-only — tracks user activity for showing and hiding controls.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="controls" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `userActive` | `boolean` | Whether the user has recently interacted |
|
||||
| `controlsVisible` | `boolean` | Whether controls should be visible (active or paused) |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectControls` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to controls state. Returns `undefined` if the controls feature is not configured.
|
||||
@@ -55,5 +50,3 @@ class ControlsOverlay extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectControls" />
|
||||
|
||||
@@ -3,25 +3,15 @@ title: Error
|
||||
description: Media error state and actions for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Tracks media errors.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="error" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `error` | `MediaError \| null` | The current error, or `null` |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `dismissError()` | Clear the current error |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectError` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to error state. Returns `undefined` if the error feature is not configured.
|
||||
@@ -61,5 +51,3 @@ class ErrorDisplay extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectError" />
|
||||
|
||||
@@ -3,27 +3,15 @@ title: Fullscreen
|
||||
description: Fullscreen state and actions for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Controls fullscreen mode. Tries the container element first, falls back to the media element.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="fullscreen" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `fullscreen` | `boolean` | Whether fullscreen is active |
|
||||
| `fullscreenAvailability` | `MediaFeatureAvailability` | Whether fullscreen is supported |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `requestFullscreen()` | Enter fullscreen (returns a `Promise`) |
|
||||
| `exitFullscreen()` | Exit fullscreen (returns a `Promise`) |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectFullscreen` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to fullscreen state. Returns `undefined` if the fullscreen feature is not configured.
|
||||
@@ -62,5 +50,3 @@ class FullscreenButton extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectFullscreen" />
|
||||
|
||||
@@ -3,27 +3,15 @@ title: Picture-in-picture
|
||||
description: Picture-in-picture state and actions for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Controls picture-in-picture mode.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="pip" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `pip` | `boolean` | Whether picture-in-picture is active |
|
||||
| `pipAvailability` | `MediaFeatureAvailability` | Whether PiP is supported |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `requestPictureInPicture()` | Enter picture-in-picture (returns a `Promise`) |
|
||||
| `exitPictureInPicture()` | Exit picture-in-picture (returns a `Promise`) |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectPiP` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to picture-in-picture state. Returns `undefined` if the PiP feature is not configured.
|
||||
@@ -62,5 +50,3 @@ class PiPButton extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectPiP" />
|
||||
|
||||
@@ -3,26 +3,15 @@ title: Playback rate
|
||||
description: Playback speed state and actions for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Controls speed of playback.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="playbackRate" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `playbackRate` | `number` | Current playback speed (1 = normal) |
|
||||
| `playbackRates` | `readonly number[]` | Available playback rates |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `setPlaybackRate(rate)` | Set the playback speed |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectPlaybackRate` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to playback rate state. Returns `undefined` if the playback rate feature is not configured.
|
||||
@@ -57,5 +46,3 @@ class RateDisplay extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectPlaybackRate" />
|
||||
|
||||
@@ -3,29 +3,15 @@ title: Playback
|
||||
description: Play/pause state and actions for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Controls play/pause state and tracks whether playback has started or is stalled.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="playback" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `paused` | `boolean` | Whether playback is paused |
|
||||
| `ended` | `boolean` | Whether playback reached the end |
|
||||
| `started` | `boolean` | Whether playback has started (played or seeked) |
|
||||
| `waiting` | `boolean` | Whether playback is stalled waiting for data |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `play()` | Start playback (returns a `Promise`) |
|
||||
| `pause()` | Pause playback |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectPlayback` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to playback state. Returns `undefined` if the playback feature is not configured.
|
||||
@@ -59,5 +45,3 @@ class PlayButton extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectPlayback" />
|
||||
|
||||
@@ -3,26 +3,15 @@ title: Source
|
||||
description: Media source state and actions for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Tracks the current media source and readiness.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="source" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `source` | `string \| null` | Current media source URL |
|
||||
| `canPlay` | `boolean` | Whether enough data is loaded to begin playback |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `loadSource(src)` | Load a new media source |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectSource` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to source state. Returns `undefined` if the source feature is not configured.
|
||||
@@ -57,5 +46,3 @@ class SourceInfo extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectSource" />
|
||||
|
||||
@@ -3,29 +3,15 @@ title: Text tracks
|
||||
description: Subtitles, captions, and chapter track state for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Manages subtitles, captions, chapters, and thumbnail tracks.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="textTrack" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `textTrackList` | `MediaTextTrack[]` | All text tracks on the media element |
|
||||
| `subtitlesShowing` | `boolean` | Whether captions/subtitles are enabled |
|
||||
| `chaptersCues` | `MediaTextCue[]` | Cues from the first chapters track |
|
||||
| `thumbnailCues` | `MediaTextCue[]` | Cues from the first thumbnails track |
|
||||
| `thumbnailTrackSrc` | `string \| null` | Track `src` for resolving relative thumbnail URLs |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `toggleSubtitles(forceShow?)` | Toggle subtitle visibility. Pass `true`/`false` to force. |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectTextTracks` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to text track state. Returns `undefined` if the text tracks feature is not configured.
|
||||
@@ -64,5 +50,3 @@ class CaptionsButton extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectTextTracks" />
|
||||
|
||||
@@ -3,27 +3,15 @@ title: Time
|
||||
description: Playback position and duration state for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Tracks playback position and duration.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="time" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `currentTime` | `number` | Current playback position in seconds |
|
||||
| `duration` | `number` | Total duration in seconds (0 if unknown) |
|
||||
| `seeking` | `boolean` | Whether a seek operation is in progress |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `seek(time)` | Seek to a position in seconds (returns a `Promise`) |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectTime` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to time state. Returns `undefined` if the time feature is not configured.
|
||||
@@ -62,5 +50,3 @@ class TimeDisplay extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectTime" />
|
||||
|
||||
@@ -3,28 +3,15 @@ title: Volume
|
||||
description: Volume level and mute state for the player store
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import FeatureReference from "@/components/docs/api-reference/FeatureReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
Controls volume level and mute state.
|
||||
|
||||
## State
|
||||
<FeatureReference feature="volume" />
|
||||
|
||||
| State | Type | Description |
|
||||
|---|---|---|
|
||||
| `volume` | `number` | Volume level from 0 (silent) to 1 (max) |
|
||||
| `muted` | `boolean` | Whether audio is muted |
|
||||
| `volumeAvailability` | `MediaFeatureAvailability` | Whether volume can be set on this platform |
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `setVolume(volume)` | Set volume (clamped 0–1). Auto-unmutes when raising above zero. |
|
||||
| `toggleMuted()` | Toggle mute. Restores volume to 0.25 when unmuting at zero. |
|
||||
|
||||
## Selector
|
||||
### Selector
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Pass `selectVolume` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to volume state. Returns `undefined` if the volume feature is not configured.
|
||||
@@ -68,5 +55,3 @@ class VolumeSlider extends MediaElement {
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<UtilReference util="selectVolume" />
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Zod schemas for feature API reference JSON files.
|
||||
*
|
||||
* FeatureStateDef and FeatureActionDef reuse StateDefSchema (identical shape)
|
||||
* from component-reference.ts, following the same pattern as util-reference.ts.
|
||||
*/
|
||||
import { z } from 'astro/zod';
|
||||
import { StateDefSchema } from './component-reference';
|
||||
|
||||
export const FeatureStateDefSchema = StateDefSchema;
|
||||
|
||||
export const FeatureActionDefSchema = StateDefSchema;
|
||||
|
||||
export const FeatureReferenceSchema = z.object({
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
description: z.string().optional(),
|
||||
state: z.record(z.string(), FeatureStateDefSchema),
|
||||
actions: z.record(z.string(), FeatureActionDefSchema),
|
||||
});
|
||||
|
||||
export type FeatureStateDef = z.infer<typeof FeatureStateDefSchema>;
|
||||
export type FeatureActionDef = z.infer<typeof FeatureActionDefSchema>;
|
||||
export type FeatureReference = z.infer<typeof FeatureReferenceSchema>;
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Zod schemas for preset API reference JSON files.
|
||||
*/
|
||||
import { z } from 'astro/zod';
|
||||
|
||||
export const PresetSkinDefSchema = z.object({
|
||||
name: z.string(),
|
||||
tagName: z.string().optional(),
|
||||
});
|
||||
|
||||
export const PresetReferenceSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
featureBundle: z.string(),
|
||||
features: z.array(z.string()),
|
||||
html: z.object({
|
||||
skins: z.array(PresetSkinDefSchema),
|
||||
mediaElement: z.string().optional(),
|
||||
}),
|
||||
react: z.object({
|
||||
skins: z.array(PresetSkinDefSchema),
|
||||
mediaElement: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type PresetSkinDef = z.infer<typeof PresetSkinDefSchema>;
|
||||
export type PresetReference = z.infer<typeof PresetReferenceSchema>;
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Centralized feature API reference subsection definitions.
|
||||
*
|
||||
* Mirrors componentReferenceModel.js for feature APIs. Produces heading/id data
|
||||
* consumed by both FeatureReference.astro and remarkConditionalHeadings.
|
||||
*
|
||||
* Structure:
|
||||
* ## API Reference (H2)
|
||||
* ### State (H3) — if feature has state properties
|
||||
* ### Actions (H3) — if feature has action methods
|
||||
*/
|
||||
|
||||
function hasEntries(value) {
|
||||
return Object.keys(value ?? {}).length > 0;
|
||||
}
|
||||
|
||||
export function createFeatureReferenceModel(name, ref) {
|
||||
if (!ref) return null;
|
||||
|
||||
const sections = [];
|
||||
|
||||
if (hasEntries(ref.state)) {
|
||||
sections.push({ key: 'state', title: 'State', id: 'state', depth: 3 });
|
||||
}
|
||||
|
||||
if (hasEntries(ref.actions)) {
|
||||
sections.push({ key: 'actions', title: 'Actions', id: 'actions', depth: 3 });
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
description: ref.description,
|
||||
heading: { id: 'api-reference', depth: 2, text: 'API Reference' },
|
||||
sections,
|
||||
data: ref,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFeatureReferenceTocHeadings(model) {
|
||||
if (!model) return [];
|
||||
|
||||
const headings = [
|
||||
{
|
||||
depth: model.heading.depth,
|
||||
text: model.heading.text,
|
||||
slug: model.heading.id,
|
||||
},
|
||||
];
|
||||
|
||||
for (const section of model.sections) {
|
||||
headings.push({
|
||||
depth: section.depth,
|
||||
text: section.title,
|
||||
slug: section.id,
|
||||
});
|
||||
}
|
||||
|
||||
return headings;
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import { fileURLToPath } from 'node:url';
|
||||
import { kebabCase } from 'es-toolkit/string';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { buildComponentReferenceTocHeadings, createComponentReferenceModel } from './componentReferenceModel';
|
||||
import { buildFeatureReferenceTocHeadings, createFeatureReferenceModel } from './featureReferenceModel';
|
||||
import { buildUtilReferenceTocHeadings, createUtilReferenceModel } from './utilReferenceModel';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const COMPONENT_REF_DIR = path.resolve(__dirname, '../content/generated-component-reference');
|
||||
const FEATURE_REF_DIR = path.resolve(__dirname, '../content/generated-feature-reference');
|
||||
const UTIL_REF_DIR = path.resolve(__dirname, '../content/generated-util-reference');
|
||||
|
||||
function readComponentRefJson(componentName) {
|
||||
@@ -65,6 +67,8 @@ export default function remarkConditionalHeadings() {
|
||||
return;
|
||||
} else if (node.name === 'ComponentReference') {
|
||||
injectComponentReferenceHeadings(node, headingsWithMetadata, reservedSlugs);
|
||||
} else if (node.name === 'FeatureReference') {
|
||||
injectFeatureReferenceHeadings(node, headingsWithMetadata, reservedSlugs);
|
||||
} else if (node.name === 'UtilReference') {
|
||||
injectUtilReferenceHeadings(node, headingsWithMetadata, reservedSlugs);
|
||||
return;
|
||||
@@ -148,6 +152,32 @@ function injectComponentReferenceHeadings(node, headingsWithMetadata, reservedSl
|
||||
}
|
||||
}
|
||||
|
||||
function readFeatureRefJson(featureName) {
|
||||
const filePath = path.join(FEATURE_REF_DIR, `${featureName}.json`);
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function injectFeatureReferenceHeadings(node, headingsWithMetadata, reservedSlugs) {
|
||||
const featureAttr = node.attributes?.find((a) => a.name === 'feature');
|
||||
const featureName = typeof featureAttr?.value === 'string' ? featureAttr.value : null;
|
||||
if (!featureName) return;
|
||||
|
||||
const json = readFeatureRefJson(featureName);
|
||||
if (!json) return;
|
||||
|
||||
const featureModel = createFeatureReferenceModel(featureName, json);
|
||||
const featureHeadings = buildFeatureReferenceTocHeadings(featureModel);
|
||||
|
||||
headingsWithMetadata.push(...featureHeadings);
|
||||
for (const heading of featureHeadings) {
|
||||
reservedSlugs.add(heading.slug);
|
||||
}
|
||||
}
|
||||
|
||||
function readUtilRefJson(slug) {
|
||||
const filePath = path.join(UTIL_REF_DIR, `${slug}.json`);
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user