feat: updates

This commit is contained in:
qier222 2022-08-03 23:48:39 +08:00
parent 47e41dea9b
commit ebebf2a733
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
160 changed files with 4148 additions and 2001 deletions

View file

@ -0,0 +1,42 @@
import Icon from '../../Icon'
const Actions = ({
onPlay,
onLike,
isLiked,
}: {
isLiked?: boolean
onPlay: () => void
onLike?: () => void
}) => {
return (
<div className='mt-11 flex items-end justify-between lg:mt-4 lg:justify-start'>
<div className='flex items-end'>
{/* Menu */}
<button className='mr-2.5 flex h-14 w-14 items-center justify-center rounded-full text-white/40 transition duration-400 hover:text-white/70 dark:bg-white/10 hover:dark:bg-white/30'>
<Icon name='more' className='h-7 w-7' />
</button>
{/* Like */}
{onLike && (
<button
onClick={() => onLike()}
className='flex h-14 w-14 items-center justify-center rounded-full text-white/40 transition duration-400 hover:text-white/70 dark:bg-white/10 hover:dark:bg-white/30 lg:mr-2.5'
>
<Icon
name={isLiked ? 'heart' : 'heart-outline'}
className='h-7 w-7'
/>
</button>
)}
</div>
<button
onClick={() => onPlay()}
className='h-14 rounded-full px-10 text-18 font-medium text-white dark:bg-brand-700'
>
Play
</button>
</div>
)
}
export default Actions

View file

@ -0,0 +1,33 @@
import { isIOS, isSafari, resizeImage } from '@/web/utils/common'
import Image from '@/web/components/New/Image'
import { memo, useEffect } from 'react'
import useVideoCover from '@/web/hooks/useVideoCover'
import { motion } from 'framer-motion'
import { ease } from '@/web/utils/const'
import useAppleMusicAlbum from '@/web/hooks/useAppleMusicAlbum'
import uiStates from '@/web/states/uiStates'
import VideoCover from './VideoCover'
const Cover = memo(
({ cover, videoCover }: { cover?: string; videoCover?: string }) => {
useEffect(() => {
if (cover) uiStates.blurBackgroundImage = cover
}, [cover])
return (
<>
<div className='relative aspect-square w-full overflow-hidden rounded-24 '>
<Image
className='absolute inset-0'
src={resizeImage(cover || '', 'lg')}
/>
{videoCover && <VideoCover videoCover={videoCover} />}
</div>
</>
)
}
)
Cover.displayName = 'Cover'
export default Cover

View file

@ -0,0 +1,59 @@
import { formatDate } from '@/web/utils/common'
import Icon from '@/web/components/Icon'
import dayjs from 'dayjs'
import { useNavigate } from 'react-router-dom'
import useIsMobile from '@/web/hooks/useIsMobile'
import { ReactNode } from 'react'
const Info = ({
title,
creatorName,
creatorLink,
description,
extraInfo,
}: {
title?: string
creatorName?: string
creatorLink?: string
description?: string
extraInfo?: string | ReactNode
}) => {
const navigate = useNavigate()
const isMobile = useIsMobile()
return (
<div>
{/* Title */}
<div className='mt-2.5 text-28 font-semibold dark:text-white/80 lg:mt-0 lg:text-36 lg:font-medium'>
{title}
</div>
{/* Creator */}
<div className='mt-2.5 lg:mt-6'>
<span
onClick={() => creatorLink && navigate(creatorLink)}
className='text-24 font-medium transition-colors duration-300 dark:text-white/40 hover:dark:text-neutral-100 '
>
{creatorName}
</span>
</div>
{/* Extra info */}
<div className='mt-1 flex items-center text-12 font-medium dark:text-white/40 lg:text-14 lg:font-bold'>
{extraInfo}
</div>
{/* Description */}
{!isMobile && (
<div
className='line-clamp-3 mt-6 whitespace-pre-wrap text-14 font-bold dark:text-white/40'
dangerouslySetInnerHTML={{
__html: description || '',
}}
></div>
)}
</div>
)
}
export default Info

View file

@ -0,0 +1,16 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import TrackListHeader from './TrackListHeader'
export default {
title: 'Components/TrackListHeader',
component: TrackListHeader,
} as ComponentMeta<typeof TrackListHeader>
const Template: ComponentStory<typeof TrackListHeader> = args => (
<div className='w-[calc(100vw_-_32px)] rounded-24 bg-[#F8F8F8] p-10 dark:bg-black'>
<TrackListHeader />
</div>
)
export const Default = Template.bind({})

View file

@ -0,0 +1,60 @@
import { css, cx } from '@emotion/css'
import Cover from './Cover'
import Actions from './Actions'
import Info from './Info'
import React from 'react'
interface Props {
className?: string
title?: string
creatorName?: string
creatorLink?: string
description?: string
extraInfo?: string | React.ReactNode
cover?: string
videoCover?: string
isLiked: boolean
onPlay: () => void
onLike?: () => void
}
const TrackListHeader = ({
className,
title,
creatorName,
creatorLink,
description,
extraInfo,
cover,
videoCover,
isLiked,
onPlay,
onLike,
}: Props) => {
return (
<div
className={cx(
className,
'mx-2.5 rounded-48 p-8 dark:bg-white/10',
'lg:mx-0 lg:grid lg:grid-rows-1 lg:gap-10 lg:rounded-none lg:p-0 lg:dark:bg-transparent',
css`
grid-template-columns: 318px auto;
`
)}
>
<Cover {...{ cover, videoCover }} />
<div className='flex flex-col justify-between'>
<Info
{...{ title, creatorName, creatorLink, description, extraInfo }}
/>
<Actions {...{ onPlay, onLike, isLiked }} />
</div>
</div>
)
}
const memoized = React.memo(TrackListHeader)
memoized.displayName = 'TrackListHeader'
export default memoized

View file

@ -0,0 +1,51 @@
import { useEffect, useRef } from 'react'
import Hls from 'hls.js'
import { injectGlobal } from '@emotion/css'
import { isIOS, isSafari } from '@/web/utils/common'
import { motion } from 'framer-motion'
injectGlobal`
.plyr__video-wrapper,
.plyr--video {
background-color: transparent !important;
}
`
const VideoCover = ({ videoCover }: { videoCover?: string }) => {
const ref = useRef<HTMLVideoElement>(null)
const hls = useRef<Hls>(new Hls())
useEffect(() => {
if (videoCover && Hls.isSupported()) {
const video = document.querySelector('#video-cover') as HTMLVideoElement
hls.current.loadSource(videoCover)
hls.current.attachMedia(video)
}
}, [videoCover])
return (
<motion.div
initial={{ opacity: isIOS ? 1 : 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6 }}
className='absolute inset-0'
>
{isSafari ? (
<video
src={videoCover}
className='h-full w-full'
autoPlay
loop
muted
playsInline
></video>
) : (
<div className='aspect-square'>
<video id='video-cover' ref={ref} autoPlay muted loop />
</div>
)}
</motion.div>
)
}
export default VideoCover

View file

@ -0,0 +1,3 @@
import TrackListHeader from './TrackListHeader'
export default TrackListHeader