parent
e2d65c89c9
commit
9e737bcb4b
@ -1,28 +0,0 @@
|
||||
// 播放器按钮
|
||||
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
interface Props {
|
||||
size: number;
|
||||
img: string;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function ControlButton({ size, img, onClick, className = '' }: Props) {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
className={`block bg-cover bg-center bg-no-repeat ${className}`}
|
||||
style={{ width: `${size}px`, height: `${size}px` }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
<Image width={size} height={size} src={img} alt="Pause" priority className={`w-[${size}px] h-[${size}px]`} />
|
||||
</a>
|
||||
);
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
'use client';
|
||||
|
||||
// 样式层
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
import { PlayerButton, CollectButton } from '@/components';
|
||||
import { secondToDate } from '@/utils';
|
||||
|
||||
interface Props {
|
||||
audio: SongInfo | null;
|
||||
order: PlayOrder;
|
||||
showOrder: boolean;
|
||||
playStatus: boolean;
|
||||
showCard: boolean;
|
||||
onSwitchShowCard: () => void;
|
||||
onPlay: () => void;
|
||||
/** 切换播放顺序 */
|
||||
onOrder: () => void;
|
||||
onPrev: () => void;
|
||||
onNext: () => void;
|
||||
/** 改变播放进度 */
|
||||
onChangeProgress: (value: string) => void;
|
||||
trackProgress: number;
|
||||
duration: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function AudioPlayer({
|
||||
audio,
|
||||
order,
|
||||
playStatus,
|
||||
showCard,
|
||||
showOrder,
|
||||
onPlay,
|
||||
onOrder,
|
||||
onPrev,
|
||||
onNext,
|
||||
onChangeProgress,
|
||||
onSwitchShowCard,
|
||||
trackProgress,
|
||||
duration,
|
||||
className,
|
||||
}: Props) {
|
||||
const currentPercentage = duration ? `${(trackProgress / duration) * 100}%` : '0%';
|
||||
const trackStyling: string = `
|
||||
-webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(${currentPercentage}, rgb(24,24,24)), color-stop(${currentPercentage}, rgba(0,0,0,0.1)))
|
||||
`; // 进度条样式
|
||||
|
||||
// 播放顺序
|
||||
const orderList: Array<{ value: string; label: string; icon: string }> = [
|
||||
{ value: 'list_loop', label: '列表循环', icon: '/img/audio-player/order-list_loop.svg' },
|
||||
{ value: 'random', label: '随机播放', icon: '/img/audio-player/order-random.svg' },
|
||||
{ value: 'single_loop', label: '单曲循环', icon: '/img/audio-player/order-single.svg' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`flex justify-between items-center w-1200px h-full ${className}`}>
|
||||
<div className="flex flex-row items-center w-fit h-full">
|
||||
{/* 专辑封面 */}
|
||||
<div
|
||||
className={`relative flex-shrink-0 w-48px h-48px rounded-3px bg-[#000] overflow-hidden cursor-pointer ${showCard ? styles.album_pic_overlay_collapse : styles.album_pic_overlay_expand}`}
|
||||
onClick={onSwitchShowCard}
|
||||
>
|
||||
{audio?.pic && (
|
||||
<Image src={audio.pic} alt="music" width={48} height={48} unoptimized className="w-full h-full" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 歌曲信息 */}
|
||||
<div className="w-158px ml-15px mr-36px cursor-default">
|
||||
{/* <AutoScrollContainer key={audio?.id} auto hover width="140px" speed={50}> */}
|
||||
{/* <div className="w-auto h-auto"> */}
|
||||
<p className="text-17px leading-24px text-base text-overflow">{audio?.title ?? ''}</p>
|
||||
<p className="text-13px leading-18px mt-2px text-[rgba(0,0,0,0.7)] text-overflow">{`${audio?.artist}/${audio?.album}`}</p>
|
||||
{/* </div> */}
|
||||
{/* </AutoScrollContainer> */}
|
||||
</div>
|
||||
|
||||
<div className="h-fit mt-2px">
|
||||
{/* 进度条 */}
|
||||
<div className="w-686px h-fit cursor-pointer">
|
||||
<input
|
||||
type="range"
|
||||
value={trackProgress}
|
||||
step="1"
|
||||
min="0"
|
||||
max={duration ? duration : `${duration}`}
|
||||
className={styles['range-input']}
|
||||
onChange={(e) => onChangeProgress(e.target.value)}
|
||||
style={{ background: trackStyling }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 时间 */}
|
||||
<p className="mt-1px text-11px leading-14px">
|
||||
<span>{`${secondToDate(trackProgress)}`}</span>
|
||||
<span className="text-[rgba(0,0,0,0.4)]">{` / ${secondToDate(duration || 0)}`}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* control */}
|
||||
<div className="flex items-center h-fit pb-4px">
|
||||
{/* 收藏歌曲 */}
|
||||
{!!audio?.id && (
|
||||
<CollectButton id={audio.id} active={audio.haveCollect} collectType="0" color="dark" className="ml-24px" />
|
||||
)}
|
||||
{/* 播放顺序 */}
|
||||
{showOrder &&
|
||||
orderList.map((item) => (
|
||||
<PlayerButton
|
||||
className={`${item.value === order ? 'block' : 'hidden'} ml-24px`}
|
||||
key={item.value}
|
||||
size={24}
|
||||
img={item.icon}
|
||||
onClick={onOrder}
|
||||
/>
|
||||
))}
|
||||
{/* 上一首 */}
|
||||
<PlayerButton size={24} img={'/img/audio-player/next.svg'} onClick={onPrev} className="rotate-180 ml-24px" />
|
||||
{/* 播放/暂停 */}
|
||||
<div className="mx-24px">
|
||||
<PlayerButton
|
||||
className={playStatus ? 'block' : 'hidden'}
|
||||
size={54}
|
||||
img={'/img/audio-player/pause.svg'}
|
||||
onClick={onPlay}
|
||||
/>
|
||||
<PlayerButton
|
||||
className={!playStatus ? 'block' : 'hidden'}
|
||||
size={54}
|
||||
img={'/img/audio-player/play.svg'}
|
||||
onClick={onPlay}
|
||||
/>
|
||||
</div>
|
||||
{/* 下一首 */}
|
||||
<PlayerButton size={24} img="/img/audio-player/next.svg" onClick={onNext} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// 播放器按钮
|
||||
|
||||
'use client';
|
||||
|
||||
interface Props {
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function ControlButton({ children, className, onClick }: Props) {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
className={`block w-fit h-fit ${className}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
{!!children && children}
|
||||
</a>
|
||||
);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import ButtonProvide from './ButtonProvide';
|
||||
|
||||
export default function Next({ className, onClick }: { className?: string; onClick: () => void }) {
|
||||
return (
|
||||
<ButtonProvide className={className} onClick={onClick}>
|
||||
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" className="group">
|
||||
<path
|
||||
className="fill-#000/70 group-hover:fill-#000/95 transition-all"
|
||||
d="M19.8333 5.36009C19.8333 5.02405 19.8333 4.85604 19.8987 4.72769C19.9562 4.61479 20.048 4.52301 20.1609 4.46548C20.2893 4.40009 20.4573 4.40009 20.7933 4.40009H22.3733C22.7094 4.40009 22.8774 4.40009 23.0057 4.46548C23.1186 4.52301 23.2104 4.61479 23.2679 4.72769C23.3333 4.85604 23.3333 5.02405 23.3333 5.36009V22.6401C23.3333 22.9761 23.3333 23.1441 23.2679 23.2725C23.2104 23.3854 23.1186 23.4772 23.0057 23.5347C22.8774 23.6001 22.7094 23.6001 22.3733 23.6001H20.7933C20.4573 23.6001 20.2893 23.6001 20.1609 23.5347C20.048 23.4772 19.9562 23.3854 19.8987 23.2725C19.8333 23.1441 19.8333 22.9761 19.8333 22.6401V5.36009Z"
|
||||
/>
|
||||
<path
|
||||
className="fill-#000/70 group-hover:fill-#000/95 transition-all"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.2415 13.0134C17.9308 13.4906 17.9308 14.5095 17.2415 14.9867L5.38306 23.1964C4.58718 23.7474 3.50001 23.1778 3.50001 22.2098L3.50001 5.79034C3.50001 4.82235 4.58718 4.25272 5.38306 4.80371L17.2415 13.0134Z"
|
||||
/>
|
||||
</svg>
|
||||
</ButtonProvide>
|
||||
);
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import ButtonProvide from './ButtonProvide';
|
||||
|
||||
export default function Order({
|
||||
order,
|
||||
className,
|
||||
onClick,
|
||||
}: {
|
||||
order: PlayOrder;
|
||||
className?: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
const orderList: Array<{ value: string; label: string; icon: ReactElement }> = [
|
||||
{ value: 'list_loop', label: '列表循环', icon: <ListLoop /> },
|
||||
{ value: 'random', label: '随机播放', icon: <Random /> },
|
||||
{ value: 'single_loop', label: '单曲循环', icon: <SingleLoop /> },
|
||||
];
|
||||
|
||||
// return orderList.map((item) => (
|
||||
// <ButtonProvide className={className} key={item.value} onClick={onClick}>
|
||||
// {item.icon}
|
||||
// </ButtonProvide>
|
||||
// ));
|
||||
|
||||
return (
|
||||
<ButtonProvide className={className} onClick={onClick}>
|
||||
{orderList.find((item) => item.value === order)?.icon ?? null}
|
||||
</ButtonProvide>
|
||||
);
|
||||
}
|
||||
|
||||
const ListLoop = () => (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="group">
|
||||
<path
|
||||
className="fill-[rgba(0,0,0,70)] group-hover:fill-[rgba(0,0,0,0.95)] transition-all"
|
||||
fill-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M18.5303 1.46967C18.2374 1.17678 17.7626 1.17678 17.4697 1.46967C17.1768 1.76256 17.1768 2.23744 17.4697 2.53033L19.1893 4.25H8C4.82436 4.25 2.25 6.82436 2.25 10V12C2.25 12.4142 2.58579 12.75 3 12.75C3.41421 12.75 3.75 12.4142 3.75 12V10C3.75 7.65279 5.65279 5.75 8 5.75H19.1893L17.4697 7.46967C17.1768 7.76256 17.1768 8.23744 17.4697 8.53033C17.7626 8.82322 18.2374 8.82322 18.5303 8.53033L21.5303 5.53033C21.6768 5.38388 21.75 5.19194 21.75 5C21.75 4.89831 21.7298 4.80134 21.6931 4.71291C21.6565 4.62445 21.6022 4.54158 21.5303 4.46967L18.5303 1.46967Z"
|
||||
/>
|
||||
<path
|
||||
className="fill-[rgba(0,0,0,70)] group-hover:fill-[rgba(0,0,0,0.95)] transition-all"
|
||||
fill-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.46967 22.5303C5.76256 22.8232 6.23744 22.8232 6.53033 22.5303C6.82322 22.2374 6.82322 21.7626 6.53033 21.4697L4.81066 19.75H16C19.1756 19.75 21.75 17.1756 21.75 14V12C21.75 11.5858 21.4142 11.25 21 11.25C20.5858 11.25 20.25 11.5858 20.25 12V14C20.25 16.3472 18.3472 18.25 16 18.25H4.81066L6.53033 16.5303C6.82322 16.2374 6.82322 15.7626 6.53033 15.4697C6.23744 15.1768 5.76256 15.1768 5.46967 15.4697L2.46967 18.4697C2.32322 18.6161 2.25 18.8081 2.25 19C2.25 19.1017 2.27024 19.1987 2.30691 19.2871C2.34351 19.3755 2.39776 19.4584 2.46967 19.5303L5.46967 22.5303Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const Random = () => (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="group">
|
||||
<path
|
||||
className="stroke-[rgba(0,0,0,0.7)] group-hover:stroke-[rgba(0,0,0,0.95)] transition-all"
|
||||
d="M15.5 3H20.5V8"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="stroke-[rgba(0,0,0,0.7)] group-hover:stroke-[rgba(0,0,0,0.95)] transition-all"
|
||||
d="M3.5 20L20.5 3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="stroke-[rgba(0,0,0,0.7)] group-hover:stroke-[rgba(0,0,0,0.95)] transition-all"
|
||||
d="M20.5 16V21H15.5"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="stroke-[rgba(0,0,0,0.7)] group-hover:stroke-[rgba(0,0,0,0.95)] transition-all"
|
||||
d="M14.5 15L20.5 21"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="stroke-[rgba(0,0,0,0.7)] group-hover:stroke-[rgba(0,0,0,0.95)] transition-all"
|
||||
d="M3.5 4L8.5 9"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SingleLoop = () => (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="group">
|
||||
<path
|
||||
className="fill-[rgba(0,0,0,70)] group-hover:fill-[rgba(0,0,0,0.95)] transition-all"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M18.5303 1.46967C18.2374 1.17678 17.7626 1.17678 17.4697 1.46967C17.1768 1.76256 17.1768 2.23744 17.4697 2.53033L19.1893 4.25H8C4.82436 4.25 2.25 6.82436 2.25 10V16C2.25 19.1756 4.82436 21.75 8 21.75H16C19.1757 21.75 21.75 19.1755 21.75 15.9999V14.5C21.75 14.0858 21.4142 13.75 21 13.75C20.5858 13.75 20.25 14.0858 20.25 14.5V15.9999C20.25 18.3471 18.3472 20.25 16 20.25H8C5.65279 20.25 3.75 18.3472 3.75 16V10C3.75 7.65279 5.65279 5.75 8 5.75H19.1893L17.4697 7.46967C17.1768 7.76256 17.1768 8.23744 17.4697 8.53033C17.7626 8.82322 18.2374 8.82322 18.5303 8.53033L21.5303 5.53033C21.6768 5.38388 21.75 5.19194 21.75 5C21.75 4.89831 21.7298 4.80134 21.6931 4.71291C21.6565 4.62445 21.6022 4.54158 21.5303 4.46967L18.5303 1.46967Z"
|
||||
/>
|
||||
<path
|
||||
className="fill-[rgba(0,0,0,70)] group-hover:fill-[rgba(0,0,0,0.95)] transition-all"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.6707 9.12895C12.8768 9.26848 13.0002 9.50114 13.0002 9.75V16.25C13.0002 16.6642 12.6644 17 12.2502 17C11.836 17 11.5002 16.6642 11.5002 16.25V10.8578L10.0287 11.4464C9.64415 11.6002 9.20768 11.4131 9.05384 11.0285C8.90001 10.6439 9.08707 10.2075 9.47166 10.0536L11.9717 9.05365C12.2027 8.96122 12.4646 8.98943 12.6707 9.12895Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as Slider from '@radix-ui/react-slider';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
export default function ProgressBar({
|
||||
value,
|
||||
duration,
|
||||
className,
|
||||
onChange,
|
||||
}: {
|
||||
value: number;
|
||||
duration: number;
|
||||
className?: string;
|
||||
onChange: (value: number) => void;
|
||||
}) {
|
||||
const handleChange = (value: number) => {
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Slider.Root
|
||||
value={[value]}
|
||||
max={duration}
|
||||
className={`${styles.ProgressBarRoot} ${className}`}
|
||||
onValueChange={(value: number[]) => handleChange(value[0])}
|
||||
>
|
||||
<Slider.Track className={`${styles.ProgressBarTrack}`}>
|
||||
<Slider.Range className={`${styles.ProgressBarRange}`} />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className={`${styles.ProgressBarThumb}`} />
|
||||
</Slider.Root>
|
||||
);
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import ButtonProvide from './ButtonProvide';
|
||||
import VolumeBar from './VolumeBar';
|
||||
|
||||
export default function Volume({
|
||||
value,
|
||||
className,
|
||||
onChange,
|
||||
}: {
|
||||
value: number;
|
||||
className?: string;
|
||||
onChange: (value: number) => void;
|
||||
}) {
|
||||
const [hover, setHover] = useState<boolean>(false);
|
||||
|
||||
const handleClick = () => {
|
||||
onChange(value > 0 ? 0 : 50);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col justify-between relative w-22px cursor-pointer ${hover ? 'h-173px' : 'h-22px'} ${className}`}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<VolumeBar value={value} className={`${!hover && '!hidden'} transition-all`} onChange={onChange} />
|
||||
|
||||
<ButtonProvide className="group" onClick={handleClick}>
|
||||
{value >= 50 ? (
|
||||
<VolumeFull hover={hover} />
|
||||
) : value === 0 ? (
|
||||
<VolumeOff hover={hover} />
|
||||
) : (
|
||||
<VolumeLow hover={hover} />
|
||||
)}
|
||||
</ButtonProvide>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const VolumeLow = ({ hover }: { hover: boolean }) => (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
className="transition-all"
|
||||
d="M11 4L6 8.57143H2V15.4286H6L11 20V4Z"
|
||||
stroke={hover ? 'rgba(0,0,0,0.95)' : 'rgba(0,0,0,0.7)'}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="transition-all"
|
||||
d="M15.54 8.46C16.4773 9.39764 17.0039 10.6692 17.0039 11.995C17.0039 13.3208 16.4773 14.5924 15.54 15.53"
|
||||
stroke="black"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const VolumeOff = ({ hover }: { hover: boolean }) => (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
className="transition-all"
|
||||
d="M11 4L6 8.57143H2V15.4286H6L11 20V4Z"
|
||||
stroke={hover ? 'rgba(0,0,0,0.95)' : 'rgba(0,0,0,0.7)'}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="transition-all"
|
||||
d="M23 9L17 15"
|
||||
stroke={hover ? 'rgba(0,0,0,0.95)' : 'rgba(0,0,0,0.7)'}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="transition-all"
|
||||
d="M17 9L23 15"
|
||||
stroke={hover ? 'rgba(0,0,0,0.95)' : 'rgba(0,0,0,0.7)'}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const VolumeFull = ({ hover }: { hover: boolean }) => (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
className="transition-all"
|
||||
d="M11 4L6 8.57143H2V15.4286H6L11 20V4Z"
|
||||
stroke={hover ? 'rgba(0,0,0,0.95)' : 'rgba(0,0,0,0.7)'}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="transition-all"
|
||||
d="M19.07 4.93C20.9447 6.80528 21.9979 9.34836 21.9979 12C21.9979 14.6516 20.9447 17.1947 19.07 19.07M15.54 8.46C16.4774 9.39764 17.0039 10.6692 17.0039 11.995C17.0039 13.3208 16.4774 14.5924 15.54 15.53"
|
||||
stroke={hover ? 'rgba(0,0,0,0.95)' : 'rgba(0,0,0,0.7)'}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
@ -0,0 +1,31 @@
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
export default function Volumne({
|
||||
value,
|
||||
className,
|
||||
onChange,
|
||||
}: {
|
||||
value: number;
|
||||
className?: string;
|
||||
onChange: (value: number) => void;
|
||||
}) {
|
||||
const handleChange = (value: number) => {
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
value={[value]}
|
||||
orientation="vertical"
|
||||
className={`${styles.VolumeBarRoot} ${className}`}
|
||||
onValueChange={(value: number[]) => handleChange(value[0])}
|
||||
>
|
||||
<SliderPrimitive.Track className={`${styles.VolumeBarTrack}`}>
|
||||
<SliderPrimitive.Range className={`${styles.VolumeBarRange}`} />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className={`${styles.VolumeBarThumb}`} />
|
||||
</SliderPrimitive.Root>
|
||||
);
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/* 播放进度条 */
|
||||
.ProgressBarRoot {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
width: 718px;
|
||||
height: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ProgressBarTrack {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.ProgressBarRange {
|
||||
position: absolute;
|
||||
background-color: #181818;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.ProgressBarThumb {
|
||||
display: block;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
background-color: #181818;
|
||||
border-radius: 50%;
|
||||
outline: none;
|
||||
}
|
||||
.ProgressBarThumb:hover {
|
||||
/* background-color: var(--violet-3); */
|
||||
}
|
||||
|
||||
/* 音量条 */
|
||||
.VolumeBarRoot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
.VolumeBarRoot[data-orientation='vertical'] {
|
||||
flex-direction: column;
|
||||
width: 20px;
|
||||
height: 100px;
|
||||
border-radius: 37px;
|
||||
}
|
||||
|
||||
/* 底色 */
|
||||
.VolumeBarTrack {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.VolumeBarTrack[data-orientation='vertical'] {
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
/* 激活区域 */
|
||||
.VolumeBarRange {
|
||||
position: absolute;
|
||||
background-color: #646464;
|
||||
}
|
||||
.VolumeBarRange[data-orientation='vertical'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 滑块 */
|
||||
.VolumeBarThumb {
|
||||
display: block;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 50%;
|
||||
background-color: #646464;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.VolumeBarRoot:hover .VolumeBarRange {
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
.VolumeBarRoot:hover .VolumeBarThumb {
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import ButtonProvide from '../AudioPlayer/widget/ButtonProvide';
|
||||
|
||||
export default function PlayButton({
|
||||
playState,
|
||||
size,
|
||||
className,
|
||||
onClick,
|
||||
}: {
|
||||
playState: boolean;
|
||||
size: number;
|
||||
className?: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<ButtonProvide className={`group ${className}`} onClick={onClick}>
|
||||
{playState ? (
|
||||
<svg width={size} height={size} viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="27" cy="27" r="27" className={`fill-brand group-hover:fill-#B13232 transition-all`} />
|
||||
<rect x="20.7" y="18.9" width="4.5" height="16.2" rx="0.736" fill="white" />
|
||||
<rect x="28.8" y="18.9" width="4.5" height="16.2" rx="0.736" fill="white" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width={size} height={size} viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="27" cy="27" r="27" className={`fill-brand group-hover:fill-#B13232 transition-all`} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20.0103 10.2691C21.3409 11.0395 21.3409 12.9605 20.0103 13.7309L7.00207 21.262C5.66874 22.0339 4 21.0718 4 19.5311L4 4.4689C4 2.92823 5.66874 1.96611 7.00207 2.73804L20.0103 10.2691Z"
|
||||
fill="white"
|
||||
fillOpacity="0.95"
|
||||
transform="translate(15, 15)"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</ButtonProvide>
|
||||
);
|
||||
}
|
Loading…
Reference in new issue