Skip to content

feat: add real-time project search and filtering functionality#710

Open
ankitkr104 wants to merge 3 commits intoAOSSIE-Org:mainfrom
ankitkr104:feature/real-time-project-search
Open

feat: add real-time project search and filtering functionality#710
ankitkr104 wants to merge 3 commits intoAOSSIE-Org:mainfrom
ankitkr104:feature/real-time-project-search

Conversation

@ankitkr104
Copy link
Copy Markdown
Contributor

@ankitkr104 ankitkr104 commented Mar 27, 2026

📋 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:

  1. Search Input UI
    • Added a centered, styled search bar below the page heading
    • FontAwesome search icon inside the input (dynamically changes color on focus)
    • Glassmorphism styling with backdrop-blur, hover shadow, and smooth focus ring transitions
    • Fully responsive — works across all screen sizes
    • Supports dark mode via Tailwind dark variants
  2. Real-time Filtering Logic
    • Used useState to track the live search query
    • Used useMemo to sort all projects alphabetically by name (memoized to avoid re-computation)
    • Used a second useMemo to filter sorted projects based on the trimmed, case-insensitive search query
    • Filtering is instantaneous — no debounce needed at this data scale
  3. Animated Results with Framer Motion
    • Wrapped results in <AnimatePresence mode="popLayout"> for smooth enter/exit animations
    • Each project card animates with opacity and scale transitions on mount/unmount
    • layout prop on motion divs ensures smooth reflow as results change
  4. Empty State Handling
    • When no projects match the query, a friendly "No projects found matching ..." message is shown
    • Prevents a blank/broken-looking grid

Addressed 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:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: TODO

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

  • New Features
    • Added a search field to filter projects by name in real time (case-insensitive, trimmed substring match).
    • Implemented smooth enter/exit/layout animations for projects in the grid.
    • Added an accessible ARIA live status for search updates.
    • Added an explicit empty-state message when no projects match the search.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

Added 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

Cohort / File(s) Summary
Projects page (search + animations)
src/app/projects/page.jsx
Added searchQuery state, useMemo for sortedProjects and filteredProjects (trimmed, case-insensitive substring match). Replaced rendering with filteredProjects, added search input (with faSearch and ARIA live status), integrated AnimatePresence + motion.div for animated list enter/exit/layout, and added an explicit empty-state when no results match.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibble letters as you type away,
Projects hop closer with each bright relay.
Motion and search make the list feel spry,
Find what you seek with a rabbit’s keen eye. ✨

🚥 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 reflects the main feature added: real-time project search and filtering functionality implemented in the Projects page.
Linked Issues check ✅ Passed All requirements from issue #709 are met: search bar UI added with real-time filtering, case-insensitive name matching, instant filtering as user types, and responsive design.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the real-time search feature; no unrelated modifications to other functionality or areas detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown

@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: 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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c178762-7ff8-4d3b-9d18-1929cacb47e3

📥 Commits

Reviewing files that changed from the base of the PR and between bb70285 and edb78f0.

📒 Files selected for processing (1)
  • src/app/projects/page.jsx

Comment on lines +239 to +259
<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 &quot;{searchQuery}&quot;
</p>
</div>
)}
Copy link
Copy Markdown

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, 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"
fi

Repository: 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:


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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Real-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

Cohort / File(s) Summary
Real-time Search Implementation
src/app/projects/page.jsx
Added useState for search query state and useMemo for memoized sortedProjects and filteredProjects derivations. Implemented case-insensitive substring matching filter. Integrated AnimatePresence and motion.div for enter/exit animations. Added search input field with faSearch icon. Included empty-state message when no results match.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With whiskers twitching, I hop through your list,
A search bar appears—oh what joy to assist!
Each letter you type makes projects align,
Animated magic makes finding feel fine! ✨

🚥 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 summarizes the main change: adding real-time project search and filtering functionality, which matches the core objective of the changeset.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #709: real-time search, case-insensitive filtering by project name, responsive search input, animations using Framer Motion, and empty-state messaging.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the search and filtering feature. State management, memoization, animations, and UI components are all within scope of 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

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.

@ankitkr104
Copy link
Copy Markdown
Contributor Author

Due to my internet issue i submitted 2 same commits at a time. any issue ?

Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
src/app/projects/page.jsx (1)

249-269: ⚠️ Potential issue | 🟡 Minor

Hold the empty state until the last exit completes.

Line 263 is keyed off filteredProjects.length alone, 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 &quot;{searchQuery}&quot;
                 </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 uses trim(). Pull trimmedQuery / normalizedQuery up 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 &quot;{searchQuery}&quot;
+                  No projects found matching &quot;{trimmedQuery}&quot;
                 </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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8730fa3b-0636-4b1d-bcc1-1df3f4077f6b

📥 Commits

Reviewing files that changed from the base of the PR and between edb78f0 and 5bf05a3.

📒 Files selected for processing (1)
  • src/app/projects/page.jsx

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Real-time Project Search and Filtering

1 participant