UNPKG

unified-video-framework

Version:

Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more

246 lines 12.3 kB
import React, { useEffect, useState } from 'react'; export const PortraitMoreMenu = ({ video, isOpen, onClose, onDescription, onReport, onNotInterested, onCopyLink, onWishlist, onWatchLater, showDescription = true, showReport = true, showNotInterested = true, showCopyLink = true, showWishlist = true, showWatchLater = true, moreButtonRef, theme, }) => { const wishlistColor = theme?.wishlistColor || '#ff2d55'; const watchLaterColor = theme?.watchLaterColor || '#ff2d55'; const [isMobile, setIsMobile] = useState(() => typeof window !== 'undefined' && window.innerWidth < 768); const [desktopPosition, setDesktopPosition] = useState({ right: 72, bottom: 120 }); useEffect(() => { if (!moreButtonRef?.current || isMobile) return; const updatePosition = () => { const button = moreButtonRef.current; if (!button) return; const rect = button.getBoundingClientRect(); const gap = 12; const right = window.innerWidth - rect.left + gap; const menuWidth = 240; const minRight = 16; const maxRight = Math.max(minRight, window.innerWidth - menuWidth - 16); const clampedRight = Math.max(minRight, Math.min(right, maxRight)); const menuApproxHeight = 280; const buttonCenterY = rect.top + rect.height / 2; const dropdownTop = buttonCenterY - menuApproxHeight / 2; const bottom = window.innerHeight - dropdownTop - menuApproxHeight; const clampedBottom = Math.max(20, Math.min(bottom, window.innerHeight - menuApproxHeight - 20)); setDesktopPosition({ right: clampedRight, bottom: clampedBottom }); }; updatePosition(); window.addEventListener('resize', updatePosition); window.addEventListener('scroll', updatePosition, true); return () => { window.removeEventListener('resize', updatePosition); window.removeEventListener('scroll', updatePosition, true); }; }, [moreButtonRef, isOpen, isMobile]); useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth < 768); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); useEffect(() => { if (!isOpen) return; const handleEscape = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleEscape); return () => window.removeEventListener('keydown', handleEscape); }, [isOpen, onClose]); if (!isOpen) return null; const iconSize = isMobile ? 24 : 20; const options = []; if (showDescription && onDescription) { options.push({ icon: (React.createElement("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, React.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), React.createElement("polyline", { points: "14 2 14 8 20 8" }), React.createElement("line", { x1: "16", y1: "13", x2: "8", y2: "13" }), React.createElement("line", { x1: "16", y1: "17", x2: "8", y2: "17" }), React.createElement("line", { x1: "10", y1: "9", x2: "8", y2: "9" }))), label: 'Description', onClick: () => { onDescription(); onClose(); }, }); } if (showWishlist && onWishlist) { const inWishlist = video.isInWishlist || false; options.push({ icon: (React.createElement("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: inWishlist ? 'currentColor' : 'none', stroke: "currentColor", strokeWidth: "2" }, React.createElement("path", { d: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" }))), label: inWishlist ? 'Added to Wishlist' : 'Add to Wishlist', onClick: () => { onWishlist(video); onClose(); }, active: inWishlist, activeColor: wishlistColor, }); } if (showWatchLater && onWatchLater) { const inWatchLater = video.isInWatchLater || false; options.push({ icon: (React.createElement("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, React.createElement("circle", { cx: "12", cy: "12", r: "10", fill: inWatchLater ? 'currentColor' : 'none' }), React.createElement("polyline", { points: "12 6 12 12 16 14", stroke: inWatchLater ? '#000' : 'currentColor', strokeWidth: "2", fill: "none" }))), label: inWatchLater ? 'Added to Watch Later' : 'Watch Later', onClick: () => { onWatchLater(video); onClose(); }, active: inWatchLater, activeColor: watchLaterColor, }); } if (showCopyLink && onCopyLink) { options.push({ icon: (React.createElement("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, React.createElement("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }), React.createElement("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" }))), label: 'Copy Link', onClick: () => { onCopyLink(video); onClose(); }, }); } if (showNotInterested && onNotInterested) { options.push({ icon: (React.createElement("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, React.createElement("circle", { cx: "12", cy: "12", r: "10" }), React.createElement("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), React.createElement("line", { x1: "9", y1: "9", x2: "15", y2: "15" }))), label: 'Not Interested', onClick: () => { onNotInterested(video); onClose(); }, }); } if (showReport && onReport) { options.push({ icon: (React.createElement("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, React.createElement("circle", { cx: "12", cy: "12", r: "10" }), React.createElement("line", { x1: "12", y1: "8", x2: "12", y2: "12" }), React.createElement("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" }))), label: 'Report', onClick: () => { onReport(video); onClose(); }, danger: true, }); } return (React.createElement(React.Fragment, null, React.createElement("div", { onClick: onClose, style: { position: 'fixed', inset: 0, background: isMobile ? 'rgba(0,0,0,0.6)' : 'transparent', zIndex: 100, opacity: isOpen ? 1 : 0, transition: 'opacity 0.25s ease', pointerEvents: isOpen ? 'auto' : 'none', } }), React.createElement("div", { style: { position: 'fixed', ...(isMobile ? { left: 0, right: 0, bottom: 0, borderTopLeftRadius: 16, borderTopRightRadius: 16, transform: isOpen ? 'translateY(0)' : 'translateY(100%)', maxHeight: '70vh', } : { right: desktopPosition.right, bottom: desktopPosition.bottom, transform: isOpen ? 'scale(1)' : 'scale(0.95)', opacity: isOpen ? 1 : 0, width: 240, borderRadius: 8, maxHeight: '50vh', }), background: '#1a1a1a', zIndex: 101, transition: isMobile ? 'transform 0.25s ease' : 'transform 0.2s ease, opacity 0.2s ease', boxShadow: '0 8px 32px rgba(0,0,0,0.6)', overflowY: 'auto', border: isMobile ? 'none' : '1px solid rgba(255,255,255,0.1)', } }, 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)', } }))), isMobile && (React.createElement("div", { style: { padding: '8px 20px 12px 20px', borderBottom: '1px solid rgba(255,255,255,0.1)', } }, React.createElement("h3", { style: { margin: 0, fontSize: 16, fontWeight: 600, color: 'rgba(255,255,255,0.9)', } }, "More Options"))), React.createElement("div", { style: { padding: isMobile ? '8px 0 16px 0' : '4px 0' } }, options.map((option, index) => (React.createElement("button", { key: index, onClick: option.onClick, style: { width: '100%', display: 'flex', alignItems: 'center', gap: isMobile ? 16 : 12, padding: isMobile ? '14px 20px' : '10px 16px', background: 'none', border: 'none', color: option.danger ? '#ff3b30' : '#fff', cursor: 'pointer', fontSize: isMobile ? 15 : 14, fontWeight: 500, textAlign: 'left', transition: 'background 0.15s ease', WebkitTapHighlightColor: 'transparent', }, onMouseEnter: (e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.08)'; }, onMouseLeave: (e) => { e.currentTarget.style.background = 'none'; } }, React.createElement("div", { style: { width: isMobile ? 24 : 20, height: isMobile ? 24 : 20, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, color: option.active ? (option.activeColor || '#fff') : (option.danger ? '#ff3b30' : '#fff'), } }, option.icon), React.createElement("span", { style: { color: option.danger ? '#ff3b30' : '#fff' } }, option.label))))), isMobile && (React.createElement("div", { style: { padding: '0 20px 20px 20px', borderTop: '1px solid rgba(255,255,255,0.1)', } }, React.createElement("button", { onClick: onClose, style: { width: '100%', padding: '14px', background: 'rgba(255,255,255,0.1)', border: 'none', borderRadius: 12, color: '#fff', fontSize: 15, fontWeight: 600, cursor: 'pointer', marginTop: 12, WebkitTapHighlightColor: 'transparent', } }, "Cancel")))))); }; //# sourceMappingURL=PortraitMoreMenu.js.map