Conversation
…tency across ExplorePropertiesBanner, ChooseButton, EverythingElseCard, ScalableCard, EverythingElseSection, FeaturesSection, and ScalableSolutionSection.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR enhances responsive design across six UI components and sections through spacing refinements, responsive typography, conditional icon rendering at breakpoints, and introduces mobile/tablet/desktop scroll variants with auto-scrolling behavior in FeaturesSection. Changes
Sequence Diagram(s)sequenceDiagram
participant Observer as IntersectionObserver
participant Component as FeaturesSection
participant DOM as Scroll Container
Note over Component: Component Mounts
Component->>Component: useRef() creates refs for mobile/tablet/desktop
Component->>Component: useEffect sets up Observer
Component->>Observer: Register scroll container(s) for observation
Observer->>Component: onIntersectionObserved (visible)
alt hasAutoScrolled = false
Component->>Component: setHasAutoScrolled(true)
Component->>DOM: auto-scroll to position
Note over Component: Executes once on first view
else hasAutoScrolled = true
Component->>Component: Skip auto-scroll (already done)
end
Component->>DOM: User clicks PaginationButton (prev/next)
Component->>Component: handleMobilePrevClick/Next triggered
Component->>DOM: Scroll to target position (immediate)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20–30 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ 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: 1
🧹 Nitpick comments (5)
src/pages/home/sections/ScalableSolutionSection.tsx (1)
7-16: Check 2‑column grid density on very small screensUsing
grid-cols-2plus tight gaps even at the smallest breakpoint may make eachScalableCardfeel cramped on narrow phones. It might be worth verifying on a 320–360px viewport and, if it feels too tight, reverting togrid-cols-1 sm:grid-cols-2to keep readability high on the smallest screens.src/components/common/buttons/ChooseButton.tsx (1)
11-12: DRY up icon markup and mark decorative icon as hidden from ATThe
Sendicon markup is duplicated for the Link and button paths, and it looks purely decorative.You can factor it out and mark it as decorative to improve readability and accessibility:
const baseClasses = "w-full flex items-center justify-center gap-1 sm:gap-2 py-2 sm:py-3 rounded-full bg-[var(--color-card-1)] text-black text-xs sm:text-sm font-medium shadow-md hover:shadow-lg transition-all duration-200"; const iconClasses = "inline-flex items-center justify-center w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-[var(--color-primary)] text-white"; export default function ChooseButton({ text = "Choose", onClick, to, className = "" }: ChooseButtonProps) { const classes = `${baseClasses} ${className}`; + + const icon = ( + <span className={iconClasses} aria-hidden="true"> + <Send size={14} className="sm:hidden" /> + <Send size={16} className="hidden sm:inline" /> + </span> + ); if (to) { return ( <Link to={to} className={classes} onClick={onClick} > - {text} - <span className={iconClasses}> - <Send size={14} className="sm:hidden" /> - <Send size={16} className="hidden sm:inline" /> - </span> + {text} + {icon} </Link> ); } return ( @@ -38,9 +44,8 @@ export default function ChooseButton({ text = "Choose", onClick, to, className = onClick={onClick} className={classes} > {text} - <span className={iconClasses}> - <Send size={14} className="sm:hidden" /> - <Send size={16} className="hidden sm:inline" /> - </span> + {icon} </button> ); }Also applies to: 26-27, 40-41
src/components/ExplorePropertiesBanner.tsx (1)
5-8: Treat abstract side images as decorative for accessibilityThe left/right abstract images appear purely decorative, but they currently have meaningful
alttext, which will be announced by screen readers. Consider making them decorative instead:- <img - src="https://res.cloudinary.com/dxwspucxw/image/upload/v1761905090/abstractleft_yhtzva.png" - alt="Abstract Left" + <img + src="https://res.cloudinary.com/dxwspucxw/image/upload/v1761905090/abstractleft_yhtzva.png" + alt="" + aria-hidden="true" @@ - <img + <img src="https://res.cloudinary.com/dxwspucxw/image/upload/v1761905090/abstractright_qhdpgm.png" - alt="Abstract Right" + alt="" + aria-hidden="true"This keeps the visuals while avoiding extra noise for assistive technologies.
Also applies to: 15-22
src/pages/home/sections/EverythingElseSection.tsx (1)
8-27: Verify 2‑column card grid usability on narrow viewportsThe right-hand cards use
grid grid-cols-2even at the smallest breakpoint with reduced gaps. Combined withEverythingElseCard’s compact typography, this is probably fine, but on ~320px devices each card will be quite narrow.It’s worth checking on a small handset; if it feels cramped, consider
grid-cols-1 sm:grid-cols-2while keeping the tighter gaps.src/pages/home/sections/FeaturesSection.tsx (1)
14-18: Hoist static feature config and simplifyuseEffectdependenciesThe scroll logic and auto-scroll behavior look good, but
features/duplicatedFeaturesare static config and get recreated on every render, whileuseEffectdepends onfeatures.lengthyet closes overfeaturesitself.To keep things simple and future‑proof if
featuresever becomes dynamic, you could:
- Hoist
features(andduplicatedFeatures) to module scope so they’re created once.- Drop
features.lengthfrom the dependency array (or replace it with an empty array) since the data then truly is static.For example:
-import React, { useRef, useEffect } from "react"; +import React, { useRef, useEffect } from "react"; @@ -const FeaturesSection: React.FC = () => { - const { scrollContainerRef, handlePrevClick, handleNextClick } = - useHorizontalInfiniteScroll({ - scrollAmount: 430, - }); - - const mobileScrollRef = useRef<HTMLDivElement>(null); - const tabletScrollRef = useRef<HTMLDivElement>(null); - const hasAutoScrolledMobile = useRef(false); - const hasAutoScrolledTablet = useRef(false); - - const features = [ +const FEATURES = [ @@ - ]; +]; + +const DUPLICATED_FEATURES = [...FEATURES, ...FEATURES, ...FEATURES]; + +const FeaturesSection: React.FC = () => { + const { scrollContainerRef, handlePrevClick, handleNextClick } = + useHorizontalInfiniteScroll({ + scrollAmount: 430, + }); + + const mobileScrollRef = useRef<HTMLDivElement>(null); + const tabletScrollRef = useRef<HTMLDivElement>(null); + const hasAutoScrolledMobile = useRef(false); + const hasAutoScrolledTablet = useRef(false); + + const features = FEATURES; @@ - useEffect(() => { + useEffect(() => { @@ - }, [features.length]); + }, []); @@ - // Duplicate features for infinite scroll - const duplicatedFeatures = [...features, ...features, ...features]; + // Duplicate features for infinite scroll + const duplicatedFeatures = DUPLICATED_FEATURES;Functionally this keeps behavior the same, while reducing work per render and making the effect dependencies more obviously correct.
Also applies to: 61-115, 116-180, 182-184
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/components/ExplorePropertiesBanner.tsx(3 hunks)src/components/common/buttons/ChooseButton.tsx(3 hunks)src/components/common/cards/EverythingElseCard.tsx(1 hunks)src/components/common/cards/ScalableCard.tsx(2 hunks)src/pages/home/sections/EverythingElseSection.tsx(1 hunks)src/pages/home/sections/FeaturesSection.tsx(4 hunks)src/pages/home/sections/ScalableSolutionSection.tsx(1 hunks)
🔇 Additional comments (1)
src/components/common/cards/ScalableCard.tsx (1)
13-26: Responsive spacing and typography look solidThe tighter padding, responsive image height, and scaled-down mobile typography read well and keep the card compact on small screens while preserving hierarchy at
sm+. No functional issues spotted.
| <div className="flex flex-col border rounded-lg p-3 sm:p-8 w-full max-w-[320px] shadow-sm hover:shadow-md transition-all duration-200 border-[var(--color-primary)]/20 hover:bg-[#0CA474] group cursor-pointer"> | ||
| <div className="flex justify-between items-start mb-2 sm:mb-3"> | ||
| <span className="text-xl sm:text-3xl font-semibold text-[var(--color-primary)] group-hover:text-white transition-colors duration-200">{number}</span> | ||
| <div className="text-[var(--color-primary)] group-hover:text-white transition-colors duration-200"> | ||
| {React.isValidElement(icon) | ||
| ? React.cloneElement(icon as React.ReactElement<any>, { | ||
| size: 20, | ||
| className: "sm:hidden" | ||
| } as any) | ||
| : icon} | ||
| {React.isValidElement(icon) | ||
| ? React.cloneElement(icon as React.ReactElement<any>, { | ||
| size: 28, | ||
| className: "hidden sm:block" | ||
| } as any) | ||
| : null} | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Preserve icon props when cloning for different breakpoints
The new breakpoint-based cloning overwrites className (and potentially other props) on icon. If callers pass a custom icon with its own classes, those will be lost.
You can preserve the original className and avoid duplicating the validity check like this:
- <div className="flex justify-between items-start mb-2 sm:mb-3">
- <span className="text-xl sm:text-3xl font-semibold text-[var(--color-primary)] group-hover:text-white transition-colors duration-200">{number}</span>
- <div className="text-[var(--color-primary)] group-hover:text-white transition-colors duration-200">
- {React.isValidElement(icon)
- ? React.cloneElement(icon as React.ReactElement<any>, {
- size: 20,
- className: "sm:hidden"
- } as any)
- : icon}
- {React.isValidElement(icon)
- ? React.cloneElement(icon as React.ReactElement<any>, {
- size: 28,
- className: "hidden sm:block"
- } as any)
- : null}
- </div>
- </div>
+ <div className="flex justify-between items-start mb-2 sm:mb-3">
+ <span className="text-xl sm:text-3xl font-semibold text-[var(--color-primary)] group-hover:text-white transition-colors duration-200">
+ {number}
+ </span>
+ <div className="text-[var(--color-primary)] group-hover:text-white transition-colors duration-200">
+ {React.isValidElement(icon) ? (
+ <>
+ {(() => {
+ const iconElement = icon as React.ReactElement<any>;
+ const baseClassName = iconElement.props.className ?? "";
+ return React.cloneElement(iconElement, {
+ size: 20,
+ className: `${baseClassName} sm:hidden`.trim(),
+ } as any);
+ })()}
+ {(() => {
+ const iconElement = icon as React.ReactElement<any>;
+ const baseClassName = iconElement.props.className ?? "";
+ return React.cloneElement(iconElement, {
+ size: 28,
+ className: `${baseClassName} hidden sm:block`.trim(),
+ } as any);
+ })()}
+ </>
+ ) : (
+ icon
+ )}
+ </div>
+ </div>This keeps any styling the caller applied to the icon while still giving you responsive sizing.
🤖 Prompt for AI Agents
In src/components/common/cards/EverythingElseCard.tsx around lines 18 to 35, the
cloning of the icon for different breakpoints overwrites the icon's existing
props (notably className) and repeats the React.isValidElement check; change
this to check isValidElement once, capture the original element and its props,
and when cloning spread the original props first then override size and merge
className (e.g. concatenate original className with "sm:hidden" / "hidden
sm:block") so caller-supplied classes are preserved and other props remain
intact.
Summary by CodeRabbit
Release Notes
New Features
Style