mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
chore(root): streamline agent guidance and skills
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
# Demo Patterns
|
||||
|
||||
File structure and conventions for interactive component demos.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
site/src/components/docs/demos/{component}/
|
||||
├── html/css/
|
||||
│ ├── BasicUsage.astro # Astro wrapper (renders HTML, imports CSS, bundles script)
|
||||
│ ├── BasicUsage.html # Markup only (no <style> or <script>)
|
||||
│ ├── BasicUsage.css # Styles
|
||||
│ └── BasicUsage.ts # Side-effect imports for custom element registration
|
||||
└── react/css/
|
||||
├── BasicUsage.tsx # React component
|
||||
└── BasicUsage.css # Styles
|
||||
```
|
||||
|
||||
## BEM Naming
|
||||
|
||||
Block = `{framework}-{component}-{variant}`, element = `__{part}`:
|
||||
|
||||
```
|
||||
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 */
|
||||
```
|
||||
|
||||
The framework prefix (`html-` / `react-`) prevents CSS leaking between HTML and React demos on the same page (both render but one is hidden).
|
||||
|
||||
## HTML Demo Files
|
||||
|
||||
### .astro wrapper
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
The `.astro` wrapper is required because only Astro `<script>` tags go through Vite's bundling pipeline.
|
||||
|
||||
### .html (markup only)
|
||||
|
||||
```html
|
||||
<video-player class="html-mute-button-basic">
|
||||
<video
|
||||
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
loop
|
||||
></video>
|
||||
<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>
|
||||
</video-player>
|
||||
```
|
||||
|
||||
- No `<style>` or `<script>` tags
|
||||
- Video attributes: `autoplay muted playsinline loop`
|
||||
- State labels use CSS class names toggled by data attributes
|
||||
|
||||
### .css (styles)
|
||||
|
||||
```css
|
||||
.html-mute-button-basic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.html-mute-button-basic video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.html-mute-button-basic__button {
|
||||
padding-block: 8px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
color: black;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 9999px;
|
||||
padding-inline: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 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; }
|
||||
```
|
||||
|
||||
### .ts (registration imports)
|
||||
|
||||
```ts
|
||||
import '@videojs/html/video/player';
|
||||
import '@videojs/html/ui/mute-button';
|
||||
```
|
||||
|
||||
Import registration for:
|
||||
|
||||
- `@videojs/html/video/player` — always needed (registers `<video-player>`)
|
||||
- `@videojs/html/ui/{component}` — registers the component's custom element
|
||||
|
||||
## React Demo Files
|
||||
|
||||
### .tsx (component)
|
||||
|
||||
```tsx
|
||||
import { createPlayer, MuteButton } from '@videojs/react';
|
||||
import { Video, videoFeatures } from '@videojs/react/video';
|
||||
|
||||
import './BasicUsage.css';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
|
||||
export default function BasicUsage() {
|
||||
return (
|
||||
<Player.Provider>
|
||||
<Player.Container className="react-mute-button-basic">
|
||||
<Video
|
||||
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
/>
|
||||
<MuteButton
|
||||
className="react-mute-button-basic__button"
|
||||
render={(props, state) => (
|
||||
<button {...props}>{state.muted ? 'Unmute' : 'Mute'}</button>
|
||||
)}
|
||||
/>
|
||||
</Player.Container>
|
||||
</Player.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Key patterns:
|
||||
|
||||
- `createPlayer({ features: videoFeatures })` creates the player
|
||||
- Video attributes: `autoPlay muted playsInline loop` (React camelCase)
|
||||
- `render` prop for state-based rendering: `render={(props, state) => ...}`
|
||||
- Spread `{...props}` on the rendered element for accessibility attributes
|
||||
|
||||
### .css (styles)
|
||||
|
||||
Same base styling as HTML but with `react-` BEM prefix:
|
||||
|
||||
```css
|
||||
.react-mute-button-basic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-mute-button-basic video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-mute-button-basic__button {
|
||||
padding-block: 8px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
color: black;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 9999px;
|
||||
padding-inline: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
```
|
||||
|
||||
React CSS files typically don't need data-attribute selectors since the `render` prop handles state-based rendering. Include them only when CSS state reflection is used.
|
||||
|
||||
## State Reflection Patterns
|
||||
|
||||
### HTML: Data attribute selectors
|
||||
|
||||
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; }
|
||||
|
||||
/* 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; }
|
||||
```
|
||||
|
||||
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 {
|
||||
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; }
|
||||
```
|
||||
|
||||
### React: Render prop
|
||||
|
||||
```tsx
|
||||
<MuteButton
|
||||
render={(props, state) => (
|
||||
<button {...props}>
|
||||
{state.volumeLevel === 'off'
|
||||
? 'Off'
|
||||
: state.volumeLevel === 'low'
|
||||
? 'Low'
|
||||
: state.volumeLevel === 'medium'
|
||||
? 'Medium'
|
||||
: 'High'}
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Three-state pattern (Play/Pause/Replay)
|
||||
|
||||
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; }
|
||||
```
|
||||
|
||||
React uses nested ternary in the render prop:
|
||||
|
||||
```tsx
|
||||
render={(props, state) => (
|
||||
<button {...props}>{state.ended ? 'Replay' : state.paused ? 'Play' : 'Pause'}</button>
|
||||
)}
|
||||
```
|
||||
|
||||
## Video Sources
|
||||
|
||||
- **Video**: `https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4`
|
||||
- **Poster**: `https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.jpg`
|
||||
|
||||
## Base Button Styles
|
||||
|
||||
All button demos share this base overlay style:
|
||||
|
||||
```css
|
||||
.__button {
|
||||
padding-block: 8px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
color: black;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 9999px;
|
||||
padding-inline: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user