mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 08:10:25 +00:00
Remove music player and related context/hooks
Deleted the audio player component, songs context, and use-music hook, along with all related code and configuration. Updated affected components and pages to remove music player dependencies and UI. Also improved sidebar, background, and about page UI, and refactored site config icons to use react-icons.
This commit is contained in:
@@ -1,425 +0,0 @@
|
||||
import { Button } from '@heroui/button';
|
||||
import { Card, CardBody, CardHeader } from '@heroui/card';
|
||||
import { Image } from '@heroui/image';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@heroui/popover';
|
||||
import { Slider } from '@heroui/slider';
|
||||
import { Tooltip } from '@heroui/tooltip';
|
||||
import { useLocalStorage } from '@uidotdev/usehooks';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
BiSolidSkipNextCircle,
|
||||
BiSolidSkipPreviousCircle,
|
||||
} from 'react-icons/bi';
|
||||
import {
|
||||
FaPause,
|
||||
FaPlay,
|
||||
FaRegHandPointRight,
|
||||
FaRepeat,
|
||||
FaShuffle,
|
||||
} from 'react-icons/fa6';
|
||||
import { TbRepeatOnce } from 'react-icons/tb';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import { PlayMode } from '@/const/enum';
|
||||
import key from '@/const/key';
|
||||
|
||||
import { VolumeHighIcon, VolumeLowIcon } from './icons';
|
||||
|
||||
export interface AudioPlayerProps
|
||||
extends React.AudioHTMLAttributes<HTMLAudioElement> {
|
||||
src: string
|
||||
title?: string
|
||||
artist?: string
|
||||
cover?: string
|
||||
pressNext?: () => void
|
||||
pressPrevious?: () => void
|
||||
onPlayEnd?: () => void
|
||||
onChangeMode?: (mode: PlayMode) => void
|
||||
mode?: PlayMode
|
||||
}
|
||||
|
||||
export default function AudioPlayer (props: AudioPlayerProps) {
|
||||
const {
|
||||
src,
|
||||
pressNext,
|
||||
pressPrevious,
|
||||
cover = 'https://nextui.org/images/album-cover.png',
|
||||
title = '未知',
|
||||
artist = '未知',
|
||||
onTimeUpdate,
|
||||
onLoadedData,
|
||||
onPlay,
|
||||
onPause,
|
||||
onPlayEnd,
|
||||
onChangeMode,
|
||||
autoPlay,
|
||||
mode = PlayMode.Loop,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [volume, setVolume] = useState(100);
|
||||
const [isCollapsed, setIsCollapsed] = useLocalStorage(
|
||||
key.isCollapsedMusicPlayer,
|
||||
false
|
||||
);
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const startY = useRef(0);
|
||||
const startX = useRef(0);
|
||||
const [translateY, setTranslateY] = useState(0);
|
||||
const [translateX, setTranslateX] = useState(0);
|
||||
const isSmallScreen = useMediaQuery({ maxWidth: 767 });
|
||||
const isMediumUp = useMediaQuery({ minWidth: 768 });
|
||||
const shouldAdd = useRef(false);
|
||||
const currentProgress = (currentTime / duration) * 100;
|
||||
const [storageAutoPlay, setStorageAutoPlay] = useLocalStorage(
|
||||
key.autoPlay,
|
||||
true
|
||||
);
|
||||
|
||||
const handleTimeUpdate = (event: React.SyntheticEvent<HTMLAudioElement>) => {
|
||||
const audio = event.target as HTMLAudioElement;
|
||||
setCurrentTime(audio.currentTime);
|
||||
onTimeUpdate?.(event);
|
||||
};
|
||||
|
||||
const handleLoadedData = (event: React.SyntheticEvent<HTMLAudioElement>) => {
|
||||
const audio = event.target as HTMLAudioElement;
|
||||
setDuration(audio.duration);
|
||||
onLoadedData?.(event);
|
||||
};
|
||||
|
||||
const handlePlay = (e: React.SyntheticEvent<HTMLAudioElement>) => {
|
||||
setIsPlaying(true);
|
||||
setStorageAutoPlay(true);
|
||||
onPlay?.(e);
|
||||
};
|
||||
|
||||
const handlePause = (e: React.SyntheticEvent<HTMLAudioElement>) => {
|
||||
setIsPlaying(false);
|
||||
onPause?.(e);
|
||||
};
|
||||
|
||||
const changeMode = () => {
|
||||
const modes = [PlayMode.Loop, PlayMode.Random, PlayMode.Single];
|
||||
const currentIndex = modes.findIndex((_mode) => _mode === mode);
|
||||
const nextIndex = currentIndex + 1;
|
||||
const nextMode = modes[nextIndex] || modes[0];
|
||||
onChangeMode?.(nextMode);
|
||||
};
|
||||
|
||||
const volumeChange = (value: number) => {
|
||||
setVolume(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current;
|
||||
if (audio) {
|
||||
audio.volume = volume / 100;
|
||||
}
|
||||
}, [volume]);
|
||||
|
||||
const handleTouchStart = (e: React.TouchEvent) => {
|
||||
startY.current = e.touches[0].clientY;
|
||||
startX.current = e.touches[0].clientX;
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent) => {
|
||||
const deltaY = e.touches[0].clientY - startY.current;
|
||||
const deltaX = e.touches[0].clientX - startX.current;
|
||||
const container = cardRef.current;
|
||||
const header = cardRef.current?.querySelector('[data-header]');
|
||||
const headerHeight = header?.clientHeight || 20;
|
||||
const addHeight = (container?.clientHeight || headerHeight) - headerHeight;
|
||||
const _shouldAdd = isCollapsed && deltaY < 0;
|
||||
if (isSmallScreen) {
|
||||
shouldAdd.current = _shouldAdd;
|
||||
setTranslateY(_shouldAdd ? deltaY + addHeight : deltaY);
|
||||
} else {
|
||||
setTranslateX(deltaX);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
if (isSmallScreen) {
|
||||
const container = cardRef.current;
|
||||
const header = cardRef.current?.querySelector('[data-header]');
|
||||
const headerHeight = header?.clientHeight || 20;
|
||||
const addHeight = (container?.clientHeight || headerHeight) - headerHeight;
|
||||
const _translateY = translateY - (shouldAdd.current ? addHeight : 0);
|
||||
if (_translateY > 100) {
|
||||
setIsCollapsed(true);
|
||||
} else if (_translateY < -100) {
|
||||
setIsCollapsed(false);
|
||||
}
|
||||
setTranslateY(0);
|
||||
} else {
|
||||
if (translateX > 100) {
|
||||
setIsCollapsed(true);
|
||||
} else if (translateX < -100) {
|
||||
setIsCollapsed(false);
|
||||
}
|
||||
setTranslateX(0);
|
||||
}
|
||||
};
|
||||
|
||||
const dragTranslate = isSmallScreen
|
||||
? translateY
|
||||
? `translateY(${translateY}px)`
|
||||
: ''
|
||||
: translateX
|
||||
? `translateX(${translateX}px)`
|
||||
: '';
|
||||
const collapsedTranslate = isCollapsed
|
||||
? isSmallScreen
|
||||
? 'translateY(90%)'
|
||||
: 'translateX(96%)'
|
||||
: '';
|
||||
|
||||
const translateStyle = dragTranslate || collapsedTranslate;
|
||||
|
||||
if (!src) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'fixed right-0 bottom-0 z-[52] w-full md:w-96',
|
||||
!translateX && !translateY && 'transition-transform',
|
||||
isCollapsed && 'md:hover:!translate-x-80'
|
||||
)}
|
||||
style={{
|
||||
transform: translateStyle,
|
||||
}}
|
||||
>
|
||||
<audio
|
||||
src={src}
|
||||
onLoadedData={handleLoadedData}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onPlay={handlePlay}
|
||||
onPause={handlePause}
|
||||
onEnded={onPlayEnd}
|
||||
autoPlay={autoPlay ?? storageAutoPlay}
|
||||
{...rest}
|
||||
controls={false}
|
||||
hidden
|
||||
ref={audioRef}
|
||||
/>
|
||||
|
||||
<Card
|
||||
ref={cardRef}
|
||||
className={clsx(
|
||||
'border-none bg-background/60 dark:bg-default-300/50 w-full max-w-full transform transition-transform backdrop-blur-md duration-300 overflow-visible',
|
||||
isSmallScreen ? 'rounded-t-3xl' : 'md:rounded-l-xl'
|
||||
)}
|
||||
classNames={{
|
||||
body: 'p-0',
|
||||
}}
|
||||
shadow='sm'
|
||||
radius='none'
|
||||
>
|
||||
{isMediumUp && (
|
||||
<Button
|
||||
isIconOnly
|
||||
className={clsx(
|
||||
'absolute data-[hover]:bg-foreground/10 text-lg z-50',
|
||||
isCollapsed
|
||||
? 'top-0 left-0 w-full h-full rounded-xl bg-opacity-0 hover:bg-opacity-30'
|
||||
: 'top-3 -left-8 rounded-l-full bg-opacity-50 backdrop-blur-md'
|
||||
)}
|
||||
variant='solid'
|
||||
color='primary'
|
||||
size='sm'
|
||||
onPress={() => setIsCollapsed(!isCollapsed)}
|
||||
>
|
||||
<FaRegHandPointRight />
|
||||
</Button>
|
||||
)}
|
||||
{isSmallScreen && (
|
||||
<CardHeader
|
||||
data-header
|
||||
className='flex-row justify-center pt-4'
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
>
|
||||
<div className='w-24 h-2 rounded-full bg-content2-foreground shadow-sm' />
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardBody>
|
||||
<div className='grid grid-cols-6 md:grid-cols-12 gap-6 md:gap-4 items-center justify-center overflow-hidden p-6 md:p-2 m-0'>
|
||||
<div className='relative col-span-6 md:col-span-4 flex justify-center'>
|
||||
<Image
|
||||
alt='Album cover'
|
||||
className='object-cover'
|
||||
classNames={{
|
||||
wrapper: 'w-36 aspect-square md:w-24 flex',
|
||||
img: 'block w-full h-full',
|
||||
}}
|
||||
shadow='md'
|
||||
src={cover}
|
||||
width='100%'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col col-span-6 md:col-span-8'>
|
||||
<div className='flex flex-col gap-0'>
|
||||
<h1 className='font-medium truncate'>{title}</h1>
|
||||
<p className='text-xs text-foreground/80 truncate'>{artist}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col'>
|
||||
<Slider
|
||||
aria-label='Music progress'
|
||||
classNames={{
|
||||
track: 'bg-default-500/30 border-none',
|
||||
thumb: 'w-2 h-2 after:w-1.5 after:h-1.5',
|
||||
filler: 'rounded-full',
|
||||
}}
|
||||
color='foreground'
|
||||
value={currentProgress || 0}
|
||||
defaultValue={0}
|
||||
size='sm'
|
||||
onChange={(value) => {
|
||||
value = Array.isArray(value) ? value[0] : value;
|
||||
const audio = audioRef.current;
|
||||
if (audio) {
|
||||
audio.currentTime = (value / 100) * duration;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className='flex justify-between h-3'>
|
||||
<p className='text-xs'>
|
||||
{Math.floor(currentTime / 60)}:
|
||||
{Math.floor(currentTime % 60)
|
||||
.toString()
|
||||
.padStart(2, '0')}
|
||||
</p>
|
||||
<p className='text-xs text-foreground/50'>
|
||||
{Math.floor(duration / 60)}:
|
||||
{Math.floor(duration % 60)
|
||||
.toString()
|
||||
.padStart(2, '0')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex w-full items-center justify-center'>
|
||||
<Tooltip
|
||||
content={
|
||||
mode === PlayMode.Loop
|
||||
? '列表循环'
|
||||
: mode === PlayMode.Random
|
||||
? '随机播放'
|
||||
: '单曲循环'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
isIconOnly
|
||||
className='data-[hover]:bg-foreground/10 text-lg md:text-medium'
|
||||
radius='full'
|
||||
variant='light'
|
||||
size='md'
|
||||
onPress={changeMode}
|
||||
>
|
||||
{mode === PlayMode.Loop && (
|
||||
<FaRepeat className='text-foreground/80' />
|
||||
)}
|
||||
{mode === PlayMode.Random && (
|
||||
<FaShuffle className='text-foreground/80' />
|
||||
)}
|
||||
{mode === PlayMode.Single && (
|
||||
<TbRepeatOnce className='text-foreground/80 text-xl' />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content='上一首'>
|
||||
<Button
|
||||
isIconOnly
|
||||
className='data-[hover]:bg-foreground/10 text-2xl md:text-xl'
|
||||
radius='full'
|
||||
variant='light'
|
||||
size='md'
|
||||
onPress={pressPrevious}
|
||||
>
|
||||
<BiSolidSkipPreviousCircle />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content={isPlaying ? '暂停' : '播放'}>
|
||||
<Button
|
||||
isIconOnly
|
||||
className='data-[hover]:bg-foreground/10 text-3xl md:text-3xl'
|
||||
radius='full'
|
||||
variant='light'
|
||||
size='lg'
|
||||
onPress={() => {
|
||||
if (isPlaying) {
|
||||
audioRef.current?.pause();
|
||||
setStorageAutoPlay(false);
|
||||
} else {
|
||||
audioRef.current?.play();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isPlaying ? <FaPause /> : <FaPlay className='ml-1' />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content='下一首'>
|
||||
<Button
|
||||
isIconOnly
|
||||
className='data-[hover]:bg-foreground/10 text-2xl md:text-xl'
|
||||
radius='full'
|
||||
variant='light'
|
||||
size='md'
|
||||
onPress={pressNext}
|
||||
>
|
||||
<BiSolidSkipNextCircle />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
placement='top'
|
||||
classNames={{
|
||||
content: 'bg-opacity-30 backdrop-blur-md',
|
||||
}}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
isIconOnly
|
||||
className='data-[hover]:bg-foreground/10 text-xl md:text-xl'
|
||||
radius='full'
|
||||
variant='light'
|
||||
size='md'
|
||||
>
|
||||
<VolumeHighIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<Slider
|
||||
orientation='vertical'
|
||||
showTooltip
|
||||
aria-label='Volume'
|
||||
className='h-40'
|
||||
color='primary'
|
||||
defaultValue={volume}
|
||||
onChange={(value) => {
|
||||
value = Array.isArray(value) ? value[0] : value;
|
||||
volumeChange(value);
|
||||
}}
|
||||
startContent={<VolumeHighIcon className='text-2xl' />}
|
||||
size='sm'
|
||||
endContent={<VolumeLowIcon className='text-2xl' />}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ const HoverEffectCard: React.FC<HoverEffectCardProps> = (props) => {
|
||||
ref={lightRef}
|
||||
className={clsx(
|
||||
isShowLight ? 'opacity-100' : 'opacity-0',
|
||||
'absolute rounded-full blur-[150px] filter transition-opacity duration-300 dark:bg-[#2850ff] bg-[#ff4132] w-[100px] h-[100px]',
|
||||
'absolute rounded-full blur-[100px] filter transition-opacity duration-300 bg-gradient-to-r from-primary-400 to-secondary-400 w-[150px] h-[150px]',
|
||||
lightClassName
|
||||
)}
|
||||
style={{
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
import { Image } from '@heroui/image';
|
||||
|
||||
import bkg_color from '@/assets/images/bkg-color.png';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
const PageBackground = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='fixed w-full h-full -z-[0] flex justify-end opacity-80'>
|
||||
<Image
|
||||
className='overflow-hidden object-contain -top-42 h-[160%] -right-[30%] -rotate-45 pointer-events-none select-none -z-10 relative'
|
||||
src={bkg_color}
|
||||
/>
|
||||
</div>
|
||||
<div className='fixed w-full h-full overflow-hidden -z-[0] hue-rotate-90 flex justify-start opacity-80'>
|
||||
<Image
|
||||
className='relative -top-92 h-[180%] object-contain pointer-events-none rotate-90 select-none -z-10 top-44'
|
||||
src={bkg_color}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div className='fixed inset-0 w-full h-full -z-10 overflow-hidden bg-gradient-to-br from-indigo-50 via-white to-pink-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900'>
|
||||
{/* 动态呼吸光斑 - ACG风格 */}
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
rotate: [0, 90, 0],
|
||||
opacity: [0.3, 0.5, 0.3]
|
||||
}}
|
||||
transition={{ duration: 15, repeat: Infinity, ease: "easeInOut" }}
|
||||
className='absolute top-[-10%] left-[-10%] w-[500px] h-[500px] rounded-full bg-primary-200/40 blur-[100px]'
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
x: [0, 100, 0],
|
||||
opacity: [0.3, 0.6, 0.3]
|
||||
}}
|
||||
transition={{ duration: 18, repeat: Infinity, ease: "easeInOut", delay: 2 }}
|
||||
className='absolute top-[20%] right-[-10%] w-[400px] h-[400px] rounded-full bg-secondary-200/40 blur-[90px]'
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.1, 1],
|
||||
y: [0, -50, 0],
|
||||
opacity: [0.2, 0.4, 0.2]
|
||||
}}
|
||||
transition={{ duration: 12, repeat: Infinity, ease: "easeInOut", delay: 5 }}
|
||||
className='absolute bottom-[-10%] left-[20%] w-[600px] h-[600px] rounded-full bg-pink-200/30 blur-[110px]'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button } from '@heroui/button';
|
||||
import { Image } from '@heroui/image';
|
||||
import clsx from 'clsx';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import React from 'react';
|
||||
@@ -10,15 +9,13 @@ import useAuth from '@/hooks/auth';
|
||||
import useDialog from '@/hooks/use-dialog';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
|
||||
import logo from '@/assets/images/logo.png';
|
||||
import type { MenuItem } from '@/config/site';
|
||||
|
||||
import Menus from './menus';
|
||||
|
||||
interface SideBarProps {
|
||||
open: boolean
|
||||
items: MenuItem[]
|
||||
onClose?: () => void
|
||||
open: boolean;
|
||||
items: MenuItem[];
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const SideBar: React.FC<SideBarProps> = (props) => {
|
||||
@@ -61,40 +58,33 @@ const SideBar: React.FC<SideBarProps> = (props) => {
|
||||
}}
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
<motion.div className='w-64 flex flex-col items-stretch h-full transition-transform duration-300 ease-in-out z-30 relative float-right'>
|
||||
<div className='flex justify-center items-center my-2 gap-2'>
|
||||
<Image radius='none' height={40} src={logo} className='mb-2' />
|
||||
<div
|
||||
className={clsx(
|
||||
'flex items-center font-bold',
|
||||
'!text-2xl shiny-text'
|
||||
)}
|
||||
>
|
||||
<motion.div className='w-64 flex flex-col items-stretch h-full transition-transform duration-300 ease-in-out z-30 relative float-right p-4'>
|
||||
<div className='flex items-center justify-start gap-3 px-2 my-8 ml-2'>
|
||||
<div className="h-5 w-1 bg-primary rounded-full shadow-sm" />
|
||||
<div className="text-xl font-bold text-default-900 dark:text-white tracking-wide select-none">
|
||||
NapCat
|
||||
</div>
|
||||
</div>
|
||||
<div className='overflow-y-auto flex flex-col flex-1 px-4'>
|
||||
<div className='overflow-y-auto flex flex-col flex-1 px-2'>
|
||||
<Menus items={items} />
|
||||
<div className='mt-auto mb-10 md:mb-0'>
|
||||
<div className='mt-auto mb-10 md:mb-0 space-y-3 px-2'>
|
||||
<Button
|
||||
className='w-full'
|
||||
color='primary'
|
||||
className='w-full bg-primary-50/50 hover:bg-primary-100/80 text-primary-600 font-medium shadow-sm hover:shadow-md transition-all duration-300 backdrop-blur-sm'
|
||||
radius='full'
|
||||
variant='light'
|
||||
variant='flat'
|
||||
onPress={toggleTheme}
|
||||
startContent={
|
||||
!isDark ? <MdLightMode size={16} /> : <MdDarkMode size={16} />
|
||||
!isDark ? <MdLightMode size={18} /> : <MdDarkMode size={18} />
|
||||
}
|
||||
>
|
||||
切换主题
|
||||
</Button>
|
||||
<Button
|
||||
className='w-full mb-2'
|
||||
color='primary'
|
||||
className='w-full mb-2 bg-danger-50/50 hover:bg-danger-100/80 text-danger-500 font-medium shadow-sm hover:shadow-md transition-all duration-300 backdrop-blur-sm'
|
||||
radius='full'
|
||||
variant='light'
|
||||
variant='flat'
|
||||
onPress={onRevokeAuth}
|
||||
startContent={<IoMdLogOut size={16} />}
|
||||
startContent={<IoMdLogOut size={18} />}
|
||||
>
|
||||
退出登录
|
||||
</Button>
|
||||
|
||||
@@ -50,12 +50,13 @@ const renderItems = (items: MenuItem[], children = false) => {
|
||||
<div key={item.href + item.label}>
|
||||
<Button
|
||||
className={clsx(
|
||||
'flex items-center w-full text-left justify-start dark:text-white',
|
||||
// children && 'rounded-l-lg',
|
||||
isActive && 'bg-opacity-60',
|
||||
'flex items-center w-full text-left justify-start dark:text-white transition-all duration-300',
|
||||
isActive
|
||||
? 'bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-400 shadow-none font-semibold translate-x-1'
|
||||
: 'hover:bg-default-100 hover:translate-x-1',
|
||||
b64img && 'backdrop-blur-md text-white'
|
||||
)}
|
||||
color='primary'
|
||||
color={isActive ? 'primary' : 'default'}
|
||||
endContent={
|
||||
canOpen
|
||||
? (
|
||||
@@ -104,7 +105,6 @@ const renderItems = (items: MenuItem[], children = false) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
radius='full'
|
||||
startContent={
|
||||
customIcons[item.label]
|
||||
? (
|
||||
@@ -147,7 +147,7 @@ const renderItems = (items: MenuItem[], children = false) => {
|
||||
};
|
||||
|
||||
interface MenusProps {
|
||||
items: MenuItem[]
|
||||
items: MenuItem[];
|
||||
}
|
||||
const Menus: React.FC<MenusProps> = (props) => {
|
||||
const { items } = props;
|
||||
|
||||
Reference in New Issue
Block a user