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 <cursoragent@cursor.com>
This commit is contained in:
@@ -3,7 +3,8 @@ import dynamic from "next/dynamic";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import messages from "../../../messages/en/index";
|
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 PageHeader from "../../components/type/PageHeader";
|
||||||
import CaseStudy from "../../components/cards/CaseStudy";
|
import CaseStudy from "../../components/cards/CaseStudy";
|
||||||
import UseCasesOrgs from "../../components/sections/UseCasesOrgs";
|
import UseCasesOrgs from "../../components/sections/UseCasesOrgs";
|
||||||
@@ -102,7 +103,10 @@ export default function UseCasesPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const allPosts = getAllBlogPosts();
|
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 slugOrder = allPosts.map((p) => p.slug);
|
||||||
|
|
||||||
const tripleStepSteps = asArray<{ title: string; body: string }>(
|
const tripleStepSteps = asArray<{ title: string; body: string }>(
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ const ContentContainerContainer = memo<ContentContainerProps>(
|
|||||||
|
|
||||||
const titleClasses =
|
const titleClasses =
|
||||||
size === "xs"
|
size === "xs"
|
||||||
? `font-bricolage font-medium text-[18px] leading-[22px] transition-colors ${titleColor}`
|
? `font-bricolage-grotesque 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-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 =
|
const descriptionClasses =
|
||||||
size === "xs"
|
size === "xs"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
|
|||||||
heading,
|
heading,
|
||||||
}) => {
|
}) => {
|
||||||
const messages = useMessages();
|
const messages = useMessages();
|
||||||
|
const isUseCases = variant === "useCases";
|
||||||
// Memoize filtered posts to prevent unnecessary re-computations
|
// Memoize filtered posts to prevent unnecessary re-computations
|
||||||
const filteredPosts = useMemo(
|
const filteredPosts = useMemo(
|
||||||
() => relatedPosts.filter((post) => post.slug !== currentPostSlug),
|
() => relatedPosts.filter((post) => post.slug !== currentPostSlug),
|
||||||
@@ -29,6 +30,8 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
|
|||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const autoScroll = !isUseCases && isMobile;
|
||||||
|
const manualScroll = isUseCases || !isMobile;
|
||||||
|
|
||||||
// Memoize the mouse down handler to prevent unnecessary re-renders
|
// Memoize the mouse down handler to prevent unnecessary re-renders
|
||||||
const handleMouseDown = useCallback(
|
const handleMouseDown = useCallback(
|
||||||
@@ -57,14 +60,15 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
|
|||||||
// Memoize transform style to prevent unnecessary recalculations
|
// Memoize transform style to prevent unnecessary recalculations
|
||||||
const transformStyle = useMemo(
|
const transformStyle = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
transform: isMobile
|
transform:
|
||||||
|
!isUseCases && isMobile
|
||||||
? `translateX(calc(50% - 130px - ${currentIndex * 260}px))`
|
? `translateX(calc(50% - 130px - ${currentIndex * 260}px))`
|
||||||
: "none",
|
: "none",
|
||||||
scrollBehavior: (!isMobile
|
scrollBehavior: (manualScroll
|
||||||
? "smooth"
|
? "smooth"
|
||||||
: "auto") as React.CSSProperties["scrollBehavior"],
|
: "auto") as React.CSSProperties["scrollBehavior"],
|
||||||
}),
|
}),
|
||||||
[isMobile, currentIndex],
|
[isMobile, currentIndex, isUseCases, manualScroll],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Memoize progress bar style calculation
|
// Memoize progress bar style calculation
|
||||||
@@ -80,9 +84,9 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
|
|||||||
[currentIndex, progress],
|
[currentIndex, progress],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-advance every 3 seconds (only on mobile)
|
// Auto-advance every 3 seconds (blog carousel on small viewports only)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (filteredPosts.length <= 1 || !isMobile) return;
|
if (!autoScroll || filteredPosts.length <= 1) return;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setProgress(0);
|
setProgress(0);
|
||||||
@@ -90,11 +94,11 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [filteredPosts.length, isMobile]);
|
}, [autoScroll, filteredPosts.length]);
|
||||||
|
|
||||||
// Progress animation (only on mobile)
|
// Progress animation (only on mobile)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (filteredPosts.length <= 1 || !isMobile) return;
|
if (!autoScroll || filteredPosts.length <= 1) return;
|
||||||
|
|
||||||
const progressInterval = setInterval(() => {
|
const progressInterval = setInterval(() => {
|
||||||
setProgress((prev) => {
|
setProgress((prev) => {
|
||||||
@@ -106,10 +110,9 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
|
|||||||
}, 30); // 30ms intervals for smooth animation
|
}, 30); // 30ms intervals for smooth animation
|
||||||
|
|
||||||
return () => clearInterval(progressInterval);
|
return () => clearInterval(progressInterval);
|
||||||
}, [currentIndex, filteredPosts.length, isMobile]);
|
}, [autoScroll, currentIndex, filteredPosts.length]);
|
||||||
|
|
||||||
const useCasesHeadingLines =
|
const useCasesHeadingLines = isUseCases
|
||||||
variant === "useCases"
|
|
||||||
? messages.pages.useCases.relatedArticles.title
|
? messages.pages.useCases.relatedArticles.title
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
@@ -118,6 +121,7 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
|
|||||||
filteredPosts={filteredPosts}
|
filteredPosts={filteredPosts}
|
||||||
slugOrder={slugOrder}
|
slugOrder={slugOrder}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
|
manualScroll={manualScroll}
|
||||||
transformStyle={transformStyle}
|
transformStyle={transformStyle}
|
||||||
getProgressStyle={getProgressStyle}
|
getProgressStyle={getProgressStyle}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface RelatedArticlesViewProps {
|
|||||||
filteredPosts: BlogPost[];
|
filteredPosts: BlogPost[];
|
||||||
slugOrder: string[];
|
slugOrder: string[];
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
|
manualScroll: boolean;
|
||||||
transformStyle: React.CSSProperties;
|
transformStyle: React.CSSProperties;
|
||||||
getProgressStyle: (_index: number) => React.CSSProperties;
|
getProgressStyle: (_index: number) => React.CSSProperties;
|
||||||
onMouseDown?: (_e: React.MouseEvent<HTMLDivElement>) => void;
|
onMouseDown?: (_e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export function RelatedArticlesView({
|
|||||||
filteredPosts,
|
filteredPosts,
|
||||||
slugOrder,
|
slugOrder,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
manualScroll,
|
||||||
transformStyle,
|
transformStyle,
|
||||||
getProgressStyle,
|
getProgressStyle,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
@@ -76,12 +77,13 @@ export function RelatedArticlesView({
|
|||||||
? "lg:gap-[var(--spacing-scale-012)] lg:pl-[var(--spacing-scale-024)]"
|
? "lg:gap-[var(--spacing-scale-012)] lg:pl-[var(--spacing-scale-024)]"
|
||||||
: ""
|
: ""
|
||||||
} ${
|
} ${
|
||||||
!isMobile
|
manualScroll
|
||||||
? "overflow-x-auto scrollbar-hide cursor-grab active:cursor-grabbing"
|
? "overflow-x-auto scrollbar-hide cursor-grab active:cursor-grabbing"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
|
data-testid="related-articles-track"
|
||||||
style={transformStyle}
|
style={transformStyle}
|
||||||
onMouseDown={!isMobile ? onMouseDown : undefined}
|
onMouseDown={manualScroll ? onMouseDown : undefined}
|
||||||
>
|
>
|
||||||
{filteredPosts.map((relatedPost) => (
|
{filteredPosts.map((relatedPost) => (
|
||||||
<div
|
<div
|
||||||
@@ -100,8 +102,7 @@ export function RelatedArticlesView({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Progress bars - only show on mobile */}
|
{isMobile && !isUseCases && (
|
||||||
{isMobile && (
|
|
||||||
<div className="flex justify-center gap-[var(--measures-spacing-008)] px-[var(--measures-spacing-064)]">
|
<div className="flex justify-center gap-[var(--measures-spacing-008)] px-[var(--measures-spacing-064)]">
|
||||||
{filteredPosts.map((relatedPost, index) => (
|
{filteredPosts.map((relatedPost, index) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -98,11 +98,28 @@ describe("RelatedArticles", () => {
|
|||||||
variant="useCases"
|
variant="useCases"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(
|
const heading = screen.getByRole("heading", {
|
||||||
screen.getByRole("heading", {
|
|
||||||
level: 2,
|
level: 2,
|
||||||
name: /Tools to set your group up for success/,
|
name: /Tools to set your group up for success/,
|
||||||
}),
|
});
|
||||||
).toBeInTheDocument();
|
expect(heading).toBeInTheDocument();
|
||||||
|
expect(heading).toHaveClass(
|
||||||
|
"font-bricolage-grotesque",
|
||||||
|
"lg:text-xx-large-heading",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("useCases variant uses a mouse-draggable track", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockPosts}
|
||||||
|
currentPostSlug="current"
|
||||||
|
variant="useCases"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId("related-articles-track")).toHaveClass(
|
||||||
|
"overflow-x-auto",
|
||||||
|
"cursor-grab",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ describe("ContentContainer", () => {
|
|||||||
const title = screen.getByText("Test Article Title");
|
const title = screen.getByText("Test Article Title");
|
||||||
expect(title).toBeInTheDocument();
|
expect(title).toBeInTheDocument();
|
||||||
expect(title).toHaveClass(
|
expect(title).toHaveClass(
|
||||||
"font-bricolage",
|
"font-bricolage-grotesque",
|
||||||
"font-medium",
|
"font-medium",
|
||||||
"text-xx-small-display",
|
"text-xx-small-display",
|
||||||
"text-[var(--color-content-inverse-brand-royal)]",
|
"text-[var(--color-content-inverse-brand-royal)]",
|
||||||
|
|||||||
Reference in New Issue
Block a user