-
Notifications
You must be signed in to change notification settings - Fork 26
feat(ui): Implement responsive 'Show More/Less' toggle for features s… #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(ui): Implement responsive 'Show More/Less' toggle for features s… #77
Conversation
|
@MohammadRehaanDev is attempting to deploy a commit to the idan lodzki's projects Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughA new custom hook Changes
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
hooks/useIsMobile.ts (1)
3-20: Consider using CSS media queries or matchMedia API for better performance.While the custom hook works, the
window.matchMediaAPI is more performant and better suited for responsive breakpoint detection. It only fires when the media query condition changes, not on every pixel of resize.Here's an alternative implementation:
-const useIsMobile = (breakpoint = 768) => { - const [isMobile, setIsMobile] = useState( - typeof window !== 'undefined' ? window.innerWidth < breakpoint : false - ); +const useIsMobile = (breakpoint = 768) => { + const [isMobile, setIsMobile] = useState(() => { + if (typeof window === 'undefined') return false; + return window.matchMedia(`(max-width: ${breakpoint - 1}px)`).matches; + }); useEffect(() => { - const checkMobile = () => { - setIsMobile(window.innerWidth < breakpoint); - }; + const mediaQuery = window.matchMedia(`(max-width: ${breakpoint - 1}px)`); + + const handleChange = (e: MediaQueryListEvent) => { + setIsMobile(e.matches); + }; - window.addEventListener('resize', checkMobile); - checkMobile(); + mediaQuery.addEventListener('change', handleChange); + // Sync initial state + setIsMobile(mediaQuery.matches); - return () => window.removeEventListener('resize', checkMobile); + return () => mediaQuery.removeEventListener('change', handleChange); }, [breakpoint]); return isMobile; };components/FeaturesSection.tsx (2)
69-71: Consider adding scroll behavior when collapsing.When users click "Show Less", they might be scrolled below the visible features, creating a jarring experience. Consider scrolling to the features section when collapsing.
Update the handleToggle function:
const handleToggle = () => { setIsExpanded(!isExpanded); + + // Scroll to features section when collapsing + if (isExpanded) { + const featuresSection = document.getElementById('features'); + if (featuresSection) { + featuresSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + } };
89-98: Consider adding smooth height transitions.The instant appearance/disappearance of features could be jarring. Adding CSS transitions would improve the user experience.
You could add a CSS transition by:
- Using a CSS class with transition properties
- Or using a library like
framer-motionfor smooth animationsExample with CSS:
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6"> + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 transition-all duration-300">And wrap the feature cards in a container that handles the transition:
{featuresToDisplay.map((feature, index) => ( <div key={index} className="animate-fadeIn"> <FeatureCard icon={feature.icon} title={feature.title} description={feature.description} /> </div> ))}Add to your CSS:
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .animate-fadeIn { animation: fadeIn 0.3s ease-out; }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
components/FeaturesSection.tsx(5 hunks)hooks/useIsMobile.ts(1 hunks)
🔇 Additional comments (3)
components/FeaturesSection.tsx (3)
1-2: Imports look good.The imports are correctly structured with useState for managing expansion state and the custom useIsMobile hook.
14-19: State management and constants are well-structured.The MOBILE_VISIBLE_COUNT constant makes the code maintainable, and the state initialization is appropriate.
64-74: Logic for conditional display is correct.The implementation correctly slices the features array on mobile when not expanded and properly determines when the toggle button should be shown.
| {requiresToggle && ( | ||
| <div className="flex justify-center mt-8 md:hidden"> | ||
| <button | ||
| onClick={handleToggle} | ||
| className="py-2 px-6 text-sm font-semibold rounded-lg bg-primary-600 text-white hover:bg-primary-700 transition-colors duration-300" | ||
| > | ||
| {isExpanded ? 'Show Less Features' : 'Show More Features'} | ||
| </button> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add accessibility attributes to the toggle button.
The toggle button is missing ARIA attributes that are essential for screen reader users to understand the button's purpose and state.
Apply this diff to add proper accessibility:
{requiresToggle && (
<div className="flex justify-center mt-8 md:hidden">
<button
onClick={handleToggle}
+ aria-expanded={isExpanded}
+ aria-controls="features-grid"
+ aria-label={isExpanded ? 'Show fewer features' : 'Show more features'}
className="py-2 px-6 text-sm font-semibold rounded-lg bg-primary-600 text-white hover:bg-primary-700 transition-colors duration-300"
>
{isExpanded ? 'Show Less Features' : 'Show More Features'}
</button>
</div>
)}Also add the corresponding id to the features grid:
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6">
+ <div id="features-grid" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {requiresToggle && ( | |
| <div className="flex justify-center mt-8 md:hidden"> | |
| <button | |
| onClick={handleToggle} | |
| className="py-2 px-6 text-sm font-semibold rounded-lg bg-primary-600 text-white hover:bg-primary-700 transition-colors duration-300" | |
| > | |
| {isExpanded ? 'Show Less Features' : 'Show More Features'} | |
| </button> | |
| </div> | |
| )} | |
| {requiresToggle && ( | |
| <div className="flex justify-center mt-8 md:hidden"> | |
| <button | |
| onClick={handleToggle} | |
| aria-expanded={isExpanded} | |
| aria-controls="features-grid" | |
| aria-label={isExpanded ? 'Show fewer features' : 'Show more features'} | |
| className="py-2 px-6 text-sm font-semibold rounded-lg bg-primary-600 text-white hover:bg-primary-700 transition-colors duration-300" | |
| > | |
| {isExpanded ? 'Show Less Features' : 'Show More Features'} | |
| </button> | |
| </div> | |
| )} |
🤖 Prompt for AI Agents
In components/FeaturesSection.tsx around lines 100 to 109, the toggle button
lacks ARIA attributes and the features grid has no id; add accessibility by
giving the features grid a stable id (e.g., id="features-grid") and update the
button to include aria-expanded={isExpanded} and aria-controls="features-grid"
plus a descriptive aria-label (e.g., aria-label={isExpanded ? 'Collapse
features' : 'Expand features'}) so screen readers know the button's purpose and
state.
| useEffect(() => { | ||
| const checkMobile = () => { | ||
| setIsMobile(window.innerWidth < breakpoint); | ||
| }; | ||
|
|
||
| window.addEventListener('resize', checkMobile); | ||
| checkMobile(); | ||
|
|
||
| return () => window.removeEventListener('resize', checkMobile); | ||
| }, [breakpoint]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add debounce/throttle to the resize event listener.
The resize event can fire 100+ times per second during active window resizing, causing excessive state updates and re-renders. This degrades performance, especially on lower-end mobile devices.
Apply this diff to add a debounced resize handler:
useEffect(() => {
+ let timeoutId: NodeJS.Timeout;
+
const checkMobile = () => {
- setIsMobile(window.innerWidth < breakpoint);
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => {
+ setIsMobile(window.innerWidth < breakpoint);
+ }, 150);
};
window.addEventListener('resize', checkMobile);
- checkMobile();
+ // Initial check without debounce
+ setIsMobile(window.innerWidth < breakpoint);
- return () => window.removeEventListener('resize', checkMobile);
+ return () => {
+ window.removeEventListener('resize', checkMobile);
+ clearTimeout(timeoutId);
+ };
}, [breakpoint]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| const checkMobile = () => { | |
| setIsMobile(window.innerWidth < breakpoint); | |
| }; | |
| window.addEventListener('resize', checkMobile); | |
| checkMobile(); | |
| return () => window.removeEventListener('resize', checkMobile); | |
| }, [breakpoint]); | |
| useEffect(() => { | |
| let timeoutId: NodeJS.Timeout; | |
| const checkMobile = () => { | |
| clearTimeout(timeoutId); | |
| timeoutId = setTimeout(() => { | |
| setIsMobile(window.innerWidth < breakpoint); | |
| }, 150); | |
| }; | |
| window.addEventListener('resize', checkMobile); | |
| // Initial check without debounce | |
| setIsMobile(window.innerWidth < breakpoint); | |
| return () => { | |
| window.removeEventListener('resize', checkMobile); | |
| clearTimeout(timeoutId); | |
| }; | |
| }, [breakpoint]); |
🤖 Prompt for AI Agents
In hooks/useIsMobile.ts around lines 8 to 17, the resize handler calls
setIsMobile on every resize event which can fire many times; debounce (or
throttle) the checkMobile function to limit updates. Create a debounced version
of checkMobile (using a small utility debounce or lodash.debounce) with a
sensible delay (e.g., 100–200ms), attach the debounced handler to the resize
event, call the debounced handler once immediately (or call the original
checkMobile once for initial state), and ensure the cleanup removes the
debounced listener and cancels any pending debounce timer on unmount to prevent
memory leaks.
Issue Reference
Closes #[Issue Number]
What Was Changed
This PR introduces responsive behavior to the Features section by implementing a Show More/Show Less toggle on mobile devices.
The changes include:
Creation of a custom useIsMobile React hook to reliably detect screen width.
Conditional rendering in FeaturesSection.tsx to display only the first 4 feature cards by default on screens under 768px (mobile).
Addition of the toggle button functionality using React useState.
Adjustment of the Tailwind gap utility for a more compact mobile layout (gap-4 md:gap-6).
Why Was It Changed
The original Features section displayed all 8 feature cards stacked vertically on mobile, causing the page to feel overloaded and requiring excessive scrolling, which hurts user experience and engagement.
This change improves mobile UX by showing key highlights first, making the section compact and visually balanced without affecting the original desktop layout.
Summary by CodeRabbit