unified-video-framework
Version:
Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more
178 lines • 9.28 kB
JavaScript
import React, { useEffect, useState } from 'react';
function formatCount(n) {
if (!n)
return '0';
if (n >= 1000000)
return (n / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
if (n >= 1000)
return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
return String(n);
}
function getCountLabel(n, singular, plural) {
return n === 1 ? singular : plural;
}
function formatDate(value) {
if (!value)
return null;
const date = value instanceof Date ? value : new Date(value);
if (Number.isNaN(date.getTime())) {
const raw = String(value).trim();
if (!raw)
return null;
return { monthDay: raw, year: '' };
}
return {
monthDay: date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),
year: String(date.getFullYear()),
};
}
export const PortraitDescriptionPanel = ({ video, isOpen, onClose, desktopInline = false, }) => {
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
const transitionMs = 240;
const [shouldRender, setShouldRender] = useState(isOpen);
useEffect(() => {
if (isOpen) {
setShouldRender(true);
return;
}
const timer = window.setTimeout(() => setShouldRender(false), transitionMs);
return () => window.clearTimeout(timer);
}, [isOpen]);
useEffect(() => {
if (!isOpen)
return;
const handleEscape = (e) => {
if (e.key === 'Escape')
onClose();
};
window.addEventListener('keydown', handleEscape);
return () => window.removeEventListener('keydown', handleEscape);
}, [isOpen, onClose]);
if (!shouldRender)
return null;
const panelDate = formatDate(video.publishDate ?? video.timestamp);
return (React.createElement(React.Fragment, null,
isMobile && !desktopInline && (React.createElement("div", { onClick: onClose, style: {
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.5)',
zIndex: 100,
opacity: isOpen ? 1 : 0,
transition: `opacity ${transitionMs}ms cubic-bezier(0.22, 1, 0.36, 1)`,
pointerEvents: isOpen ? 'auto' : 'none',
} })),
React.createElement("div", { "data-uvf-description-panel": true, style: {
position: 'fixed',
...(isMobile
? {
left: 0,
right: 0,
bottom: 0,
top: 'auto',
maxHeight: '85vh',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
transform: isOpen ? 'translateY(0)' : 'translateY(100%)',
}
: {
...(desktopInline
? {
position: 'relative',
top: 'auto',
right: 'auto',
bottom: 'auto',
width: '100%',
maxWidth: '100%',
height: 'calc(100% - 24px)',
margin: '12px 0',
borderRadius: 14,
transform: 'none',
}
: {
top: 14,
right: 96,
bottom: 14,
width: '100%',
maxWidth: 500,
borderRadius: 14,
transform: isOpen ? 'translateX(0)' : 'translateX(calc(100% + 20px))',
}),
}),
background: '#202124',
zIndex: 101,
display: 'flex',
flexDirection: 'column',
transition: desktopInline ? 'none' : `transform ${transitionMs}ms cubic-bezier(0.22, 1, 0.36, 1), opacity ${transitionMs}ms ease`,
opacity: desktopInline ? 1 : (isOpen ? 1 : 0),
boxShadow: isMobile ? '0 -4px 20px rgba(0,0,0,0.5)' : '-8px 0 28px rgba(0,0,0,0.55)',
border: isMobile ? 'none' : '1px solid rgba(255,255,255,0.18)',
overflow: 'hidden',
} },
isMobile && (React.createElement("div", { style: { display: 'flex', justifyContent: 'center', padding: '12px 0 8px 0', cursor: 'grab' } },
React.createElement("div", { style: { width: 36, height: 4, borderRadius: 2, background: 'rgba(255,255,255,0.3)' } }))),
React.createElement("div", { style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: isMobile ? '10px 18px 14px 18px' : '14px 20px',
borderBottom: '1px solid rgba(255,255,255,0.1)',
background: 'rgba(0,0,0,0.18)',
} },
React.createElement("h2", { style: {
margin: 0,
fontSize: isMobile ? 24 : 16,
fontWeight: 700,
color: '#fff',
lineHeight: 1.2,
letterSpacing: 0,
} }, "Description"),
React.createElement("button", { onClick: onClose, style: {
background: 'none',
border: 'none',
color: '#fff',
cursor: 'pointer',
padding: 6,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
WebkitTapHighlightColor: 'transparent',
}, "aria-label": "Close" },
React.createElement("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
React.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
React.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))),
React.createElement("div", { "data-uvf-description-scroll": true, style: {
flex: 1,
overflowY: 'auto',
padding: isMobile ? '16px 18px 22px 18px' : '16px 20px 20px 20px',
marginTop: isMobile ? 4 : 6,
marginBottom: isMobile ? 8 : 10,
} },
React.createElement("div", { style: {
color: 'rgba(255,255,255,0.92)',
fontSize: isMobile ? 18 : 13,
lineHeight: isMobile ? 1.35 : 1.4,
fontWeight: isMobile ? 600 : 600,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
marginTop: isMobile ? 6 : 10,
marginBottom: isMobile ? 18 : 16,
minHeight: isMobile ? undefined : 42,
} }, video.description || video.title || 'No description available.'),
React.createElement("div", { style: { borderTop: '1px solid rgba(255,255,255,0.16)', margin: '0 0 16px 0' } }),
React.createElement("div", { style: {
display: 'grid',
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
gap: 6,
alignItems: 'start',
} },
React.createElement("div", { style: { textAlign: 'center' } },
React.createElement("div", { style: { color: '#fff', fontSize: isMobile ? 30 : 22, lineHeight: 1.1, fontWeight: 700 } }, formatCount(video.likes)),
React.createElement("div", { style: { color: 'rgba(255,255,255,0.72)', fontSize: isMobile ? 15 : 11, marginTop: 2 } }, getCountLabel(video.likes, 'Like', 'Likes'))),
React.createElement("div", { style: { textAlign: 'center' } },
React.createElement("div", { style: { color: '#fff', fontSize: isMobile ? 30 : 22, lineHeight: 1.1, fontWeight: 700 } }, video.views != null ? formatCount(video.views) : '0'),
React.createElement("div", { style: { color: 'rgba(255,255,255,0.72)', fontSize: isMobile ? 15 : 11, marginTop: 2 } }, getCountLabel(video.views, 'View', 'Views'))),
React.createElement("div", { style: { textAlign: 'center' } },
React.createElement("div", { style: { color: '#fff', fontSize: isMobile ? 30 : 22, lineHeight: 1.1, fontWeight: 700 } }, panelDate?.monthDay || '-'),
React.createElement("div", { style: { color: 'rgba(255,255,255,0.72)', fontSize: isMobile ? 15 : 11, marginTop: 2 } }, panelDate?.year || '')))))));
};
//# sourceMappingURL=PortraitDescriptionPanel.js.map