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:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user