import React, { useState, useRef, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { createPortal } from 'react-dom';
import { X, GripHorizontal, Maximize2, Minimize2 } from 'lucide-react';
import { Button } from '../ui/button';
import { cn } from '../shared/utils';
interface FloatingMediaWrapperProps {
children: React.ReactNode;
isFloating: boolean;
setIsFloating: (floating: boolean) => void;
title?: string;
onClose?: () => void;
onCloseMedia?: () => void;
aspectRatio?: number;
className?: string;
minWidth?: number;
minHeight?: number;
colorVariant?: 'default' | 'primary';
playerId?: string;
enablePadding?: boolean;
}
export function FloatingMediaWrapper({
children,
isFloating,
setIsFloating,
title,
onClose,
onCloseMedia,
aspectRatio = 16 / 9,
className,
minWidth = 320,
minHeight = 110,
colorVariant = 'default',
playerId = 'default',
enablePadding = false,
}: FloatingMediaWrapperProps) {
const { t } = useTranslation();
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
return () => setIsMounted(false);
}, []);
// Placeholder when content is floating
if (isFloating) {
return (
<>
{t('media.playingFloating')}
{isMounted &&
createPortal(
{children}
,
document.body
)}
>
);
}
return (
{children}
);
}
interface FloatingContainerProps {
children: React.ReactNode;
setIsFloating: (floating: boolean) => void;
onClose?: () => void;
onCloseMedia?: () => void;
title?: string;
aspectRatio: number;
minWidth: number;
minHeight: number;
colorVariant: 'default' | 'primary';
playerId?: string;
enablePadding?: boolean;
}
function FloatingContainer({
children,
setIsFloating,
onClose,
onCloseMedia,
title,
aspectRatio,
minWidth,
minHeight,
colorVariant,
playerId = 'default',
enablePadding = false,
}: FloatingContainerProps) {
const { t } = useTranslation();
const minPlayerHeight = minWidth / aspectRatio;
const containerRef = useRef(null);
const resizeHandleRef = useRef(null);
const getInitialState = useCallback(() => {
const storageKey = `xertica-media-player-${playerId}`;
const stored = typeof window !== 'undefined' ? window.localStorage?.getItem(storageKey) : null;
if (stored) {
try {
return JSON.parse(stored);
} catch {
// invalid stored data
}
}
return {
position: {
x: typeof window !== 'undefined' ? window.innerWidth - minWidth - 24 : 0,
y: typeof window !== 'undefined' ? window.innerHeight - minPlayerHeight - 24 : 0,
},
size: {
width: minWidth,
height: minPlayerHeight,
},
};
}, [minWidth, minPlayerHeight, playerId]);
interface FloatingState {
position: { x: number; y: number };
size: { width: number; height: number };
}
const [state, setState] = useState(getInitialState);
const { position, size } = state;
const [isDragging, setIsDragging] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const dragStartPos = useRef({ x: 0, y: 0 });
const dragStartOffset = useRef({ x: 0, y: 0 });
const resizeStartState = useRef({ position: { x: 0, y: 0 }, size: { width: 0, height: 0 } });
const saveState = useCallback(
(newPosition: { x: number; y: number }, newSize: { width: number; height: number }) => {
if (typeof window === 'undefined') return;
const storageKey = `xertica-media-player-${playerId}`;
window.localStorage?.setItem(
storageKey,
JSON.stringify({ position: newPosition, size: newSize })
);
},
[playerId]
);
const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true);
dragStartPos.current = { x: e.clientX, y: e.clientY };
dragStartOffset.current = { x: position.x, y: position.y };
};
const handleResizeStart = (e: React.MouseEvent, direction: string) => {
e.stopPropagation();
setIsResizing(true);
resizeHandleRef.current = direction;
dragStartPos.current = { x: e.clientX, y: e.clientY };
resizeStartState.current = { position: { ...position }, size: { ...size } };
};
const handleMouseMove = useCallback(
(e: MouseEvent) => {
if (isDragging) {
const deltaX = e.clientX - dragStartPos.current.x;
const deltaY = e.clientY - dragStartPos.current.y;
const newPos = {
x: dragStartOffset.current.x + deltaX,
y: dragStartOffset.current.y + deltaY,
};
setState(prev => ({ ...prev, position: newPos }));
saveState(newPos, state.size);
}
if (isResizing && resizeHandleRef.current) {
const deltaX = e.clientX - dragStartPos.current.x;
const deltaY = e.clientY - dragStartPos.current.y;
const direction = resizeHandleRef.current;
let newPos = { ...resizeStartState.current.position };
let newSize = { ...resizeStartState.current.size };
// Right resize - only increase width
if (direction.includes('right')) {
newSize.width = Math.max(minWidth, resizeStartState.current.size.width + deltaX);
}
// Left resize - adjust width and position
if (direction.includes('left')) {
const newW = Math.max(minWidth, resizeStartState.current.size.width - deltaX);
newPos.x =
resizeStartState.current.position.x + (resizeStartState.current.size.width - newW);
newSize.width = newW;
}
// Bottom resize - only increase height
if (direction.includes('bottom')) {
newSize.height = Math.max(minHeight, resizeStartState.current.size.height + deltaY);
}
// Top resize - adjust height and position
if (direction.includes('top')) {
const newH = Math.max(minHeight, resizeStartState.current.size.height - deltaY);
newPos.y =
resizeStartState.current.position.y + (resizeStartState.current.size.height - newH);
newSize.height = newH;
}
setState({ position: newPos, size: newSize });
saveState(newPos, newSize);
}
},
[isDragging, isResizing, state.size, minWidth, minHeight, saveState]
);
const handleMouseUp = useCallback(() => {
setIsDragging(false);
setIsResizing(false);
resizeHandleRef.current = null;
}, []);
useEffect(() => {
if (!isDragging && !isResizing) return;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, isResizing, handleMouseMove, handleMouseUp]);
return (
{title || t('media.playingMedia')}
{children}
{/* Resize handles — mouse-only, hidden from assistive tech */}
handleResizeStart(e, 'left')}
/>
handleResizeStart(e, 'right')}
/>
handleResizeStart(e, 'top')}
/>
handleResizeStart(e, 'bottom')}
/>
handleResizeStart(e, 'top-left')}
/>
handleResizeStart(e, 'top-right')}
/>
handleResizeStart(e, 'bottom-left')}
/>
handleResizeStart(e, 'bottom-right')}
/>
);
}