Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 32 additions & 30 deletions .agents/skills/write-api-reference/references/demo-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ site/src/components/docs/demos/{component}/
└── BasicUsage.css # Styles
```

## BEM Naming
## CSS Scoping

Block = `{framework}-{component}-{variant}`, element = `__{part}`:
Give every demo a unique root class using `{framework}-{component}-{variant}`. Use flat semantic names for additional
component or part hooks:

```
html-play-button-basic /* HTML framework, block */
html-play-button-basic__button /* HTML framework, element */
react-play-button-basic /* React framework, block */
react-play-button-basic__button /* React framework, element */
html-play-button-basic /* HTML framework, root */
html-play-button-basic-button /* HTML framework, button */
react-play-button-basic /* React framework, root */
react-play-button-basic-button /* React framework, button */
```

The framework prefix (`html-` / `react-`) prevents CSS leaking between HTML and React demos on the same page (both render but one is hidden).
The framework prefix (`html-` / `react-`) prevents CSS leaking between HTML and React demos on the same page (both
render but one is hidden). React and HTML demos for the same variant should use matching semantic hooks.

## HTML Demo Files

Expand Down Expand Up @@ -58,7 +60,7 @@ The `.astro` wrapper is required because only Astro `<script>` tags go through V
playsinline
loop
></video>
<media-mute-button class="html-mute-button-basic__button">
<media-mute-button class="html-mute-button-basic-button">
<span class="show-when-muted">Unmute</span>
<span class="show-when-unmuted">Mute</span>
</media-mute-button>
Expand All @@ -80,7 +82,7 @@ The `.astro` wrapper is required because only Astro `<script>` tags go through V
width: 100%;
}

.html-mute-button-basic__button {
.html-mute-button-basic-button {
padding-block: 8px;
position: absolute;
bottom: 10px;
Expand All @@ -95,10 +97,10 @@ The `.astro` wrapper is required because only Astro `<script>` tags go through V
}

/* State-based visibility via data attributes */
.html-mute-button-basic__button .show-when-muted { display: none; }
.html-mute-button-basic__button .show-when-unmuted { display: none; }
.html-mute-button-basic__button[data-muted] .show-when-muted { display: inline; }
.html-mute-button-basic__button:not([data-muted]) .show-when-unmuted { display: inline; }
.html-mute-button-basic-button .show-when-muted { display: none; }
.html-mute-button-basic-button .show-when-unmuted { display: none; }
.html-mute-button-basic-button[data-muted] .show-when-muted { display: inline; }
.html-mute-button-basic-button:not([data-muted]) .show-when-unmuted { display: inline; }
```

