diff --git a/src/components/features/home/PlaylistCarousel.tsx b/src/components/features/home/PlaylistCarousel.tsx
index c76f2a4..d3363f4 100644
--- a/src/components/features/home/PlaylistCarousel.tsx
+++ b/src/components/features/home/PlaylistCarousel.tsx
@@ -1,31 +1,12 @@
-import { useRef, useState, useEffect } from 'react';
-import { ChevronLeft, ChevronRight } from 'lucide-react';
import type { Playlist } from '../../../types/spotify';
import { HomeSection } from './HomeSection';
import { Skeleton } from '../../ui/skeleton';
import { PlaylistCard } from './PlaylistCard';
+import { ScrollArrow } from '../../ui/ScrollArrow';
+import { useCarouselScroll } from '../../../hooks/useCarouselScroll';
-const CARD_WIDTH = 168;
const SKELETON_KEYS = ['s1', 's2', 's3', 's4', 's5', 's6', 's7', 's8'];
-function ScrollArrow({ dir, onClick }: Readonly<{ dir: 'left' | 'right'; onClick: () => void }>) {
- const isLeft = dir === 'left';
- const Icon = isLeft ? ChevronLeft : ChevronRight;
- return (
-
-
-
- );
-}
-
interface PlaylistCarouselProps {
title: string;
playlists: Playlist[];
@@ -45,31 +26,7 @@ export function PlaylistCarousel({
onPlay,
onPause,
}: Readonly) {
- const scrollRef = useRef(null);
- const [canScrollLeft, setCanScrollLeft] = useState(false);
- const [canScrollRight, setCanScrollRight] = useState(false);
-
- useEffect(() => {
- const el = scrollRef.current;
- if (!el) return;
-
- const updateArrows = () => {
- setCanScrollLeft(el.scrollLeft > 0);
- setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
- };
-
- updateArrows();
- el.addEventListener('scroll', updateArrows);
- globalThis.addEventListener('resize', updateArrows);
- return () => {
- el.removeEventListener('scroll', updateArrows);
- globalThis.removeEventListener('resize', updateArrows);
- };
- }, [playlists]);
-
- const scroll = (dir: 'left' | 'right') => {
- scrollRef.current?.scrollBy({ left: dir === 'left' ? -CARD_WIDTH * 2 : CARD_WIDTH * 2, behavior: 'smooth' });
- };
+ const { scrollRef, canScrollLeft, canScrollRight, scroll } = useCarouselScroll(playlists);
if (isLoading) {
return (
diff --git a/src/components/ui/ScrollArrow.tsx b/src/components/ui/ScrollArrow.tsx
new file mode 100644
index 0000000..fc3783d
--- /dev/null
+++ b/src/components/ui/ScrollArrow.tsx
@@ -0,0 +1,26 @@
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+
+interface ScrollArrowProps {
+ dir: 'left' | 'right';
+ onClick: () => void;
+ fromColor?: string;
+}
+
+export function ScrollArrow({ dir, onClick, fromColor = 'from-surface' }: Readonly) {
+ const isLeft = dir === 'left';
+ const Icon = isLeft ? ChevronLeft : ChevronRight;
+ return (
+
+
+
+ );
+}
diff --git a/src/hooks/useCarouselScroll.ts b/src/hooks/useCarouselScroll.ts
new file mode 100644
index 0000000..a2d1ba9
--- /dev/null
+++ b/src/hooks/useCarouselScroll.ts
@@ -0,0 +1,34 @@
+import { useRef, useState, useEffect } from 'react';
+
+const CARD_WIDTH = 168;
+
+export function useCarouselScroll(dependency: unknown) {
+ const scrollRef = useRef(null);
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
+ const [canScrollRight, setCanScrollRight] = useState(false);
+
+ useEffect(() => {
+ const el = scrollRef.current;
+ if (!el) return;
+
+ const updateArrows = () => {
+ setCanScrollLeft(el.scrollLeft > 0);
+ setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
+ };
+
+ updateArrows();
+ el.addEventListener('scroll', updateArrows);
+ globalThis.addEventListener('resize', updateArrows);
+ return () => {
+ el.removeEventListener('scroll', updateArrows);
+ globalThis.removeEventListener('resize', updateArrows);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [dependency]);
+
+ const scroll = (dir: 'left' | 'right') => {
+ scrollRef.current?.scrollBy({ left: dir === 'left' ? -CARD_WIDTH * 2 : CARD_WIDTH * 2, behavior: 'smooth' });
+ };
+
+ return { scrollRef, canScrollLeft, canScrollRight, scroll };
+}
diff --git a/src/pages/ArtistDetail.tsx b/src/pages/ArtistDetail.tsx
index d7039ee..de6b50c 100644
--- a/src/pages/ArtistDetail.tsx
+++ b/src/pages/ArtistDetail.tsx
@@ -5,6 +5,8 @@ import { usePlaybackControls } from '../hooks/useSpotifyMutations';
import { ErrorState } from '../components/ui/ErrorState';
import { ArtistDetailSkeleton } from '../components/features/artist/ArtistDetailSkeleton';
import { ArtistAlbumCard } from '../components/features/artist/ArtistAlbumCard';
+import { ScrollArrow } from '../components/ui/ScrollArrow';
+import { useCarouselScroll } from '../hooks/useCarouselScroll';
export default function ArtistDetail() {
const { id } = useParams<{ id: string }>();
@@ -12,10 +14,14 @@ export default function ArtistDetail() {
const { data: artist, isLoading: artistLoading, isError: artistError, refetch: refetchArtist } = useArtist(id ?? '');
const { data: albumsData, isLoading: albumsLoading, isError: albumsError, refetch: refetchAlbums } = useArtistAlbums(id ?? '');
const { play } = usePlaybackControls();
+ const { scrollRef, canScrollLeft, canScrollRight, scroll } = useCarouselScroll(albumsData?.items);
const isLoading = artistLoading || albumsLoading;
const isError = artistError || albumsError;
+ const albums = (albumsData?.items ?? []).slice(0, 10);
+ const artistImage = artist?.images?.[0]?.url;
+
if (isLoading) return ;
if (isError || !artist) {
return (
@@ -26,9 +32,6 @@ export default function ArtistDetail() {
);
}
- const albums = (albumsData?.items ?? []).slice(0, 10);
- const artistImage = artist.images?.[0]?.url;
-
return (
{/* Hero section */}
@@ -73,12 +76,19 @@ export default function ArtistDetail() {
Albums
-
-
+
+ {canScrollLeft &&
scroll('left')} fromColor="from-[#121212]" />}
+
{albums.map((album) => (
))}
+ {canScrollRight && scroll('right')} fromColor="from-[#121212]" />}