doc: refactor testimonials section with dual scrolling marquees#1374
doc: refactor testimonials section with dual scrolling marquees#1374
Conversation
Replace the static grid layout of the quotes/testimonials section with an animated dual-marquee layout inspired by @ncdai's testimonials-marquee component (chanhdai.com/components/testimonials-marquee). - Add react-fast-marquee dependency - Create reusable Marquee wrapper components (Marquee, MarqueeContent, MarqueeFade, MarqueeItem) - Create Testimonial compound component following @ncdai's pattern (Testimonial, TestimonialQuote, TestimonialAuthor, etc.) - Split the 33 quotes into two rows scrolling in opposite directions - Add gradient fade overlays on marquee edges - Marquees pause on hover for readability
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The Tailwind `[&_...]` selectors on the Marquee className prop were not reliably propagating flex height through react-fast-marquee's internal wrapper elements (.rfm-marquee-container, .rfm-marquee, .rfm-child). Move the overrides to a global CSS layer with !important to ensure all internal layers use align-items: stretch and flex display.
There was a problem hiding this comment.
Pull request overview
Refactors the docs landing page testimonials (“QuotesSection”) from a static grid into a dual-row, animated marquee presentation, adding reusable marquee/testimonial building blocks and the required dependency.
Changes:
- Added
react-fast-marqueeand introduced aMarqueewrapper component with fade overlays and pause-on-hover. - Added a
Testimonialcompound component for semantic quote card structure. - Reworked
QuotesSectionto render two opposite-direction marquee rows and added global CSS tweaks for equal-height marquee children.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks react-fast-marquee addition (and incidental lockfile updates). |
| packages/docs/package.json | Adds the new marquee dependency to the docs package. |
| packages/docs/src/components/marquee.tsx | New wrapper components around react-fast-marquee plus fade overlays/items. |
| packages/docs/src/components/testimonial.tsx | New semantic testimonial compound components used by quote cards. |
| packages/docs/src/app/globals.css | Adds react-fast-marquee class overrides to enforce equal-height cards. |
| packages/docs/src/app/(pages)/_landing/quotes/quotes-section.tsx | Replaces the grid layout with two marquee rows and new quote card rendering. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| type QuoteData = { | ||
| text: React.ReactNode | ||
| author: { | ||
| name: string |
There was a problem hiding this comment.
QuoteData and getTextLength use React.ReactNode, but this module doesn’t import React (or ReactNode) anywhere. This will fail type-checking in TS. Import React/ReactNode (e.g., import type React from 'react' or import type { ReactNode } from 'react') or change the types to avoid the React. namespace.
| {firstRow.map((quote, i) => ( | ||
| <MarqueeItem key={i} className="px-2"> | ||
| <QuoteCard quote={quote} /> | ||
| </MarqueeItem> | ||
| ))} | ||
| </MarqueeContent> | ||
| <MarqueeFadeRight /> | ||
| </Marquee> | ||
| <Marquee> | ||
| <MarqueeFadeLeft /> | ||
| <MarqueeContent direction="right"> | ||
| {secondRow.map((quote, i) => ( | ||
| <MarqueeItem key={i} className="px-2"> | ||
| <QuoteCard quote={quote} /> | ||
| </MarqueeItem> |
There was a problem hiding this comment.
Using the array index as the React key here is unstable because the list is derived from a sorted array; any change to quote contents/order will remount many items and can cause marquee animation/state resets. Prefer a stable key such as quote.url (or a derived id from author+url).
| { | ||
| text: 'Big thanks to nuqs for making URL state management actually enjoyable! useState but synced with the URL? Type-safe? Works everywhere (Next.js, Remix, React Router)? Only 6kb? Happy to support such a well-crafted library', | ||
| author: { | ||
| name: 'Ajay Patel', | ||
| avatar: { service: 'x', handle: 'ajaypatel_aj' } | ||
| }, | ||
| url: 'https://x.com/ajaypatel_aj/status/2004082719047778362' | ||
| }, |
There was a problem hiding this comment.
The PR description says “All 33 original quotes are preserved”, but several quotes have been materially transformed from their original rich content (e.g., the Ajay Patel quote previously contained line breaks, a list, and emojis, and is now collapsed into a single plain string). Either keep the original ReactNode content for these entries, or update the PR description to match the new behavior/content normalization.
| "react": "catalog:react19", | ||
| "react-dom": "catalog:react19", | ||
| "react-fast-marquee": "^1.6.5", | ||
| "recharts": "3.8.0", |
There was a problem hiding this comment.
react-fast-marquee@1.6.5 declares peer deps for React/ReactDOM up to ^18, but this repo uses React 19 (catalog:react19). This will produce unmet peer dependency warnings (and may break installs in stricter CI setups) and is a compatibility risk. Please verify React 19 support (or upgrade to a version that declares React 19), otherwise consider an alternative marquee implementation or a pnpm override/packageExtension to relax the peer range intentionally.
| export function MarqueeFade({ className, ...props }: ComponentProps<'div'>) { | ||
| return ( | ||
| <div | ||
| data-slot="marquee-fade" | ||
| className={cn( | ||
| 'pointer-events-none absolute inset-y-0 z-10 w-16 sm:w-24', | ||
| className | ||
| )} | ||
| {...props} | ||
| /> |
There was a problem hiding this comment.
The fade overlay elements are purely decorative. Consider setting aria-hidden="true" on MarqueeFade (or the left/right variants) to ensure they’re ignored by assistive technologies.
- Import ReactNode type instead of using React.ReactNode namespace - Use quote.url as stable React key instead of array index - Add aria-hidden to decorative fade overlays - Restore original rich content (emojis, JSX, line breaks) for quotes
Tasks
Summary
Replaces the static CSS grid layout of the testimonials/quotes section on the landing page with an animated dual-marquee scrolling layout, inspired by @ncdai's testimonials-marquee component.
Changes
react-fast-marqueeto the docs packageMarqueewrapper component (packages/docs/src/components/marquee.tsx): Reusable building blocks (Marquee,MarqueeContent,MarqueeFade,MarqueeItem) wrappingreact-fast-marqueewith gradient fade overlays and pause-on-hover behaviorTestimonialcompound component (packages/docs/src/components/testimonial.tsx): Semantic<figure>/<blockquote>/<figcaption>structure following @ncdai's pattern with avatar ring overlays and flexible layoutQuotesSection: Quotes are split into two rows scrolling in opposite directions (left and right), each with gradient fades on the edges. Quotes are rendered as linked cards pointing to their original tweets/postsVisual behavior
Notes
quote.tsxcomponent is intentionally kept as-is in case it is used elsewhere