feat: add three-panel layout
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
Sun,
|
||||
Moon,
|
||||
} from 'lucide-react';
|
||||
import type { PaneType } from '../panes/types';
|
||||
import type { PaneType, NavigablePaneType } from '../panes/types';
|
||||
import type { Theme } from '@/hooks/useTheme';
|
||||
|
||||
interface LeftToolbarProps {
|
||||
@@ -24,7 +24,7 @@ interface LeftToolbarProps {
|
||||
onAddStuffClick: () => void;
|
||||
onRefreshClick: () => void;
|
||||
onSettingsClick: () => void;
|
||||
onPaneTypeClick: (paneType: PaneType) => void;
|
||||
onPaneTypeClick: (paneType: NavigablePaneType) => void;
|
||||
isExpanded: boolean;
|
||||
onToggleExpanded: () => void;
|
||||
openTabTypes: Set<PaneType>;
|
||||
@@ -36,7 +36,7 @@ interface LeftToolbarProps {
|
||||
const NAV_WIDTH_COLLAPSED = 50;
|
||||
const NAV_WIDTH_EXPANDED = 280;
|
||||
|
||||
const VIEW_ITEMS: Array<{ paneType: PaneType; label: string; icon: typeof LayoutList }> = [
|
||||
const VIEW_ITEMS: Array<{ paneType: NavigablePaneType; label: string; icon: typeof LayoutList }> = [
|
||||
{ paneType: 'views', label: 'Nodes', icon: LayoutList },
|
||||
{ paneType: 'skills', label: 'Skills', icon: BookOpen },
|
||||
{ paneType: 'map', label: 'Map', icon: Map },
|
||||
|
||||
@@ -1,75 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { GripVertical } from 'lucide-react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
|
||||
interface SplitHandleProps {
|
||||
isSecondPaneOpen: boolean;
|
||||
onOpenSecondPane: () => void;
|
||||
onResize: (newWidthPercent: number) => void;
|
||||
onCloseSecondPane: () => void;
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
toolbarWidth?: number;
|
||||
onResize: (clientX: number) => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export default function SplitHandle({
|
||||
isSecondPaneOpen,
|
||||
onOpenSecondPane,
|
||||
onResize,
|
||||
onCloseSecondPane,
|
||||
containerRef,
|
||||
toolbarWidth = 50,
|
||||
title = 'Drag to resize panes',
|
||||
}: SplitHandleProps) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const startXRef = useRef<number>(0);
|
||||
const startWidthRef = useRef<number>(50);
|
||||
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!isSecondPaneOpen) {
|
||||
// First drag opens the second pane at 50%
|
||||
onOpenSecondPane();
|
||||
}
|
||||
|
||||
setIsDragging(true);
|
||||
startXRef.current = e.clientX;
|
||||
|
||||
// Calculate current width from container
|
||||
if (containerRef.current) {
|
||||
const containerWidth = containerRef.current.offsetWidth - toolbarWidth;
|
||||
// Assume current position is at the split point
|
||||
startWidthRef.current = 50; // Default to 50% for new split
|
||||
}
|
||||
}, [isSecondPaneOpen, onOpenSecondPane, containerRef, toolbarWidth]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
const containerWidth = containerRect.width - toolbarWidth;
|
||||
const mouseX = e.clientX - containerRect.left - toolbarWidth;
|
||||
|
||||
// Calculate what percentage of the available space should be Slot B
|
||||
// mouseX is distance from left edge, so slotA width = mouseX
|
||||
// slotB width = containerWidth - mouseX
|
||||
const slotBWidthPercent = ((containerWidth - mouseX) / containerWidth) * 100;
|
||||
|
||||
// Clamp between 20% and 70%
|
||||
const clampedWidth = Math.max(20, Math.min(70, slotBWidthPercent));
|
||||
|
||||
// If dragged to less than 15%, close the pane
|
||||
if (slotBWidthPercent < 15) {
|
||||
onCloseSecondPane();
|
||||
setIsDragging(false);
|
||||
return;
|
||||
}
|
||||
|
||||
onResize(clampedWidth);
|
||||
onResize(e.clientX);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
@@ -87,48 +41,21 @@ export default function SplitHandle({
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
}, [isDragging, containerRef, toolbarWidth, onResize, onCloseSecondPane]);
|
||||
}, [isDragging, onResize]);
|
||||
|
||||
// When second pane is closed, show a wider handle with grip icon
|
||||
if (!isSecondPaneOpen) {
|
||||
return (
|
||||
<div
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{
|
||||
width: '12px',
|
||||
cursor: 'col-resize',
|
||||
background: isHovered ? 'var(--rah-bg-active)' : 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'background 0.15s ease',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
title="Drag to split view (⌘\)"
|
||||
>
|
||||
<GripVertical
|
||||
size={12}
|
||||
color={isHovered ? '#888' : '#444'}
|
||||
style={{ transition: 'color 0.15s ease' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// When second pane is open, show resize handle
|
||||
return (
|
||||
<div
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
title={title}
|
||||
style={{
|
||||
width: '8px',
|
||||
width: '4px',
|
||||
cursor: 'col-resize',
|
||||
background: isDragging ? '#22c55e' : (isHovered ? 'var(--rah-bg-active)' : 'transparent'),
|
||||
background: isDragging ? 'var(--rah-accent-green)' : (isHovered ? 'var(--rah-bg-active)' : 'transparent'),
|
||||
transition: isDragging ? 'none' : 'background 0.15s ease',
|
||||
flexShrink: 0,
|
||||
borderRadius: '999px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user