mirror of
https://github.com/LibreChat-AI/librechat.ai.git
synced 2026-03-27 02:38:32 +07:00
* chore: upgrade eslint to v9 * chore: update package dependencies in package.json and pnpm-lock.yaml - Added `minimatch` and `serialize-javascript` dependencies with updated versions. - Upgraded `ajv` to version 6.14.0. - Removed outdated dependencies from pnpm-lock.yaml for better package management. * feat: add Stripe logos to Companies section - Introduced new company entry for Stripe in the Companies component, including both light and dark logo variants. - Updated the Companies array to display 10 logos instead of 8. - Adjusted TypeScript environment reference to point to the development types directory.
90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
import 'vidstack/styles/base.css'
|
|
import { cn } from '@/lib/utils'
|
|
import { MediaPlayer, MediaOutlet, useMediaRemote, useMediaStore } from '@vidstack/react'
|
|
import { Play } from 'lucide-react'
|
|
import { useState, useRef } from 'react'
|
|
|
|
/** Video embed component using vidstack. Supports poster overlays, gif-style autoplay, and lazy loading. */
|
|
export const Video = ({
|
|
src,
|
|
poster,
|
|
aspectRatio,
|
|
className,
|
|
gifStyle = false,
|
|
title,
|
|
}: {
|
|
src: string
|
|
poster?: string
|
|
aspectRatio?: number
|
|
gifStyle?: boolean
|
|
className?: string
|
|
title?: string
|
|
}) => {
|
|
const [panelDismissed, setPanelDismissed] = useState(false)
|
|
const mediaPlayerRef = useRef(null)
|
|
const remote = useMediaRemote(mediaPlayerRef)
|
|
const { duration } = useMediaStore(mediaPlayerRef)
|
|
const durationString = duration
|
|
? `${Math.floor(duration / 60)}:${Math.floor(duration % 60)} min`
|
|
: null
|
|
|
|
return (
|
|
<MediaPlayer
|
|
ref={mediaPlayerRef}
|
|
src={src}
|
|
controls={!gifStyle && panelDismissed}
|
|
autoplay={gifStyle}
|
|
muted={gifStyle}
|
|
loop={gifStyle}
|
|
load={gifStyle ? 'eager' : 'custom'}
|
|
playsinline={gifStyle}
|
|
aspectRatio={aspectRatio}
|
|
className={cn(
|
|
'my-4 overflow-hidden shadow-lg ring-1 ring-slate-700 bg-cover object-cover',
|
|
className,
|
|
)}
|
|
>
|
|
{gifStyle ? (
|
|
// Capture mouse events, they broke scrolling on iOS
|
|
<div className="absolute inset-0 z-10" />
|
|
) : panelDismissed ? null : (
|
|
// Overlay with play button and poster image
|
|
<div
|
|
role="button"
|
|
tabIndex={0}
|
|
aria-label={title ? `Play ${title}` : 'Play video'}
|
|
className="group cursor-pointer absolute inset-0 z-10 flex flex-col justify-center items-center bg-cover"
|
|
style={{
|
|
backgroundImage: poster ? `url(${poster})` : undefined,
|
|
}}
|
|
onMouseEnter={() => {
|
|
remote.startLoading()
|
|
}}
|
|
onClick={() => {
|
|
remote.play()
|
|
setPanelDismissed(true)
|
|
}}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault()
|
|
remote.play()
|
|
setPanelDismissed(true)
|
|
}
|
|
}}
|
|
>
|
|
<div className="p-3 md:p-6 rounded-full bg-black/75 group-hover:ring-8 ring-black/20 hover:bg-black/90 transition flex">
|
|
<Play className="size-6 text-white" />
|
|
</div>
|
|
<div className="mt-3 md:mt-6 md:opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
<span className="flex gap-2 text-xs md:text-sm font-semibold bg-black/90 text-white py-1 px-3 rounded-full">
|
|
{title && <span>{title}</span>}
|
|
{durationString && <span className="text-white/70">{durationString}</span>}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<MediaOutlet />
|
|
</MediaPlayer>
|
|
)
|
|
}
|