feat: implement sidebar docked call panel with participant management and sync status handling

This commit is contained in:
2026-02-28 00:54:55 +11:00
parent 29c409fad8
commit b185e0b129
14 changed files with 625 additions and 77 deletions

View File

@@ -28,10 +28,16 @@ interface DragPosition {
/** Threshold in pixels for docking detection */
const DOCK_THRESHOLD = 50;
/** Combined width of space panel (68px) + room list panel (256px) for dock detection */
const ROOM_LIST_EDGE = 324;
/** Dock zone type */
type DockZone = 'none' | 'right' | 'sidebar';
/** Custom hook for draggable functionality with viewport constraints and docking detection */
function useDraggable(
onDockRequest?: () => void,
onDockRight?: () => void,
onDockSidebar?: () => void,
onUndockRequest?: () => void,
isDocked?: boolean,
pendingDrag?: PendingDragState | null,
@@ -39,7 +45,7 @@ function useDraggable(
) {
const [position, setPosition] = useState<DragPosition>({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const [isNearDockZone, setIsNearDockZone] = useState(false);
const [nearDockZone, setNearDockZone] = useState<DockZone>('none');
const dragRef = useRef<HTMLDivElement>(null);
const dragStartRef = useRef<{ x: number; y: number; startX: number; startY: number } | null>(null);
@@ -145,9 +151,20 @@ function useDraggable(
const handleMouseMove = (e: MouseEvent) => {
if (!dragStartRef.current || !dragRef.current) return;
// Check if near right edge for docking
// Check dock zones
const nearRightEdge = e.clientX > window.innerWidth - DOCK_THRESHOLD;
setIsNearDockZone(nearRightEdge && !isDocked);
const nearSidebarZone = e.clientX < ROOM_LIST_EDGE + DOCK_THRESHOLD &&
e.clientY > window.innerHeight - 150;
if (!isDocked) {
if (nearSidebarZone) {
setNearDockZone('sidebar');
} else if (nearRightEdge) {
setNearDockZone('right');
} else {
setNearDockZone('none');
}
}
// If docked and dragging away from edge, trigger undock
if (isDocked) {
@@ -191,12 +208,20 @@ function useDraggable(
};
const handleMouseUp = (e: MouseEvent) => {
// Check if should dock on mouse release
if (!isDocked && e.clientX > window.innerWidth - DOCK_THRESHOLD) {
onDockRequest?.();
// Check which dock zone to use
const nearRightEdge = e.clientX > window.innerWidth - DOCK_THRESHOLD;
const nearSidebarZone = e.clientX < ROOM_LIST_EDGE + DOCK_THRESHOLD &&
e.clientY > window.innerHeight - 150;
if (!isDocked) {
if (nearSidebarZone) {
onDockSidebar?.();
} else if (nearRightEdge) {
onDockRight?.();
}
}
setIsDragging(false);
setIsNearDockZone(false);
setNearDockZone('none');
dragStartRef.current = null;
};
@@ -207,9 +232,9 @@ function useDraggable(
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, position, isDocked, onDockRequest, onUndockRequest]);
}, [isDragging, position, isDocked, onDockRight, onDockSidebar, onUndockRequest]);
return { position, isDragging, isNearDockZone, handleMouseDown, dragRef };
return { position, isDragging, nearDockZone, handleMouseDown, dragRef };
}
/**
@@ -687,6 +712,7 @@ export function CallOverlay() {
// Docking state
const [isDocked, setIsDocked] = useSetting(settingsAtom, 'isCallPanelDocked');
const [, setDockPosition] = useSetting(settingsAtom, 'callPanelDockPosition');
// Pending drag state from undocking
const [pendingDrag, setPendingDrag] = useAtom(pendingUndockDragAtom);
@@ -695,16 +721,23 @@ export function CallOverlay() {
setPendingDrag(null);
}, [setPendingDrag]);
const handleDockRequest = useCallback(() => {
const handleDockRight = useCallback(() => {
setDockPosition('right');
setIsDocked(true);
}, [setIsDocked]);
}, [setIsDocked, setDockPosition]);
const handleDockSidebar = useCallback(() => {
setDockPosition('sidebar');
setIsDocked(true);
}, [setIsDocked, setDockPosition]);
const handleUndockRequest = useCallback(() => {
setIsDocked(false);
}, [setIsDocked]);
const { position, isDragging, isNearDockZone, handleMouseDown, dragRef } = useDraggable(
handleDockRequest,
const { position, isDragging, nearDockZone, handleMouseDown, dragRef } = useDraggable(
handleDockRight,
handleDockSidebar,
handleUndockRequest,
isDocked,
pendingDrag,
@@ -1047,8 +1080,9 @@ export function CallOverlay() {
return (
<>
{/* Dock indicator shown when dragging near the right edge */}
{isNearDockZone && !isDocked && <div className={css.DockIndicator} />}
{/* Dock indicators shown when dragging near dock zones */}
{nearDockZone === 'right' && !isDocked && <div className={css.DockIndicator} />}
{nearDockZone === 'sidebar' && !isDocked && <div className={css.SidebarDockIndicator} />}
<div
ref={setContainerRef}
className={containerClass}