From aa4fe4755458adda46500d4f017fef6c75b10909 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:29:03 -0600 Subject: [PATCH] Limit the Use Cases tools row to the three catalog articles, make it mouse-draggable instead of auto-advancing, and put card titles on Bricolage Grotesque so they match the design. Co-authored-by: Cursor --- app/(marketing)/use-cases/page.tsx | 8 +++-- .../ContentContainer.container.tsx | 4 +-- .../RelatedArticles.container.tsx | 32 +++++++++++-------- .../RelatedArticles/RelatedArticles.types.ts | 1 + .../RelatedArticles/RelatedArticles.view.tsx | 9 +++--- tests/components/RelatedArticles.test.tsx | 29 +++++++++++++---- tests/unit/ContentContainer.test.jsx | 2 +- 7 files changed, 56 insertions(+), 29 deletions(-) diff --git a/app/(marketing)/use-cases/page.tsx b/app/(marketing)/use-cases/page.tsx index bdb4ffb..217941f 100644 --- a/app/(marketing)/use-cases/page.tsx +++ b/app/(marketing)/use-cases/page.tsx @@ -3,7 +3,8 @@ import dynamic from "next/dynamic"; import Link from "next/link"; import { Suspense } from "react"; import messages from "../../../messages/en/index"; -import { getAllBlogPosts } from "../../../lib/content"; +import { CONTENT_CATALOG_SLUG_ORDER } from "../../../lib/assetUtils"; +import { getAllBlogPosts, getRelatedBlogPosts } from "../../../lib/content"; import PageHeader from "../../components/type/PageHeader"; import CaseStudy from "../../components/cards/CaseStudy"; import UseCasesOrgs from "../../components/sections/UseCasesOrgs"; @@ -102,7 +103,10 @@ export default function UseCasesPage() { }; const allPosts = getAllBlogPosts(); - const relatedPosts = allPosts.slice(0, 8); + const relatedPosts = getRelatedBlogPosts( + USE_CASES_RELATED_SENTINEL_SLUG, + CONTENT_CATALOG_SLUG_ORDER.slice(0, 3), + ); const slugOrder = allPosts.map((p) => p.slug); const tripleStepSteps = asArray<{ title: string; body: string }>( diff --git a/app/components/content/ContentContainer/ContentContainer.container.tsx b/app/components/content/ContentContainer/ContentContainer.container.tsx index 4acea87..4ee159b 100644 --- a/app/components/content/ContentContainer/ContentContainer.container.tsx +++ b/app/components/content/ContentContainer/ContentContainer.container.tsx @@ -74,8 +74,8 @@ const ContentContainerContainer = memo( const titleClasses = size === "xs" - ? `font-bricolage font-medium text-[18px] leading-[22px] transition-colors ${titleColor}` - : `font-bricolage font-medium text-xx-small-display sm:text-x-small-display md:text-[32px] md:leading-[110%] lg:text-medium-display xl:text-x-large-display transition-colors ${titleColor}`; + ? `font-bricolage-grotesque font-medium text-[18px] leading-[22px] transition-colors ${titleColor}` + : `font-bricolage-grotesque font-medium text-xx-small-display sm:text-x-small-display md:text-[32px] md:leading-[110%] lg:text-medium-display xl:text-x-large-display transition-colors ${titleColor}`; const descriptionClasses = size === "xs" diff --git a/app/components/sections/RelatedArticles/RelatedArticles.container.tsx b/app/components/sections/RelatedArticles/RelatedArticles.container.tsx index 29ab438..d9e46c5 100644 --- a/app/components/sections/RelatedArticles/RelatedArticles.container.tsx +++ b/app/components/sections/RelatedArticles/RelatedArticles.container.tsx @@ -20,6 +20,7 @@ const RelatedArticlesContainer = memo( heading, }) => { const messages = useMessages(); + const isUseCases = variant === "useCases"; // Memoize filtered posts to prevent unnecessary re-computations const filteredPosts = useMemo( () => relatedPosts.filter((post) => post.slug !== currentPostSlug), @@ -29,6 +30,8 @@ const RelatedArticlesContainer = memo( const [currentIndex, setCurrentIndex] = useState(0); const [progress, setProgress] = useState(0); const isMobile = useIsMobile(); + const autoScroll = !isUseCases && isMobile; + const manualScroll = isUseCases || !isMobile; // Memoize the mouse down handler to prevent unnecessary re-renders const handleMouseDown = useCallback( @@ -57,14 +60,15 @@ const RelatedArticlesContainer = memo( // Memoize transform style to prevent unnecessary recalculations const transformStyle = useMemo( () => ({ - transform: isMobile - ? `translateX(calc(50% - 130px - ${currentIndex * 260}px))` - : "none", - scrollBehavior: (!isMobile + transform: + !isUseCases && isMobile + ? `translateX(calc(50% - 130px - ${currentIndex * 260}px))` + : "none", + scrollBehavior: (manualScroll ? "smooth" : "auto") as React.CSSProperties["scrollBehavior"], }), - [isMobile, currentIndex], + [isMobile, currentIndex, isUseCases, manualScroll], ); // Memoize progress bar style calculation @@ -80,9 +84,9 @@ const RelatedArticlesContainer = memo( [currentIndex, progress], ); - // Auto-advance every 3 seconds (only on mobile) + // Auto-advance every 3 seconds (blog carousel on small viewports only) useEffect(() => { - if (filteredPosts.length <= 1 || !isMobile) return; + if (!autoScroll || filteredPosts.length <= 1) return; const interval = setInterval(() => { setProgress(0); @@ -90,11 +94,11 @@ const RelatedArticlesContainer = memo( }, 3000); return () => clearInterval(interval); - }, [filteredPosts.length, isMobile]); + }, [autoScroll, filteredPosts.length]); // Progress animation (only on mobile) useEffect(() => { - if (filteredPosts.length <= 1 || !isMobile) return; + if (!autoScroll || filteredPosts.length <= 1) return; const progressInterval = setInterval(() => { setProgress((prev) => { @@ -106,18 +110,18 @@ const RelatedArticlesContainer = memo( }, 30); // 30ms intervals for smooth animation return () => clearInterval(progressInterval); - }, [currentIndex, filteredPosts.length, isMobile]); + }, [autoScroll, currentIndex, filteredPosts.length]); - const useCasesHeadingLines = - variant === "useCases" - ? messages.pages.useCases.relatedArticles.title - : undefined; + const useCasesHeadingLines = isUseCases + ? messages.pages.useCases.relatedArticles.title + : undefined; return ( React.CSSProperties; onMouseDown?: (_e: React.MouseEvent) => void; diff --git a/app/components/sections/RelatedArticles/RelatedArticles.view.tsx b/app/components/sections/RelatedArticles/RelatedArticles.view.tsx index ae7840d..4ef23f9 100644 --- a/app/components/sections/RelatedArticles/RelatedArticles.view.tsx +++ b/app/components/sections/RelatedArticles/RelatedArticles.view.tsx @@ -5,6 +5,7 @@ export function RelatedArticlesView({ filteredPosts, slugOrder, isMobile, + manualScroll, transformStyle, getProgressStyle, onMouseDown, @@ -76,12 +77,13 @@ export function RelatedArticlesView({ ? "lg:gap-[var(--spacing-scale-012)] lg:pl-[var(--spacing-scale-024)]" : "" } ${ - !isMobile + manualScroll ? "overflow-x-auto scrollbar-hide cursor-grab active:cursor-grabbing" : "" }`} + data-testid="related-articles-track" style={transformStyle} - onMouseDown={!isMobile ? onMouseDown : undefined} + onMouseDown={manualScroll ? onMouseDown : undefined} > {filteredPosts.map((relatedPost) => (
- {/* Progress bars - only show on mobile */} - {isMobile && ( + {isMobile && !isUseCases && (
{filteredPosts.map((relatedPost, index) => (
{ variant="useCases" />, ); - expect( - screen.getByRole("heading", { - level: 2, - name: /Tools to set your group up for success/, - }), - ).toBeInTheDocument(); + const heading = screen.getByRole("heading", { + level: 2, + name: /Tools to set your group up for success/, + }); + expect(heading).toBeInTheDocument(); + expect(heading).toHaveClass( + "font-bricolage-grotesque", + "lg:text-xx-large-heading", + ); + }); + + it("useCases variant uses a mouse-draggable track", () => { + render( + , + ); + expect(screen.getByTestId("related-articles-track")).toHaveClass( + "overflow-x-auto", + "cursor-grab", + ); }); }); diff --git a/tests/unit/ContentContainer.test.jsx b/tests/unit/ContentContainer.test.jsx index bfcb847..d4f87bf 100644 --- a/tests/unit/ContentContainer.test.jsx +++ b/tests/unit/ContentContainer.test.jsx @@ -72,7 +72,7 @@ describe("ContentContainer", () => { const title = screen.getByText("Test Article Title"); expect(title).toBeInTheDocument(); expect(title).toHaveClass( - "font-bricolage", + "font-bricolage-grotesque", "font-medium", "text-xx-small-display", "text-[var(--color-content-inverse-brand-royal)]",