mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
chore(site): audit and encode docs patterns (#535)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
84b7b0774a
commit
b1c8022794
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'Writing guides for Video.js'
|
||||
title: 'Write guides for Video.js'
|
||||
description: 'A guide on writing documentation, and a test bed for all our MDX components'
|
||||
---
|
||||
|
||||
@@ -11,17 +11,31 @@ import { TabsRoot, TabsList, TabsPanel, Tab } from '@/components/Tabs.tsx';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
import Aside from '@/components/Aside.astro';
|
||||
|
||||
## 1. Create a guide in the correct content folder
|
||||
What is a guide? In this context, it's a document in the docs that's not an API reference. For API references, see <DocsLink slug="reference/write-references">Write reference pages</DocsLink>.
|
||||
|
||||
First, read and understand [Diátaxis](https://diataxis.fr/).
|
||||
<Aside type="tip">
|
||||
Oh hey by the way. A lot of this knowledge is encoded in the `docs-guide` Claude skill. You should still read it, but, now your LLM can help you along the way, too.
|
||||
</Aside>
|
||||
|
||||
Next, know that our [MDX](https://mdxjs.com) guides are separated into two categories:
|
||||
1. How-to guides: Focused on achieving a specific outcome. Place these in `src/content/docs/how-to/[slug].mdx`
|
||||
2. Concept guides: Focused on understanding a topic. Place these in `src/content/docs/concepts/[slug].mdx`
|
||||
## 1. Understand our documentation structure
|
||||
|
||||
(You might also notice that we've written some `src/content/docs/reference` guides, but soon those will be auto-generated from source code.)
|
||||
First, read and understand [Diátaxis](https://diataxis.fr/). We organize documentation into three modes:
|
||||
|
||||
1. **Concept pages** (`src/content/docs/concepts/`): Explain how and why things work. General understanding, spanning multiple APIs, can be applied to multiple outcomes. As we write guides, what are things we need people to understand in multiple places and don’t want to duplicate the content?
|
||||
2. **How-to guides** (`src/content/docs/how-to/`): Spans multiple concepts in order to achieve a specific outcome with step-by-step instructions.
|
||||
3. **Reference pages** (`src/content/docs/reference/`): Component API documentation. These are scaffolded using the `api-reference` skill and the api-docs-builder. Again, see <DocsLink slug="reference/write-references">Write reference pages</DocsLink> for details.
|
||||
|
||||
When in doubt, you probably want a concept page.
|
||||
|
||||
## 2. Create a guide in the correct content folder
|
||||
|
||||
Our guides are [MDX](https://mdxjs.com) files placed in the matching directory:
|
||||
|
||||
- Concept pages go in `src/content/docs/concepts/[slug].mdx`
|
||||
- How-to guides go in `src/content/docs/how-to/[slug].mdx`
|
||||
|
||||
## 3. Add that guide to the sidebar
|
||||
|
||||
## 2. Add that guide to the sidebar
|
||||
Next, open `src/docs.config.ts` and add your guide to the appropriate section of the sidebar. For example, to add a how-to guide on "Writing guides", you would add:
|
||||
|
||||
```ts
|
||||
@@ -42,7 +56,21 @@ If you want the guide to only apply to specific frameworks or styles, you can sp
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Understand MDX and our components
|
||||
## 4. Follow our writing style
|
||||
|
||||
Keep documentation clear, human, and useful. Here are the key rules:
|
||||
|
||||
- **Sentence case for headings** — capitalize only the first word and proper nouns (e.g., "Choose your JS framework", not "Choose Your JS Framework")
|
||||
- **Active voice, second person** — speak directly to the reader with "you"
|
||||
- **Collaborative pronouns** — use "we," "us," and "our" when talking about the project or team
|
||||
- **Avoid gerunds in headings** — prefer "Write good headings" over "Writing good headings"
|
||||
- **Oxford comma** — always use the serial comma in lists of three or more
|
||||
- **Gender-neutral language** — use "they" or "their" instead of "his" or "her"
|
||||
- **Cut filler words** — remove "In order to," "basically," "simply," "might," "could," "perhaps"
|
||||
- **Be precise** — make claims as strong as possible without becoming false; avoid vague qualifiers like "somewhat" or "fairly"
|
||||
- **Read it out loud** — if it sounds awkward, rewrite it
|
||||
|
||||
## 5. Understand MDX and our components
|
||||
|
||||
### `<FrameworkCase>` and `<StyleCase>`
|
||||
First, understand that the guide you write will be rendered for every framework / style combination (e.g., HTML + CSS, React + CSS) unless you restrict it in the sidebar config as shown above.
|
||||
@@ -69,8 +97,8 @@ Use the `<StyleCase>` component to show content only for specific styling approa
|
||||
```mdx
|
||||
<StyleCase styles={["css"]}>
|
||||
CSS-only content
|
||||
</StyleCase>
|
||||
```
|
||||
</StyleCase>
|
||||
```
|
||||
|
||||
|
||||
<StyleCase styles={["css"]}>
|
||||
@@ -299,7 +327,7 @@ The elements have a gross Tailwind class that manages their margins which makes
|
||||
|
||||
### Tabs
|
||||
|
||||
Use `<TabsRoot>`, `<TabsList>`, `<Tab>`, and `<TabsPanel>` to show multiple items side-by-side.
|
||||
Use `<TabsRoot>`, `<TabsList>`, `<Tab>`, and `<TabsPanel>` to show multiple items side-by-side.
|
||||
|
||||
<Aside type="note">
|
||||
- Set `initial` on your first `<Tab>` and first `<TabsPanel>` to make them active by default
|
||||
|
||||
@@ -35,6 +35,14 @@ import basicUsageHtmlTs from "@/components/docs/demos/buffering-indicator/html/c
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
Shows a loading indicator when the media is waiting to buffer and not paused, but only after a configurable `delay` (default 500ms). This delay prevents the indicator from flickering during brief stalls. The indicator hides immediately when buffering ends.
|
||||
|
||||
## Styling
|
||||
|
||||
Hide and show the indicator based on the `data-visible` attribute.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
@@ -41,6 +41,39 @@ Import the component and assemble its parts:
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
If the user is active, or if the video is paused, this component will show controls. Otherwise, it will hide them after a short delay.
|
||||
|
||||
User activity is tracked via pointer movement, keyboard input, and focus events on the player container. On touch devices, a quick tap toggles visibility. `mouseleave` immediately sets the user as inactive.
|
||||
|
||||
## Styling
|
||||
By default, controls have the following styles:
|
||||
|
||||
```css
|
||||
/* Click-through: clicks pass through controls to video beneath */
|
||||
media-controls {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
media-controls-group {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Fade transition */
|
||||
media-controls {
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
media-controls:not([data-visible]) {
|
||||
opacity: 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
No ARIA role is applied to `<media-controls>` — it is a layout wrapper, not a landmark. `<media-controls-group>` automatically receives `role="group"` when an `aria-label` or `aria-labelledby` attribute is provided; otherwise no role is assigned.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
@@ -24,27 +24,56 @@ import basicUsageHtmlTs from "@/components/docs/demos/fullscreen-button/html/css
|
||||
## Anatomy
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
```tsx
|
||||
<FullscreenButton />
|
||||
```
|
||||
```tsx
|
||||
<FullscreenButton />
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
```html
|
||||
<media-fullscreen-button></media-fullscreen-button>
|
||||
```
|
||||
```html
|
||||
<media-fullscreen-button></media-fullscreen-button>
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
Toggles fullscreen mode. Detects platform support through `availability` — when fullscreen is `"unsupported"`, the toggle does nothing.
|
||||
|
||||
## Styling
|
||||
|
||||
You can style the button based on fullscreen state:
|
||||
|
||||
```css
|
||||
/* In fullscreen */
|
||||
media-fullscreen-button[data-fullscreen] {
|
||||
background: red;
|
||||
}
|
||||
```
|
||||
|
||||
Consider hiding the button when unsupported:
|
||||
|
||||
```css
|
||||
media-fullscreen-button[data-availability="unsupported"] {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
Renders a `<button>` with an automatic `aria-label`: "Enter fullscreen" or "Exit fullscreen". Override with the `label` prop. Keyboard activation: <kbd>Enter</kbd> / <kbd>Space</kbd>.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
|
||||
]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
|
||||
]}
|
||||
>
|
||||
<BasicUsageDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
@@ -52,11 +81,13 @@ import basicUsageHtmlTs from "@/components/docs/demos/fullscreen-button/html/css
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "index.html", code: basicUsageHtml, lang: "html" },
|
||||
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
|
||||
]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "index.html", code: basicUsageHtml, lang: "html" },
|
||||
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
|
||||
]}
|
||||
>
|
||||
<BasicUsageDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
|
||||
@@ -21,6 +21,17 @@ import basicUsageHtml from "@/components/docs/demos/mute-button/html/css/BasicUs
|
||||
import basicUsageHtmlCss from "@/components/docs/demos/mute-button/html/css/BasicUsage.css?raw";
|
||||
import basicUsageHtmlTs from "@/components/docs/demos/mute-button/html/css/BasicUsage.ts?raw";
|
||||
|
||||
{/* React demos — Volume Levels */}
|
||||
import VolumeLevelsDemoReact from "@/components/docs/demos/mute-button/react/css/VolumeLevels";
|
||||
import volumeLevelsReactTsx from "@/components/docs/demos/mute-button/react/css/VolumeLevels.tsx?raw";
|
||||
import volumeLevelsReactCss from "@/components/docs/demos/mute-button/react/css/VolumeLevels.css?raw";
|
||||
|
||||
{/* HTML demos — Volume Levels */}
|
||||
import VolumeLevelsDemoHtml from "@/components/docs/demos/mute-button/html/css/VolumeLevels.astro";
|
||||
import volumeLevelsHtml from "@/components/docs/demos/mute-button/html/css/VolumeLevels.html?raw";
|
||||
import volumeLevelsHtmlCss from "@/components/docs/demos/mute-button/html/css/VolumeLevels.css?raw";
|
||||
import volumeLevelsHtmlTs from "@/components/docs/demos/mute-button/html/css/VolumeLevels.ts?raw";
|
||||
|
||||
## Anatomy
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
@@ -35,6 +46,32 @@ import basicUsageHtmlTs from "@/components/docs/demos/mute-button/html/css/Basic
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
Toggles mute on and off, and exposes a derived `volumeLevel` based on the current volume and mute state.
|
||||
|
||||
## Styling
|
||||
|
||||
Style the button based on muted state:
|
||||
|
||||
```css
|
||||
media-mute-button[data-muted] .icon-muted { display: inline; }
|
||||
media-mute-button:not([data-muted]) .icon-unmuted { display: inline; }
|
||||
```
|
||||
|
||||
Use `data-volume-level` for multi-level icon switching:
|
||||
|
||||
```css
|
||||
media-mute-button[data-volume-level="off"] .icon-off { display: inline; }
|
||||
media-mute-button[data-volume-level="low"] .icon-low { display: inline; }
|
||||
media-mute-button[data-volume-level="medium"] .icon-medium { display: inline; }
|
||||
media-mute-button[data-volume-level="high"] .icon-high { display: inline; }
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
Renders a `<button>` with an automatic `aria-label`: "Unmute" when muted, "Mute" when unmuted. Override with the `label` prop. Keyboard activation: <kbd>Enter</kbd> / <kbd>Space</kbd>.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
@@ -62,4 +99,29 @@ import basicUsageHtmlTs from "@/components/docs/demos/mute-button/html/css/Basic
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
### Volume Levels
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "App.tsx", code: volumeLevelsReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: volumeLevelsReactCss, lang: "css" },
|
||||
]}>
|
||||
<VolumeLevelsDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "index.html", code: volumeLevelsHtml, lang: "html" },
|
||||
{ title: "index.css", code: volumeLevelsHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: volumeLevelsHtmlTs, lang: "ts" },
|
||||
]}>
|
||||
<VolumeLevelsDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<ApiReference component="MuteButton" />
|
||||
|
||||
@@ -35,6 +35,33 @@ import basicUsageHtmlTs from "@/components/docs/demos/pip-button/html/css/BasicU
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
Toggles picture-in-picture (PiP) mode. Detects platform support through `availability` — when PiP is `"unsupported"`, the toggle does nothing.
|
||||
|
||||
## Styling
|
||||
|
||||
You can style the button based on PiP state:
|
||||
|
||||
```css
|
||||
/* In PiP mode */
|
||||
media-pip-button[data-pip] {
|
||||
background: red;
|
||||
}
|
||||
```
|
||||
|
||||
Consider hiding the button when unsupported:
|
||||
|
||||
```css
|
||||
media-pip-button[data-availability="unsupported"] {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
Renders a `<button>` with an automatic `aria-label`: "Enter PiP" or "Exit PiP". Override with the `label` prop. Keyboard activation: <kbd>Enter</kbd> / <kbd>Space</kbd>.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
@@ -35,6 +35,36 @@ import basicUsageHtmlTs from "@/components/docs/demos/play-button/html/css/Basic
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
PlayButton is a three-state button: **play**, **pause**, and **replay**. When media reaches the end (`ended` state), clicking restarts playback from the beginning.
|
||||
|
||||
## Styling
|
||||
|
||||
Style with the `[data-paused]` and `[data-ended]` attributes to show/hide play/pause/replay icons based on state. For example:
|
||||
|
||||
```css
|
||||
/* Paused (but not ended) */
|
||||
media-play-button[data-paused]:not([data-ended]) .play-icon { display: inline; }
|
||||
|
||||
/* Playing */
|
||||
media-play-button:not([data-paused]) .pause-icon { display: inline; }
|
||||
|
||||
/* Ended */
|
||||
media-play-button[data-ended] .replay-icon { display: inline; }
|
||||
```
|
||||
|
||||
After first play, the `data-started` attribute is added and remains present until a new source is loaded. Use this to hide the play button when media hasn't started yet:
|
||||
|
||||
```css
|
||||
/* Hide play button before first play */
|
||||
media-play-button:not([data-started]) .play-icon { display: none; }
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
Renders a `<button>` element with an automatic `aria-label` that updates based on state: "Play", "Pause", or "Replay". Override with the `label` prop (accepts a string or function). Keyboard activation: <kbd>Enter</kbd> / <kbd>Space</kbd>.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
@@ -39,6 +39,26 @@ Import the component:
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
The poster is visible before playback starts. Once the user plays or seeks, the poster hides permanently — pausing does not bring it back. The poster reappears when a new source is loaded.
|
||||
|
||||
## Styling
|
||||
|
||||
Style the poster with the `[data-visible]` attribute:
|
||||
|
||||
```css
|
||||
media-poster:not([data-visible]) {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
You control the child `<img>` — this means `srcset`, `sizes`, `loading="lazy"`, and framework image components all work naturally.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Unlike the native `<video poster>` attribute, this component allows you to provide accessible text alternatives for screen readers via the `alt` attribute on your child `<img>`. This means you can describe the poster image (e.g., `alt="Keynote speaker at a conference"`) or mark it as decorative with `alt=""` if it doesn't convey meaningful information.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
@@ -35,6 +35,14 @@ import basicUsageHtmlTs from "@/components/docs/demos/seek-button/html/css/Basic
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
Seeks media by a configurable number of `seconds` (default 30). Positive values seek forward, negative values seek backward. The seek is clamped to media bounds (0 to duration).
|
||||
|
||||
## Accessibility
|
||||
|
||||
Renders a `<button>` with an automatic `aria-label` describing the action, e.g. "Seek forward 30 seconds" or "Seek backward 10 seconds". Override with the `label` prop. Keyboard activation: <kbd>Enter</kbd> / <kbd>Space</kbd>.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
@@ -75,6 +75,42 @@ import customNegativeSignHtmlTs from "@/components/docs/demos/time/html/css/Cust
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
Three display types — `current`, `duration`, and `remaining` — in digital format with smart padding:
|
||||
|
||||
- **Hours** are never padded (`1:05:30`, not `01:05:30`)
|
||||
- **Minutes** are padded when hours are shown (`1:05:30`, but `5:30`)
|
||||
- **Seconds** are always padded (`1:05`, not `1:5`)
|
||||
|
||||
Hour display is triggered when either the current value or the duration exceeds 1 hour, ensuring consistency within a Group. Remaining time displays a negative sign (customizable via the `negativeSign` prop).
|
||||
|
||||
## Styling
|
||||
|
||||
The negative sign is rendered inside `<span aria-hidden="true">` and can be hidden with CSS:
|
||||
|
||||
```css
|
||||
[data-type="remaining"] > span[aria-hidden] {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
Each `<media-time>` has:
|
||||
- `aria-label` for the static role label ("Current time", "Duration", "Remaining")
|
||||
- `aria-valuetext` for the dynamic human-readable time ("1 minute, 30 seconds")
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Each `<Time.Value>` has:
|
||||
- `aria-label` for the static role label ("Current time", "Duration", "Remaining")
|
||||
- `aria-valuetext` for the dynamic human-readable time ("1 minute, 30 seconds")
|
||||
</FrameworkCase>
|
||||
|
||||
No `aria-live` region is used — time updates too frequently and might overwhelm screen readers. The separator is `aria-hidden="true"` since screen readers already hear each time value separately. The negative sign is also `aria-hidden` because `aria-valuetext` already conveys "remaining". In React, `<time datetime>` provides machine-readable time for parsers.
|
||||
|
||||
## Examples
|
||||
|
||||
### Current Time
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
title: Write reference pages
|
||||
description: How to create component API reference pages for the Video.js documentation site
|
||||
---
|
||||
|
||||
import Aside from '@/components/Aside.astro';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
|
||||
This guide covers how to create component reference pages — the API documentation under `reference/` in the docs sidebar.
|
||||
|
||||
<Aside type="tip">
|
||||
Reference pages are scaffolded with the `api-reference` Claude skill. Run `/api-reference play-button` to generate a reference page interactively.
|
||||
</Aside>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before creating a reference page, the component must exist in:
|
||||
|
||||
- **Core**: `packages/core/src/core/ui/{name}/{name}-core.ts` (props, state, behavior)
|
||||
- **HTML**: `packages/html/src/ui/{name}/` (custom element)
|
||||
- **React**: `packages/react/src/ui/{name}/` (React component)
|
||||
|
||||
The component should be feature-complete enough that its props, state, and data attributes are stable.
|
||||
|
||||
## Generate the API reference JSON
|
||||
|
||||
The api-docs-builder extracts type information from TypeScript sources and outputs JSON files that the `<ApiReference />` component renders as tables.
|
||||
|
||||
```bash
|
||||
pnpm -F site api-docs
|
||||
```
|
||||
|
||||
This generates JSON to `site/src/content/generated-api-reference/{name}.json`. These files are gitignored and regenerated automatically on `pnpm dev` and `pnpm build`.
|
||||
|
||||
### Builder naming conventions
|
||||
|
||||
The builder relies on file naming conventions to discover components:
|
||||
|
||||
| Convention | Pattern | Example |
|
||||
|------------|---------|---------|
|
||||
| Core file | `{name}-core.ts` | `play-button-core.ts` |
|
||||
| Data attrs | `{name}-data-attrs.ts` | `play-button-data-attrs.ts` |
|
||||
| HTML element | `{name}-element.ts` | `play-button-element.ts` |
|
||||
| React component | `packages/react/src/ui/{name}/` | `packages/react/src/ui/play-button/` |
|
||||
| Multi-part detection | `index.parts.ts` | `packages/react/src/ui/slider/index.parts.ts` |
|
||||
|
||||
If the builder output is missing or incomplete, check that your files match these conventions. See `.claude/skills/api-reference/references/builder-conventions.md` for the full list.
|
||||
|
||||
## Create demo files
|
||||
|
||||
Each reference page needs at least a BasicUsage demo in both HTML and React.
|
||||
|
||||
### HTML demo (4 files)
|
||||
|
||||
```
|
||||
src/components/docs/demos/{name}/html/css/
|
||||
├── BasicUsage.astro # Wrapper: imports CSS, renders HTML, bundles script
|
||||
├── BasicUsage.html # Markup only (no <style> or <script>)
|
||||
├── BasicUsage.css # Styles
|
||||
└── BasicUsage.ts # Side-effect imports for custom element registration
|
||||
```
|
||||
|
||||
The `.astro` wrapper ties everything together:
|
||||
|
||||
```astro
|
||||
---
|
||||
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||
import html from './BasicUsage.html?raw';
|
||||
import './BasicUsage.css';
|
||||
---
|
||||
<HtmlDemo html={html} />
|
||||
<script>
|
||||
import './BasicUsage.ts';
|
||||
</script>
|
||||
```
|
||||
|
||||
### React demo (2 files)
|
||||
|
||||
```
|
||||
src/components/docs/demos/{name}/react/css/
|
||||
├── BasicUsage.tsx # React component
|
||||
└── BasicUsage.css # Styles
|
||||
```
|
||||
|
||||
### BEM naming
|
||||
|
||||
Use BEM class names for CSS scoping. The block name follows the pattern `{framework}-{component}-{variant}`:
|
||||
|
||||
```css
|
||||
/* HTML demo */
|
||||
.html-play-button-basic__button { /* ... */ }
|
||||
|
||||
/* React demo */
|
||||
.react-play-button-basic__button { /* ... */ }
|
||||
```
|
||||
|
||||
React and HTML demos for the same variant should use matching BEM structures.
|
||||
|
||||
### Video and poster sources
|
||||
|
||||
All demos use these URLs:
|
||||
|
||||
```
|
||||
Video: https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4
|
||||
Poster: https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg
|
||||
```
|
||||
|
||||
All demo videos use `autoplay muted playsinline loop` (React: `autoPlay muted playsInline loop`).
|
||||
|
||||
## Create the MDX reference page
|
||||
|
||||
Create `site/src/content/docs/reference/{name}.mdx` with this structure:
|
||||
|
||||
### Frontmatter
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: PlayButton
|
||||
frameworkTitle:
|
||||
html: media-play-button
|
||||
description: A button component for playing and pausing media playback
|
||||
---
|
||||
```
|
||||
|
||||
Use `frameworkTitle` to show the HTML custom element tag name when the HTML framework is selected.
|
||||
|
||||
### Imports
|
||||
|
||||
```tsx
|
||||
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
import Demo from "@/components/docs/demos/Demo.astro";
|
||||
|
||||
{/* React demos */}
|
||||
import BasicUsageDemoReact from "@/components/docs/demos/{name}/react/css/BasicUsage";
|
||||
import basicUsageReactTsx from "@/components/docs/demos/{name}/react/css/BasicUsage.tsx?raw";
|
||||
import basicUsageReactCss from "@/components/docs/demos/{name}/react/css/BasicUsage.css?raw";
|
||||
|
||||
{/* HTML demos */}
|
||||
import BasicUsageDemoHtml from "@/components/docs/demos/{name}/html/css/BasicUsage.astro";
|
||||
import basicUsageHtml from "@/components/docs/demos/{name}/html/css/BasicUsage.html?raw";
|
||||
import basicUsageHtmlCss from "@/components/docs/demos/{name}/html/css/BasicUsage.css?raw";
|
||||
import basicUsageHtmlTs from "@/components/docs/demos/{name}/html/css/BasicUsage.ts?raw";
|
||||
```
|
||||
|
||||
#### Import naming conventions
|
||||
|
||||
| What | Naming pattern | Example |
|
||||
|------|---------------|---------|
|
||||
| React demo component | `{Variant}DemoReact` | `BasicUsageDemoReact` |
|
||||
| React source (TSX) | `{variant}ReactTsx` | `basicUsageReactTsx` |
|
||||
| React source (CSS) | `{variant}ReactCss` | `basicUsageReactCss` |
|
||||
| HTML demo component | `{Variant}DemoHtml` | `BasicUsageDemoHtml` |
|
||||
| HTML source (HTML) | `{variant}Html` | `basicUsageHtml` |
|
||||
| HTML source (CSS) | `{variant}HtmlCss` | `basicUsageHtmlCss` |
|
||||
| HTML source (TS) | `{variant}HtmlTs` | `basicUsageHtmlTs` |
|
||||
|
||||
### Page sections
|
||||
|
||||
After imports, the page follows this order:
|
||||
|
||||
1. **Anatomy** — show the component markup for each framework using `<FrameworkCase>`
|
||||
2. **Prose sections** (optional, as needed):
|
||||
- **Behavior** — state transitions, timing, interaction logic
|
||||
- **Styling** — data attribute CSS selectors
|
||||
- **Accessibility** — ARIA attributes, keyboard interactions
|
||||
- Other sections as appropriate
|
||||
3. **Examples** — at least BasicUsage, wrapped in `<Demo>` with `<FrameworkCase>`
|
||||
4. **`<ApiReference />`** — renders the generated JSON as props, state, and data attribute tables
|
||||
|
||||
```mdx
|
||||
<ApiReference component="PlayButton" />
|
||||
```
|
||||
|
||||
The component automatically handles single-part and multi-part layouts.
|
||||
|
||||
## Add to the sidebar
|
||||
|
||||
Open `site/src/docs.config.ts` and add your page alphabetically within the Components section:
|
||||
|
||||
```ts
|
||||
{
|
||||
sidebarLabel: 'Components',
|
||||
contents: [
|
||||
// sorted alphabetically
|
||||
{ slug: 'reference/play-button' },
|
||||
{ slug: 'reference/your-component' }, // add here
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
1. Run `pnpm dev` from the repo root
|
||||
2. Navigate to your reference page in both HTML and React framework modes
|
||||
3. Confirm the anatomy, demos, and API reference tables render correctly
|
||||
4. Check that the page appears in the sidebar
|
||||
|
||||
## Examples
|
||||
|
||||
For reference, look at existing pages:
|
||||
|
||||
- <DocsLink slug="reference/play-button">PlayButton</DocsLink> — single-part, interactive
|
||||
- <DocsLink slug="reference/controls">Controls</DocsLink> — behavior-heavy (auto-hide)
|
||||
- <DocsLink slug="reference/time">Time</DocsLink> — multi-part, formatting
|
||||
Reference in New Issue
Block a user