Files
gridpilot.gg/apps/website/ui/InfoFlyout.tsx
2026-01-19 12:35:16 +01:00

64 lines
2.1 KiB
TypeScript

import { HelpCircle, X } from 'lucide-react';
import React, { ReactNode, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Box } from './Box';
import { Heading } from './Heading';
import { Icon } from './Icon';
import { IconButton } from './IconButton';
import { Surface } from './Surface';
export interface InfoFlyoutProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: ReactNode;
anchorRef: React.RefObject<HTMLElement>;
}
export const InfoFlyout = ({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutProps) => {
const [position, setPosition] = useState({ top: 0, left: 0 });
const flyoutRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen && anchorRef.current) {
const rect = anchorRef.current.getBoundingClientRect();
const flyoutWidth = 380;
const padding = 16;
let left = rect.right + 12;
let top = rect.top;
if (left + flyoutWidth > window.innerWidth - padding) {
left = rect.left - flyoutWidth - 12;
}
setPosition({ top, left });
}
}, [isOpen, anchorRef]);
if (!isOpen) return null;
return createPortal(
<Box
ref={flyoutRef as any}
position="fixed"
zIndex={100}
style={{ top: position.top, left: position.left, width: '24rem' }}
>
<Surface variant="muted" rounded="xl" shadow="xl" style={{ border: '1px solid var(--ui-color-border-default)', overflow: 'hidden' }}>
<Box display="flex" alignItems="center" justifyContent="between" padding={4} bg="var(--ui-color-bg-surface-muted)" style={{ borderBottom: '1px solid var(--ui-color-border-default)' }}>
<Box display="flex" alignItems="center" gap={2}>
<Icon icon={HelpCircle} size={4} intent="primary" />
<Heading level={6}>{title}</Heading>
</Box>
<IconButton icon={X} size="sm" variant="ghost" onClick={onClose} title="Close" />
</Box>
<Box padding={4} style={{ maxHeight: '20rem', overflowY: 'auto' }}>
{children}
</Box>
</Surface>
</Box>,
document.body
);
};