feat(music-player): add minimize feature with floating icon and centered controls

Layout Changes:
- Center all control buttons (shuffle, prev, play/pause, next, repeat)
- Center progress bar alongside controls
- Keep volume control + close button on the right
- Song info remains on the left

New Feature - Minimize Player:
- Add isMinimized state to track player visibility
- Replace close button with minimize functionality
- Show floating music icon when minimized (bottom-right corner)
- Click floating icon to restore player bar
- Floating icon has hover scale animation for better UX

UI/UX Improvements:
- Better visual hierarchy with centered controls
- Floating icon uses blue bg with white music icon
- Smooth transitions between states
- Icon scales on hover for interactive feedback
- Persistent player state (song continues playing when minimized)

Files changed:
- src/app/darmasaba/_com/FixedPlayerBar.tsx: Complete redesign

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-05 14:14:46 +08:00
parent ce46d3b5f7
commit ee39b88b00

View File

@@ -12,6 +12,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { import {
IconArrowsShuffle, IconArrowsShuffle,
IconMusic,
IconPlayerPauseFilled, IconPlayerPauseFilled,
IconPlayerPlayFilled, IconPlayerPlayFilled,
IconPlayerSkipBackFilled, IconPlayerSkipBackFilled,
@@ -45,7 +46,7 @@ export default function FixedPlayerBar() {
} = useMusic(); } = useMusic();
const [showVolume, setShowVolume] = useState(false); const [showVolume, setShowVolume] = useState(false);
const [isPlayerVisible, setIsPlayerVisible] = useState(true); const [isMinimized, setIsMinimized] = useState(false);
// Format time // Format time
const formatTime = (seconds: number) => { const formatTime = (seconds: number) => {
@@ -69,12 +70,52 @@ export default function FixedPlayerBar() {
toggleShuffle(); toggleShuffle();
}; };
// Handle close player // Handle minimize player (show floating icon)
const handleClosePlayer = () => { const handleMinimizePlayer = () => {
setIsPlayerVisible(false); setIsMinimized(true);
}; };
if (!currentSong || !isPlayerVisible) { // Handle restore player from floating icon
const handleRestorePlayer = () => {
setIsMinimized(false);
};
// If minimized, show floating icon instead of player bar
if (isMinimized) {
return (
<>
{/* Floating Music Icon - Shows when player is minimized */}
<Paper
pos="fixed"
bottom={20}
right={20}
p="md"
shadow="xl"
radius="xl"
bg="blue"
style={{
zIndex: 1001,
cursor: 'pointer',
transition: 'transform 0.2s',
}}
onClick={handleRestorePlayer}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'scale(1.1)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'scale(1)';
}}
>
<IconMusic size={28} color="white" />
</Paper>
{/* Spacer to prevent content from being hidden behind player */}
<Box h={20} />
</>
);
}
if (!currentSong) {
return null; return null;
} }
@@ -94,7 +135,7 @@ export default function FixedPlayerBar() {
}} }}
> >
<Flex align="center" gap="md" justify="space-between"> <Flex align="center" gap="md" justify="space-between">
{/* Song Info */} {/* Song Info - Left */}
<Group gap="sm" flex={1} style={{ minWidth: 0 }}> <Group gap="sm" flex={1} style={{ minWidth: 0 }}>
<Avatar <Avatar
src={currentSong.coverImage?.link || ''} src={currentSong.coverImage?.link || ''}
@@ -113,78 +154,81 @@ export default function FixedPlayerBar() {
</Box> </Box>
</Group> </Group>
{/* Controls */} {/* Controls + Progress - Center */}
<Group gap="xs"> <Group gap="xs" flex={2} justify="center">
<ActionIcon {/* Control Buttons */}
variant={isShuffle ? 'filled' : 'subtle'} <Group gap="xs">
color={isShuffle ? 'blue' : 'gray'} <ActionIcon
size="lg" variant={isShuffle ? 'filled' : 'subtle'}
onClick={handleToggleShuffle} color={isShuffle ? 'blue' : 'gray'}
title="Shuffle" size="lg"
> onClick={handleToggleShuffle}
<IconArrowsShuffle size={18} /> title="Shuffle"
</ActionIcon> >
<IconArrowsShuffle size={18} />
</ActionIcon>
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color="gray" color="gray"
size="lg" size="lg"
onClick={playPrev} onClick={playPrev}
title="Previous" title="Previous"
> >
<IconPlayerSkipBackFilled size={20} /> <IconPlayerSkipBackFilled size={20} />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
variant="filled" variant="filled"
color={isPlaying ? 'blue' : 'gray'} color={isPlaying ? 'blue' : 'gray'}
size="xl" size="xl"
radius="xl" radius="xl"
onClick={togglePlayPause} onClick={togglePlayPause}
title={isPlaying ? 'Pause' : 'Play'} title={isPlaying ? 'Pause' : 'Play'}
> >
{isPlaying ? ( {isPlaying ? (
<IconPlayerPauseFilled size={24} /> <IconPlayerPauseFilled size={24} />
) : ( ) : (
<IconPlayerPlayFilled size={24} /> <IconPlayerPlayFilled size={24} />
)} )}
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color="gray" color="gray"
size="lg" size="lg"
onClick={playNext} onClick={playNext}
title="Next" title="Next"
> >
<IconPlayerSkipForwardFilled size={20} /> <IconPlayerSkipForwardFilled size={20} />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color={isRepeat ? 'blue' : 'gray'} color={isRepeat ? 'blue' : 'gray'}
size="lg" size="lg"
onClick={toggleRepeat} onClick={toggleRepeat}
title={isRepeat ? 'Repeat On' : 'Repeat Off'} title={isRepeat ? 'Repeat On' : 'Repeat Off'}
> >
{isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />} {isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />}
</ActionIcon> </ActionIcon>
</Group>
{/* Progress Bar - Desktop */}
<Box w={200} display={{ base: 'none', md: 'block' }}>
<Slider
value={currentTime}
max={duration || 100}
onChange={handleSeek}
size="sm"
color="blue"
label={(value) => formatTime(value)}
/>
</Box>
</Group> </Group>
{/* Progress Bar - Desktop */} {/* Right Controls - Volume + Close */}
<Box w={200} display={{ base: 'none', md: 'block' }}> <Group gap="xs" flex={1} justify="flex-end">
<Slider
value={currentTime}
max={duration || 100}
onChange={handleSeek}
size="sm"
color="blue"
label={(value) => formatTime(value)}
/>
</Box>
{/* Right Controls */}
<Group gap="xs">
<Box <Box
onMouseEnter={() => setShowVolume(true)} onMouseEnter={() => setShowVolume(true)}
onMouseLeave={() => setShowVolume(false)} onMouseLeave={() => setShowVolume(false)}
@@ -241,8 +285,8 @@ export default function FixedPlayerBar() {
variant="subtle" variant="subtle"
color="gray" color="gray"
size="lg" size="lg"
onClick={handleClosePlayer} onClick={handleMinimizePlayer}
title="Close player" title="Minimize player"
> >
<IconX size={18} /> <IconX size={18} />
</ActionIcon> </ActionIcon>