Skip to content

feat: Add collapsible sidebar with persistent state and responsive grid#1116

Open
VasuS609 wants to merge 6 commits intoAOSSIE-Org:mainfrom
VasuS609:sidebar-toggle
Open

feat: Add collapsible sidebar with persistent state and responsive grid#1116
VasuS609 wants to merge 6 commits intoAOSSIE-Org:mainfrom
VasuS609:sidebar-toggle

Conversation

@VasuS609
Copy link
Contributor

@VasuS609 VasuS609 commented Jan 31, 2026

Fixes: #942

  • Toggle button in sidebar footer with smooth rotation animation
  • Sidebar state persists across app restarts using localStorage
  • Smooth 300ms transitions for all sidebar animations
  • Footer info (version/copyright) hides when collapsed
  • Tooltip on toggle button for better UX
  • Image grid responds to sidebar state - more images shown when collapsed
  • Grid spacing optimized for collapsed sidebar (200px vs 224px)
  • No visual glitches during transitions
Screen.Recording.2026-01-31.194938.mp4

Summary by CodeRabbit

  • New Features

    • Sidebar toggle to collapse/expand with persisted state across sessions.
    • Gallery layout now responsively adjusts when the sidebar state changes.
    • Footer reveals app version and current year when expanded.
  • Style

    • Smooth animations for sidebar open/close, footer reveal, and gallery resizing.
    • Toggle chevron rotates and contextual tooltips are shown.
  • Other

    • App title updated to "PictoPy - AI-Powered Photo Manager."

- 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
@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 31, 2026

Warning

Rate limit exceeded

@VasuS609 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 1 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds a collapsible, persisted sidebar toggle and uses its state to adjust ChronologicalGallery's responsive grid min column size; updates app title and applies small code cleanups (predicate simplification and strict equality).

Changes

Cohort / File(s) Summary
Sidebar — UI & state
frontend/src/components/Navigation/Sidebar/AppSidebar.tsx, frontend/src/components/ui/sidebar.tsx
Adds collapse/expand toggle (chevron + tooltip), transition/animation classes, persisted open state restored from a cookie and guarded cookie writes. Note: unresolved merge-conflict markers present in AppSidebar.tsx.
Gallery — responsive grid
frontend/src/components/Media/ChronologicalGallery.tsx
Replaces fixed Tailwind grid with dynamic inline gridTemplateColumns: repeat(auto-fill, minmax(gridMinSize, 1fr)), where gridMinSize is derived from sidebar state (224px open, 200px closed); adds transitions for smoother resize.
App shell / static
frontend/index.html
Updates document title to "PictoPy - AI-Powered Photo Manager".
Small behavioral tweaks
frontend/src/pages/Home/MyFav.tsx, landing-page/src/Pages/Landing page/Home1.tsx
Minor code cleanups: simplified favourite predicate (image.isFavourite) and changed loose inequality to strict (!==) in landing page shuffle loop.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Sidebar as AppSidebar
    participant Store as SidebarStore (cookie)
    participant Gallery as ChronologicalGallery
    rect rgba(200,200,255,0.5)
    Client->>Sidebar: Click toggle button
    end
    rect rgba(200,255,200,0.5)
    Sidebar->>Store: write open state cookie (try/catch)
    Store-->>Sidebar: persist confirmation
    Sidebar->>Gallery: update open state via useSidebar
    Gallery->>Gallery: compute gridMinSize (224px / 200px)
    Gallery->>Client: re-render grid with new gridTemplateColumns
    Client->>Sidebar: observe chevron rotation and footer collapse/expand
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

frontend

Suggested reviewers

  • rahulharpal1603

Poem

