Replace Carousel with ScrollArea, remove embla-carousel dependency

- Delete carousel.tsx and replace all Carousel/CarouselContent/CarouselItem
  usage across title rows, cast carousel, continue watching, and explore
  sections with ScrollArea + scrollFade horizontal scroll
- Update ScrollArea to accept a `scrollFade` prop that renders a
  gradient edge overlay instead of the old per-site absolute div hack
- Remove "use client" directive from title-row and cast-carousel now
  that they have no client-side hooks
- Delete unused resizable.tsx; inline TypeBadge icon into TitleHero
  and delete the standalone type-badge.tsx file
- Add TitleTheme component for per-title accent color CSS injection
- Swap IconSparkles → IconThumbUp in recommendations section and grid
- Simplify ambient glow to a single size (remove mobile breakpoint override)
This commit is contained in:
2026-03-08 09:53:12 -04:00
parent 7ed172d675
commit 02466e94f9
20 changed files with 207 additions and 576 deletions
@@ -4,11 +4,7 @@ import { useState, useTransition } from "react";
import { TitleCardSkeleton } from "@/components/skeletons";
import { TitleCard } from "@/components/title-card";
import { Button } from "@/components/ui/button";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import { ScrollArea } from "@/components/ui/scroll-area";
import { discoverByGenre } from "@/lib/actions/explore";
import {
fetchEpisodeProgress,
@@ -105,8 +101,8 @@ export function FilterableTitleRow({
</div>
{/* Genre chips */}
<div className="relative">
<div className="no-scrollbar -mx-4 flex gap-2 overflow-x-auto px-4 pb-1 sm:mx-0 sm:px-0">
<ScrollArea scrollFade>
<div className="flex gap-2">
{genres.map((genre) => (
<Button
key={genre.id}
@@ -123,8 +119,7 @@ export function FilterableTitleRow({
</Button>
))}
</div>
<div className="pointer-events-none absolute top-0 right-0 h-full w-8 bg-gradient-to-l from-background to-transparent sm:hidden" />
</div>
</ScrollArea>
{/* Loading skeleton */}
{isPending && (
@@ -148,45 +143,40 @@ export function FilterableTitleRow({
</p>
)}
{/* Carousel */}
{/* Title cards */}
{!isPending && items.length > 0 && (
<div key={selectedGenre ?? "default"}>
<Carousel
opts={{
align: "start",
dragFree: true,
containScroll: "trimSnaps",
}}
className="carousel-tilt -mx-6 sm:-mx-2"
>
<CarouselContent className="px-6 sm:px-2">
{items.slice(0, 20).map((item, i) => (
<CarouselItem
key={`${item.type}-${item.tmdbId}`}
className="w-[140px] shrink-0 basis-auto pl-4 sm:w-[160px]"
<ScrollArea
key={selectedGenre ?? "default"}
scrollFade
className="-mx-6 sm:-mx-2"
>
<div className="flex gap-4 px-6 py-2 sm:px-2">
{items.slice(0, 20).map((item, i) => (
<div
key={`${item.type}-${item.tmdbId}`}
className="w-[140px] shrink-0 sm:w-[160px]"
>
<div
className="animate-stagger-item"
style={{ "--stagger-index": i } as React.CSSProperties}
>
<div
className="animate-stagger-item"
style={{ "--stagger-index": i } as React.CSSProperties}
>
<TitleCard
tmdbId={item.tmdbId}
type={item.type}
title={item.title}
posterPath={item.posterPath}
releaseDate={item.releaseDate}
voteAverage={item.voteAverage}
userStatus={userStatuses[`${item.tmdbId}-${item.type}`]}
episodeProgress={
episodeProgress[`${item.tmdbId}-${item.type}`]
}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</div>
<TitleCard
tmdbId={item.tmdbId}
type={item.type}
title={item.title}
posterPath={item.posterPath}
releaseDate={item.releaseDate}
voteAverage={item.voteAverage}
userStatus={userStatuses[`${item.tmdbId}-${item.type}`]}
episodeProgress={
episodeProgress[`${item.tmdbId}-${item.type}`]
}
/>
</div>
</div>
))}
</div>
</ScrollArea>
)}
</section>
);
+8 -17
View File
@@ -1,11 +1,5 @@
"use client";
import { TitleCard } from "@/components/title-card";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import { ScrollArea } from "@/components/ui/scroll-area";
interface TitleRowItem {
tmdbId: number;
@@ -41,15 +35,12 @@ export function TitleRow({
{heading}
</h2>
</div>
<Carousel
opts={{ align: "start", dragFree: true, containScroll: "trimSnaps" }}
className="carousel-tilt -mx-6 sm:-mx-2"
>
<CarouselContent className="px-6 sm:px-2">
<ScrollArea scrollFade className="-mx-6 sm:-mx-2">
<div className="flex gap-4 px-6 py-2 sm:px-2">
{items.map((item, i) => (
<CarouselItem
<div
key={`${item.type}-${item.tmdbId}`}
className="w-[140px] shrink-0 basis-auto pl-4 sm:w-[160px]"
className="w-[140px] shrink-0 sm:w-[160px]"
>
<div
className="animate-stagger-item"
@@ -68,10 +59,10 @@ export function TitleRow({
}
/>
</div>
</CarouselItem>
</div>
))}
</CarouselContent>
</Carousel>
</div>
</ScrollArea>
</section>
);
}