forked from akai/readest
160 lines
4.9 KiB
TypeScript
160 lines
4.9 KiB
TypeScript
import clsx from 'clsx';
|
|
import { Position } from '@/utils/sel';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
|
import { useKeyDownActions } from '@/hooks/useKeyDownActions';
|
|
|
|
const Popup = ({
|
|
width,
|
|
height,
|
|
minHeight,
|
|
maxHeight,
|
|
position,
|
|
trianglePosition,
|
|
children,
|
|
className = '',
|
|
triangleClassName = '',
|
|
additionalStyle = {},
|
|
isOpen = true,
|
|
onDismiss,
|
|
}: {
|
|
isOpen?: boolean;
|
|
width: number;
|
|
height?: number;
|
|
minHeight?: number;
|
|
maxHeight?: number;
|
|
position?: Position;
|
|
trianglePosition?: Position;
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
triangleClassName?: string;
|
|
additionalStyle?: React.CSSProperties;
|
|
onDismiss?: () => void;
|
|
}) => {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [adjustedPosition, setAdjustedPosition] = useState(position);
|
|
const [childrenHeight, setChildrenHeight] = useState(height || minHeight || 0);
|
|
|
|
useKeyDownActions({ onCancel: onDismiss, elementRef: containerRef, enabled: isOpen });
|
|
|
|
const popupPadding = useResponsiveSize(10);
|
|
let availableHeight = window.innerHeight - 2 * popupPadding;
|
|
if (trianglePosition?.dir === 'up') {
|
|
availableHeight = trianglePosition.point.y - popupPadding;
|
|
} else if (trianglePosition?.dir === 'down') {
|
|
availableHeight = window.innerHeight - trianglePosition.point.y - popupPadding;
|
|
}
|
|
maxHeight = Math.min(maxHeight || availableHeight, availableHeight);
|
|
if (minHeight) {
|
|
minHeight = Math.min(minHeight, availableHeight);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!containerRef.current) return;
|
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
for (const entry of entries) {
|
|
const newHeight = entry.contentRect.height;
|
|
if (newHeight !== childrenHeight) {
|
|
setChildrenHeight(newHeight);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
|
|
resizeObserver.observe(containerRef.current);
|
|
return () => {
|
|
resizeObserver.disconnect();
|
|
};
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!containerRef.current) return;
|
|
if (!position || !trianglePosition || position.dir !== 'up') {
|
|
setAdjustedPosition(position);
|
|
return;
|
|
}
|
|
const containerHeight = childrenHeight || containerRef.current.offsetHeight;
|
|
const newPosition = {
|
|
...position,
|
|
point: {
|
|
...position.point,
|
|
y: trianglePosition.point.y - containerHeight,
|
|
},
|
|
};
|
|
setAdjustedPosition(newPosition);
|
|
}, [position, trianglePosition, childrenHeight]);
|
|
|
|
return (
|
|
<div>
|
|
<div
|
|
id='popup-container'
|
|
ref={containerRef}
|
|
className={clsx(
|
|
'bg-base-300 absolute rounded-lg font-sans',
|
|
trianglePosition?.dir !== 'up' && 'shadow-xl',
|
|
className,
|
|
)}
|
|
style={{
|
|
width: `${width}px`,
|
|
height: height ? `${height}px` : 'auto',
|
|
minHeight: minHeight ? `${minHeight}px` : 'none',
|
|
maxHeight: maxHeight ? `${maxHeight}px` : 'none',
|
|
left: `${adjustedPosition ? adjustedPosition.point.x : -999}px`,
|
|
top: `${adjustedPosition ? adjustedPosition.point.y : -999}px`,
|
|
...additionalStyle,
|
|
}}
|
|
>
|
|
{children}
|
|
</div>
|
|
<div
|
|
className={`triangle text-base-300 absolute ${triangleClassName}`}
|
|
style={{
|
|
left:
|
|
trianglePosition?.dir === 'left'
|
|
? `${trianglePosition.point.x}px`
|
|
: trianglePosition?.dir === 'right'
|
|
? `${trianglePosition.point.x}px`
|
|
: `${trianglePosition ? trianglePosition.point.x : -999}px`,
|
|
top:
|
|
trianglePosition?.dir === 'up'
|
|
? `${trianglePosition.point.y}px`
|
|
: trianglePosition?.dir === 'down'
|
|
? `${trianglePosition.point.y}px`
|
|
: `${trianglePosition ? trianglePosition.point.y : -999}px`,
|
|
borderLeft:
|
|
trianglePosition?.dir === 'right'
|
|
? 'none'
|
|
: trianglePosition?.dir === 'left'
|
|
? `7px solid`
|
|
: '7px solid transparent',
|
|
borderRight:
|
|
trianglePosition?.dir === 'left'
|
|
? 'none'
|
|
: trianglePosition?.dir === 'right'
|
|
? `7px solid`
|
|
: '7px solid transparent',
|
|
borderTop:
|
|
trianglePosition?.dir === 'down'
|
|
? 'none'
|
|
: trianglePosition?.dir === 'up'
|
|
? `7px solid`
|
|
: '7px solid transparent',
|
|
borderBottom:
|
|
trianglePosition?.dir === 'up'
|
|
? 'none'
|
|
: trianglePosition?.dir === 'down'
|
|
? `7px solid`
|
|
: '7px solid transparent',
|
|
transform:
|
|
trianglePosition?.dir === 'left' || trianglePosition?.dir === 'right'
|
|
? 'translateY(-50%)'
|
|
: 'translateX(-50%)',
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Popup;
|