feat: add real-time project search and filtering functionality#710
feat: add real-time project search and filtering functionality#710ankitkr104 wants to merge 3 commits intoAOSSIE-Org:mainfrom
Conversation
📝 WalkthroughWalkthroughAdded a client-side real-time search UI to the Projects page that filters projects by name, uses memoized sorted/filtered lists, animates list enter/exit with Framer Motion, and displays an empty-state when no matches exist. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/projects/page.jsx`:
- Around line 221-259: The search input currently only has a placeholder and no
accessible label or live announcement for results; add a visible or
visually-hidden <label> tied to the input (give the input an id and reference it
from the label) so the control has a stable accessible name (referencing the
input that uses searchQuery and setSearchQuery), and render a polite live region
(e.g., a <div role="status" aria-live="polite" aria-atomic="true">) that updates
whenever filteredProjects changes to announce the number of results (use
filteredProjects.length and product.slug/context from the mapping that renders
CardProduct) so screen readers are notified of result updates.
- Around line 239-259: The empty-state message currently appears immediately
when filteredProjects becomes empty causing overlap with cards still animating
out; modify the component to wait for AnimatePresence's onExitComplete before
showing the empty message by adding a local state flag (e.g., exitsComplete or
showEmpty) and toggling it in AnimatePresence's onExitComplete callback, render
the "No projects found matching..." block only when filteredProjects.length ===
0 && exitsComplete is true, and ensure to reset the flag to false when
filteredProjects becomes non-empty (watch filteredProjects in useEffect) so
AnimatePresence (layout/motion divs) and CardProduct continue to behave as
before.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| <AnimatePresence mode="popLayout"> | ||
| {filteredProjects.map((product) => ( | ||
| <motion.div | ||
| layout | ||
| key={product.slug} | ||
| initial={{ opacity: 0, scale: 0.9 }} | ||
| animate={{ opacity: 1, scale: 1 }} | ||
| exit={{ opacity: 0, scale: 0.9 }} | ||
| transition={{ duration: 0.3 }} | ||
| > | ||
| <CardProduct product={product} /> | ||
| </motion.div> | ||
| ))} | ||
| </AnimatePresence> | ||
| {filteredProjects.length === 0 && ( | ||
| <div className="col-span-full py-20 text-center"> | ||
| <p className="text-xl text-zinc-500 dark:text-zinc-400 font-mono"> | ||
| No projects found matching "{searchQuery}" | ||
| </p> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and examine the code at the specified lines
if [ -f "src/app/projects/page.jsx" ]; then
wc -l src/app/projects/page.jsx
echo "---"
sed -n '235,265p' src/app/projects/page.jsx
else
echo "File not found"
fiRepository: AOSSIE-Org/Website
Length of output: 1223
🌐 Web query:
Framer Motion AnimatePresence popLayout mode behavior exit animation
💡 Result:
Framer Motion's AnimatePresence with mode="popLayout" pops exiting elements out of the document layout immediately using position: absolute, allowing surrounding elements to reflow right away while the exit animation plays. This pairs well with the layout prop for smooth list removals, as shown in the official example: {items.map(item => ( <motion.li layout exit={{ opacity: 0 }} key={item.id} /> ))} Key behaviors: - Exiting elements animate from their popped position (absolute positioned to match original layout spot). - Custom child components must use forwardRef to forward the ref to the DOM element. - Parent container needs position: relative (or non-static) for correct positioning during layout shifts, as transforms create new offset parents. - Exit animations like opacity: 0 should trigger, but there's a known open bug (Issue #2416 since 2023, still reported in 2025) where opacity exit animations on layout elements fail inconsistently—every other item may skip. Workaround: add another property like x: 100 to the exit. Official docs confirm exit animations work as intended with popLayout when used properly, emphasizing unique keys and proper refs.
Citations:
- 1: https://www.framer.com/motion/animate-presence/
- 2: https://motion.dev/docs/react-animate-presence
- 3: https://motion.dev/tutorials/react-animate-presence-modes
- 4: [BUG] layout parent with
<AnimatePresence mode=popLayout>children produces incorrect exit animation motiondivision/motion#3026 - 5: https://ogblocks.dev/blog/framer-motion-animate-presence
- 6: [BUG] AnimatePresence with mode=popLayout doesn't consistently trigger opacity exit animations on elements with layout motiondivision/motion#2416
- 7: [BUG] AnimatePresence with mode=popLayout doesn't consistently trigger opacity exit animations on elements with layout motiondivision/motion#2416
Delay the empty state message until exit animations complete.
At line 253, the empty message renders immediately when filteredProjects.length becomes zero, but with mode="popLayout", the exiting cards remain visible during their 0.3s exit animation. This creates a visual overlap where "No projects found" appears while the last cards are still fading out. Use the onExitComplete callback on AnimatePresence to gate the empty state render, or introduce a state variable to track when all exits finish.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/projects/page.jsx` around lines 239 - 259, The empty-state message
currently appears immediately when filteredProjects becomes empty causing
overlap with cards still animating out; modify the component to wait for
AnimatePresence's onExitComplete before showing the empty message by adding a
local state flag (e.g., exitsComplete or showEmpty) and toggling it in
AnimatePresence's onExitComplete callback, render the "No projects found
matching..." block only when filteredProjects.length === 0 && exitsComplete is
true, and ensure to reset the flag to false when filteredProjects becomes
non-empty (watch filteredProjects in useEffect) so AnimatePresence
(layout/motion divs) and CardProduct continue to behave as before.
|
Caution Review failedAn error occurred during the review process. Please try again later. 📝 WalkthroughWalkthroughReal-time project search and filtering functionality added to the Projects page, enabling users to search by project name with live results and animated transitions as matches are displayed or removed. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Due to my internet issue i submitted 2 same commits at a time. any issue ? |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/app/projects/page.jsx (1)
249-269:⚠️ Potential issue | 🟡 MinorHold the empty state until the last exit completes.
Line 263 is keyed off
filteredProjects.lengthalone, so the fallback can render while the last cards from Lines 249-262 are still fading out. Gate the fallback behind an exit-complete flag to avoid the brief overlap.🩹 Suggested fix
-import { useState, useMemo } from 'react' +import { useEffect, useMemo, useState } from 'react' ... export default function Projects() { const [searchQuery, setSearchQuery] = useState('') + const [showEmpty, setShowEmpty] = useState(false) ... + useEffect(() => { + if (filteredProjects.length > 0) { + setShowEmpty(false) + } + }, [filteredProjects.length]) ... - <AnimatePresence mode="popLayout"> + <AnimatePresence + mode="popLayout" + onExitComplete={() => setShowEmpty(filteredProjects.length === 0)} + > {filteredProjects.map((product) => ( <motion.div layout key={product.slug} initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.9 }} transition={{ duration: 0.3 }} > <CardProduct product={product} /> </motion.div> ))} </AnimatePresence> - {filteredProjects.length === 0 && ( + {showEmpty && ( <div className="col-span-full py-20 text-center"> <p className="text-xl text-zinc-500 dark:text-zinc-400 font-mono"> No projects found matching "{searchQuery}" </p> </div> )}Please verify in the browser by going from a one-result query to a zero-result query; the empty state should appear only after the last card disappears.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/projects/page.jsx` around lines 249 - 269, The empty-state currently renders based only on filteredProjects.length so it can appear while cards are still animating out; add an exit-complete flag and use AnimatePresence's onExitComplete to set it. Concretely: add a local state like exitComplete (true/false), set exitComplete=false whenever filteredProjects becomes non-empty, pass onExitComplete={() => setExitComplete(true)} to the <AnimatePresence> that wraps the motion.div list, and change the fallback render condition to filteredProjects.length === 0 && exitComplete (still display the searchQuery in the message). This ensures the empty state appears only after the last <motion.div> (and <CardProduct>) finish exiting.
🧹 Nitpick comments (1)
src/app/projects/page.jsx (1)
263-267: Reuse one trimmed query across filtering and messaging.Line 266 still echoes the raw
searchQuery, while Line 190 filters against the trimmed value and Line 243 already usestrim(). PulltrimmedQuery/normalizedQueryup once so the filter and both status strings stay aligned.♻️ Suggested cleanup
export default function Projects() { const [searchQuery, setSearchQuery] = useState('') + const trimmedQuery = searchQuery.trim() + const normalizedQuery = trimmedQuery.toLowerCase() const sortedProjects = useMemo(() => { return [...projects].sort((a, b) => (a.name || '').localeCompare(b.name || '', undefined, { sensitivity: 'base' }) ) }, []) const filteredProjects = useMemo(() => { - const query = searchQuery.toLowerCase().trim() - if (!query) return sortedProjects + if (!normalizedQuery) return sortedProjects return sortedProjects.filter((project) => - (project.name || '').toLowerCase().includes(query) + (project.name || '').toLowerCase().includes(normalizedQuery) ) - }, [searchQuery, sortedProjects]) + }, [normalizedQuery, sortedProjects]) ... <p id="project-search-status" className="sr-only" aria-live="polite"> {filteredProjects.length === 0 - ? `No projects found matching ${searchQuery.trim()}.` + ? `No projects found matching ${trimmedQuery}.` : `${filteredProjects.length} project${filteredProjects.length === 1 ? '' : 's'} shown.`} </p> ... <p className="text-xl text-zinc-500 dark:text-zinc-400 font-mono"> - No projects found matching "{searchQuery}" + No projects found matching "{trimmedQuery}" </p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/projects/page.jsx` around lines 263 - 267, The UI uses searchQuery directly in the "No projects found" message while filtering already uses a trimmed/normalized value; extract a single normalized query variable (e.g., trimmedQuery or normalizedQuery) at the top of the component and use it both when computing filteredProjects and when rendering the message so the filter and displayed query match exactly; locate where filteredProjects is derived and replace usages of searchQuery with the single normalized variable, and update the JSX that renders the "No projects found matching" text to use that same normalized variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/app/projects/page.jsx`:
- Around line 249-269: The empty-state currently renders based only on
filteredProjects.length so it can appear while cards are still animating out;
add an exit-complete flag and use AnimatePresence's onExitComplete to set it.
Concretely: add a local state like exitComplete (true/false), set
exitComplete=false whenever filteredProjects becomes non-empty, pass
onExitComplete={() => setExitComplete(true)} to the <AnimatePresence> that wraps
the motion.div list, and change the fallback render condition to
filteredProjects.length === 0 && exitComplete (still display the searchQuery in
the message). This ensures the empty state appears only after the last
<motion.div> (and <CardProduct>) finish exiting.
---
Nitpick comments:
In `@src/app/projects/page.jsx`:
- Around line 263-267: The UI uses searchQuery directly in the "No projects
found" message while filtering already uses a trimmed/normalized value; extract
a single normalized query variable (e.g., trimmedQuery or normalizedQuery) at
the top of the component and use it both when computing filteredProjects and
when rendering the message so the filter and displayed query match exactly;
locate where filteredProjects is derived and replace usages of searchQuery with
the single normalized variable, and update the JSX that renders the "No projects
found matching" text to use that same normalized variable.
📋 Summary
This PR implements a real-time search and filtering feature on the Projects page, addressing the feature request in issue #709. Users can now instantly search across all projects by name, with smooth animated transitions for matching results.
Key Features Implemented:
backdrop-blur, hover shadow, and smooth focus ring transitionsuseStateto track the live search queryuseMemoto sort all projects alphabetically by name (memoized to avoid re-computation)useMemoto filter sorted projects based on the trimmed, case-insensitive search query<AnimatePresence mode="popLayout">for smooth enter/exit animationsopacityandscaletransitions on mount/unmountlayoutprop on motion divs ensures smooth reflow as results changeAddressed Issues:
Closes #709
Recordings:
Aossie.web.mp4
AI Usage Disclosure:
We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.
Check one of the checkboxes below:
I have used the following AI models and tools: TODO
Checklist
Summary by CodeRabbit