mirror of
https://github.com/zoriya/v10.git
synced 2026-08-14 01:49:30 +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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user