Skip to content

Commit 49a32c4

Browse files
committed
feat: Add collapsible sidebar with persistent state and responsive grid
- Added chevron toggle button in sidebar footer with smooth 180° rotation - Implemented persistent sidebar state using localStorage across app restarts - Added smooth transitions (300ms ease-in-out) for all sidebar animations - Footer text (version & copyright) displayed on single line, hidden when collapsed - Tooltip shows 'Collapse/Expand sidebar' on hover for better UX - Grid layout now responsive to sidebar state - shows more images per row when collapsed - Grid min-size adjusts from 224px to 200px when sidebar collapses for optimal space usage - All transitions synchronized and smooth without visual glitches
1 parent d94c588 commit 49a32c4

File tree

2 files changed

+74
-6
lines changed

2 files changed

+74
-6
lines changed

frontend/src/components/Media/ChronologicalGallery.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { groupImagesByYearMonthFromMetadata } from '@/utils/dateUtils';
66
import { setCurrentViewIndex } from '@/features/imageSlice';
77
import { MediaView } from './MediaView';
88
import { selectIsImageViewOpen } from '@/features/imageSelectors';
9+
import { useSidebar } from '@/components/ui/sidebar';
910

1011
export type MonthMarker = {
1112
offset: number;
@@ -34,6 +35,11 @@ export const ChronologicalGallery = ({
3435
const monthHeaderRefs = useRef<Map<string, HTMLDivElement | null>>(new Map());
3536
const galleryRef = useRef<HTMLDivElement>(null);
3637
const isImageViewOpen = useSelector(selectIsImageViewOpen);
38+
const { open: sidebarOpen } = useSidebar();
39+
40+
// Adjust grid minmax based on sidebar state
41+
// When sidebar is collapsed, reduce min size to fit more images per row
42+
const gridMinSize = sidebarOpen ? '224px' : '200px';
3743

3844
// Optimized grouping with proper date handling
3945
const grouped = useMemo(
@@ -157,7 +163,12 @@ export const ChronologicalGallery = ({
157163
</div>
158164

159165
{/* Images Grid */}
160-
<div className="grid grid-cols-[repeat(auto-fill,_minmax(224px,_1fr))] gap-4 p-2">
166+
<div
167+
className="grid gap-4 p-2 transition-all duration-300"
168+
style={{
169+
gridTemplateColumns: `repeat(auto-fill, minmax(${gridMinSize}, 1fr))`
170+
}}
171+
>
161172
{imgs.map((img) => {
162173
const chronologicalIndex =
163174
imageIndexMap.get(img.id) ?? -1;

frontend/src/components/Navigation/Sidebar/AppSidebar.tsx

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
SidebarMenuButton,
88
SidebarMenuItem,
99
SidebarSeparator,
10+
useSidebar,
1011
} from '@/components/ui/sidebar';
1112
import {
1213
Bolt,
@@ -16,15 +17,40 @@ import {
1617
Video,
1718
BookImage,
1819
ClockFading,
20+
ChevronLeft,
1921
} from 'lucide-react';
2022
import { useLocation, Link } from 'react-router';
2123
import { ROUTES } from '@/constants/routes';
2224
import { getVersion } from '@tauri-apps/api/app';
2325
import { useEffect, useState } from 'react';
26+
import { Button } from '@/components/ui/button';
27+
import {
28+
Tooltip,
29+
TooltipContent,
30+
TooltipProvider,
31+
TooltipTrigger,
32+
} from '@/components/ui/tooltip';
2433

2534
export function AppSidebar() {
2635
const location = useLocation();
2736
const [version, setVersion] = useState<string>('1.0.0');
37+
const { open, setOpen, toggleSidebar } = useSidebar();
38+
39+
// Load collapsed state from localStorage on mount
40+
useEffect(() => {
41+
const savedState = localStorage.getItem('sidebar-open');
42+
if (savedState !== null) {
43+
const shouldBeOpen = savedState === 'true';
44+
if (open !== shouldBeOpen) {
45+
toggleSidebar();
46+
}
47+
}
48+
}, []);
49+
50+
// Save collapsed state to localStorage whenever it changes
51+
useEffect(() => {
52+
localStorage.setItem('sidebar-open', String(open));
53+
}, [open]);
2854

2955
useEffect(() => {
3056
getVersion().then((version) => {
@@ -59,7 +85,7 @@ export function AppSidebar() {
5985
<Sidebar
6086
variant="sidebar"
6187
collapsible="icon"
62-
className="border-border/40 border-r shadow-sm"
88+
className="border-border/40 border-r shadow-sm transition-all duration-300 ease-in-out"
6389
>
6490
<SidebarHeader className="flex justify-center py-3">
6591
<div className="text-lg font-semibold">.</div>
@@ -73,7 +99,7 @@ export function AppSidebar() {
7399
asChild
74100
isActive={isActive(item.path)}
75101
tooltip={item.name}
76-
className="rounded-sm"
102+
className="rounded-sm transition-all duration-200"
77103
>
78104
<Link to={item.path} className="flex items-center gap-3">
79105
<item.icon className="h-5 w-5" />
@@ -85,9 +111,40 @@ export function AppSidebar() {
85111
</SidebarMenu>
86112
</SidebarContent>
87113
<SidebarFooter className="border-border/40 mt-auto border-t py-4">
88-
<div className="text-muted-foreground space-y-1 px-4 text-xs">
89-
<div className="font-medium">PictoPy v{version}</div>
90-
<div>© 2025 PictoPy</div>
114+
<div className="space-y-3">
115+
<div
116+
className={`text-muted-foreground space-y-1 px-4 text-xs transition-all duration-300 ${
117+
open
118+
? 'opacity-100 translate-x-0'
119+
: 'opacity-0 -translate-x-2 pointer-events-none absolute'
120+
}`}
121+
>
122+
<div className="font-medium whitespace-nowrap">PictoPy v{version}</div>
123+
<div className="whitespace-nowrap">© 2025 PictoPy</div>
124+
</div>
125+
<div className={`flex transition-all duration-300 ${open ? 'justify-end px-4' : 'justify-center'}`}>
126+
<TooltipProvider>
127+
<Tooltip>
128+
<TooltipTrigger asChild>
129+
<Button
130+
variant="ghost"
131+
size="icon"
132+
onClick={toggleSidebar}
133+
className="h-8 w-8 transition-transform duration-300 ease-in-out hover:bg-accent"
134+
>
135+
<ChevronLeft
136+
className={`h-5 w-5 transition-transform duration-300 ease-in-out ${
137+
!open ? 'rotate-180' : ''
138+
}`}
139+
/>
140+
</Button>
141+
</TooltipTrigger>
142+
<TooltipContent side="right">
143+
<p>{open ? 'Collapse sidebar' : 'Expand sidebar'}</p>
144+
</TooltipContent>
145+
</Tooltip>
146+
</TooltipProvider>
147+
</div>
91148
</div>
92149
</SidebarFooter>
93150
</Sidebar>

0 commit comments

Comments
 (0)