Skip to content

Commit

Permalink
feat(nx-dev): Enhance customer testimonial carousel and icon grid layout
Browse files Browse the repository at this point in the history
  • Loading branch information
ndcunningham committed Jan 8, 2025
1 parent 7c313b3 commit 482178a
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 116 deletions.
2 changes: 1 addition & 1 deletion nx-dev/ui-customers/src/lib/customer-icon-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function getBorderClass(index: number, totalIcons: number, columns = 4) {

const CustomerIconGrid: FC<CustomerIconGridProps> = ({ icons }) => {
return (
<div className="grid grid-cols-2 justify-between px-4 md:grid-cols-4">
<div className="grid grid-cols-2 justify-between md:grid-cols-4">
{icons.map((customerIcon, index) => {
const borderClass = getBorderClass(index, icons.length);

Expand Down
203 changes: 115 additions & 88 deletions nx-dev/ui-customers/src/lib/customer-testimonial-carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
'use client';
import { Fragment, useState, useEffect } from 'react';
import { Dialog, Transition } from '@headlessui/react';
import { PlayIcon } from '@heroicons/react/24/outline';
import {
ChevronLeftIcon,
ChevronRightIcon,
PlayIcon,
} from '@heroicons/react/24/outline';
import Image from 'next/image';
import {
CasewareIcon,
Expand Down Expand Up @@ -56,12 +60,20 @@ const testimonials: Testimonial[] = [
{
title: 'Customer story',
subtitle:
'Scaling 700+ projects: How Nx Enterprise became a no-brainer for Caseware',
'Scaling 700+ projects: How Nx Enterprise became a no-brainer for Caseware.',
metrics: [
{ value: '700+', label: 'Monorepo projects scaled effortlessly' },
{
value: 'Efficiency',
label: 'Unified workflows: frontend to backend',
value: 'Massive scale',
label: '700+ projects, unifying frontends and backends company wide.',
},
{
value: 'Instant impact',
label: 'Trialing Nx Enterprise cut build times immediately.',
},
{
value: 'Actionable insights',
label:
'Nx Cloud’s metrics uncovered inefficiencies across 10+ year old codebase.',
},
],
company: 'Caseware',
Expand All @@ -76,14 +88,24 @@ const testimonials: Testimonial[] = [
{
title: 'Customer story',
subtitle:
'How SiriusXM stays competitive by iterating and getting to market fast',
quote: {
text: 'For me Nx means tooling and efficiency around our software development lifecycle that empowers us to move faster and ship code more reliably.',
author: {
name: 'Justin Schwartzenberger',
role: 'Principal Software Engineer',
'How SiriusXM stays competitive by iterating and getting to market fast.',
metrics: [
{
value: 'Faster releases',
label:
'Nx streamlines feature management, accelerating delivery cycles.',
},
},
{
value: 'Seamless adoption',
label:
'Nx Cloud integration delivers continuous benefits from day one.',
},
{
value: 'Expert guidance',
label:
'Nx Enterprise support provides quick, informed assistance on demand.',
},
],
company: 'SiriusXM',
videoId: 'Q0ky-8oJcro',
thumbnail: '/images/customers/video-story-siriusxm.avif',
Expand All @@ -96,12 +118,20 @@ const testimonials: Testimonial[] = [
{
title: 'Customer story',
subtitle:
'From 5 days to 2 hours: How Payfit improved velocity and offloads complexity',
'From 5 days to 2 hours: How Payfit improved velocity and offloads complexity.',
metrics: [
{ value: '2h vs 5 days', label: 'Faster delivery with Nx and Nx Cloud' },
{
value: 'Offload complexity',
label: 'Nx Agents handle the load, automatically',
value: 'From 5 days → 2 hours',
label: 'Nx & Nx Cloud drastically accelerate feature deployment',
},
{
value: 'Eliminated CI Complexity',
label: 'Nx Cloud offloads CI load balancing headaches.',
},
{
value: 'Faster time-to-market',
label:
'Nx boosts velocity, helping teams deliver faster and more reliably.',
},
],
company: 'Payfit',
Expand All @@ -116,18 +146,23 @@ const testimonials: Testimonial[] = [
{
title: 'Customer story',
subtitle:
'How UKG reduced build times while scaling development across teams',
'How UKG reduced build times while scaling development across teams.',
metrics: [
{ value: 'Web + Mobile', label: 'Maximum code reuse' },
{ value: 'Zero Tuning', label: 'CI that just works' },
],
quote: {
text: "I can't see a future where we don't have Nx.",
author: {
name: 'Sid Govindaraju',
role: 'Engineering Manager',
{
value: 'From 1 day → instant builds',
label: 'Nx Cloud slashed build wait times, enabling dev productivity.',
},
},
{
value: 'Eliminated CI Maintenance',
label:
'Nx Cloud frees teams from managing CI, letting devs focus on code.',
},
{
value: 'Reduced duplication',
label:
'Shared libraries cut down redundant code across mobile and web apps.',
},
],
company: 'UKG',
videoId: 'rSC8wihnfP4',
thumbnail: '/images/customers/video-story-ukg.avif',
Expand All @@ -140,13 +175,21 @@ const testimonials: Testimonial[] = [
{
title: 'Customer story',
subtitle: 'How Broadcom stays efficient and nimble with monorepos',
quote: {
text: "The best developer experience I've had in my career.",
author: {
name: 'Laurent Delamare',
role: 'Frontend Architect',
metrics: [
{
value: '2x faster',
label: 'Nx doubles speed, supporting fast, nimble development.',
},
},
{
value: 'Unmatched DX',
label: 'Nx empowers teams to be more productive than ever.',
},
{
value: 'Always ahead',
label:
'Nx’s rapid feature delivery keeps teams at the cutting edge of development.',
},
],
company: 'Broadcom (VMware)',
videoId: 'RWTgYNKqxNc',
thumbnail: '/images/customers/video-story-broadcom.avif',
Expand All @@ -167,7 +210,6 @@ export function CustomerTestimonialCarousel(): JSX.Element {
useEffect(() => {
let timer: NodeJS.Timeout;

// Clear the current timer and start a new one
if (!isOpen) {
timer = setInterval(() => {
setCurrentIndex((prevIndex) => {
Expand All @@ -177,74 +219,46 @@ export function CustomerTestimonialCarousel(): JSX.Element {
}, slideLogoTimeOut);
}

// Cleanup on unmount or when dependencies change
return () => {
clearInterval(timer);
};
}, [currentIndex, setCurrentIndex, isOpen]);

return (
<div className="w-full">
<div className="mx-auto grid max-w-7xl grid-cols-2 gap-2 px-4 lg:grid-cols-4 lg:gap-4">
<div className="mx-auto grid max-w-7xl grid-cols-2 gap-2 px-4 lg:grid-cols-5 lg:gap-4">
{/* Left side - Quote or Metrics */}
<div className="col-span hidden lg:block">
{currentTestimonial.quote ? (
<figure className="flex h-full flex-col justify-center">
<blockquote className="relative">
{/* Quote mark SVG */}
<svg
className="absolute start-0 top-0 size-24 -translate-x-8 -translate-y-4 transform text-slate-200 dark:text-slate-800"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M7.39762 10.3C7.39762 11.0733 7.14888 11.7 6.6514 12.18C6.15392 12.6333 5.52552 12.86 4.76621 12.86C3.84979 12.86 3.09047 12.5533 2.48825 11.94C1.91222 11.3266 1.62421 10.4467 1.62421 9.29999C1.62421 8.07332 1.96459 6.87332 2.64535 5.69999C3.35231 4.49999 4.33418 3.55332 5.59098 2.85999L6.4943 4.25999C5.81354 4.73999 5.26369 5.27332 4.84476 5.85999C4.45201 6.44666 4.19017 7.12666 4.05926 7.89999C4.29491 7.79332 4.56983 7.73999 4.88403 7.73999C5.61716 7.73999 6.21938 7.97999 6.69067 8.45999C7.16197 8.93999 7.39762 9.55333 7.39762 10.3ZM14.6242 10.3C14.6242 11.0733 14.3755 11.7 13.878 12.18C13.3805 12.6333 12.7521 12.86 11.9928 12.86C11.0764 12.86 10.3171 12.5533 9.71484 11.94C9.13881 11.3266 8.85079 10.4467 8.85079 9.29999C8.85079 8.07332 9.19117 6.87332 9.87194 5.69999C10.5789 4.49999 11.5608 3.55332 12.8176 2.85999L13.7209 4.25999C13.0401 4.73999 12.4903 5.27332 12.0713 5.85999C11.6786 6.44666 11.4168 7.12666 11.2858 7.89999C11.5215 7.79332 11.7964 7.73999 12.1106 7.73999C12.8437 7.73999 13.446 7.97999 13.9173 8.45999C14.3886 8.93999 14.6242 9.55333 14.6242 10.3Z"
fill="currentColor"
/>
</svg>

<div className="relative z-10">
<p className="text-xl font-medium italic text-slate-800 md:text-2xl md:leading-normal xl:text-3xl xl:leading-normal dark:text-neutral-200">
"{currentTestimonial.quote.text}"
</p>
<div className="col-span-2 hidden lg:block">
<div className="flex h-full flex-col justify-center space-y-8">
{currentTestimonial.metrics?.map((metric, index) => (
<div key={index} className="space-y-2">
<div className="border-l-2 border-sky-900/70 pl-4 text-3xl font-bold text-slate-700 dark:border-blue-300/60 dark:text-slate-200">
{metric.value}
</div>

<figcaption className="mt-6 flex items-center gap-4">
<div className="flex-auto">
<div className="text-base font-semibold">
{currentTestimonial.quote.author.name}
</div>
<div className="text-sm text-slate-600 dark:text-slate-500">
{currentTestimonial.quote.author.role},{' '}
{currentTestimonial.company}
</div>
</div>
</figcaption>
</blockquote>
</figure>
) : (
<div className="flex h-full flex-col justify-center space-y-8">
{currentTestimonial.metrics?.map((metric, index) => (
<div key={index} className="space-y-2">
<div className="text-4xl font-bold text-blue-500 lg:text-5xl">
{metric.value}
</div>
<div className="text-base text-slate-500 lg:text-lg dark:text-slate-400">
{metric.label}
</div>
<div className="text-balance pl-[18px] text-lg text-slate-500 dark:text-slate-400">
{metric.label}
</div>
))}
</div>
)}
</div>
))}
</div>
</div>

{/* Right side - Video Card */}
<div className="col-span-2 md:col-span-3">
<div className="flex items-center gap-4">
{/* Prev Button Mobile only */}
<button
disabled={currentIndex === 0}
title={`See ${testimonials[currentIndex - 1]?.company} again!`}
className="flex h-12 w-12 items-center justify-center rounded-full p-2 transition hover:text-slate-950 disabled:pointer-events-none disabled:opacity-0 md:hidden dark:hover:text-white"
onClick={() => {
setCurrentIndex(
(currentIndex - 1 + testimonials.length) % testimonials.length
);
}}
>
<ChevronLeftIcon className="h-8 w-8" />
</button>
<div
className="group relative h-[450px] w-full cursor-pointer self-stretch overflow-hidden rounded-lg xl:shadow-2xl"
onClick={() => setIsOpen(true)}
Expand Down Expand Up @@ -272,6 +286,19 @@ export function CustomerTestimonialCarousel(): JSX.Element {
</button>
</div>
</div>
{/* Next Button - Mobile only */}
<button
className="flex h-12 w-12 items-center justify-center rounded-full p-2 transition hover:text-slate-950 disabled:pointer-events-none disabled:opacity-0 md:hidden dark:hover:text-white"
disabled={currentIndex === testimonials.length - 1}
title={`Next ${testimonials[currentIndex + 1]?.company}!`}
onClick={() => {
setCurrentIndex(
(currentIndex + 1 + testimonials.length) % testimonials.length
);
}}
>
<ChevronRightIcon className="h-8 w-8" />
</button>
</div>

{/* Mobile Navigation display dots */}
Expand All @@ -290,7 +317,7 @@ export function CustomerTestimonialCarousel(): JSX.Element {
</div>
</div>

{/* Carosel Navigation */}
{/* Carosel Navigation - Larger screens */}
<div className="relative mx-auto hidden max-w-7xl grid-cols-6 items-center justify-center px-4 pt-16 md:grid">
{testimonials.map(({ company, logo }, i) => (
<button
Expand All @@ -308,7 +335,7 @@ export function CustomerTestimonialCarousel(): JSX.Element {
className={`${logo.height} ${logo.width} transition-transform duration-300`}
/>

{/* Progress Bar at the Top */}
{/* Progress Bar */}
{i === currentIndex && !isOpen && (
<div className="absolute left-0 top-0 h-[2px] w-full overflow-hidden bg-gray-300/80 transition-all">
<div
Expand Down
2 changes: 1 addition & 1 deletion nx-dev/ui-customers/src/lib/enterprise-customers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export function EnterpriseCustomers(): JSX.Element {
<div className="mx-auto max-w-7xl">
<CustomerIconGrid icons={secondCustomerIcons} />

<div className="grid-cols grid justify-center gap-4 px-4 py-6 md:grid-cols-3">
<div className="grid-cols grid justify-center gap-4 px-2 py-6 md:grid-cols-3">
<DownloadCaseStudy
title="Financial Institution Case Study"
description="$28B Fortune 500 financial institution reduces CI times by 79% with Nx Cloud."
Expand Down
41 changes: 15 additions & 26 deletions nx-dev/ui-enterprise/src/lib/download-case-study.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { ButtonLink } from '@nx/nx-dev/ui-common';
import { ReactElement } from 'react';
import {
ArrowDownTrayIcon,
ChevronRightIcon,
} from '@heroicons/react/24/outline';

export interface DownloadCaseStudyProps {
title: string;
Expand All @@ -23,31 +19,24 @@ export function DownloadCaseStudy({
variant = 'primary',
}: DownloadCaseStudyProps): ReactElement {
return (
<div className="border border-slate-100 bg-white shadow-lg sm:rounded-lg dark:border-slate-800/60 dark:bg-slate-950">
<div className="px-4 py-5 sm:p-6">
<div className="flex h-full flex-col border border-slate-100 bg-white shadow-lg sm:rounded-lg dark:border-slate-800/60 dark:bg-slate-950">
<div className="flex flex-1 flex-col px-4 py-5 sm:p-6">
<h3 className="text-base font-semibold leading-6 text-slate-900 dark:text-slate-100">
{title}
</h3>
<div className="mt-2 sm:flex sm:items-start sm:justify-between">
<div className="max-w-xl text-sm">
<p>{description}</p>
</div>
<div className="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 sm:items-center">
<ButtonLink
href={buttonHref}
title={`${buttonCTA} ${title}`}
variant={variant}
target="_blank"
size="small"
>
{buttonText}{' '}
{buttonCTA === 'Read more' ? (
<ChevronRightIcon className="h-4 w-4" />
) : (
<ArrowDownTrayIcon className="h-4 w-4 translate-x-1" />
)}
</ButtonLink>
</div>
<div className="mt-2 max-w-xl flex-1 text-sm">
<p>{description}</p>
</div>
<div className="mt-auto pt-5">
<ButtonLink
href={buttonHref}
title={`${buttonCTA} ${title}`}
variant={variant}
target={buttonCTA === 'Download' ? '_blank' : undefined}
size="small"
>
{buttonText}
</ButtonLink>
</div>
</div>
</div>
Expand Down

0 comments on commit 482178a

Please sign in to comment.