Files
sim/apps/docs/components/ui/video.tsx
T
Waleed dcaa118752 improvement(docs): restructure sidebar, align chrome, rename Mothership to Chat (#6296)
Sidebar: 11 separator groups become 5, with each module a collapsible folder
that auto-opens on the active page. 61 always-visible rows drop to 16. Groups
mirror the app's own nav (Chats/Workspace/Workflows) rather than inventing a
taxonomy; Enterprise and Self-Hosting are hoisted out of Platform.

Chrome: register the `hover-hover` variant, without which every @sim/emcn hover
state silently compiled to nothing; restore the sidebar's Geist font stack; add
11 emcn tokens that were falling back to currentColor; adopt the named type
scale; align row geometry, hover tokens and group labels with the app.

Rename: mothership/ -> chat/ with redirects for the old URLs. Asset paths,
the @mothership.sim.ai domain and the `mothership` log-trigger enum value are
deliberately left alone -- they are CDN objects, a real domain, and a live
product value.

Also removes the page-type badge, drops the "Next" heading from the ToC, and
lets FAQ rows open independently so expanding one no longer shifts the page.
2026-08-05 13:49:17 -07:00

109 lines
2.4 KiB
TypeScript

'use client'
import { useEffect, useRef, useState } from 'react'
import { cn, getAssetUrl } from '@/lib/utils'
import { Lightbox } from './lightbox'
interface VideoProps {
src: string
className?: string
autoPlay?: boolean
loop?: boolean
muted?: boolean
playsInline?: boolean
enableLightbox?: boolean
width?: number
height?: number
}
export function Video({
src,
className = 'w-full',
autoPlay = true,
loop = true,
muted = true,
playsInline = true,
enableLightbox = true,
width,
height,
}: VideoProps) {
const videoRef = useRef<HTMLVideoElement>(null)
const startTimeRef = useRef(0)
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
const [isInView, setIsInView] = useState(false)
useEffect(() => {
const el = videoRef.current
if (!el) return
if (typeof IntersectionObserver === 'undefined') {
setIsInView(true)
return
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true)
observer.disconnect()
}
},
{ rootMargin: '200px' }
)
observer.observe(el)
return () => observer.disconnect()
}, [])
const openLightbox = () => {
startTimeRef.current = videoRef.current?.currentTime ?? 0
setIsLightboxOpen(true)
}
const video = (
<video
ref={videoRef}
autoPlay={isInView && autoPlay}
loop={loop}
muted={muted}
playsInline={playsInline}
preload='none'
width={width}
height={height}
className={cn(
'overflow-hidden rounded-xl border border-[var(--border)] outline-none focus:outline-none',
enableLightbox && 'cursor-pointer transition-opacity group-hover:opacity-[0.97]',
className
)}
src={isInView ? getAssetUrl(src) : undefined}
/>
)
return (
<>
{enableLightbox ? (
<button
type='button'
onClick={openLightbox}
aria-label={`Open ${src} in media viewer`}
className='group contents'
>
{video}
</button>
) : (
video
)}
{enableLightbox && (
<Lightbox
isOpen={isLightboxOpen}
onClose={() => setIsLightboxOpen(false)}
src={src}
alt={`Video: ${src}`}
type='video'
startTime={startTimeRef.current}
/>
)}
</>
)
}