From 71d7a9d086428e6c33ddfcfba59d883e8ce5c6ce Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 1 Feb 2026 20:55:39 +0200 Subject: [PATCH] feat: render Settings in a windowed dialog - Open Settings as a centered windowed dialog on desktop with a blurred backdrop - Update SettingsView to support windowed mode and adjust Mac titlebar padding for windowed use - Export SettingsWindow and switch MainLayout to use it instead of full-screen overlay --- .../ui/src/components/layout/MainLayout.tsx | 16 ++++----- .../src/components/ui/ScrollableOverlay.tsx | 12 +++++-- .../ui/src/components/views/SettingsView.tsx | 13 ++++--- .../src/components/views/SettingsWindow.tsx | 35 +++++++++++++++++++ packages/ui/src/components/views/index.ts | 1 + 5 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 packages/ui/src/components/views/SettingsWindow.tsx diff --git a/packages/ui/src/components/layout/MainLayout.tsx b/packages/ui/src/components/layout/MainLayout.tsx index efbf2a78..a6c299b8 100644 --- a/packages/ui/src/components/layout/MainLayout.tsx +++ b/packages/ui/src/components/layout/MainLayout.tsx @@ -15,7 +15,7 @@ import { useDeviceInfo } from '@/lib/device'; import { useEdgeSwipe } from '@/hooks/useEdgeSwipe'; import { cn } from '@/lib/utils'; -import { ChatView, PlanView, GitView, DiffView, TerminalView, FilesView, SettingsView } from '@/components/views'; +import { ChatView, PlanView, GitView, DiffView, TerminalView, FilesView, SettingsView, SettingsWindow } from '@/components/views'; export const MainLayout: React.FC = () => { const { @@ -301,7 +301,6 @@ export const MainLayout: React.FC = () => { }, [activeMainTab]); const isChatActive = activeMainTab === 'chat'; - const isSettingsActive = isSettingsDialogOpen && !isMobile; return ( @@ -371,7 +370,7 @@ export const MainLayout: React.FC = () => { {/* Desktop: Header always on top, then Sidebar + Content below */}
{/* Normal view: Header above Sidebar + content (like SettingsView) */} -
+
@@ -404,12 +403,11 @@ export const MainLayout: React.FC = () => { )}
- {/* Settings view: full screen overlay */} - {isSettingsActive && ( -
- setSettingsDialogOpen(false)} /> -
- )} + {/* Desktop settings: windowed dialog with blur */} + )} diff --git a/packages/ui/src/components/ui/ScrollableOverlay.tsx b/packages/ui/src/components/ui/ScrollableOverlay.tsx index 1ca61570..b54eddce 100644 --- a/packages/ui/src/components/ui/ScrollableOverlay.tsx +++ b/packages/ui/src/components/ui/ScrollableOverlay.tsx @@ -11,6 +11,8 @@ type ScrollableOverlayProps = React.HTMLAttributes & { disableHorizontal?: boolean; fillContainer?: boolean; keyboardAvoid?: boolean; + /** Prevent scroll from propagating to parent when at boundaries */ + preventOverscroll?: boolean; }; export const ScrollableOverlay = React.forwardRef( @@ -26,6 +28,7 @@ export const ScrollableOverlay = React.forwardRef { const containerRef = React.useRef(null); @@ -34,13 +37,18 @@ export const ScrollableOverlay = React.forwardRef } className={cn( - "overlay-scrollbar-target overlay-scrollbar-container overscroll-none", + "overlay-scrollbar-target overlay-scrollbar-container", + preventOverscroll && "overscroll-none", fillContainer ? "flex-1 min-h-0 w-full" : "flex-none w-full h-auto", disableHorizontal ? "overflow-y-auto overflow-x-hidden" : "overflow-auto", className diff --git a/packages/ui/src/components/views/SettingsView.tsx b/packages/ui/src/components/views/SettingsView.tsx index 218b83e6..15b53e2e 100644 --- a/packages/ui/src/components/views/SettingsView.tsx +++ b/packages/ui/src/components/views/SettingsView.tsx @@ -52,15 +52,17 @@ const SETTINGS_SIDEBAR_MAX_WIDTH = 500; const SETTINGS_SIDEBAR_DEFAULT_WIDTH = 264; // Width threshold for hiding tab labels (show icons only) -const TAB_LABELS_MIN_WIDTH = 940; +const TAB_LABELS_MIN_WIDTH = 1024; interface SettingsViewProps { onClose?: () => void; /** Force mobile layout regardless of device detection */ forceMobile?: boolean; + /** Rendered inside a window/dialog (skip traffic light padding) */ + isWindowed?: boolean; } -export const SettingsView: React.FC = ({ onClose, forceMobile }) => { +export const SettingsView: React.FC = ({ onClose, forceMobile, isWindowed }) => { const deviceInfo = useDeviceInfo(); const isMobile = forceMobile ?? deviceInfo.isMobile; @@ -342,13 +344,16 @@ export const SettingsView: React.FC = ({ onClose, forceMobile // Keyboard shortcut display based on platform const shortcutKey = getModifierLabel(); - // Desktop padding for Mac titlebar area + // Desktop padding for Mac titlebar area (only when full-screen, not windowed) const desktopPaddingClass = React.useMemo(() => { + if (isWindowed) { + return 'pl-1.5'; // Balanced padding for windowed mode + } if (isDesktopApp && isMacPlatform) { return 'pl-[5.75rem]'; // Space for traffic lights } return ''; - }, [isDesktopApp, isMacPlatform]); + }, [isDesktopApp, isMacPlatform, isWindowed]); return ( diff --git a/packages/ui/src/components/views/SettingsWindow.tsx b/packages/ui/src/components/views/SettingsWindow.tsx new file mode 100644 index 00000000..8750629a --- /dev/null +++ b/packages/ui/src/components/views/SettingsWindow.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { cn } from '@/lib/utils'; +import { SettingsView } from './SettingsView'; + +interface SettingsWindowProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +/** + * Settings rendered as a centered window with blurred backdrop. + * Used for desktop and web (non-mobile) environments. + */ +export const SettingsWindow: React.FC = ({ open, onOpenChange }) => { + return ( + + + + + onOpenChange(false)} isWindowed /> + + + + ); +}; diff --git a/packages/ui/src/components/views/index.ts b/packages/ui/src/components/views/index.ts index 5ac27a6e..90c6202d 100644 --- a/packages/ui/src/components/views/index.ts +++ b/packages/ui/src/components/views/index.ts @@ -5,3 +5,4 @@ export { DiffView, useDiffFileCount } from './DiffView'; export { TerminalView } from './TerminalView'; export { FilesView } from './FilesView'; export { SettingsView } from './SettingsView'; +export { SettingsWindow } from './SettingsWindow';