🐰 I nudged the sidebar, gave it a hop,
A chevron spins, the footer drops,
Thumbs stretch wider, pixels breathe,
A cookie keeps my hidden wreath,
Happy hops for gallery crops! 📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main changes: adding a collapsible sidebar with persistent state and responsive grid layout.
Linked Issues check ✅ Passed The PR successfully implements all core requirements from issue #942: collapsible toggle button, persistent sidebar state via cookies, responsive grid adapting to sidebar state, and smooth animations.
Out of Scope Changes check ✅ Passed All changes are directly related to the sidebar feature: toggle UI, sidebar persistence, responsive grid, title update, and minor code refinements align with the stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/src/components/Navigation/Sidebar/AppSidebar.tsx`:
- Line 37: The destructuring of useSidebar includes an unused variable `setOpen`
which causes lint/pipeline errors; update the statement `const { open, setOpen,
toggleSidebar } = useSidebar()` in AppSidebar.tsx to remove `setOpen` so it
simply destructures `open` and `toggleSidebar` from the `useSidebar()` hook.
🧹 Nitpick comments (2)
frontend/src/components/Navigation/Sidebar/AppSidebar.tsx (2)

40-53: Consider using setOpen directly to avoid race condition.

There's a subtle race condition between the load and save effects:

  1. On mount, save effect runs with the hook's initial open value before the load effect's toggle takes effect.
  2. This briefly overwrites the user's saved preference.

Using setOpen directly would be cleaner and avoid the issue:

♻️ Proposed refactor
+  const { open, setOpen, toggleSidebar } = useSidebar();
+
   // Load collapsed state from localStorage on mount
   useEffect(() => {
     const savedState = localStorage.getItem('sidebar-open');
     if (savedState !== null) {
-      const shouldBeOpen = savedState === 'true';
-      if (open !== shouldBeOpen) {
-        toggleSidebar();
-      }
+      setOpen(savedState === 'true');
     }
-  }, []);
+  }, [setOpen]);

   // Save collapsed state to localStorage whenever it changes
   useEffect(() => {
     localStorage.setItem('sidebar-open', String(open));
   }, [open]);

Note: This approach would require keeping setOpen in the destructuring (reverting the fix for the unused variable warning), but it's a more robust solution.


123-123: Consider using dynamic year for copyright.

The hardcoded "2025" will need manual updates each year.

♻️ Proposed refactor
-            <div className="whitespace-nowrap">© 2025 PictoPy</div>
+            <div className="whitespace-nowrap">© {new Date().getFullYear()} PictoPy</div>

- Implement collapsible sidebar with localStorage persistence
- Add responsive gallery grid that adjusts to sidebar state (224px when open, 200px when closed)
- Fix race condition in sidebar state management by using setOpen directly
- Make copyright year dynamic using new Date().getFullYear()
- Remove redundant boolean comparison in favorites filter
- Update HTML title to 'PictoPy - AI-Powered Photo Manager'
- Fix strict equality operator in landing page

Fixes AOSSIE-Org#942
@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

1 similar comment
@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@github-actions github-actions bot added UI enhancement New feature or request labels Jan 31, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/src/components/Media/ChronologicalGallery.tsx`:
- Around line 38-42: Tests rendering Home and AITagging fail because
ChronologicalGallery calls useSidebar() which requires being inside
SidebarProvider; update allPages.test.tsx to wrap the rendered pages with the
SidebarProvider used in production. Specifically, modify the test render helpers
(or individual renders) to wrap <Home /> and <AITagging /> (the components that
render ChronologicalGallery) with <SidebarProvider> so useSidebar() no longer
throws; ensure you import the SidebarProvider from the same module used by
Layout and update any wrapper/setup utilities used across tests.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/src/components/Navigation/Sidebar/AppSidebar.tsx`:
- Around line 97-141: Remove the Git conflict markers and keep the
sidebar-toggle implementation (the block that uses open, toggleSidebar,
Tooltip/TooltipTrigger/TooltipContent, Button and ChevronLeft) instead of the
simpler main branch content; delete the lines starting with <<<<<<<, =======,
and >>>>>>> and ensure the JSX uses the animated container with classes that
reference open and the toggle button that calls toggleSidebar and displays
PictoPy v{version} and the current year.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/src/components/ui/sidebar.tsx`:
- Around line 74-87: Read the cookie synchronously inside the useState
initializer instead of in the React.useEffect to avoid first-paint flicker:
compute initialOpen by checking typeof document !== 'undefined' then parsing
document.cookie for SIDEBAR_COOKIE_NAME and falling back to defaultOpen, and
pass that initialOpen to the state hook that ultimately calls _setOpen; remove
or keep a no-op effect (React.useEffect) that currently reads the cookie to
avoid duplicated logic. Ensure you reference SIDEBAR_COOKIE_NAME and use the
same state setter (_setOpen) so behavior is unchanged and SSR (document
undefined) is guarded.
🧹 Nitpick comments (1)
frontend/src/components/ui/sidebar.tsx (1)

