Introducing Nuvyx UI v1.0.0

Music Player

A customizable music player component with multiple themes, playback controls, progress tracking, and responsive design.

Installation Guide

1

Install Dependencies

Lucide React

npm install lucide-react
2

Copy Component Code

Music Player.tsx
TypeScript
1"use client";
2
3import React, { useState, useEffect } from "react";
4import {
5  Play,
6  Pause,
7  SkipForward,
8  SkipBack,
9  Heart,
10  Repeat,
11  Shuffle,
12} from "lucide-react";
13import Image from "next/image";
14
15// Types
16export interface MusicPlayerProps {
17  theme?: "default" | "spotify" | "cosmic" | "nebula" | string;
18  shadow?: boolean;
19  rounded?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
20  artwork?: string;
21  trackTitle?: string;
22  artist?: string;
23  album?: string;
24  initialTime?: number;
25  totalDuration?: number;
26  className?: string;
27  autoPlay?: boolean;
28  onPlayPause?: (isPlaying: boolean) => void;
29  onTimeChange?: (time: number) => void;
30  onTrackEnd?: () => void;
31  controls?: {
32    shuffle?: boolean;
33    repeat?: boolean;
34    heart?: boolean;
35  };
36}
37
38export const MusicPlayer = ({
39  theme = "default",
40  shadow = true,
41  rounded = "xl",
42  artwork = "/api/placeholder/400/400",
43  trackTitle = "undefined",
44  artist = "undefined",
45  album = "undefined",
46  initialTime = 0,
47  totalDuration = 217,
48  className = "",
49  autoPlay = false,
50  onPlayPause,
51  onTimeChange,
52  onTrackEnd,
53  controls = {
54    shuffle: true,
55    repeat: true,
56    heart: true,
57  },
58}: MusicPlayerProps) => {
59  const [isPlaying, setIsPlaying] = useState(autoPlay);
60  const [currentTime, setCurrentTime] = useState(initialTime);
61  const [liked, setLiked] = useState(false);
62  const [mounted, setMounted] = useState(false);
63
64  useEffect(() => {
65    setMounted(true);
66  }, []);
67
68  useEffect(() => {
69    let interval: NodeJS.Timeout;
70
71    if (isPlaying) {
72      interval = setInterval(() => {
73        setCurrentTime((time) => {
74          if (time >= totalDuration) {
75            setIsPlaying(false);
76            if (onTrackEnd) onTrackEnd();
77            return 0;
78          }
79          const newTime = time + 1;
80          if (onTimeChange) onTimeChange(newTime);
81          return newTime;
82        });
83      }, 1000);
84    }
85
86    return () => {
87      if (interval) clearInterval(interval);
88    };
89  }, [isPlaying, totalDuration, onTimeChange, onTrackEnd]);
90
91  const formatTime = (seconds: number) => {
92    const min = Math.floor(seconds / 60);
93    const sec = seconds % 60;
94    return `${min}:${sec < 10 ? "0" + sec : sec}`;
95  };
96
97  const togglePlay = () => {
98    const newPlayState = !isPlaying;
99    setIsPlaying(newPlayState);
100    if (onPlayPause) onPlayPause(newPlayState);
101  };
102
103  const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => {
104    const progressBar = e.currentTarget;
105    const { left, width } = progressBar.getBoundingClientRect();
106    const clickPosition = e.clientX - left;
107    const percentage = clickPosition / width;
108    const newTime = Math.floor(totalDuration * percentage);
109
110    setCurrentTime(newTime);
111    if (onTimeChange) onTimeChange(newTime);
112  };
113
114  const toggleLike = () => {
115    setLiked(!liked);
116  };
117
118  const getThemeStyles = () => {
119    const baseStyles = "transition-all duration-300";
120    const shadowStyles = shadow ? "shadow-xl" : "";
121    const roundedStyles = getRoundedClass(rounded);
122
123    switch (theme) {
124      case "spotify":
125        return `
126          bg-white text-green-600 border-2 border-green-400
127          dark:bg-black dark:text-green-400 dark:border-green-400
128          ${shadowStyles} ${roundedStyles} ${baseStyles}
129        `
130          .replace(/\s+/g, " ")
131          .trim();
132
133      case "cosmic":
134        return `
135          bg-indigo-50 text-indigo-900 border border-indigo-200
136          dark:bg-gradient-to-br dark:from-indigo-950 dark:to-blue-950 dark:text-white dark:border-indigo-300
137          ${shadowStyles} ${roundedStyles} ${baseStyles}
138        `
139          .replace(/\s+/g, " ")
140          .trim();
141
142      case "nebula":
143        return `
144          bg-purple-100 text-purple-700 border border-purple-300
145          dark:bg-gradient-to-br dark:from-purple-900 dark:to-indigo-900
146          dark:text-purple-200 dark:border-purple-600
147          ${shadowStyles} ${roundedStyles} ${baseStyles}
148        `
149          .replace(/\s+/g, " ")
150          .trim();
151
152      default:
153        return `
154          bg-white text-zinc-900
155          dark:bg-zinc-900 dark:text-white
156          ${shadowStyles} ${roundedStyles} ${baseStyles}
157        `
158          .replace(/\s+/g, " ")
159          .trim();
160    }
161  };
162
163  const getRoundedClass = (size: string) => {
164    switch (size) {
165      case "sm":
166        return "rounded-sm";
167      case "md":
168        return "rounded-md";
169      case "lg":
170        return "rounded-lg";
171      case "xl":
172        return "rounded-xl";
173      case "2xl":
174        return "rounded-2xl";
175      case "full":
176        return "rounded-full";
177      default:
178        return "rounded-md";
179    }
180  };
181
182  if (!mounted) {
183    return null;
184  }
185
186  return (
187    <div className={`${getThemeStyles()} ${className} overflow-hidden`}>
188      <div className="w-full h-44 relative">
189        <Image
190          src={artwork}
191          alt={`${trackTitle} by ${artist}`}
192          width={400}
193          height={256}
194          className={`w-full h-full object-cover ${getRoundedClass(
195            rounded
196          ).replace("rounded", "rounded-t")}`}
197        />
198        <div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent"></div>
199      </div>
200
201      <div className="p-6 flex flex-col">
202        <div className="flex items-start justify-between mb-6">
203          <div>
204            <h1 className="text-xl font-bold mb-1">{trackTitle}</h1>
205            <p className="text-sm opacity-70">
206              {artist}{album}
207            </p>
208          </div>
209          {controls.heart && (
210            <button
211              onClick={toggleLike}
212              aria-label="Like"
213              className={`transition-colors hover:scale-105 ${
214                liked ? "text-red-500" : "text-gray-400 hover:text-white"
215              }`}
216            >
217              <Heart className={`h-6 w-6 ${liked ? "fill-current" : ""}`} />
218            </button>
219          )}
220        </div>
221
222        <div className="mb-2">
223          <div
224            className="relative h-1 bg-gray-700 rounded-full overflow-hidden cursor-pointer"
225            onClick={handleProgressClick}
226          >
227            <div
228              className={`absolute top-0 left-0 h-full ${
229                theme === "spotify"
230                  ? "bg-green-500"
231                  : theme === "cosmic"
232                  ? "bg-indigo-500"
233                  : theme === "nebula"
234                  ? "bg-purple-500"
235                  : "bg-white"
236              } rounded-full`}
237              style={{ width: `${(currentTime / totalDuration) * 100}%` }}
238            ></div>
239          </div>
240          <div className="flex justify-between mt-2">
241            <span className="text-xs opacity-70">
242              {formatTime(currentTime)}
243            </span>
244            <span className="text-xs opacity-70">
245              {formatTime(totalDuration)}
246            </span>
247          </div>
248        </div>
249
250        <div className="flex flex-col gap-5">
251          <div className="flex justify-between items-center">
252            {controls.shuffle && (
253              <button
254                aria-label="Shuffle"
255                className="opacity-70 hover:opacity-100 transition-opacity"
256              >
257                <Shuffle className="h-5 w-5" />
258              </button>
259            )}
260            <div className="flex items-center gap-5">
261              <button
262                aria-label="Skip Back"
263                className="opacity-70 hover:opacity-100 transition-opacity"
264              >
265                <SkipBack className="h-6 w-6" />
266              </button>
267              <button
268                aria-label="Play/Pause"
269                className={`${
270                  theme === "spotify"
271                    ? "bg-green-500"
272                    : theme === "cosmic"
273                    ? "bg-indigo-500"
274                    : theme === "nebula"
275                    ? "bg-purple-500"
276                    : "bg-white"
277                } text-black rounded-full p-3 hover:scale-105 transition-transform`}
278                onClick={togglePlay}
279              >
280                {isPlaying ? (
281                  <Pause className="h-6 w-6" />
282                ) : (
283                  <Play className="h-6 w-6 fill-current" />
284                )}
285              </button>
286              <button
287                aria-label="Skip Forward"
288                className="opacity-70 hover:opacity-100 transition-opacity"
289              >
290                <SkipForward className="h-6 w-6" />
291              </button>
292            </div>
293            {controls.repeat && (
294              <button
295                aria-label="Repeat"
296                className="opacity-70 hover:opacity-100 transition-opacity"
297              >
298                <Repeat className="h-5 w-5" />
299              </button>
300            )}
301          </div>
302        </div>
303      </div>
304    </div>
305  );
306};
307
3

Final Steps

Update Import Paths

Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button to match your UI components location.

Props

NameTypeDefaultDescription
themestring"default"Visual theme of the music player (default, spotify, cosmic, nebula, or custom).
shadowbooleantrueWhether to display shadow effect on the player.
roundedstring"xl"Corner radius of the player component.
artworkstring"/placeholder.svg"Path to the album artwork image.
trackTitlestring"undefined"Title of the track being played.
artiststring"undefined"Name of the artist or band.
albumstring"After Hours"Name of the album.
initialTimenumber0Initial playback position in seconds.
totalDurationnumber217Total duration of the track in seconds.
classNamestring""Additional CSS classes to apply to the component.
autoPlaybooleanfalseWhether to start playback automatically.
onPlayPausefunctionundefinedCallback function triggered when play/pause is toggled.
onTimeChangefunctionundefinedCallback function triggered when playback time changes.
onTrackEndfunctionundefinedCallback function triggered when track playback ends.
controls{ shuffle?: boolean; repeat?: boolean; heart?: boolean; }{ shuffle: true, repeat: true, heart: true }Controls visibility of additional player controls.

Examples