feat: updates

This commit is contained in:
qier222 2022-06-08 11:48:22 +08:00
parent 133881d287
commit d4d8dd817b
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
22 changed files with 667 additions and 172 deletions

View file

@ -45,10 +45,10 @@ const ArtistRow = ({
{/* 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'>
<div className='no-scrollbar -ml-2.5 flex w-screen snap-x 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'
className='mr-5 snap-start first-of-type:ml-2.5 last-of-type:mr-2.5 lg:mr-0'
key={artist.id}
>
<Artist artist={artist} key={artist.id} />
@ -65,8 +65,14 @@ const ArtistRow = ({
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'>
<div
className='aspect-square w-full rounded-full bg-white dark:bg-neutral-800'
style={{
minHeight: '96px',
minWidth: '96px',
}}
/>
<div className='mt-2.5 text-12 font-medium text-transparent lg:text-16 lg:font-bold'>
PLACE
</div>
</div>

View file

@ -21,8 +21,8 @@ const CoverWall = ({
'2xl': 'md',
},
large: {
sm: 'sm',
md: 'sm',
sm: 'md',
md: 'md',
lg: 'md',
xl: 'md',
'2xl': 'lg',

View file

@ -1,7 +1,7 @@
import Main from '@/web/components/New/Main'
import Player from '@/web/components/New/Player'
import MenuBar from '@/web/components/New/MenuBar'
import Topbar from '@/web/components/New/Topbar'
import Topbar from '@/web/components/New/Topbar/TopbarDesktop'
import { css, cx } from '@emotion/css'
import { useMemo } from 'react'
import { player } from '@/web/store'

View file

@ -5,6 +5,8 @@ import { player } from '@/web/store'
import { useSnapshot } from 'valtio'
import Router from '@/web/components/New/Router'
import MenuBar from './MenuBar'
import TopbarMobile from './Topbar/TopbarMobile'
import { isIOS, isPWA, isSafari } from '@/web/utils/common'
const LayoutMobile = () => {
const playerSnapshot = useSnapshot(player)
@ -13,21 +15,36 @@ const LayoutMobile = () => {
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'>
<TopbarMobile />
<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'>
<div
className={cx(
'fixed bottom-0 left-0 right-0 pt-3 dark:bg-black',
css`
padding-bottom: calc(
${isIOS && isSafari && isPWA
? '24px'
: 'env(safe-area-inset-bottom)'} + 0.75rem
);
`
)}
>
{showPlayer && (
<div
className={cx(
'absolute left-7 right-7',
css`
top: calc(
-100% - 6px + ${isIOS && isSafari && isPWA ? '24px' : 'env(safe-area-inset-bottom)'}
);
`
)}
>
<Player />
</div>
)}
<MenuBar />
</div>
</div>

View file

@ -79,8 +79,8 @@ const TabName = () => {
const Tabs = () => {
const navigate = useNavigate()
const location = useLocation()
const controls = useAnimation()
const [active, setActive] = useState<string>(tabs[0].path)
const animate = async (path: string) => {
await controls.start((p: string) =>
@ -97,7 +97,10 @@ const Tabs = () => {
animate={controls}
transition={{ ease, duration: 0.18 }}
onMouseDown={() => animate(tab.path)}
onClick={() => navigate(tab.path)}
onClick={() => {
setActive(tab.path)
navigate(tab.path)
}}
custom={tab.path}
variants={{
scale: { scale: 0.8 },
@ -108,9 +111,9 @@ const Tabs = () => {
name={tab.icon}
className={cx(
'app-region-no-drag h-10 w-10 transition-colors duration-500',
location.pathname === tab.path
active === tab.path
? 'text-brand-600 dark:text-brand-700'
: 'hover:text-black dark:hover:text-white'
: 'lg:hover:text-black lg:dark:hover:text-white'
)}
/>
</motion.div>

View file

@ -1,5 +1,6 @@
import { motion } from 'framer-motion'
import { ease } from '@/web/utils/const'
import useIsMobile from '@/web/hooks/useIsMobile'
const PageTransition = ({
children,
@ -8,6 +9,11 @@ const PageTransition = ({
children: React.ReactNode
disableEnterAnimation?: boolean
}) => {
const isMobile = useIsMobile()
if (isMobile) {
return <>{children}</>
}
return (
<motion.div
initial={{ opacity: disableEnterAnimation ? 1 : 0 }}

View file

@ -6,26 +6,33 @@ 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'
import { useLockBodyScroll } from 'react-use'
import { useState } from 'react'
const PlayerMobile = () => {
const playerSnapshot = useSnapshot(player)
const bgColor = useCoverColor(playerSnapshot.track?.al?.picUrl ?? '')
const [locked, setLocked] = useState(false)
useLockBodyScroll(locked)
const x = useMotionValue(0)
const onDragEnd = (
event: MouseEvent | TouchEvent | PointerEvent,
info: PanInfo
) => {
console.log(JSON.stringify(info))
const x = info.offset.x
const offset = 100
if (x > offset) player.nextTrack()
if (x < -offset) player.prevTrack()
if (x > offset) player.prevTrack()
if (x < -offset) player.nextTrack()
setLocked(false)
}
return (
<div
className={cx(
'relative flex h-16 w-full items-center rounded-20 py-2.5 px-3',
'relative flex h-16 w-full items-center rounded-20 px-3',
css`
background-color: ${bgColor.to};
`
@ -40,21 +47,32 @@ const PlayerMobile = () => {
)}
></div>
<Image
src={resizeImage(playerSnapshot.track?.al.picUrl || '', 'sm')}
alt='Cover'
className='z-10 aspect-square h-full rounded-lg'
/>
<div className='h-full py-2.5'>
<Image
src={resizeImage(playerSnapshot.track?.al.picUrl || '', 'sm')}
alt='Cover'
className='z-10 aspect-square h-full rounded-lg'
/>
</div>
<div className='relative flex-grow overflow-hidden px-3'>
<div className='relative flex h-full flex-grow items-center overflow-hidden px-3'>
<motion.div
drag='x'
style={{ x }}
dragConstraints={{ left: 0, right: 0 }}
onDragStart={() => setLocked(true)}
onDragEnd={onDragEnd}
className='line-clamp-1 text-14 font-bold text-white'
className=' flex h-full flex-grow items-center '
>
{playerSnapshot.track?.name}
<div className='flex-shrink-0'>
<div className='line-clamp-1 text-14 font-bold text-white'>
{playerSnapshot.track?.name}
</div>
<div className='line-clamp-1 mt-1 text-12 font-bold text-white/60'>
{playerSnapshot.track?.ar?.map(a => a.name).join(', ')}
</div>
</div>
<div className='h-full flex-grow'></div>
</motion.div>
<div

View file

@ -1,6 +1,6 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Topbar from './Topbar'
import Topbar from './Topbar/TopbarDesktop'
export default {
title: 'Components/Topbar',

View file

@ -1,125 +0,0 @@
import { css, cx } from '@emotion/css'
import { motion, useAnimation } from 'framer-motion'
import { useLocation, useNavigate } from 'react-router-dom'
import { ease } from '@/web/utils/const'
import Icon from '../Icon'
import { resizeImage } from '@/web/utils/common'
import useUser from '@/web/api/hooks/useUser'
const NavigationButtons = () => {
const navigate = useNavigate()
const controlsBack = useAnimation()
const controlsForward = useAnimation()
const transition = { duration: 0.18, ease }
return (
<>
<button
onClick={() => navigate(-1)}
onMouseDown={async () => {
await controlsBack.start({ x: -5 })
await controlsBack.start({ x: 0 })
}}
className='app-region-no-drag rounded-full bg-day-600 p-2.5 dark:bg-night-600'
>
<motion.div animate={controlsBack} transition={transition}>
<Icon name='back' className='h-7 w-7 text-neutral-500' />
</motion.div>
</button>
<button
onClick={async () => {
navigate(1)
}}
onMouseDown={async () => {
await controlsForward.start({ x: 5 })
await controlsForward.start({ x: 0 })
}}
className='app-region-no-drag ml-2.5 rounded-full bg-day-600 p-2.5 dark:bg-night-600'
>
<motion.div animate={controlsForward} transition={transition}>
<Icon name='forward' className='h-7 w-7 text-neutral-500' />
</motion.div>
</button>
</>
)
}
const Avatar = ({ className }: { className?: string }) => {
const navigate = useNavigate()
const { data: user } = useUser()
const avatarUrl = user?.profile?.avatarUrl
? resizeImage(user?.profile?.avatarUrl ?? '', 'sm')
: ''
return (
<>
{avatarUrl ? (
<img
src={avatarUrl}
onClick={() => navigate('/login')}
className={cx(
'app-region-no-drag rounded-full',
className || 'h-12 w-12'
)}
/>
) : (
<div
onClick={() => navigate('/login')}
className={cx(
'rounded-full bg-day-600 p-2.5 dark:bg-night-600',
className || 'h-12 w-12'
)}
>
<Icon name='user' className='h-7 w-7 text-neutral-500' />
</div>
)}
</>
)
}
const Topbar = () => {
const location = useLocation()
return (
<div
className={cx(
'app-region-drag fixed top-0 right-0 z-20 flex items-center justify-between overflow-hidden rounded-tr-24 pt-11 pb-10 pr-6 pl-10 ',
css`
left: 104px;
`,
!location.pathname.startsWith('/album/') &&
!location.pathname.startsWith('/playlist/') &&
'bg-gradient-to-b from-white dark:from-black'
)}
>
{/* Left Part */}
<div className='flex items-center'>
<NavigationButtons />
{/* Dividing line */}
<div className='mx-6 h-4 w-px bg-black/20 dark:bg-white/20'></div>
{/* Search Box */}
<div className='app-region-no-drag flex min-w-[284px] items-center rounded-full bg-day-600 p-2.5 text-neutral-500 dark:bg-night-600'>
<Icon name='search' className='mr-2.5 h-7 w-7' />
<input
placeholder='Artist, songs and more'
className='bg-transparent font-medium placeholder:text-neutral-500 dark:text-neutral-200'
/>
</div>
</div>
{/* Right Part */}
<div className='flex'>
<button className='app-region-no-drag rounded-full bg-day-600 p-2.5 dark:bg-night-600'>
<Icon name='placeholder' className='h-7 w-7 text-neutral-500' />
</button>
<Avatar className='ml-3 h-12 w-12' />
</div>
</div>
)
}
export default Topbar

