feat: draggable resizer between plot and legend in chart cards
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useRef, useState, useCallback } from 'react';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -32,23 +32,55 @@ interface CardShellProps extends HeaderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CardShell({ title, onEdit, onDelete, empty, children, legendContainerRef }: CardShellProps) {
|
export function CardShell({ title, onEdit, onDelete, empty, children, legendContainerRef }: CardShellProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [legendHeight, setLegendHeight] = useState<number | null>(null);
|
||||||
|
const dragRef = useRef<{ startY: number; startH: number } | null>(null);
|
||||||
|
|
||||||
|
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const el = legendContainerRef?.current;
|
||||||
|
if (!el) return;
|
||||||
|
dragRef.current = { startY: e.clientY, startH: el.offsetHeight };
|
||||||
|
|
||||||
|
function onMove(ev: MouseEvent) {
|
||||||
|
if (!dragRef.current) return;
|
||||||
|
const delta = dragRef.current.startY - ev.clientY;
|
||||||
|
const containerH = containerRef.current?.offsetHeight ?? 400;
|
||||||
|
const newH = Math.max(32, Math.min(containerH - 64, dragRef.current.startH + delta));
|
||||||
|
setLegendHeight(newH);
|
||||||
|
}
|
||||||
|
function onUp() {
|
||||||
|
dragRef.current = null;
|
||||||
|
document.removeEventListener('mousemove', onMove);
|
||||||
|
document.removeEventListener('mouseup', onUp);
|
||||||
|
}
|
||||||
|
document.addEventListener('mousemove', onMove);
|
||||||
|
document.addEventListener('mouseup', onUp);
|
||||||
|
}, [legendContainerRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col bg-gray-900 rounded border border-gray-700 overflow-hidden">
|
<div ref={containerRef} className="h-full flex flex-col bg-gray-900 rounded border border-gray-700 overflow-hidden">
|
||||||
<Header title={title} onEdit={onEdit} onDelete={onDelete} />
|
<Header title={title} onEdit={onEdit} onDelete={onDelete} />
|
||||||
{empty ? (
|
{empty ? (
|
||||||
<EmptyState />
|
<EmptyState />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Plot fills remaining space */}
|
|
||||||
<div className="flex-1 min-h-0">
|
<div className="flex-1 min-h-0">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
{/* Legend scrolls independently, capped at 25% card height */}
|
{legendContainerRef && <>
|
||||||
<div
|
<div
|
||||||
ref={legendContainerRef}
|
onMouseDown={handleMouseDown}
|
||||||
className="shrink-0 max-h-[25%] overflow-y-auto border-t border-gray-800 px-2 py-1 text-[10px] text-gray-400 [&_.u-legend]:w-full [&_.u-series]:flex [&_.u-series]:items-center [&_.u-series_th]:flex [&_.u-series_th]:items-center [&_.u-series_th]:gap-1 [&_.u-marker]:w-3 [&_.u-marker]:h-0.5 [&_.u-marker]:shrink-0 [&_.u-label]:truncate [&_.u-value]:ml-auto [&_.u-value]:font-mono [&_.u-value]:shrink-0"
|
className="shrink-0 h-1.5 cursor-row-resize bg-gray-800 hover:bg-gray-700 active:bg-gray-600"
|
||||||
/>
|
/>
|
||||||
<style>{'.u-legend .u-series:first-child { display: none; }'}</style>
|
<div
|
||||||
|
ref={legendContainerRef}
|
||||||
|
className="shrink-0 overflow-y-auto px-2 py-1 text-[10px] text-gray-400 [&_.u-legend]:w-full [&_.u-series]:flex [&_.u-series]:items-center [&_.u-series_th]:flex [&_.u-series_th]:items-center [&_.u-series_th]:gap-1 [&_.u-marker]:w-3 [&_.u-marker]:h-0.5 [&_.u-marker]:shrink-0 [&_.u-label]:truncate [&_.u-value]:ml-auto [&_.u-value]:font-mono [&_.u-value]:shrink-0"
|
||||||
|
style={legendHeight != null ? { height: legendHeight, maxHeight: legendHeight } : { maxHeight: '25%' }}
|
||||||
|
/>
|
||||||
|
<style>{'.u-legend .u-series:first-child { display: none; }'}</style>
|
||||||
|
</>}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user