### .ts (registration imports)
Expand Down Expand Up @@ -139,7 +141,7 @@ export default function BasicUsage() {
loop
/>
<MuteButton
className="react-mute-button-basic__button"
className="react-mute-button-basic-button"
render={(props, state) => (
<button {...props}>{state.muted ? 'Unmute' : 'Mute'}</button>
)}
Expand All @@ -159,7 +161,7 @@ Key patterns:

### .css (styles)

Same base styling as HTML but with `react-` BEM prefix:
Use the same semantic hooks as HTML with the `react-` demo prefix:

```css
.react-mute-button-basic {
Expand All @@ -170,7 +172,7 @@ Same base styling as HTML but with `react-` BEM prefix:
width: 100%;
}

.react-mute-button-basic__button {
.react-mute-button-basic-button {
padding-block: 8px;
position: absolute;
bottom: 10px;
Expand All @@ -195,26 +197,26 @@ Components expose state via `data-*` attributes. CSS toggles visibility:

```css
/* Hide all by default */
.html-play-button-basic__button .show-when-paused { display: none; }
.html-play-button-basic__button .show-when-playing { display: none; }
.html-play-button-basic-button .show-when-paused { display: none; }
.html-play-button-basic-button .show-when-playing { display: none; }

/* Show based on state */
.html-play-button-basic__button[data-paused] .show-when-paused { display: inline; }
.html-play-button-basic__button:not([data-paused]) .show-when-playing { display: inline; }
.html-play-button-basic-button[data-paused] .show-when-paused { display: inline; }
.html-play-button-basic-button:not([data-paused]) .show-when-playing { display: inline; }
```

For multi-value attributes (e.g., `data-volume-level`):

```css
.html-mute-button-volume-levels__button .level-off,
.html-mute-button-volume-levels__button .level-low,
.html-mute-button-volume-levels__button .level-medium,
.html-mute-button-volume-levels__button .level-high {
.html-mute-button-volume-levels-button .level-off,
.html-mute-button-volume-levels-button .level-low,
.html-mute-button-volume-levels-button .level-medium,
.html-mute-button-volume-levels-button .level-high {
display: none;
}

.html-mute-button-volume-levels__button[data-volume-level="off"] .level-off { display: inline; }
.html-mute-button-volume-levels__button[data-volume-level="low"] .level-low { display: inline; }
.html-mute-button-volume-levels-button[data-volume-level="off"] .level-off { display: inline; }
.html-mute-button-volume-levels-button[data-volume-level="low"] .level-low { display: inline; }
```

### React: Render prop
Expand All @@ -240,9 +242,9 @@ For multi-value attributes (e.g., `data-volume-level`):
HTML uses `:not()` combinators to handle mutually exclusive states:

```css
.html-play-button-basic__button[data-paused]:not([data-ended]) .show-when-paused { display: inline; }
.html-play-button-basic__button:not([data-paused]) .show-when-playing { display: inline; }
.html-play-button-basic__button[data-ended] .show-when-ended { display: inline; }
.html-play-button-basic-button[data-paused]:not([data-ended]) .show-when-paused { display: inline; }
.html-play-button-basic-button:not([data-paused]) .show-when-playing { display: inline; }
.html-play-button-basic-button[data-ended] .show-when-ended { display: inline; }
```

React uses nested ternary in the render prop:
Expand All @@ -263,7 +265,7 @@ render={(props, state) => (
All button demos share this base overlay style:

```css
.__button {
.demo-button {
padding-block: 8px;
position: absolute;
bottom: 10px;
Expand Down
19 changes: 19 additions & 0 deletions site/src/components/docs/api-reference/ComponentImports.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
import type { BundledLanguage } from 'shiki';

import ServerCode from '@/components/Code/ServerCode.astro';
import RegistryInstall from '@/components/docs/RegistryInstall.astro';
import CodeFrame from '@/components/typography/CodeFrame.astro';
import { isValidFramework } from '@/types/docs';
import { registryItemForComponent } from '@/utils/installation/shadcn-registry';
import FrameworkCase from '../FrameworkCase.astro';

interface Props {
Expand All @@ -11,6 +14,10 @@ interface Props {
}

const { component, html } = Astro.props;
const framework = Astro.params.framework;
if (!isValidFramework(framework)) throw new Error(`Unsupported framework: ${framework ?? 'missing'}.`);

const registryItem = framework === 'react' ? await registryItemForComponent(component) : null;

const imports: Array<{ code: string; framework: 'react' | 'html'; lang: BundledLanguage }> = [
{
Expand All @@ -35,3 +42,15 @@ const imports: Array<{ code: string; framework: 'react' | 'html'; lang: BundledL
</FrameworkCase>
))
}

{
registryItem ? (
<>
<h3 id="editable-source">Editable styled source</h3>
<p>
Install the styled component and its exact dependencies with Shadcn, or copy the same generated files manually.
</p>
<RegistryInstall framework="react" item={registryItem} />
</>
) : null
}
60 changes: 46 additions & 14 deletions site/src/components/docs/api-reference/PresetReference.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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 { skinGuides } from '../skins/skinGuides';
import InlineMarkdown from './InlineMarkdown.astro';

const entries = await getCollection('presetReference');
Expand All @@ -34,6 +35,17 @@ const { framework } = Astro.params;
const pkg = framework && isValidFramework(framework) ? `@videojs/${framework}` : '@videojs/html';

const listFormat = new Intl.ListFormat('en', { style: 'long', type: 'unit' });

function getSkinReferenceSlug(name: string): string | undefined {
const entry = Object.entries(skinGuides).find(([, skin]) =>
skin.reactSkin === name || skin.htmlSkin === name
);
if (!entry) return;

return entry[0] === 'background'
? 'reference/background-video-skin'
: `reference/${entry[0]}-skin`;
}
---

<ContentWidth>
Expand Down Expand Up @@ -125,16 +137,27 @@ const listFormat = new Intl.ListFormat('en', { style: 'long', type: 'unit' });
<dd>
<FrameworkCase frameworks={["react"]}>
{preset.react.skins.length > 0
? preset.react.skins.map((skin) => (
<div class="mb-2 last:mb-0">
? preset.react.skins.map((skin) => {
const slug = getSkinReferenceSlug(skin.name);
const component = (
<MarkdownCode class="inline-block whitespace-nowrap">{`<${skin.name}>`}</MarkdownCode>
{skin.cssImport && (
<div class="mt-0.5">
<MarkdownCode class="inline-block text-code whitespace-nowrap">{`import '${skin.cssImport}';`}</MarkdownCode>
</div>
)}
</div>
))
);

return (
<div class="mb-2 last:mb-0">
{slug ? (
<DocsLink slug={slug}>{component}</DocsLink>
) : (
component
)}
{skin.cssImport && (
<div class="mt-0.5">
<MarkdownCode class="inline-block text-code whitespace-nowrap">{`import '${skin.cssImport}';`}</MarkdownCode>
</div>
)}
</div>
);
})
: "–"}
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Expand All @@ -145,13 +168,22 @@ const listFormat = new Intl.ListFormat('en', { style: 'long', type: 'unit' });
(s) => s.tagName ?? s.name,
),
)
.map((part) =>
part.type === "element" ? (
.map((part) => {
if (part.type !== "element") {
return <span>{part.value}</span>;
}

const slug = getSkinReferenceSlug(part.value);
const element = (
<MarkdownCode class="inline-block whitespace-nowrap">{`<${part.value}>`}</MarkdownCode>
);

return slug ? (
<DocsLink slug={slug}>{element}</DocsLink>
) : (
<span>{part.value}</span>
),
)
element
);
})
: "–"}
</FrameworkCase>
</dd>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,38 @@
isolation: isolate;
}

.add-background-video-html-demo__visual,
.add-background-video-html-demo__poster,
.add-background-video-html-demo__media {
.add-background-video-html-demo-visual,
.add-background-video-html-demo-poster,
.add-background-video-html-demo-media {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}

.add-background-video-html-demo__visual {
.add-background-video-html-demo-visual {
pointer-events: none;
}

.add-background-video-html-demo__visual::after {
.add-background-video-html-demo-visual::after {
position: absolute;
inset: 0;
content: "";
background: linear-gradient(90deg, rgb(0 0 0 / 85%), rgb(0 0 0 / 55%));
}

.add-background-video-html-demo__poster,
.add-background-video-html-demo__media {
.add-background-video-html-demo-poster,
.add-background-video-html-demo-media {
display: block;
object-fit: cover;
}

.add-background-video-html-demo__media {
.add-background-video-html-demo-media {
--media-object-fit: cover;
--media-object-position: center;
}

.add-background-video-html-demo__content {
.add-background-video-html-demo-content {
position: relative;
z-index: 1;
display: grid;
Expand All @@ -50,13 +50,13 @@
padding: 2rem;
}

.add-background-video-html-demo__content h3 {
.add-background-video-html-demo-content h3 {
margin: 0;
font-size: 1.75rem;
line-height: 1.1;
}

.add-background-video-html-demo__content a {
.add-background-video-html-demo-content a {
width: fit-content;
padding: 0.625rem 1rem;
color: black;
Expand All @@ -65,13 +65,13 @@
border-radius: 0.25rem;
}

.add-background-video-html-demo__content a:focus-visible {
.add-background-video-html-demo-content a:focus-visible {
outline: 0.2rem solid white;
outline-offset: 0.2rem;
}

@media (prefers-reduced-motion: reduce) {
.add-background-video-html-demo__media {
.add-background-video-html-demo-media {
display: none;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<section class="add-background-video-html-demo">
<div class="add-background-video-html-demo__visual" aria-hidden="true">
<img class="add-background-video-html-demo__poster" src="{{VJS10_DEMO_BACKGROUND_VIDEO_POSTER}}" alt="" />
<div class="add-background-video-html-demo-visual" aria-hidden="true">
<img class="add-background-video-html-demo-poster" src="{{VJS10_DEMO_BACKGROUND_VIDEO_POSTER}}" alt="" />
<background-video
class="add-background-video-html-demo__media"
class="add-background-video-html-demo-media"
src="{{VJS10_DEMO_BACKGROUND_VIDEO_MP4}}"
></background-video>
</div>
<div class="add-background-video-html-demo__content">
<div class="add-background-video-html-demo-content">
<h3>Build the next great video experience</h3>
<a href="#common-variations">Compare source options</a>
</div>
Expand Down
Loading
Loading