add touch swipe to browse songs on mobile

Swipe left/right on the carousel viewport to navigate songs.
50px threshold prevents accidental triggers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
main
name 2026-04-12 00:35:28 -07:00
parent a295d64da4
commit c249973cef
1 changed files with 29 additions and 2 deletions

View File

@ -1,4 +1,4 @@
import type { CSSProperties } from 'react'
import { useRef, type CSSProperties } from 'react'
import { SongCard } from './SongCard'
import type { Song } from './types'
@ -20,6 +20,28 @@ function getSignedOffset(currentIndex: number, index: number, length: number): n
}
export function SongCarousel({ songs, currentIndex, onSelectIndex }: SongCarouselProps) {
const touchStartX = useRef(0)
const swiped = useRef(false)
const handleTouchStart = (e: React.TouchEvent) => {
touchStartX.current = e.touches[0].clientX
swiped.current = false
}
const handleTouchMove = (e: React.TouchEvent) => {
if (swiped.current) return
const dx = e.touches[0].clientX - touchStartX.current
const threshold = 50
if (Math.abs(dx) > threshold) {
swiped.current = true
if (dx < 0) {
onSelectIndex(wrapIndex(currentIndex + 1, songs.length))
} else {
onSelectIndex(wrapIndex(currentIndex - 1, songs.length))
}
}
}
const center = songs[currentIndex]
const leftIndex = wrapIndex(currentIndex - 1, songs.length)
const rightIndex = wrapIndex(currentIndex + 1, songs.length)
@ -29,7 +51,12 @@ export function SongCarousel({ songs, currentIndex, onSelectIndex }: SongCarouse
return (
<section className="song-carousel" aria-label="Song carousel">
<div className="song-carousel__viewport" style={{ '--active-color': center.color } as CSSProperties}>
<div
className="song-carousel__viewport"
style={{ '--active-color': center.color } as CSSProperties}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
>
{displayIndexes.map((index) => {
const offset = getSignedOffset(currentIndex, index, songs.length)
return (