fix(ui): reorganize bottom bar and clarify preset requirements

Changes:
- Added TODO comment about IndexedDB unavailability in test runtime
- Moved layout preset selector to rightmost side of bottom bar
- Moved window settings button to rightmost side as well
- Removed per-workspace settings icon (now global button on right)
- Added spacer to separate workspace tabs from layout controls
- Changed preset descriptions to show "4+ windows" instead of "4 windows"
  to clarify that presets work with more windows than the minimum
- Changed dropdown alignment from "start" to "end" for right-aligned opening

The bottom bar now has clearer visual separation:
[Workspace Tabs] [+] ................ [Layout Presets] [Settings]

Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Alejandro Gómez
2025-12-18 12:27:54 +01:00
parent 4db7e9690c
commit f2599772f6
2 changed files with 43 additions and 34 deletions

View File

@@ -76,50 +76,54 @@ export function TabBar() {
return (
<>
<div className="h-8 border-t border-border bg-background flex items-center px-2 gap-1 overflow-x-auto">
{/* Left side: Workspace tabs + new workspace button */}
<div className="flex items-center gap-1 flex-nowrap">
{sortedWorkspaces.map((ws) => (
<div key={ws.id} className="relative group flex-shrink-0">
<button
onClick={() => setActiveWorkspace(ws.id)}
className={cn(
"px-3 py-1 pr-7 text-xs font-mono rounded transition-colors whitespace-nowrap",
ws.id === activeWorkspaceId
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground hover:bg-muted",
)}
>
{ws.label && ws.label.trim()
? `${ws.number} ${ws.label}`
: ws.number}
</button>
<button
onClick={(e) => handleSettingsClick(e)}
className={cn(
"absolute right-0.5 top-1/2 -translate-y-1/2 p-0.5 rounded opacity-0 group-hover:opacity-100 transition-opacity",
ws.id === activeWorkspaceId
? "text-primary-foreground hover:bg-primary-foreground/20"
: "text-muted-foreground hover:text-foreground hover:bg-muted",
)}
aria-label="Layout settings"
>
<SlidersHorizontal className="h-3 w-3" />
</button>
</div>
<button
key={ws.id}
onClick={() => setActiveWorkspace(ws.id)}
className={cn(
"px-3 py-1 text-xs font-mono rounded transition-colors whitespace-nowrap flex-shrink-0",
ws.id === activeWorkspaceId
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground hover:bg-muted",
)}
>
{ws.label && ws.label.trim()
? `${ws.number} ${ws.label}`
: ws.number}
</button>
))}
<Button
variant="ghost"
size="icon"
className="h-6 w-6 ml-1 flex-shrink-0"
onClick={handleNewTab}
aria-label="Create new workspace"
>
<Plus className="h-3 w-3" />
</Button>
</div>
{/* Spacer to push right side controls to the end */}
<div className="flex-1" />
{/* Right side: Layout controls */}
<div className="flex items-center gap-1 flex-shrink-0">
{/* Layout Preset Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 ml-1 flex-shrink-0"
className="h-6 w-6"
aria-label="Apply layout preset"
>
<Grid2X2 className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-64">
<DropdownMenuContent align="end" className="w-64">
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
Apply Layout Preset
</div>
@@ -137,7 +141,7 @@ export function TabBar() {
<div className="font-medium text-sm">{preset.name}</div>
<div className="text-xs text-muted-foreground truncate">
{canApply
? `${preset.slots} windows`
? `${preset.slots}+ windows`
: `Needs ${preset.slots} (have ${windowCount})`}
</div>
</div>
@@ -147,14 +151,15 @@ export function TabBar() {
</DropdownMenuContent>
</DropdownMenu>
{/* Window/Layout Settings */}
<Button
variant="ghost"
size="icon"
className="h-6 w-6 ml-1 flex-shrink-0"
onClick={handleNewTab}
aria-label="Create new workspace"
className="h-6 w-6"
onClick={handleSettingsClick}
aria-label="Layout settings"
>
<Plus className="h-3 w-3" />
<SlidersHorizontal className="h-3 w-3" />
</Button>
</div>
</div>

View File

@@ -8,6 +8,10 @@ import {
getContentTypeDescription,
} from "./nostr-schema";
// TODO: Some tests in this file fail because IndexedDB is not available in the Vitest runtime.
// The failures are related to Dexie/IndexedDB operations that can't run in the Node.js test environment.
// Future work: Mock IndexedDB or use an in-memory IndexedDB implementation for testing.
describe("nostr-schema", () => {
describe("loadSchema", () => {
it("should load and parse the schema", () => {