feat: eject examples (#149)

This commit is contained in:
Darius Cepulis
2025-10-30 00:24:53 -05:00
committed by GitHub
parent ed1398efbf
commit 06a1a9e2f9
32 changed files with 4395 additions and 47 deletions
+24
View File
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>React Minimal Ejected</title>
<link rel="preconnect" href="https://rsms.me/" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<style>
body {
font-family:
InterVariable, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
margin: 0;
}
* {
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
{
"name": "react-eject-minimal",
"type": "module",
"version": "1.0.0",
"private": true,
"description": "React example for Video.js 10",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@videojs/react": "workspace:*",
"clsx": "^2.1.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"screenfull": "^6.0.2",
"tailwindcss": "^4.1.13"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^5.0.3",
"typescript": "^5.9.2",
"vite": "^7.1.6"
}
}
@@ -0,0 +1,120 @@
import type { PropsWithChildren } from 'react';
import { CurrentTimeDisplay, DurationDisplay, FullscreenButton, MediaContainer, MuteButton, PlayButton, Popover, PreviewTimeDisplay, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';
import {
FullscreenEnterAltIcon,
FullscreenExitAltIcon,
PauseIcon,
PlayIcon,
VolumeHighIcon,
VolumeLowIcon,
VolumeOffIcon,
} from '@videojs/react/icons';
import './minimal.css';
type SkinProps = PropsWithChildren<{
className?: string;
}>;
export default function MinimalSkin({ children, className = '' }: SkinProps): JSX.Element {
return (
<MediaContainer className={`vjs-minimal-skin ${className}`}>
{children}
<div className="overlay" />
<div className="control-bar">
<Tooltip.Root delay={500} closeDelay={0}>
<Tooltip.Trigger>
<PlayButton className="button play-button">
<PlayIcon className="icon play-icon" />
<PauseIcon className="icon pause-icon" />
</PlayButton>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Positioner side="top-start" sideOffset={6} collisionPadding={12}>
<Tooltip.Popup className="popup-animation tooltip-popup">
<span className="tooltip play-tooltip">Play</span>
<span className="tooltip pause-tooltip">Pause</span>
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
<div className="time-display-group">
<CurrentTimeDisplay
// Use showRemaining to show count down/remaining time
// showRemaining
className="time-display"
/>
<span className="duration-display">
/
<DurationDisplay className="time-display" />
</span>
</div>
<Tooltip.Root trackCursorAxis="x">
<Tooltip.Trigger>
<TimeSlider.Root className="slider">
<TimeSlider.Track className="slider-track">
<TimeSlider.Progress className="slider-progress" />
<TimeSlider.Pointer className="slider-pointer" />
</TimeSlider.Track>
<TimeSlider.Thumb className="slider-thumb" />
</TimeSlider.Root>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Positioner side="top" sideOffset={12} collisionPadding={12}>
<Tooltip.Popup className="popup-animation tooltip-popup">
<PreviewTimeDisplay className="time-display media-preview-time-display" />
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
<div className="button-group">
<Popover.Root openOnHover delay={200} closeDelay={300}>
<Popover.Trigger>
<MuteButton className="button mute-button">
<VolumeHighIcon className="icon volume-high-icon" />
<VolumeLowIcon className="icon volume-low-icon" />
<VolumeOffIcon className="icon volume-off-icon" />
</MuteButton>
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner side="top" sideOffset={2}>
<Popover.Popup className="popup-animation popover-popup">
<VolumeSlider.Root className="slider" orientation="vertical">
<VolumeSlider.Track className="slider-track">
<VolumeSlider.Progress className="slider-progress" />
</VolumeSlider.Track>
<VolumeSlider.Thumb className="slider-thumb" />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
<Tooltip.Root delay={500} closeDelay={0}>
<Tooltip.Trigger>
<FullscreenButton className="button fullscreen-button">
<FullscreenEnterAltIcon className="icon fullscreen-enter-icon" />
<FullscreenExitAltIcon className="icon fullscreen-exit-icon" />
</FullscreenButton>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Positioner side="top-end" sideOffset={6} collisionPadding={12}>
<Tooltip.Popup className="popup-animation tooltip-popup">
<span className="tooltip fullscreen-enter-tooltip">Enter Fullscreen</span>
<span className="tooltip fullscreen-exit-tooltip">Exit Fullscreen</span>
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
</div>
</div>
</MediaContainer>
);
}
+20
View File
@@ -0,0 +1,20 @@
import { MediaProvider, Video } from '@videojs/react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import MinimalSkin from './MinimalSkin';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<MediaProvider>
<MinimalSkin>
<Video
// @ts-expect-error -- types are incorrect
src="https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA.m3u8"
poster="https://image.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/thumbnail.webp"
playsInline
/>
</MinimalSkin>
</MediaProvider>
</StrictMode>,
);
@@ -0,0 +1,349 @@
.vjs-minimal-skin * {
box-sizing: border-box;
}
.vjs-minimal-skin {
position: relative;
isolation: isolate;
container: root / inline-size;
overflow: clip;
font-size: 0.8125rem;
line-height: 1.5;
border-radius: inherit;
background: oklab(0 0 0);
}
.vjs-minimal-skin::after {
content: '';
position: absolute;
pointer-events: none;
border-radius: inherit;
z-index: 10;
inset: 0;
box-shadow: inset 0 0 0 1px oklab(0 0 0 / 0.2);
}
@media (prefers-color-scheme: dark) {
.vjs-minimal-skin::after {
box-shadow: inset 0 0 0 1px oklab(1 0 0 / 0.2);
}
}
/* Fullscreen */
.vjs-minimal-skin:fullscreen {
border-radius: 0;
}
.vjs-minimal-skin > ::slotted([slot='media']) {
display: block;
width: 100%;
height: 100%;
}
/* Media Container UI Overlay Styling */
.vjs-minimal-skin > .overlay {
position: absolute;
inset: 0;
display: flex;
flex-flow: column nowrap;
align-items: start;
pointer-events: none;
border-radius: inherit;
background-image: linear-gradient(to top, oklab(0 0 0 / 0.7), oklab(0 0 0 / 0.5) 7.5rem, rgba(0, 0, 0, 0));
transform: translateY(100%);
transition-property: transform, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
transition-delay: 500ms;
opacity: 0;
}
.vjs-minimal-skin:hover > .overlay,
.vjs-minimal-skin:has([data-paused]) > .overlay,
.vjs-minimal-skin:has([aria-expanded='true']) > .overlay {
opacity: 1;
transform: translateY(0);
transition-duration: 100ms;
transition-delay: 0ms;
}
/* Media Control Bar UI/Styles */
.vjs-minimal-skin .control-bar {
position: absolute;
bottom: 0;
inset-inline: 0;
padding: 2.5rem 0.75rem 0.75rem 0.75rem;
display: flex;
align-items: center;
gap: 0.875rem;
will-change: transform, filter, opacity;
transform: translateY(100%);
opacity: 0;
filter: blur(8px);
transition-property: transform, filter, opacity;
transition-delay: 500ms;
transition-duration: 300ms;
transition-timing-function: ease-out;
color: oklab(1 0 0);
}
.vjs-minimal-skin:hover > .control-bar,
.vjs-minimal-skin:has([data-paused]) > .control-bar,
.vjs-minimal-skin:has([aria-expanded='true']) > .control-bar {
opacity: 1;
transform: translateY(0);
filter: blur(0px);
transition-delay: 0ms;
transition-duration: 100ms;
}
/* Time Display Styling */
.vjs-minimal-skin .time-display-group {
display: flex;
align-items: center;
gap: 0.25rem;
}
.vjs-minimal-skin .duration-display {
display: contents;
color: oklab(1 0 0 / 0.5);
}
.vjs-minimal-skin .time-display {
text-shadow: 0 1px 0 oklab(0 0 0 / 0.2);
font-variant-numeric: tabular-nums;
}
.vjs-minimal-skin .button-group {
display: flex;
align-items: center;
gap: 0.375rem;
}
/* Generic Media Button Styling */
.vjs-minimal-skin .button {
display: grid;
flex-shrink: 0;
padding: 0.625rem;
cursor: pointer;
background: transparent;
border: none;
border-radius: 0.375rem;
color: oklab(1 0 0);
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
transition-property: background-color, color, outline-offset;
transition-duration: 150ms;
transition-timing-function: ease-out;
}
.vjs-minimal-skin .button:hover,
.vjs-minimal-skin .button:focus-visible,
.vjs-minimal-skin .button[aria-expanded='true'] {
color: oklab(1 0 0 / 0.8);
text-decoration: none;
}
.vjs-minimal-skin .button:focus-visible {
outline-color: oklab(1 0 0);
outline-offset: 2px;
}
.vjs-minimal-skin .button[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
.vjs-minimal-skin .button .icon {
grid-area: 1 / 1;
width: 18px;
height: 18px;
filter: drop-shadow(0 1px 0 oklab(0 0 0 / 0.4));
}
/* Media Play Button UI/Styles */
.vjs-minimal-skin .play-button .icon {
opacity: 0;
transition: opacity 150ms linear;
}
.vjs-minimal-skin .play-button:not([data-paused]) .pause-icon,
.vjs-minimal-skin .play-button[data-paused] .play-icon {
opacity: 1;
}
/* Media Fullscreen Button UI/Styles */
.vjs-minimal-skin .fullscreen-button .icon {
display: none;
}
.vjs-minimal-skin .fullscreen-button:not([data-fullscreen]) .fullscreen-enter-icon,
.vjs-minimal-skin .fullscreen-button[data-fullscreen] .fullscreen-exit-icon {
display: inline;
}
/* One way to define the "default visible" icon (CJP) */
.vjs-minimal-skin .mute-button .icon {
display: none;
}
.vjs-minimal-skin .mute-button:not([data-volume-level]) .volume-low-icon,
.vjs-minimal-skin .mute-button[data-volume-level='high'] .volume-high-icon,
.vjs-minimal-skin .mute-button[data-volume-level='low'] .volume-low-icon,
.vjs-minimal-skin .mute-button[data-volume-level='medium'] .volume-low-icon,
.vjs-minimal-skin .mute-button[data-volume-level='off'] .volume-off-icon {
display: inline;
}
/* TimeSlider Component Styles */
.vjs-minimal-skin .slider {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
border-radius: calc(infinity * 1px);
outline: none;
}
/* Horizontal orientation styles */
.vjs-minimal-skin .slider[data-orientation='horizontal'] {
min-width: 5rem;
width: 100%;
height: 1.25rem;
}
/* Vertical orientation styles */
.vjs-minimal-skin .slider[data-orientation='vertical'] {
height: 4.5rem;
width: 1.25rem;
}
.vjs-minimal-skin .slider-track {
position: relative;
isolation: isolate;
background-color: oklab(1 0 0 / 0.2);
border-radius: inherit;
overflow: hidden;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
transition: outline-offset 150ms ease-out;
box-shadow: 0 0 0 1px oklab(0 0 0 / 0.05);
}
/* Horizontal track styles */
.vjs-minimal-skin .slider-track[data-orientation='horizontal'] {
width: 100%;
height: 0.1875rem;
}
/* Vertical track styles */
.vjs-minimal-skin .slider-track[data-orientation='vertical'] {
width: 0.1875rem;
height: 100%;
}
.vjs-minimal-skin .slider:focus-visible .slider-track {
outline-color: oklab(1 0 0);
outline-offset: 6px;
}
.vjs-minimal-skin .slider-thumb {
width: 0.75rem;
height: 0.75rem;
background-color: oklab(1 0 0);
border-radius: calc(infinity * 1px);
user-select: none;
z-index: 10;
box-shadow:
0 0 0 1px oklab(0 0 0 / 0.1),
0 1px 3px 0 oklab(0 0 0 / 0.15),
0 1px 2px -1px oklab(0 0 0 / 0.15);
opacity: 0;
scale: 0.7;
transform-origin: center;
transition-property: opacity, scale;
transition-duration: 150ms;
transition-timing-function: ease-out;
}
.vjs-minimal-skin .slider:hover .slider-thumb,
.vjs-minimal-skin .slider:focus-within .slider-thumb {
opacity: 1;
scale: 1;
}
.vjs-minimal-skin .slider-track[data-orientation='horizontal'] .slider-thumb {
cursor: ew-resize;
}
.vjs-minimal-skin .slider-track[data-orientation='vertical'] .slider-thumb {
cursor: ns-resize;
}
.vjs-minimal-skin .slider-pointer {
display: none;
}
.vjs-minimal-skin .slider-progress {
background-color: oklab(1 0 0);
border-radius: inherit;
}
.vjs-minimal-skin .media-preview-time-display {
font-variant-numeric: tabular-nums;
}
.vjs-minimal-skin .popup-animation {
transition-property: transform, scale, opacity, filter;
transition-duration: 200ms;
transform: scale(1);
transform-origin: bottom;
opacity: 1;
filter: blur(0px);
}
.vjs-minimal-skin .popup-animation[data-starting-style],
.vjs-minimal-skin .popup-animation[data-ending-style] {
transform: scale(0);
opacity: 0;
filter: blur(8px);
}
.vjs-minimal-skin .popup-animation[data-instant] {
transition-duration: 0ms;
}
.vjs-minimal-skin .popover-popup {
margin: 0;
border: none;
box-shadow: none;
background: transparent;
padding: 0.75rem 0.25rem;
border-radius: calc(infinity * 1px);
}
/* Tooltip Component Styles */
.vjs-minimal-skin .tooltip-popup {
color: oklab(1 0 0);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
background-color: oklab(1 0 0 / 0.1);
backdrop-filter: blur(64px) brightness(0.9) saturate(1.5);
box-shadow:
0 4px 6px -1px oklab(0 0 0 / 0.1),
0 2px 4px -2px oklab(0 0 0 / 0.1);
}
@media (prefers-reduced-transparency: reduce) {
.vjs-minimal-skin .tooltip-popup {
background-color: oklab(0 0 0 / 0.7);
}
}
@media (prefers-contrast: more) {
.vjs-minimal-skin .tooltip-popup {
background-color: oklab(0 0 0 / 0.9);
}
}
.vjs-minimal-skin .tooltip {
display: none;
white-space: nowrap;
}
.vjs-minimal-skin .tooltip-popup[data-paused] .play-tooltip,
.vjs-minimal-skin .tooltip-popup:not([data-paused]) .pause-tooltip {
display: block;
}
.vjs-minimal-skin .tooltip-popup[data-fullscreen] .fullscreen-exit-tooltip,
.vjs-minimal-skin .tooltip-popup:not([data-fullscreen]) .fullscreen-enter-tooltip {
display: block;
}
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"baseUrl": "."
},
"include": ["src"]
}
@@ -0,0 +1,15 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
// https://vitejs.dev/config
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
port: 5173,
},
optimizeDeps: {
exclude: ['@videojs/react'],
},
});