From e1afb9474007edc02d5b9c1a0a7711b7e4170abc Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 9 Feb 2026 13:35:46 +0200 Subject: [PATCH] feat: add fullscreen toggle for bottom terminal dock --- .../components/layout/BottomTerminalDock.tsx | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/layout/BottomTerminalDock.tsx b/packages/ui/src/components/layout/BottomTerminalDock.tsx index 662371c9..bb89ee1b 100644 --- a/packages/ui/src/components/layout/BottomTerminalDock.tsx +++ b/packages/ui/src/components/layout/BottomTerminalDock.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { RiFullscreenExitLine, RiFullscreenLine } from '@remixicon/react'; import { cn } from '@/lib/utils'; import { useUIStore } from '@/stores/useUIStore'; @@ -16,12 +17,58 @@ export const BottomTerminalDock: React.FC = ({ isOpen, const bottomTerminalHeight = useUIStore((state) => state.bottomTerminalHeight); const setBottomTerminalHeight = useUIStore((state) => state.setBottomTerminalHeight); const setBottomTerminalOpen = useUIStore((state) => state.setBottomTerminalOpen); + const [isFullscreen, setIsFullscreen] = React.useState(false); + const [fullscreenHeight, setFullscreenHeight] = React.useState(null); const [isResizing, setIsResizing] = React.useState(false); + const dockRef = React.useRef(null); const startYRef = React.useRef(0); const startHeightRef = React.useRef(bottomTerminalHeight || 300); + const previousHeightRef = React.useRef(bottomTerminalHeight || 300); + + const standardHeight = React.useMemo( + () => Math.min(BOTTOM_DOCK_MAX_HEIGHT, Math.max(BOTTOM_DOCK_MIN_HEIGHT, bottomTerminalHeight || 300)), + [bottomTerminalHeight], + ); React.useEffect(() => { - if (isMobile || !isResizing) { + if (!isOpen) { + setIsFullscreen(false); + setFullscreenHeight(null); + setIsResizing(false); + } + }, [isOpen]); + + React.useEffect(() => { + if (isMobile || !isOpen || !isFullscreen) { + return; + } + + const updateFullscreenHeight = () => { + const parentHeight = dockRef.current?.parentElement?.getBoundingClientRect().height; + if (!parentHeight || parentHeight <= 0) { + return; + } + const next = Math.round(parentHeight); + setFullscreenHeight((prev) => (prev === next ? prev : next)); + }; + + updateFullscreenHeight(); + + const parent = dockRef.current?.parentElement; + if (!parent) { + return; + } + + const observer = new ResizeObserver(updateFullscreenHeight); + observer.observe(parent); + + return () => { + observer.disconnect(); + }; + }, [isFullscreen, isMobile, isOpen]); + + React.useEffect(() => { + if (isMobile || !isResizing || isFullscreen) { return; } @@ -49,26 +96,41 @@ export const BottomTerminalDock: React.FC = ({ isOpen, window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointerup', handlePointerUp); }; - }, [isMobile, isResizing, setBottomTerminalHeight, setBottomTerminalOpen]); + }, [isFullscreen, isMobile, isResizing, setBottomTerminalHeight, setBottomTerminalOpen]); if (isMobile) { return null; } const appliedHeight = isOpen - ? Math.min(BOTTOM_DOCK_MAX_HEIGHT, Math.max(BOTTOM_DOCK_MIN_HEIGHT, bottomTerminalHeight || 300)) + ? (isFullscreen ? Math.max(standardHeight, fullscreenHeight ?? standardHeight) : standardHeight) : 0; const handlePointerDown = (event: React.PointerEvent) => { - if (!isOpen) return; + if (!isOpen || isFullscreen) return; setIsResizing(true); startYRef.current = event.clientY; startHeightRef.current = appliedHeight; event.preventDefault(); }; + const toggleFullscreen = () => { + if (!isOpen) return; + + if (isFullscreen) { + setIsFullscreen(false); + const restoreHeight = Math.min(BOTTOM_DOCK_MAX_HEIGHT, Math.max(BOTTOM_DOCK_MIN_HEIGHT, previousHeightRef.current)); + setBottomTerminalHeight(restoreHeight); + return; + } + + previousHeightRef.current = standardHeight; + setIsFullscreen(true); + }; + return (
= ({ isOpen, }} aria-hidden={!isOpen || appliedHeight === 0} > - {isOpen && ( + {isOpen && !isFullscreen && (
= ({ isOpen, /> )} + {isOpen && ( + + )} +