mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 13:48:02 +00:00
feat: updates
This commit is contained in:
parent
d09c5fd171
commit
133881d287
28 changed files with 389 additions and 115 deletions
|
|
@ -2,41 +2,79 @@ import { resizeImage } from '@/web/utils/common'
|
|||
import { css, cx } from '@emotion/css'
|
||||
import Image from './Image'
|
||||
|
||||
const ArtistRow = ({
|
||||
artists,
|
||||
title,
|
||||
className,
|
||||
}: {
|
||||
artists: Artist[] | undefined
|
||||
title?: string
|
||||
className?: string
|
||||
}) => {
|
||||
const Artist = ({ artist }: { artist: Artist }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h4 className='mb-6 text-14 font-bold uppercase dark:text-neutral-300'>
|
||||
{title}
|
||||
</h4>
|
||||
)}
|
||||
|
||||
{/* Artists */}
|
||||
<div className='grid grid-cols-5 gap-10'>
|
||||
{artists?.map(artist => (
|
||||
<div key={artist.id} className='text-center'>
|
||||
<Image
|
||||
alt={artist.name}
|
||||
src={resizeImage(artist.img1v1Url, 'md')}
|
||||
className='aspect-square rounded-full'
|
||||
/>
|
||||
<div className='line-clamp-1 mt-2.5 text-14 font-bold text-neutral-700 dark:text-neutral-600'>
|
||||
{artist.name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className='text-center'>
|
||||
<Image
|
||||
alt={artist.name}
|
||||
src={resizeImage(artist.img1v1Url, 'md')}
|
||||
className={cx(
|
||||
'aspect-square rounded-full',
|
||||
css`
|
||||
min-width: 96px;
|
||||
min-height: 96px;
|
||||
`
|
||||
)}
|
||||
/>
|
||||
<div className='line-clamp-1 mt-2.5 text-12 font-medium text-neutral-700 dark:text-neutral-600 lg:text-14 lg:font-bold'>
|
||||
{artist.name}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ArtistRow = ({
|
||||
artists,
|
||||
title,
|
||||
className,
|
||||
placeholderRow,
|
||||
}: {
|
||||
artists: Artist[] | undefined
|
||||
title?: string
|
||||
className?: string
|
||||
placeholderRow?: number
|
||||
}) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h4 className='mb-6 text-12 font-medium uppercase dark:text-neutral-300 lg:text-14 lg:font-bold'>
|
||||
{title}
|
||||
</h4>
|
||||
)}
|
||||
|
||||
{/* Artists */}
|
||||
{artists && (
|
||||
<div className='no-scrollbar -ml-2.5 flex w-screen overflow-x-scroll lg:ml-auto lg:grid lg:w-auto lg:grid-cols-5 lg:gap-10'>
|
||||
{artists.map(artist => (
|
||||
<div
|
||||
className='mr-5 first-of-type:ml-2.5 last-of-type:mr-2.5 lg:mr-0'
|
||||
key={artist.id}
|
||||
>
|
||||
<Artist artist={artist} key={artist.id} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Placeholder */}
|
||||
{placeholderRow && !artists && (
|
||||
<div className='no-scrollbar -ml-2.5 flex w-screen overflow-x-scroll lg:ml-auto lg:grid lg:w-auto lg:grid-cols-5 lg:gap-10'>
|
||||
{[...new Array(placeholderRow * 5).keys()].map(i => (
|
||||
<div
|
||||
className='mr-5 first-of-type:ml-2.5 last-of-type:mr-2.5 lg:mr-0'
|
||||
key={i}
|
||||
>
|
||||
<div className='aspect-square w-full rounded-full bg-white dark:bg-neutral-800' />
|
||||
<div className='mt-2.5 text-14 font-bold text-transparent'>
|
||||
PLACE
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArtistRow
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const CoverRow = ({
|
|||
)}
|
||||
|
||||
{/* Items */}
|
||||
<div className='grid grid-cols-3 gap-10 xl:grid-cols-4 2xl:grid-cols-5'>
|
||||
<div className='grid grid-cols-3 gap-4 lg:gap-10 xl:grid-cols-4 2xl:grid-cols-5'>
|
||||
{albums?.map(album => (
|
||||
<Image
|
||||
onClick={() => goTo(album.id)}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ const CoverWall = ({
|
|||
const breakpoint = useBreakpoint()
|
||||
const sizes = {
|
||||
small: {
|
||||
sm: 'xs',
|
||||
md: 'xs',
|
||||
sm: 'sm',
|
||||
md: 'sm',
|
||||
lg: 'sm',
|
||||
xl: 'sm',
|
||||
'2xl': 'md',
|
||||
},
|
||||
large: {
|
||||
sm: 'xs',
|
||||
sm: 'sm',
|
||||
md: 'sm',
|
||||
lg: 'md',
|
||||
xl: 'md',
|
||||
|
|
@ -32,7 +32,7 @@ const CoverWall = ({
|
|||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'grid w-full grid-flow-row-dense grid-cols-8',
|
||||
'grid w-full grid-flow-row-dense grid-cols-4 lg:grid-cols-8',
|
||||
css`
|
||||
gap: 13px;
|
||||
`
|
||||
|
|
@ -48,7 +48,7 @@ const CoverWall = ({
|
|||
alt='Album Cover'
|
||||
placeholder={null}
|
||||
className={cx(
|
||||
'aspect-square h-full w-full rounded-24',
|
||||
'aspect-square h-full w-full rounded-20 lg:rounded-24',
|
||||
album.large && 'col-span-2 row-span-2'
|
||||
)}
|
||||
onClick={() => navigate(`/album/${album.id}`)}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import useBreakpoint from '@/web/hooks/useBreakpoint'
|
||||
import { ReactQueryDevtools } from 'react-query/devtools'
|
||||
|
||||
const Devtool = () => {
|
||||
const breakpoint = useBreakpoint()
|
||||
const isMobile = ['sm', 'md'].includes(breakpoint)
|
||||
return (
|
||||
<ReactQueryDevtools
|
||||
initialIsOpen={false}
|
||||
toggleButtonProps={{
|
||||
style: {
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
bottom: isMobile ? 'auto' : 0,
|
||||
right: 0,
|
||||
top: isMobile ? 0 : 1,
|
||||
left: 'auto',
|
||||
},
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Main from '@/web/components/New/Main'
|
||||
import Player from '@/web/components/New/Player'
|
||||
import Sidebar from '@/web/components/New/Sidebar'
|
||||
import MenuBar from '@/web/components/New/MenuBar'
|
||||
import Topbar from '@/web/components/New/Topbar'
|
||||
import { css, cx } from '@emotion/css'
|
||||
import { useMemo } from 'react'
|
||||
|
|
@ -16,7 +16,7 @@ const Layout = () => {
|
|||
id='layout'
|
||||
className={cx(
|
||||
'relative grid h-screen select-none overflow-hidden bg-white dark:bg-black',
|
||||
window.ipcRenderer && 'rounded-24',
|
||||
window.env?.isElectron && 'rounded-24',
|
||||
css`
|
||||
grid-template-columns: 6.5rem auto 358px;
|
||||
grid-template-rows: 132px auto;
|
||||
|
|
@ -24,17 +24,17 @@ const Layout = () => {
|
|||
showPlayer
|
||||
? css`
|
||||
grid-template-areas:
|
||||
'sidebar main -'
|
||||
'sidebar main player';
|
||||
'menubar main -'
|
||||
'menubar main player';
|
||||
`
|
||||
: css`
|
||||
grid-template-areas:
|
||||
'sidebar main main'
|
||||
'sidebar main main';
|
||||
'menubar main main'
|
||||
'menubar main main';
|
||||
`
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
<MenuBar />
|
||||
<Topbar />
|
||||
<Main />
|
||||
{showPlayer && <Player />}
|
||||
|
|
|
|||
37
packages/web/components/New/LayoutMobile.tsx
Normal file
37
packages/web/components/New/LayoutMobile.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import Player from '@/web/components/New/PlayerMobile'
|
||||
import { css, cx } from '@emotion/css'
|
||||
import { useMemo } from 'react'
|
||||
import { player } from '@/web/store'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import Router from '@/web/components/New/Router'
|
||||
import MenuBar from './MenuBar'
|
||||
|
||||
const LayoutMobile = () => {
|
||||
const playerSnapshot = useSnapshot(player)
|
||||
const showPlayer = useMemo(() => !!playerSnapshot.track, [playerSnapshot])
|
||||
|
||||
return (
|
||||
<div id='layout' className='select-none bg-white pb-32 pt-3 dark:bg-black'>
|
||||
<main className='min-h-screen overflow-y-auto overflow-x-hidden px-2.5 pb-16'>
|
||||
<Router />
|
||||
</main>
|
||||
{showPlayer && (
|
||||
<div
|
||||
className={cx(
|
||||
'fixed left-7 right-7',
|
||||
css`
|
||||
bottom: 72px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
<Player />
|
||||
</div>
|
||||
)}
|
||||
<div className='fixed bottom-0 left-0 right-0 py-3 dark:bg-black'>
|
||||
<MenuBar />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutMobile
|
||||
|
|
@ -5,13 +5,10 @@ const Main = () => {
|
|||
return (
|
||||
<main
|
||||
className={cx(
|
||||
'overflow-y-auto pb-16 pr-6 pl-10',
|
||||
'no-scrollbar overflow-y-auto pb-16 pr-6 pl-10',
|
||||
css`
|
||||
padding-top: 132px;
|
||||
grid-area: main;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { css, cx } from '@emotion/css'
|
||||
import Icon from '../Icon'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useAnimation, motion } from 'framer-motion'
|
||||
import { ease } from '@/web/utils/const'
|
||||
import TrafficLight from './TrafficLight'
|
||||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
|
|
@ -75,36 +77,68 @@ const TabName = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const Sidebar = () => {
|
||||
const Tabs = () => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const controls = useAnimation()
|
||||
|
||||
const animate = async (path: string) => {
|
||||
await controls.start((p: string) =>
|
||||
p === path && location.pathname !== path ? 'scale' : 'reset'
|
||||
)
|
||||
await controls.start('reset')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-4 justify-items-center text-black/10 dark:text-white/20 lg:grid-cols-1 lg:gap-12'>
|
||||
{tabs.map(tab => (
|
||||
<motion.div
|
||||
key={tab.name}
|
||||
animate={controls}
|
||||
transition={{ ease, duration: 0.18 }}
|
||||
onMouseDown={() => animate(tab.path)}
|
||||
onClick={() => navigate(tab.path)}
|
||||
custom={tab.path}
|
||||
variants={{
|
||||
scale: { scale: 0.8 },
|
||||
reset: { scale: 1 },
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={tab.icon}
|
||||
className={cx(
|
||||
'app-region-no-drag h-10 w-10 transition-colors duration-500',
|
||||
location.pathname === tab.path
|
||||
? 'text-brand-600 dark:text-brand-700'
|
||||
: 'hover:text-black dark:hover:text-white'
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MenuBar = () => {
|
||||
const isMobile = useIsMobile()
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'app-region-drag relative flex h-full w-full flex-col justify-center',
|
||||
css`
|
||||
grid-area: sidebar;
|
||||
grid-area: menubar;
|
||||
`
|
||||
)}
|
||||
>
|
||||
<div className='grid grid-cols-1 justify-items-center gap-12 text-black/10 dark:text-white/20'>
|
||||
{tabs.map(tab => (
|
||||
<NavLink key={tab.name} to={tab.path}>
|
||||
<Icon
|
||||
name={tab.icon}
|
||||
className={cx(
|
||||
'app-region-no-drag h-10 w-10 transition duration-500 active:scale-75',
|
||||
location.pathname === tab.path
|
||||
? 'text-brand-600 dark:text-brand-700'
|
||||
: 'hover:text-black dark:hover:text-white'
|
||||
)}
|
||||
/>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
<TabName />
|
||||
{window.env?.isMac && (
|
||||
<div className='fixed top-6 left-6 translate-y-0.5'>
|
||||
<TrafficLight />
|
||||
</div>
|
||||
)}
|
||||
<Tabs />
|
||||
{!isMobile && <TabName />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
export default MenuBar
|
||||
|
|
@ -122,8 +122,8 @@ const NowPlaying = () => {
|
|||
<div className='mt-4 flex w-full items-center justify-between'>
|
||||
<button>
|
||||
<Icon
|
||||
name='shuffle'
|
||||
className='h-7 w-7 text-black/90 dark:text-white/40'
|
||||
name='hide-list'
|
||||
className='h-7 w-7 text-black/90 dark:text-white/40'
|
||||
/>
|
||||
</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const PlayLikedSongsCard = () => {
|
|||
`
|
||||
)}
|
||||
>
|
||||
<div className='text-21 font-medium text-white/20'>
|
||||
<div className='text-18 font-medium text-white/20 lg:text-21'>
|
||||
{lyricLines.map((line, index) => (
|
||||
<div key={`${index}-${line}`}>{line}</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const Player = () => {
|
|||
)}
|
||||
>
|
||||
<PlayingNext className='mb-3 h-full' />
|
||||
<div className='pb-20'>
|
||||
<div className='pb-6'>
|
||||
<NowPlaying />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
95
packages/web/components/New/PlayerMobile.tsx
Normal file
95
packages/web/components/New/PlayerMobile.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { player } from '@/web/store'
|
||||
import { css, cx } from '@emotion/css'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import Image from '@/web/components/New/Image'
|
||||
import Icon from '@/web/components/Icon'
|
||||
import useCoverColor from '@/web/hooks/useCoverColor'
|
||||
import { resizeImage } from '@/web/utils/common'
|
||||
import { motion, PanInfo, useMotionValue } from 'framer-motion'
|
||||
|
||||
const PlayerMobile = () => {
|
||||
const playerSnapshot = useSnapshot(player)
|
||||
const bgColor = useCoverColor(playerSnapshot.track?.al?.picUrl ?? '')
|
||||
|
||||
const x = useMotionValue(0)
|
||||
const onDragEnd = (
|
||||
event: MouseEvent | TouchEvent | PointerEvent,
|
||||
info: PanInfo
|
||||
) => {
|
||||
const x = info.offset.x
|
||||
const offset = 100
|
||||
if (x > offset) player.nextTrack()
|
||||
if (x < -offset) player.prevTrack()
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'relative flex h-16 w-full items-center rounded-20 py-2.5 px-3',
|
||||
css`
|
||||
background-color: ${bgColor.to};
|
||||
`
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'absolute -top-2.5 h-1.5 w-10 rounded-full bg-brand-700',
|
||||
css`
|
||||
left: calc((100% - 40px) / 2);
|
||||
`
|
||||
)}
|
||||
></div>
|
||||
|
||||
<Image
|
||||
src={resizeImage(playerSnapshot.track?.al.picUrl || '', 'sm')}
|
||||
alt='Cover'
|
||||
className='z-10 aspect-square h-full rounded-lg'
|
||||
/>
|
||||
|
||||
<div className='relative flex-grow overflow-hidden px-3'>
|
||||
<motion.div
|
||||
drag='x'
|
||||
style={{ x }}
|
||||
dragConstraints={{ left: 0, right: 0 }}
|
||||
onDragEnd={onDragEnd}
|
||||
className='line-clamp-1 text-14 font-bold text-white'
|
||||
>
|
||||
{playerSnapshot.track?.name}
|
||||
</motion.div>
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
'absolute left-0 top-0 bottom-0 w-3 ',
|
||||
css`
|
||||
background: linear-gradient(to right, ${bgColor.to}, transparent);
|
||||
`
|
||||
)}
|
||||
></div>
|
||||
<div
|
||||
className={cx(
|
||||
'absolute right-0 top-0 bottom-0 w-3 bg-red-200',
|
||||
css`
|
||||
background: linear-gradient(to left, ${bgColor.to}, transparent);
|
||||
`
|
||||
)}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<button>
|
||||
<Icon name='heart' className='h-7 w-7 text-white/10' />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => player.playOrPause()}
|
||||
className='ml-2.5 flex items-center justify-center rounded-full bg-white/20 p-2.5'
|
||||
>
|
||||
<Icon
|
||||
name={playerSnapshot.state === 'playing' ? 'pause' : 'play'}
|
||||
className='h-6 w-6 text-white/80'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PlayerMobile
|
||||
|
|
@ -6,6 +6,7 @@ import { css, cx } from '@emotion/css'
|
|||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import Image from './Image'
|
||||
import Wave from './Wave'
|
||||
import Icon from '@/web/components/Icon'
|
||||
|
||||
const PlayingNext = ({ className }: { className?: string }) => {
|
||||
const playerSnapshot = useSnapshot(player)
|
||||
|
|
@ -15,22 +16,30 @@ const PlayingNext = ({ className }: { className?: string }) => {
|
|||
<>
|
||||
<div
|
||||
className={cx(
|
||||
'absolute top-0 z-10 pb-6 text-14 font-bold text-neutral-700 dark:text-neutral-300'
|
||||
'absolute top-0 left-0 z-10 flex w-full items-center justify-between px-4 pb-6 text-14 font-bold text-neutral-700 dark:text-neutral-300'
|
||||
)}
|
||||
>
|
||||
PLAYING NEXT
|
||||
<div className='flex'>
|
||||
<div className='mr-2 h-4 w-1 bg-brand-700'></div>
|
||||
PLAYING NEXT
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<div className='mr-2'>
|
||||
<Icon name='repeat-1' className='h-7 w-7 opacity-40' />
|
||||
</div>
|
||||
<div className='mr-1'>
|
||||
<Icon name='shuffle' className='h-7 w-7 opacity-40' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
'relative z-10 overflow-scroll',
|
||||
'no-scrollbar relative z-10 overflow-scroll',
|
||||
className,
|
||||
css``,
|
||||
css`
|
||||
padding-top: 42px;
|
||||
mask-image: linear-gradient(to bottom, transparent 0, black 42px);
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
)}
|
||||
>
|
||||
|
|
@ -60,10 +69,17 @@ const PlayingNext = ({ className }: { className?: string }) => {
|
|||
|
||||
{/* Track info */}
|
||||
<div className='mr-3 flex-grow'>
|
||||
<div className='line-clamp-1 text-18 font-medium text-neutral-700 dark:text-neutral-200'>
|
||||
<div
|
||||
className={cx(
|
||||
'line-clamp-1 text-16 font-medium ',
|
||||
playerSnapshot.trackIndex === index
|
||||
? 'text-brand-700'
|
||||
: 'text-neutral-700 dark:text-neutral-200'
|
||||
)}
|
||||
>
|
||||
{track.name}
|
||||
</div>
|
||||
<div className='line-clamp-1 mt-1 text-16 font-bold text-neutral-200 dark:text-neutral-700'>
|
||||
<div className='line-clamp-1 mt-1 text-14 font-bold text-neutral-200 dark:text-neutral-700'>
|
||||
{track.ar.map(a => a.name).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -72,7 +88,7 @@ const PlayingNext = ({ className }: { className?: string }) => {
|
|||
{playerSnapshot.trackIndex === index ? (
|
||||
<Wave playing={playerSnapshot.state === 'playing'} />
|
||||
) : (
|
||||
<div className='text-18 font-medium text-neutral-700 dark:text-neutral-200'>
|
||||
<div className='text-16 font-medium text-neutral-700 dark:text-neutral-200'>
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||
import Sidebar from './Sidebar'
|
||||
import Sidebar from './MenuBar'
|
||||
|
||||
export default {
|
||||
title: 'Components/Sidebar',
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const Tabs = ({
|
|||
onChange: (id: string) => void
|
||||
}) => {
|
||||
return (
|
||||
<div className='flex'>
|
||||
<div className='no-scrollbar flex overflow-y-auto'>
|
||||
{tabs.map(tab => (
|
||||
<div
|
||||
key={tab.id}
|
||||
|
|
@ -23,6 +23,7 @@ const Tabs = ({
|
|||
? 'bg-brand-700 text-white'
|
||||
: 'dark:bg-white/10 dark:text-white/20'
|
||||
)}
|
||||
onClick={() => onChange(tab.id)}
|
||||
>
|
||||
{tab.name}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ const NavigationButtons = () => {
|
|||
const navigate = useNavigate()
|
||||
const controlsBack = useAnimation()
|
||||
const controlsForward = useAnimation()
|
||||
const transition = { duration: 0.2, ease }
|
||||
const transition = { duration: 0.18, ease }
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={async () => {
|
||||
navigate(-1)
|
||||
onClick={() => navigate(-1)}
|
||||
onMouseDown={async () => {
|
||||
await controlsBack.start({ x: -5 })
|
||||
await controlsBack.start({ x: 0 })
|
||||
}}
|
||||
|
|
@ -29,6 +29,8 @@ const NavigationButtons = () => {
|
|||
<button
|
||||
onClick={async () => {
|
||||
navigate(1)
|
||||
}}
|
||||
onMouseDown={async () => {
|
||||
await controlsForward.start({ x: 5 })
|
||||
await controlsForward.start({ x: 0 })
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useMemo } from 'react'
|
|||
import { player } from '@/web/store'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import Wave from './Wave'
|
||||
import Icon from '@/web/components/Icon'
|
||||
|
||||
const TrackList = ({
|
||||
tracks,
|
||||
|
|
@ -35,21 +36,31 @@ const TrackList = ({
|
|||
<div
|
||||
key={track.id}
|
||||
onClick={e => handleClick(e, track.id)}
|
||||
className='relative flex items-center py-2 text-18 font-medium text-night-50 transition duration-300 ease-in-out dark:hover:text-neutral-200'
|
||||
className='group relative flex items-center py-2 text-16 font-medium text-neutral-200 transition duration-300 ease-in-out'
|
||||
>
|
||||
<div className='mr-6'>{String(track.no).padStart(2, '0')}</div>
|
||||
<div className='flex-grow'>{track.name}</div>
|
||||
<div className='h-10 w-10'></div>
|
||||
<div className='flex flex-grow items-center'>
|
||||
{track.name}
|
||||
{playingTrack?.id === track.id && playing && (
|
||||
<div className='ml-4'>
|
||||
<Wave playing={playing} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mr-12 flex opacity-0 transition-opacity group-hover:opacity-100'>
|
||||
<div className='mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-brand-600 text-white/80'>
|
||||
{/* <Icon name='play' className='h-7 w-7' /> */}
|
||||
</div>
|
||||
<div className='mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-night-900 text-white/80'>
|
||||
{/* <Icon name='play' className='h-7 w-7' /> */}
|
||||
</div>
|
||||
<div className='flex h-10 w-10 items-center justify-center rounded-full bg-night-900 text-white/80'>
|
||||
{/* <Icon name='play' className='h-7 w-7' /> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-right'>
|
||||
{formatDuration(track.dt, 'en', 'hh:mm:ss')}
|
||||
</div>
|
||||
|
||||
{/* The wave icon */}
|
||||
{playingTrack?.id === track.id && playing && (
|
||||
<div className='absolute -left-7'>
|
||||
<Wave playing={playing} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ const TrackListHeader = ({
|
|||
onClick={() => onPlay()}
|
||||
className='h-[72px] w-[170px] rounded-full dark:bg-brand-700'
|
||||
></button>
|
||||
<button className='ml-6 h-[72px] w-[72px] rounded-full dark:bg-night-50'></button>
|
||||
<button className='ml-6 h-[72px] w-[72px] rounded-full dark:bg-night-50'></button>
|
||||
<button className='ml-6 h-[72px] w-[72px] rounded-full dark:bg-white/10'></button>
|
||||
<button className='ml-6 h-[72px] w-[72px] rounded-full dark:bg-white/10'></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
13
packages/web/components/New/TrafficLight.tsx
Normal file
13
packages/web/components/New/TrafficLight.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const TrafficLight = () => {
|
||||
const className =
|
||||
'mr-2 h-3 w-3 rounded-full last-of-type:mr-0 dark:bg-white/20'
|
||||
return (
|
||||
<div className='flex'>
|
||||
<div className={className}></div>
|
||||
<div className={className}></div>
|
||||
<div className={className}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TrafficLight
|
||||
Loading…
Add table
Add a link
Reference in a new issue