View file

@ -0,0 +1,41 @@
import { css, cx } from '@emotion/css'
import { useNavigate } from 'react-router-dom'
import Icon from '../../Icon'
import { resizeImage } from '@/web/utils/common'
import useUser from '@/web/api/hooks/useUser'
const Avatar = ({ className }: { className?: string }) => {
const navigate = useNavigate()
const { data: user } = useUser()
const avatarUrl = user?.profile?.avatarUrl
? resizeImage(user?.profile?.avatarUrl ?? '', 'sm')
: ''
return (
<>
{avatarUrl ? (
<img
src={avatarUrl}
onClick={() => navigate('/login')}
className={cx(
'app-region-no-drag rounded-full',
className || 'h-12 w-12'
)}
/>
) : (
<div
onClick={() => navigate('/login')}
className={cx(
'rounded-full bg-day-600 p-2.5 dark:bg-night-600',
className || 'h-12 w-12'
)}
>
<Icon name='user' className='h-7 w-7 text-neutral-500' />
</div>
)}
</>
)
}
export default Avatar

View file

@ -0,0 +1,45 @@
import { css, cx } from '@emotion/css'
import { motion, useAnimation } from 'framer-motion'
import { useNavigate } from 'react-router-dom'
import { ease } from '@/web/utils/const'
import Icon from '../../Icon'
const NavigationButtons = () => {
const navigate = useNavigate()
const controlsBack = useAnimation()
const controlsForward = useAnimation()
const transition = { duration: 0.18, ease }
return (
<>
<button
onClick={() => navigate(-1)}
onMouseDown={async () => {
await controlsBack.start({ x: -5 })
await controlsBack.start({ x: 0 })
}}
className='app-region-no-drag rounded-full bg-day-600 p-2.5 dark:bg-night-600'
>
<motion.div animate={controlsBack} transition={transition}>
<Icon name='back' className='h-7 w-7 text-neutral-500' />
</motion.div>
</button>
<button
onClick={async () => {
navigate(1)
}}
onMouseDown={async () => {
await controlsForward.start({ x: 5 })
await controlsForward.start({ x: 0 })
}}
className='app-region-no-drag ml-2.5 rounded-full bg-day-600 p-2.5 dark:bg-night-600'
>
<motion.div animate={controlsForward} transition={transition}>
<Icon name='forward' className='h-7 w-7 text-neutral-500' />
</motion.div>
</button>
</>
)
}
export default NavigationButtons

