Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions components/TopRivalriesTicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,32 @@ describe('TopRivalriesTicker', () => {
expect(reactLabelElements.length).toBeGreaterThanOrEqual(2);
});

it('labels the default list as example comparisons', () => {
render(<TopRivalriesTicker />);

expect(screen.getByRole('region', { name: 'Example comparisons' })).toBeInTheDocument();
expect(screen.getByText('Example comparisons')).toBeInTheDocument();
});

it('does not show example label when custom rivalries are provided', () => {
render(
<TopRivalriesTicker
rivalries={[
{
u1: 'alice',
u2: 'bob',
label: 'Live rivalry',
icon: () => null,
color: 'text-orange-500',
},
]}
/>
);

expect(screen.getByRole('region', { name: 'Top rivalries' })).toBeInTheDocument();
expect(screen.queryByText('Example comparisons')).not.toBeInTheDocument();
});

it('navigates to the correct URL on click', () => {
render(<TopRivalriesTicker />);

Expand Down
113 changes: 64 additions & 49 deletions components/TopRivalriesTicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default function TopRivalriesTicker({
rivalries = MOCK_RIVALRIES,
}: TopRivalriesTickerProps = {}) {
const router = useRouter();
const isExampleList = rivalries === MOCK_RIVALRIES;

const handleRivalryClick = (u1: string, u2: string) => {
router.push(`/compare?user1=${encodeURIComponent(u1)}&user2=${encodeURIComponent(u2)}`);
Expand All @@ -76,62 +77,76 @@ export default function TopRivalriesTicker({
const items = rivalries || [];

return (
<div className="relative flex w-full items-center overflow-hidden border-b border-black/5 bg-zinc-50 py-3 dark:border-white/5 dark:bg-[#050505]">
{/* Edge gradients for smooth fade in/out */}
<div className="pointer-events-none absolute bottom-0 left-0 top-0 z-10 w-8 bg-gradient-to-r from-zinc-50 to-transparent sm:w-16 dark:from-[#050505]" />
<div className="pointer-events-none absolute bottom-0 right-0 top-0 z-10 w-8 bg-gradient-to-l from-zinc-50 to-transparent sm:w-16 dark:from-[#050505]" />
<div
role="region"
aria-label={isExampleList ? 'Example comparisons' : 'Top rivalries'}
className="relative flex w-full items-center overflow-hidden border-b border-black/5 bg-zinc-50 py-3 dark:border-white/5 dark:bg-[#050505]"
>
{isExampleList && items.length > 0 && (
<div className="shrink-0 border-r border-black/5 px-3 sm:px-4 dark:border-white/5">
<span className="text-[10px] font-semibold uppercase tracking-widest text-zinc-400 dark:text-zinc-500">
Example comparisons
</span>
</div>
)}

{/* Marquee content */}
<motion.div
className="flex whitespace-nowrap"
animate={{ x: ['0%', '-50%'] }}
transition={{
ease: 'linear',
duration: 30,
repeat: Infinity,
}}
>
{items.length === 0 ? (
<div className="flex w-full items-center justify-center px-6 py-1.5 text-xs font-medium text-zinc-400 dark:text-zinc-500">
No active rivalries
</div>
) : (
[...items, ...items].map((rivalry, idx) => {
const Icon = rivalry.icon;
<div className="relative flex min-w-0 flex-1 items-center overflow-hidden">
{/* Edge gradients for smooth fade in/out */}
<div className="pointer-events-none absolute bottom-0 left-0 top-0 z-10 w-8 bg-gradient-to-r from-zinc-50 to-transparent sm:w-16 dark:from-[#050505]" />
<div className="pointer-events-none absolute bottom-0 right-0 top-0 z-10 w-8 bg-gradient-to-l from-zinc-50 to-transparent sm:w-16 dark:from-[#050505]" />

return (
<div
key={idx}
onClick={() => handleRivalryClick(rivalry.u1, rivalry.u2)}
className="group mx-2 flex cursor-pointer items-center gap-3 rounded-full px-6 py-1.5 transition-colors hover:bg-black/5 dark:hover:bg-white/5"
>
<Icon
size={14}
className={`${rivalry.color} opacity-70 transition-opacity group-hover:opacity-100`}
/>
{/* Marquee content */}
<motion.div
className="flex whitespace-nowrap"
animate={{ x: ['0%', '-50%'] }}
transition={{
ease: 'linear',
duration: 30,
repeat: Infinity,
}}
>
{items.length === 0 ? (
<div className="flex w-full items-center justify-center px-6 py-1.5 text-xs font-medium text-zinc-400 dark:text-zinc-500">
No active rivalries
</div>
) : (
[...items, ...items].map((rivalry, idx) => {
const Icon = rivalry.icon;

<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-zinc-700 transition-colors group-hover:text-black dark:text-zinc-300 dark:group-hover:text-white">
{rivalry.u1}
</span>
return (
<div
key={idx}
onClick={() => handleRivalryClick(rivalry.u1, rivalry.u2)}
className="group mx-2 flex cursor-pointer items-center gap-3 rounded-full px-6 py-1.5 transition-colors hover:bg-black/5 dark:hover:bg-white/5"
>
<Icon
size={14}
className={`${rivalry.color} opacity-70 transition-opacity group-hover:opacity-100`}
/>

<span className="text-[10px] font-black uppercase tracking-widest text-zinc-400 dark:text-zinc-600">
VS
</span>
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-zinc-700 transition-colors group-hover:text-black dark:text-zinc-300 dark:group-hover:text-white">
{rivalry.u1}
</span>

<span className="text-[10px] font-black uppercase tracking-widest text-zinc-400 dark:text-zinc-600">
VS
</span>

<span className="text-sm font-semibold text-zinc-700 transition-colors group-hover:text-black dark:text-zinc-300 dark:group-hover:text-white">
{rivalry.u2}
<span className="text-sm font-semibold text-zinc-700 transition-colors group-hover:text-black dark:text-zinc-300 dark:group-hover:text-white">
{rivalry.u2}
</span>
</div>

<span className="rounded-md border border-black/5 bg-white px-2 py-0.5 text-[10px] font-medium text-zinc-400 dark:border-white/10 dark:bg-black/20 dark:text-zinc-500">
{rivalry.label}
</span>
</div>

<span className="rounded-md border border-black/5 bg-white px-2 py-0.5 text-[10px] font-medium text-zinc-400 dark:border-white/10 dark:bg-black/20 dark:text-zinc-500">
{rivalry.label}
</span>
</div>
);
})
)}
</motion.div>
);
})
)}
</motion.div>
</div>
</div>
);
}
Loading