1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-08-31 21:11:44 +02:00

feat: review carousel

This commit is contained in:
Arik Chakma
2025-06-27 18:24:11 +06:00
parent fc0480d919
commit 8261d19855

View File

@@ -4,8 +4,10 @@ import {
StarIcon, StarIcon,
User2Icon, User2Icon,
} from 'lucide-react'; } from 'lucide-react';
import { useState } from 'react'; import { useLayoutEffect, useMemo, useState } from 'react';
import { markdownToHtml } from '../../lib/markdown'; import { markdownToHtml } from '../../lib/markdown';
import { getTailwindScreenDimension } from '../../lib/is-mobile';
import { cn } from '../../lib/classname';
type Review = { type Review = {
name: string; name: string;
@@ -107,14 +109,23 @@ export function ReviewCarousel() {
}, },
]; ];
const batchSize = 2; const [batchSize, setBatchSize] = useState(3);
const maxBatchNumber = Math.ceil(reviews.length / batchSize); const maxBatchNumber = Math.ceil(reviews.length / batchSize);
const [currentBatchNumber, setCurrentBatchNumber] = useState(0); const [currentBatchNumber, setCurrentBatchNumber] = useState(0);
const currentBatch = reviews.slice( const currentBatch = useMemo(() => {
currentBatchNumber * batchSize, const result = reviews.slice(
(currentBatchNumber + 1) * batchSize, currentBatchNumber * batchSize,
); (currentBatchNumber + 1) * batchSize,
);
if (result.length < batchSize) {
const remaining = batchSize - result.length;
return [...result, ...reviews.slice(0, remaining)];
}
return result;
}, [currentBatchNumber, batchSize]);
const handleNextBatch = () => { const handleNextBatch = () => {
setCurrentBatchNumber((prev) => (prev + 1) % maxBatchNumber); setCurrentBatchNumber((prev) => (prev + 1) % maxBatchNumber);
@@ -126,27 +137,52 @@ export function ReviewCarousel() {
); );
}; };
useLayoutEffect(() => {
const size = getTailwindScreenDimension();
if (size === 'xl' || size === '2xl') {
setBatchSize(3);
} else {
setBatchSize(2);
}
}, []);
return ( return (
<div className="mt-24"> <div className="mt-24">
<h3 className="text-center text-2xl font-medium text-zinc-200 md:text-3xl"> <h3 className="text-center text-2xl font-medium text-zinc-200 md:text-3xl">
What other learners said What other learners said
</h3> </h3>
<div className="mt-10 flex gap-4"> <div className="mt-10 mb-6 flex items-center justify-end gap-2 xl:hidden">
<div className="flex shrink-0 flex-col items-center justify-center"> <NavigateButton
<button onClick={handlePreviousBatch}
icon={<ChevronLeftIcon className="h-4 w-4 stroke-[2.5] text-white" />}
/>
<NavigateButton
onClick={handleNextBatch}
icon={
<ChevronRightIcon className="h-4 w-4 stroke-[2.5] text-white" />
}
/>
</div>
<div className="relative mt-0 flex gap-4 xl:mt-10">
<div className="absolute inset-y-0 -left-2 hidden shrink-0 -translate-x-full flex-col items-center justify-center xl:flex">
<NavigateButton
onClick={handlePreviousBatch} onClick={handlePreviousBatch}
className="flex items-center justify-center rounded-full bg-zinc-800 p-2 hover:bg-zinc-700" icon={
> <ChevronLeftIcon className="h-5 w-5 stroke-[2.5] text-white" />
<ChevronLeftIcon className="h-6 w-6 stroke-[2.5] text-white" /> }
</button> />
</div> </div>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2 xl:grid-cols-3">
{currentBatch.map((review, index) => ( {currentBatch.map((review, index) => (
<div <div
key={index} key={index}
className="review-testimonial relative overflow-hidden rounded-2xl bg-linear-to-br from-yellow-500/10 via-yellow-500/5 to-transparent p-8 backdrop-blur-sm [&_strong]:font-normal [&_strong]:text-yellow-300/70" className={cn(
'review-testimonial relative overflow-hidden rounded-2xl bg-linear-to-br from-yellow-500/10 via-yellow-500/5 to-transparent p-8 backdrop-blur-sm [&_strong]:font-normal [&_strong]:text-yellow-300/70',
index === 2 && batchSize === 3 && 'hidden xl:block',
)}
> >
<div className="absolute -top-8 -right-8 h-32 w-32 rounded-full bg-yellow-500/5" /> <div className="absolute -top-8 -right-8 h-32 w-32 rounded-full bg-yellow-500/5" />
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
@@ -195,15 +231,33 @@ export function ReviewCarousel() {
))} ))}
</div> </div>
<div className="flex shrink-0 flex-col items-center justify-center"> <div className="absolute inset-y-0 -right-2 hidden shrink-0 translate-x-full flex-col items-center justify-center xl:flex">
<button <NavigateButton
onClick={handleNextBatch} onClick={handleNextBatch}
className="flex items-center justify-center rounded-full bg-zinc-800 p-2 hover:bg-zinc-700" icon={
> <ChevronRightIcon className="h-5 w-5 stroke-[2.5] text-white" />
<ChevronRightIcon className="h-6 w-6 stroke-[2.5] text-white" /> }
</button> />
</div> </div>
</div> </div>
</div> </div>
); );
} }
type NavigateButtonProps = {
onClick: () => void;
icon: React.ReactNode;
};
function NavigateButton(props: NavigateButtonProps) {
const { onClick, icon } = props;
return (
<button
onClick={onClick}
className="flex items-center justify-center rounded-full bg-zinc-800 p-2 hover:bg-zinc-700"
>
{icon}
</button>
);
}