diff --git a/src/components/chuni/difficulty-container.tsx b/src/components/chuni/difficulty-container.tsx index 43dcb9f..69f462a 100644 --- a/src/components/chuni/difficulty-container.tsx +++ b/src/components/chuni/difficulty-container.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from 'react'; +import React, { ReactNode } from 'react'; const BACKGROUNDS = [ ['bg-[#02a076]'], @@ -17,10 +17,10 @@ export type ChuniDifficultyContainerProps = { className?: string, difficulty: number, containerClassName?: string -}; +} & React.HTMLAttributes; -export const ChuniDifficultyContainer = ({ children, className, difficulty, containerClassName }: ChuniDifficultyContainerProps) => { - return (
+export const ChuniDifficultyContainer = ({ children, className, difficulty, containerClassName, ...props }: ChuniDifficultyContainerProps) => { + return (
{BACKGROUNDS[difficulty].map((className, i) =>
)}
{children}
) diff --git a/src/components/chuni/music-list.tsx b/src/components/chuni/music-list.tsx index ef35f42..717438f 100644 --- a/src/components/chuni/music-list.tsx +++ b/src/components/chuni/music-list.tsx @@ -4,7 +4,7 @@ import { Filterers, FilterSorter, Sorter } from '@/components/filter-sorter'; import { WindowScroller, Grid, AutoSizer, List } from 'react-virtualized'; import { Button, SelectItem } from '@nextui-org/react'; import { addFavoriteMusic, getMusic, removeFavoriteMusic } from '@/actions/chuni/music'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { worldsEndStars } from '@/helpers/chuni/worlds-end-stars'; import { ChuniDifficultyContainer } from '@/components/chuni/difficulty-container'; import { getJacketUrl } from '@/helpers/assets'; @@ -14,7 +14,7 @@ import { ChuniRating } from '@/components/chuni/rating'; import Link from 'next/link'; import { HeartIcon as OutlineHeartIcon, Squares2X2Icon } from '@heroicons/react/24/outline'; import { HeartIcon as SolidHeartIcon } from '@heroicons/react/24/solid'; -import { Ticker } from '@/components/ticker'; +import { Ticker, TickerHoverProvider } from '@/components/ticker'; import { CHUNI_DIFFICULTIES } from '@/helpers/chuni/difficulties'; import { CHUNI_SCORE_RANKS } from '@/helpers/chuni/score-ranks'; import { CHUNI_LAMPS } from '@/helpers/chuni/lamps'; @@ -93,7 +93,7 @@ const MusicGrid = ({ music, size, setMusicList, fullMusicList }: ChuniMusicListP }, [size]) return ( - {({ height, isScrolling, onChildScroll, scrollTop }) => + {useCallback(({ height, isScrolling, onChildScroll, scrollTop }) => ( {({ width }) => { const itemsPerRow = Math.max(1, Math.floor(width / itemWidth)); @@ -103,14 +103,19 @@ const MusicGrid = ({ music, size, setMusicList, fullMusicList }: ChuniMusicListP onScroll={onChildScroll} scrollTop={scrollTop} ref={listRef} rowRenderer={({ index, key, style }) =>
{music.slice(index * itemsPerRow, (index + 1) * itemsPerRow).map(item =>
- + + {setHover => setHover(true)} + onMouseLeave={() => setHover(false)}>
{item.title {item.rating && !item.worldsEndTag &&
- - {item.rating.slice(0, item.rating.indexOf('.') + 3)} - -
} + + {item.rating.slice(0, item.rating.indexOf('.') + 3)} + +
}
}
{item.scoreRank !== null && @@ -160,11 +165,12 @@ const MusicGrid = ({ music, size, setMusicList, fullMusicList }: ChuniMusicListP {item.title} {item.artist} - + } +
)}
} />) }} -
)} + ), [music, fullMusicList, pendingFavorite, itemWidth])}
); }; diff --git a/src/components/ticker.tsx b/src/components/ticker.tsx index 0fa7f53..576bad9 100644 --- a/src/components/ticker.tsx +++ b/src/components/ticker.tsx @@ -1,4 +1,6 @@ -import { ReactNode } from 'react'; +'use client'; + +import React, { createContext, ReactNode, useContext, useState } from 'react'; import './ticker.scss'; export type TickerProps = { @@ -8,12 +10,37 @@ export type TickerProps = { noDelay?: boolean } +const TickerHoverContext = createContext(null); + +type TickerHoverProviderProps = { + children: (setHover: (hovering: boolean) => void) => ReactNode +}; + +export const TickerHoverProvider = ({ children }: TickerHoverProviderProps) => { + const [hovering, setHovering] = useState(false); + + return + { children(setHovering) } + ; +}; + + export const Ticker = ({ children, hoverOnly, className, noDelay }: TickerProps) => { - const hoverClass = hoverOnly ? '[&:hover_*]:[animation-play-state:running] [&_*]:[animation-play-state:paused]' : '[&:hover_*]:[animation-play-state:paused]'; const outerAnimation = noDelay ? 'animate-[outer-overflow-nodelay_15s_linear_infinite_alternate]' : 'animate-[outer-overflow_15s_linear_infinite_alternate]'; const innerAnimation = noDelay ? 'animate-[inner-overflow-nodelay_15s_linear_infinite_alternate]' : 'animate-[inner-overflow_15s_linear_infinite_alternate]'; + const hoverContext = useContext(TickerHoverContext); + const [textHovering, setTextHovering] = useState(false); + const hovering = (hoverContext !== null && hoverContext) || textHovering; - return (
+ const hoverClass = !hoverOnly && hovering ? '[&:hover_*]:[animation-play-state:paused]' : ''; + + if (hoverOnly && !hovering) + return (
setTextHovering(true)}> + { children } +
); + + return (
setTextHovering(false)}>
{ children }