sidebar improvements

This commit is contained in:
2026-03-13 19:01:56 -05:00
parent 6a1af4b723
commit f4ad67e097
3 changed files with 136 additions and 115 deletions
+1
View File
@@ -219,6 +219,7 @@ const EstimateBuilder: Component<EstimateBuilderProps> = (props) => {
> >
<BuilderSidebar <BuilderSidebar
isCollapsed={isSidebarCollapsed()} isCollapsed={isSidebarCollapsed()}
onToggleSidebar={() => setIsSidebarCollapsed(!isSidebarCollapsed())}
unassignedItems={unassignedItems()} unassignedItems={unassignedItems()}
grandTotals={grandTotals()} grandTotals={grandTotals()}
hoveredScopeId={hoveredScopeId()} hoveredScopeId={hoveredScopeId()}
@@ -1,6 +1,6 @@
import { createSignal, onCleanup, Show } from 'solid-js'; import { createSignal, onCleanup, Show } from 'solid-js';
import type { Component } from 'solid-js'; import type { Component } from 'solid-js';
import { PanelLeftOpen, PanelLeftClose, Calculator, Plus, Upload, Table, Download, History, Save, ChevronDown, MoreHorizontal } from 'lucide-solid'; import { Calculator, Plus, Upload, Table, Download, History, Save, ChevronDown, MoreHorizontal } from 'lucide-solid';
import { appStore } from '../../store/appStore'; import { appStore } from '../../store/appStore';
interface BuilderHeaderProps { interface BuilderHeaderProps {
@@ -34,13 +34,6 @@ export const BuilderHeader: Component<BuilderHeaderProps> = (props) => {
return ( return (
<div class="min-h-[4rem] py-3 px-6 border-b border-border bg-background/50 flex flex-wrap gap-y-4 items-center justify-between sticky top-0 z-50 backdrop-blur-md"> <div class="min-h-[4rem] py-3 px-6 border-b border-border bg-background/50 flex flex-wrap gap-y-4 items-center justify-between sticky top-0 z-50 backdrop-blur-md">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<button
onClick={props.onToggleSidebar}
class="p-2 hover:bg-accent rounded-xl transition-colors text-muted-foreground shrink-0"
title={props.isSidebarCollapsed ? "Expand Sidebar" : "Collapse Sidebar"}
>
{props.isSidebarCollapsed ? <PanelLeftOpen class="w-5 h-5" /> : <PanelLeftClose class="w-5 h-5" />}
</button>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="p-2 bg-primary rounded-xl text-primary-foreground shadow-md shadow-primary/20 shrink-0"> <div class="p-2 bg-primary rounded-xl text-primary-foreground shadow-md shadow-primary/20 shrink-0">
<Calculator class="w-5 h-5" /> <Calculator class="w-5 h-5" />
@@ -1,11 +1,12 @@
import type { Component } from 'solid-js'; import type { Component } from 'solid-js';
import { For } from 'solid-js'; import { For } from 'solid-js';
import { Plus, ListTree, ChevronRight } from 'lucide-solid'; import { Plus, ListTree, ChevronRight, PanelLeftOpen, PanelLeftClose } from 'lucide-solid';
import { appStore } from '../../store/appStore'; import { appStore } from '../../store/appStore';
import type { EstimateItem } from '../../types'; import type { EstimateItem } from '../../types';
interface BuilderSidebarProps { interface BuilderSidebarProps {
isCollapsed: boolean; isCollapsed: boolean;
onToggleSidebar: () => void;
unassignedItems: EstimateItem[]; unassignedItems: EstimateItem[];
grandTotals: { grandTotal: number }; grandTotals: { grandTotal: number };
hoveredScopeId: string | null; hoveredScopeId: string | null;
@@ -24,15 +25,40 @@ export const BuilderSidebar: Component<BuilderSidebarProps> = (props) => {
return ( return (
/* /*
* The sidebar sits inside a CSS grid column (in EstimateBuilder.tsx). * The sidebar sits inside a CSS grid column (in EstimateBuilder.tsx).
* It fills its column completely. When the column width transitions to 0, * We use overflow-visible on the aside so the toggle button can remain
* the sidebar content is clipped by overflow-hidden on this element. * visible and clickable even when the grid column width is 0px.
* This means the main content genuinely reclaims the space.
*/ */
<aside <aside
class="h-[calc(100vh-var(--spacing-header))] sticky top-[var(--spacing-header)] bg-background border-r border-border flex flex-col overflow-hidden" class="h-[calc(100vh-var(--spacing-header))] sticky top-[var(--spacing-header)] bg-background border-r border-border flex flex-col overflow-visible z-40 transition-colors"
style={{ "min-width": 0 }} style={{ "min-width": 0 }}
> >
<div class="flex-1 overflow-y-auto overflow-x-hidden px-4 py-8 w-72"> {/*
* Sticky Toggle Button
* Anchored to the right edge (100% width) of the sidebar column.
* As the column width animates, the button follows perfectly.
* We use translateX to adjust horizontal offset from the edge.
*/}
<button
onClick={props.onToggleSidebar}
class="absolute top-4 z-50 w-10 h-10 bg-background hover:bg-accent rounded-xl transition-all duration-500 ease-in-out shadow-premium border border-border text-muted-foreground hover:text-primary active:scale-95 flex items-center justify-center cursor-pointer"
style={{
left: '100%',
transform: props.isCollapsed
? 'translateX(1.25rem)' // When closed (0px width), sit 20px off the left edge
: 'translateX(-0.5rem)' // When open, hang 80% out to the right (overlap only 8px)
}}
title={props.isCollapsed ? "Expand Sidebar" : "Collapse Sidebar"}
>
{props.isCollapsed ? <PanelLeftOpen class="w-5 h-5" /> : <PanelLeftClose class="w-5 h-5" />}
</button>
{/*
* Sidebar Content Wrapper
* We apply the overflow-hidden here so the content doesn't leak out
* when the grid column is 0px.
*/}
<div class={`flex-1 overflow-y-auto overflow-x-hidden transition-all duration-500 flex flex-col ${props.isCollapsed ? 'w-0 opacity-0 pointer-events-none' : 'w-72 opacity-100'}`}>
<div class="flex-1 px-4 py-8">
<div class="mb-4"> <div class="mb-4">
<div class="flex items-center justify-between mb-4 px-2"> <div class="flex items-center justify-between mb-4 px-2">
<span class="text-[10px] font-black text-muted-foreground uppercase tracking-[0.2em] whitespace-nowrap">Estimate Navigator</span> <span class="text-[10px] font-black text-muted-foreground uppercase tracking-[0.2em] whitespace-nowrap">Estimate Navigator</span>
@@ -144,6 +170,7 @@ export const BuilderSidebar: Component<BuilderSidebarProps> = (props) => {
</div> </div>
</div> </div>
</div> </div>
</div>
</aside> </aside>
); );
}; };