View file

@ -0,0 +1,16 @@
import { css, cx } from '@emotion/css'
import Icon from '../../Icon'
const SearchBox = () => {
return (
<div className='app-region-no-drag flex items-center rounded-full bg-day-600 p-2.5 text-neutral-500 dark:bg-night-600 lg:min-w-[284px]'>
<Icon name='search' className='mr-2.5 h-7 w-7' />
<input
placeholder='Artist, songs and more'
className='bg-transparent font-medium placeholder:text-neutral-500 dark:text-neutral-200'
/>
</div>
)
}
export default SearchBox

View file

@ -0,0 +1,11 @@
import Icon from '@/web/components/Icon'
const SettingsButton = () => {
return (
<button className='app-region-no-drag rounded-full bg-day-600 p-2.5 dark:bg-night-600'>
<Icon name='placeholder' className='h-7 w-7 text-neutral-500' />
</button>
)
}
export default SettingsButton

View file

@ -0,0 +1,42 @@
import { css, cx } from '@emotion/css'
import { useLocation } from 'react-router-dom'
import Avatar from './Avatar'
import SearchBox from './SearchBox'
import SettingsButton from './SettingsButton'
import NavigationButtons from './NavigationButtons'
const TopbarDesktop = () => {
const location = useLocation()
return (
<div
className={cx(
'app-region-drag fixed top-0 right-0 z-20 flex items-center justify-between overflow-hidden rounded-tr-24 pt-11 pb-10 pr-6 pl-10 ',
css`
left: 104px;
`,
!location.pathname.startsWith('/album/') &&
!location.pathname.startsWith('/playlist/') &&
'bg-gradient-to-b from-white dark:from-black'
)}
>
{/* Left Part */}
<div className='flex items-center'>
<NavigationButtons />
{/* Dividing line */}
<div className='mx-6 h-4 w-px bg-black/20 dark:bg-white/20'></div>
<SearchBox />
</div>
{/* Right Part */}
<div className='flex'>
<SettingsButton />
<Avatar className='ml-3 h-12 w-12' />
</div>
</div>
)
}
export default TopbarDesktop

View file

@ -0,0 +1,22 @@
import Avatar from './Avatar'
import SearchBox from './SearchBox'
import SettingsButton from './SettingsButton'
const TopbarMobile = () => {
return (
<div className='mb-5 mt-7 flex'>
<div className='flex-grow'>
<SearchBox />
</div>
<div className='ml-6 flex'>
<SettingsButton />
<div className='ml-3'>
<Avatar />
</div>
</div>
</div>
)
}
export default TopbarMobile

View file

@ -41,7 +41,7 @@ const TrackList = ({
<div className='mr-6'>{String(track.no).padStart(2, '0')}</div>
<div className='flex flex-grow items-center'>
{track.name}
{playingTrack?.id === track.id && playing && (
{playingTrack?.id === track.id && (
<div className='ml-4'>
<Wave playing={playing} />
</div>