From d624b5b05adf3b5da2e67745461292d3562bebbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Thu, 18 Dec 2025 12:55:26 +0100 Subject: [PATCH] ui: simplify layout settings and add smooth animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove success notification when applying layouts (keep only errors) - Add CSS transitions for smooth window resizing/repositioning - Replace large settings Dialog with compact Popover - Reduce settings UI from ~220 lines to ~97 lines - Remove verbose descriptions and preview section - Make settings match site's minimal UI patterns - Settings now update live without Save/Cancel buttons - Create popover.tsx component using Radix UI primitives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/components/TabBar.tsx | 32 ++-- src/components/WorkspaceSettings.tsx | 258 +++++++-------------------- src/components/ui/popover.tsx | 30 ++++ src/index.css | 5 + 4 files changed, 113 insertions(+), 212 deletions(-) create mode 100644 src/components/ui/popover.tsx diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx index 478ba77..85d9ad5 100644 --- a/src/components/TabBar.tsx +++ b/src/components/TabBar.tsx @@ -26,10 +26,6 @@ export function TabBar() { createWorkspace(); }; - const handleSettingsClick = (e: React.MouseEvent) => { - e.stopPropagation(); // Prevent workspace switch - setSettingsOpen(true); - }; const handleApplyPreset = (presetId: string) => { const preset = presets.find((p) => p.id === presetId); @@ -44,9 +40,6 @@ export function TabBar() { try { applyPresetLayout(preset); - toast.success(`Layout applied`, { - description: `Applied "${preset.name}" preset to workspace ${activeWorkspace.number}`, - }); } catch (error) { toast.error(`Failed to apply layout`, { description: @@ -152,22 +145,21 @@ export function TabBar() { {/* Window/Layout Settings */} - + + - - ); } diff --git a/src/components/WorkspaceSettings.tsx b/src/components/WorkspaceSettings.tsx index 679a25f..db7b77c 100644 --- a/src/components/WorkspaceSettings.tsx +++ b/src/components/WorkspaceSettings.tsx @@ -1,223 +1,97 @@ -import { useState } from "react"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "./ui/dialog"; -import { Label } from "./ui/Label"; -import { Button } from "./ui/button"; + Popover, + PopoverContent, + PopoverTrigger, +} from "./ui/popover"; +import { useGrimoire } from "@/core/state"; +import type { LayoutConfig } from "@/types/app"; +import { cn } from "@/lib/utils"; import { Sparkles, SplitSquareHorizontal, SplitSquareVertical, } from "lucide-react"; -import { useGrimoire } from "@/core/state"; -import type { LayoutConfig } from "@/types/app"; -import { cn } from "@/lib/utils"; interface WorkspaceSettingsProps { open: boolean; onOpenChange: (open: boolean) => void; + children: React.ReactNode; } export function WorkspaceSettings({ open, onOpenChange, + children, }: WorkspaceSettingsProps) { const { state, updateLayoutConfig } = useGrimoire(); + const config = state.layoutConfig; - // Local state for settings - const [insertionMode, setInsertionMode] = useState( - state.layoutConfig?.insertionMode || "smart" - ); - const [splitPercentage, setSplitPercentage] = useState( - state.layoutConfig?.splitPercentage || 50 - ); - const [insertionPosition, setInsertionPosition] = useState( - state.layoutConfig?.insertionPosition || "second" - ); - - const handleSave = () => { - updateLayoutConfig({ - insertionMode, - splitPercentage, - insertionPosition, - }); - onOpenChange(false); + const setInsertionMode = (mode: LayoutConfig["insertionMode"]) => { + updateLayoutConfig({ insertionMode: mode }); }; - const handleReset = () => { - setInsertionMode("smart"); - setSplitPercentage(50); - setInsertionPosition("second"); + const setSplitPercentage = (value: number) => { + updateLayoutConfig({ splitPercentage: value }); }; + const modes: Array<{ + id: LayoutConfig["insertionMode"]; + label: string; + icon: React.ComponentType<{ className?: string }>; + }> = [ + { id: "smart", label: "Balanced", icon: Sparkles }, + { id: "row", label: "Horizontal", icon: SplitSquareHorizontal }, + { id: "column", label: "Vertical", icon: SplitSquareVertical }, + ]; + return ( - - - - Layout Settings - - Configure how new windows are inserted into all workspaces. - - - -
- {/* Insertion Mode */} -
- -
- - - - - -
+ + {children} + +
+
+ Insert Mode
- - {/* Split Percentage */} -
-
- - - {splitPercentage}% / {100 - splitPercentage}% - -
- setSplitPercentage(Number(e.target.value))} - className="w-full h-2 bg-muted rounded-lg appearance-none cursor-pointer accent-primary" - /> -
- Existing content gets {splitPercentage}% - New window gets {100 - splitPercentage}% -
-
- - {/* Insertion Position */} -
- -
- - - -
-
- - {/* Preview */} -
-
Preview:
-
- - {insertionMode === "smart" && "Smart mode"} - {insertionMode === "row" && "Horizontal splits"} - {insertionMode === "column" && "Vertical splits"} - - {" · "} - - {splitPercentage}%/{100 - splitPercentage}% split - - {" · "} - - New window on{" "} - {insertionPosition === "first" ? "left/top" : "right/bottom"} - -
+
+ {modes.map((mode) => { + const Icon = mode.icon; + const isActive = config.insertionMode === mode.id; + return ( + + ); + })}
- {/* Footer */} -
- -
- - +
+
+ Split + + {config.splitPercentage}/{100 - config.splitPercentage} +
+ setSplitPercentage(Number(e.target.value))} + className="w-full h-1.5 bg-muted rounded-lg appearance-none cursor-pointer accent-accent" + />
- -
+ + ); } diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..f906c34 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,30 @@ +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import { cn } from "@/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/src/index.css b/src/index.css index 1453f9f..598aacf 100644 --- a/src/index.css +++ b/src/index.css @@ -123,6 +123,11 @@ background: hsl(var(--background)); } +/* Smooth animations for window resizing and repositioning */ +.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-tile { + transition: width 0.2s ease-out, height 0.2s ease-out, top 0.2s ease-out, left 0.2s ease-out; +} + .mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window .mosaic-window-toolbar {