99-101: Persist cookie from open state, not from setOpen calls.
If open is controlled or changed externally, the cookie won’t update, and setOpen can write a value the parent later rejects. Consider syncing cookie writes in an effect that watches open.

Suggested adjustment
   const setOpen = React.useCallback(
     (value: boolean | ((value: boolean) => boolean)) => {
       const openState = typeof value === 'function' ? value(open) : value;
       if (setOpenProp) {
         setOpenProp(openState);
       } else {
         _setOpen(openState);
       }
-
-      // This sets the cookie to keep the sidebar state.
-      try {
-        document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
-      } catch {}
     },
     [setOpenProp, open],
   );
+
+  React.useEffect(() => {
+    try {
+      document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
+    } catch {}
+  }, [open]);

Comment on lines +74 to +87
// Restore sidebar state from cookie on mount
React.useEffect(() => {
try {
const cookies = document.cookie
.split('; ')
.find((row) => row.startsWith(SIDEBAR_COOKIE_NAME + '='));
if (cookies) {
const savedState = cookies.split('=')[1] === 'true';
_setOpen(savedState);
}
} catch {
// Ignore cookie errors
}
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the sidebar.tsx file
find . -name "sidebar.tsx" -type f

Repository: AOSSIE-Org/PictoPy

Length of output: 102


🏁 Script executed:

# Read the sidebar.tsx file to check the actual code at lines 74-87 and surrounding context
cat -n frontend/src/components/ui/sidebar.tsx | head -120 | tail -70

Repository: AOSSIE-Org/PictoPy

Length of output: 2497


🏁 Script executed:

# Also check the full context around the useState and useEffect
cat -n frontend/src/components/ui/sidebar.tsx | grep -A 30 -B 5 "SIDEBAR_COOKIE_NAME"

Repository: AOSSIE-Org/PictoPy

Length of output: 3322


Avoid first-paint flicker by initializing sidebar state from the cookie synchronously.
The useEffect at lines 74–87 runs after paint, so if a saved cookie differs from defaultOpen, the sidebar will briefly render in the wrong state before the effect updates it. Move the cookie read into the useState initializer instead, guarding against SSR environments where document is unavailable.

Suggested adjustment
-  const [_open, _setOpen] = React.useState(defaultOpen);
+  const [_open, _setOpen] = React.useState(() => {
+    if (typeof document === 'undefined') return defaultOpen;
+    try {
+      const cookie = document.cookie
+        .split(';')
+        .map((c) => c.trim())
+        .find((row) => row.startsWith(`${SIDEBAR_COOKIE_NAME}=`));
+      return cookie ? cookie.split('=')[1] === 'true' : defaultOpen;
+    } catch {
+      return defaultOpen;
+    }
+  });
-  React.useEffect(() => {
-    try {
-      const cookies = document.cookie
-        .split('; ')
-        .find((row) => row.startsWith(SIDEBAR_COOKIE_NAME + '='));
-      if (cookies) {
-        const savedState = cookies.split('=')[1] === 'true';
-        _setOpen(savedState);
-      }
-    } catch {
-      // Ignore cookie errors
-    }
-  }, []);
🤖 Prompt for AI Agents
In `@frontend/src/components/ui/sidebar.tsx` around lines 74 - 87, Read the cookie
synchronously inside the useState initializer instead of in the React.useEffect
to avoid first-paint flicker: compute initialOpen by checking typeof document
!== 'undefined' then parsing document.cookie for SIDEBAR_COOKIE_NAME and falling
back to defaultOpen, and pass that initialOpen to the state hook that ultimately
calls _setOpen; remove or keep a no-op effect (React.useEffect) that currently
reads the cookie to avoid duplicated logic. Ensure you reference
SIDEBAR_COOKIE_NAME and use the same state setter (_setOpen) so behavior is
unchanged and SSR (document undefined) is guarded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Add toggle button to collapse sidebar and give more space for photos

1 participant