@neuctra/ui 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/components/basic/Accordation.d.ts +27 -18
  2. package/dist/components/basic/Alert.d.ts +15 -2
  3. package/dist/components/basic/Avatar.d.ts +5 -3
  4. package/dist/components/basic/Badge.d.ts +3 -3
  5. package/dist/components/basic/Button.d.ts +15 -17
  6. package/dist/components/basic/Card.d.ts +7 -49
  7. package/dist/components/basic/CheckRadioInput.d.ts +3 -1
  8. package/dist/components/basic/Container.d.ts +28 -26
  9. package/dist/components/basic/Drawer.d.ts +20 -11
  10. package/dist/components/basic/Flexbox.d.ts +18 -10
  11. package/dist/components/basic/GridView.d.ts +7 -5
  12. package/dist/components/basic/Image.d.ts +31 -6
  13. package/dist/components/basic/Input.d.ts +18 -10
  14. package/dist/components/basic/List.d.ts +11 -3
  15. package/dist/components/basic/Modal.d.ts +15 -2
  16. package/dist/components/basic/Section.d.ts +36 -0
  17. package/dist/components/basic/Stack.d.ts +27 -0
  18. package/dist/components/basic/Table.d.ts +18 -54
  19. package/dist/components/basic/Tabs.d.ts +28 -28
  20. package/dist/components/basic/Text.d.ts +19 -32
  21. package/dist/index.cjs.js +68 -178
  22. package/dist/index.cjs.js.map +1 -0
  23. package/dist/index.d.ts +17 -18
  24. package/dist/index.es.js +3542 -4766
  25. package/dist/index.es.js.map +1 -0
  26. package/dist/src/components/avatar/AvatarGroup.js +9 -0
  27. package/dist/src/components/avatar/AvatarWithStatus.js +18 -0
  28. package/dist/src/components/basic/Accordation.js +74 -0
  29. package/dist/src/components/basic/Alert.js +126 -0
  30. package/dist/src/components/basic/AudioGallery.js +425 -0
  31. package/dist/src/components/basic/AudioPlayer.js +116 -0
  32. package/dist/src/components/basic/Avatar.js +181 -0
  33. package/dist/src/components/basic/Badge.js +66 -0
  34. package/dist/src/components/basic/Button.js +101 -0
  35. package/dist/src/components/basic/Card.js +45 -0
  36. package/dist/src/components/basic/CheckRadioInput.js +83 -0
  37. package/dist/src/components/basic/Container.js +45 -0
  38. package/dist/src/components/basic/Drawer.js +94 -0
  39. package/dist/src/components/basic/DropDown.js +316 -0
  40. package/dist/src/components/basic/Flexbox.js +67 -0
  41. package/dist/src/components/basic/GridView.js +51 -0
  42. package/dist/src/components/basic/Image.js +95 -0
  43. package/dist/src/components/basic/Input.js +123 -0
  44. package/dist/src/components/basic/List.js +71 -0
  45. package/dist/src/components/basic/Modal.js +88 -0
  46. package/dist/src/components/basic/Section.js +100 -0
  47. package/dist/src/components/basic/Stack.js +75 -0
  48. package/dist/src/components/basic/Table.js +32 -0
  49. package/dist/src/components/basic/Tabs.js +149 -0
  50. package/dist/src/components/basic/Text.js +117 -0
  51. package/dist/src/index.js +44 -0
  52. package/dist/types/src/components/basic/Accordation.d.ts +44 -0
  53. package/dist/types/{components → src/components}/basic/Alert.d.ts +15 -2
  54. package/dist/types/{components → src/components}/basic/Avatar.d.ts +5 -3
  55. package/dist/types/{components → src/components}/basic/Badge.d.ts +3 -3
  56. package/dist/types/src/components/basic/Button.d.ts +26 -0
  57. package/dist/types/src/components/basic/Card.d.ts +28 -0
  58. package/dist/types/{components → src/components}/basic/CheckRadioInput.d.ts +3 -1
  59. package/dist/types/src/components/basic/Container.d.ts +32 -0
  60. package/dist/types/src/components/basic/Drawer.d.ts +33 -0
  61. package/dist/types/src/components/basic/Flexbox.d.ts +25 -0
  62. package/dist/types/{components → src/components}/basic/GridView.d.ts +7 -5
  63. package/dist/types/src/components/basic/Image.d.ts +58 -0
  64. package/dist/types/{components → src/components}/basic/Input.d.ts +18 -10
  65. package/dist/types/{components → src/components}/basic/List.d.ts +11 -3
  66. package/dist/types/src/components/basic/Modal.d.ts +24 -0
  67. package/dist/types/src/components/basic/Section.d.ts +36 -0
  68. package/dist/types/src/components/basic/Stack.d.ts +27 -0
  69. package/dist/types/src/components/basic/Table.d.ts +23 -0
  70. package/dist/types/src/components/basic/Tabs.d.ts +47 -0
  71. package/dist/types/src/components/basic/Text.d.ts +26 -0
  72. package/dist/types/{index.d.ts → src/index.d.ts} +17 -18
  73. package/dist/types/vite.config.d.ts +2 -0
  74. package/dist/ui.css +1 -1
  75. package/dist/vite.config.js +34 -0
  76. package/package.json +2 -1
  77. package/dist/components/basic/ImageGallery.d.ts +0 -21
  78. package/dist/components/basic/VideoGallery.d.ts +0 -136
  79. package/dist/components/basic/VideoPlayer.d.ts +0 -36
  80. package/dist/types/components/basic/Accordation.d.ts +0 -35
  81. package/dist/types/components/basic/Button.d.ts +0 -28
  82. package/dist/types/components/basic/Card.d.ts +0 -70
  83. package/dist/types/components/basic/Container.d.ts +0 -30
  84. package/dist/types/components/basic/Drawer.d.ts +0 -24
  85. package/dist/types/components/basic/Flexbox.d.ts +0 -17
  86. package/dist/types/components/basic/Image.d.ts +0 -33
  87. package/dist/types/components/basic/ImageGallery.d.ts +0 -21
  88. package/dist/types/components/basic/Modal.d.ts +0 -11
  89. package/dist/types/components/basic/Table.d.ts +0 -59
  90. package/dist/types/components/basic/Tabs.d.ts +0 -47
  91. package/dist/types/components/basic/Text.d.ts +0 -39
  92. package/dist/types/components/basic/VideoGallery.d.ts +0 -136
  93. package/dist/types/components/basic/VideoPlayer.d.ts +0 -36
  94. /package/dist/types/{components → src/components}/avatar/AvatarGroup.d.ts +0 -0
  95. /package/dist/types/{components → src/components}/avatar/AvatarWithStatus.d.ts +0 -0
  96. /package/dist/types/{components → src/components}/basic/AudioGallery.d.ts +0 -0
  97. /package/dist/types/{components → src/components}/basic/AudioPlayer.d.ts +0 -0
  98. /package/dist/types/{components → src/components}/basic/DropDown.d.ts +0 -0
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const AvatarGroup = ({ avatars, size = "medium", maxVisible = 3, className = "", }) => {
3
+ const avatarSizes = {
4
+ small: "w-8 h-8",
5
+ medium: "w-12 h-12",
6
+ large: "w-16 h-16",
7
+ };
8
+ return (_jsxs("div", { className: "flex -space-x-2", children: [avatars.slice(0, maxVisible).map((src, index) => (_jsx("img", { src: src, className: `rounded-full border-2 border-white object-cover ${avatarSizes[size]} ${className}` }, index))), avatars.length > maxVisible && (_jsxs("span", { className: `rounded-full border-2 border-white bg-gray-500 text-white flex items-center justify-center ${avatarSizes[size]}`, children: ["+", avatars.length - maxVisible] }))] }));
9
+ };
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ export const AvatarWithStatus = ({ src, alt = "User Avatar", status = "online", size = "medium", className = "", }) => {
4
+ const [imageError, setImageError] = useState(false);
5
+ const avatarSizes = {
6
+ small: { size: "w-8 h-8", statusSize: "w-2 h-2" },
7
+ medium: { size: "w-12 h-12", statusSize: "w-3 h-3" },
8
+ large: { size: "w-16 h-16", statusSize: "w-4 h-4" },
9
+ };
10
+ const statusColors = {
11
+ online: "bg-green-500",
12
+ offline: "bg-gray-400",
13
+ away: "bg-yellow-500",
14
+ busy: "bg-red-500",
15
+ };
16
+ return (_jsxs("div", { className: `relative inline-block ${avatarSizes[size].size}`, children: [src && !imageError ? (_jsx("img", { src: src, alt: alt, "aria-label": alt, className: `rounded-full object-cover ${avatarSizes[size].size} ${className}`, onError: () => setImageError(true) })) : (_jsx("svg", { "aria-label": "Placeholder for user avatar" // Added for accessibility
17
+ , className: `rounded-full bg-gray-300 text-gray-500 dark:bg-gray-600 dark:text-gray-400 ${avatarSizes[size].size}`, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M12 12a5 5 0 100-10 5 5 0 000 10zm-7 9c0-3 3-5 7-5s7 2 7 5v1H5v-1z", clipRule: "evenodd" }) })), _jsx("span", { className: `z-[1000] w-full h-full absolute bottom-0 right-0 border-2 border-white rounded-full ${statusColors[status]} ${avatarSizes[size].statusSize}` })] }));
18
+ };
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect, memo } from "react";
3
+ /**
4
+ * 🧠 Industry-standard, minimal, and fully customizable Accordion
5
+ */
6
+ export const Accordion = memo(({ items, allowMultiple = false, defaultOpen = [], borderColor = "#e5e7eb", backgroundColor = "#fff", textColor = "#111827", hoverBgColor = "#f9fafb", hoverTextColor = "#111827", contentBgColor = "#fff", contentTextColor = "#374151", paddingY = "1rem", paddingX = "1rem", marginY = "0.75rem", borderRadius = "0.5rem", contentPadding = "1rem", fontSize = "1rem", fontWeight = 600, contentFontSize = "0.95rem", contentFontWeight = 400, iconOpen = "−", iconClose = "+", iconSize = "1.25rem", transitionDuration = "300ms", shadow = "0 1px 4px rgba(0,0,0,0.08)", className = "", style, }) => {
7
+ const [openIndexes, setOpenIndexes] = useState(defaultOpen);
8
+ const contentRefs = useRef([]);
9
+ useEffect(() => {
10
+ contentRefs.current.forEach((el, index) => {
11
+ if (el) {
12
+ el.style.maxHeight = openIndexes.includes(index)
13
+ ? `${el.scrollHeight}px`
14
+ : "0px";
15
+ }
16
+ });
17
+ }, [openIndexes]);
18
+ const toggleItem = (index) => {
19
+ setOpenIndexes((prev) => allowMultiple
20
+ ? prev.includes(index)
21
+ ? prev.filter((i) => i !== index)
22
+ : [...prev, index]
23
+ : prev.includes(index)
24
+ ? []
25
+ : [index]);
26
+ };
27
+ return (_jsx("div", { className: className, style: { width: "100%", ...style }, children: items.map((item, index) => {
28
+ const isOpen = openIndexes.includes(index);
29
+ return (_jsxs("div", { style: {
30
+ border: `1px solid ${borderColor}`,
31
+ borderRadius,
32
+ margin: `${marginY} 0`,
33
+ boxShadow: shadow,
34
+ overflow: "hidden",
35
+ transition: `all ${transitionDuration} ease`,
36
+ }, children: [_jsxs("button", { onClick: () => toggleItem(index), style: {
37
+ width: "100%",
38
+ display: "flex",
39
+ justifyContent: "space-between",
40
+ alignItems: "center",
41
+ backgroundColor,
42
+ color: textColor,
43
+ padding: `${paddingY} ${paddingX}`,
44
+ fontWeight,
45
+ fontSize,
46
+ cursor: "pointer",
47
+ border: "none",
48
+ outline: "none",
49
+ transition: `all ${transitionDuration}`,
50
+ }, onMouseEnter: (e) => {
51
+ e.currentTarget.style.backgroundColor = hoverBgColor;
52
+ e.currentTarget.style.color = hoverTextColor;
53
+ }, onMouseLeave: (e) => {
54
+ e.currentTarget.style.backgroundColor = backgroundColor;
55
+ e.currentTarget.style.color = textColor;
56
+ }, children: [_jsx("span", { children: item.title }), _jsx("span", { style: { fontSize: iconSize }, children: isOpen ? iconOpen : iconClose })] }), _jsx("div", { ref: (el) => {
57
+ contentRefs.current[index] = el;
58
+ }, style: {
59
+ overflow: "hidden",
60
+ maxHeight: isOpen
61
+ ? `${contentRefs.current[index]?.scrollHeight}px`
62
+ : "0px",
63
+ transition: `max-height ${transitionDuration} ease-in-out`,
64
+ }, children: _jsx("div", { style: {
65
+ borderTop: `1px solid ${borderColor}`,
66
+ backgroundColor: contentBgColor,
67
+ color: contentTextColor,
68
+ padding: contentPadding,
69
+ fontSize: contentFontSize,
70
+ fontWeight: contentFontWeight,
71
+ }, children: item.content }) })] }, index));
72
+ }) }));
73
+ });
74
+ Accordion.displayName = "Accordion";
@@ -0,0 +1,126 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState, useMemo } from "react";
3
+ import { X, Info, CheckCircle, AlertCircle, AlertTriangle, } from "lucide-react";
4
+ const typeStyles = {
5
+ success: {
6
+ bg: "#ecfdf5",
7
+ border: "#34d399",
8
+ iconColor: "#059669",
9
+ Icon: _jsx(CheckCircle, { size: 20 }),
10
+ },
11
+ error: {
12
+ bg: "#fef2f2",
13
+ border: "#f87171",
14
+ iconColor: "#dc2626",
15
+ Icon: _jsx(AlertCircle, { size: 20 }),
16
+ },
17
+ warning: {
18
+ bg: "#fffbeb",
19
+ border: "#facc15",
20
+ iconColor: "#d97706",
21
+ Icon: _jsx(AlertTriangle, { size: 20 }),
22
+ },
23
+ info: {
24
+ bg: "#eff6ff",
25
+ border: "#3b82f6",
26
+ iconColor: "#2563eb",
27
+ Icon: _jsx(Info, { size: 20 }),
28
+ },
29
+ };
30
+ /** 📍 Dynamic position styles */
31
+ const getPositionStyle = (position) => {
32
+ const base = {
33
+ position: "fixed",
34
+ zIndex: 9999,
35
+ pointerEvents: "auto",
36
+ };
37
+ switch (position) {
38
+ case "top-left":
39
+ return { ...base, top: "1.25rem", left: "1.25rem" };
40
+ case "top-center":
41
+ return { ...base, top: "1.25rem", left: "50%", transform: "translateX(-50%)" };
42
+ case "top-right":
43
+ return { ...base, top: "1.25rem", right: "1.25rem" };
44
+ case "bottom-left":
45
+ return { ...base, bottom: "1.25rem", left: "1.25rem" };
46
+ case "bottom-center":
47
+ return { ...base, bottom: "1.25rem", left: "50%", transform: "translateX(-50%)" };
48
+ case "bottom-right":
49
+ default:
50
+ return { ...base, bottom: "1.25rem", right: "1.25rem" };
51
+ }
52
+ };
53
+ /** 🎯 Production-grade Alert Component */
54
+ export const Alert = ({ title, description, type = "info", dismissible = true, duration, onClose, icon, actionButton, position = "top-right", backgroundColor, borderColor, textColor = "#111827", borderRadius = "0.75rem", shadow = "0 4px 14px rgba(0,0,0,0.1)", padding = "1rem", fontSize = "0.95rem", fontWeight = 500, descriptionColor = "#374151", animationDuration = "300ms", maxWidth = "480px", className = "", style, }) => {
55
+ const [visible, setVisible] = useState(true);
56
+ /** Auto-dismiss after duration */
57
+ useEffect(() => {
58
+ if (duration) {
59
+ const timer = setTimeout(() => {
60
+ setVisible(false);
61
+ onClose?.();
62
+ }, duration);
63
+ return () => clearTimeout(timer);
64
+ }
65
+ }, [duration, onClose]);
66
+ const { bg, border, iconColor, Icon } = typeStyles[type];
67
+ const positionStyle = getPositionStyle(position);
68
+ /** Combine computed and custom styles */
69
+ const containerStyle = useMemo(() => ({
70
+ ...positionStyle,
71
+ display: "flex",
72
+ alignItems: "flex-start",
73
+ gap: "0.75rem",
74
+ backgroundColor: backgroundColor ?? bg,
75
+ borderLeft: `4px solid ${borderColor ?? border}`,
76
+ borderRadius,
77
+ color: textColor,
78
+ boxShadow: shadow,
79
+ padding,
80
+ maxWidth,
81
+ width: "calc(100% - 2.5rem)",
82
+ opacity: visible ? 1 : 0,
83
+ transform: visible
84
+ ? position.includes("bottom")
85
+ ? "translateY(0)"
86
+ : "translateY(0)"
87
+ : position.includes("bottom")
88
+ ? "translateY(20px)"
89
+ : "translateY(-20px)",
90
+ transition: `opacity ${animationDuration} ease, transform ${animationDuration} ease`,
91
+ ...style,
92
+ }), [
93
+ visible,
94
+ bg,
95
+ border,
96
+ borderColor,
97
+ borderRadius,
98
+ position,
99
+ shadow,
100
+ padding,
101
+ textColor,
102
+ maxWidth,
103
+ backgroundColor,
104
+ animationDuration,
105
+ style,
106
+ ]);
107
+ if (!visible)
108
+ return null;
109
+ return (_jsxs("div", { className: className, style: containerStyle, role: "alert", children: [_jsx("div", { style: { color: iconColor, marginTop: "2px" }, children: icon || Icon }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [title && (_jsx("div", { style: { fontWeight: 600, fontSize, marginBottom: "4px" }, children: title })), description && (_jsx("div", { style: {
110
+ fontSize: "0.875rem",
111
+ color: descriptionColor,
112
+ lineHeight: 1.4,
113
+ }, children: description })), actionButton && (_jsx("div", { style: { marginTop: "8px" }, children: actionButton }))] }), dismissible && (_jsx("button", { onClick: () => {
114
+ setVisible(false);
115
+ onClose?.();
116
+ }, style: {
117
+ background: "transparent",
118
+ border: "none",
119
+ color: "#6b7280",
120
+ cursor: "pointer",
121
+ marginLeft: "8px",
122
+ padding: 0,
123
+ lineHeight: 0,
124
+ }, "aria-label": "Close alert", children: _jsx(X, { size: 16 }) }))] }));
125
+ };
126
+ Alert.displayName = "Alert";
@@ -0,0 +1,425 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState, useEffect } from "react";
3
+ import { Play, Pause, Volume2, VolumeX, RotateCcw, SkipBack, SkipForward, Heart, Music, Shuffle, } from "lucide-react";
4
+ const defaultTracks = [
5
+ {
6
+ src: "https://www.soundjay.com/misc/sounds/bell-ringing-05.wav",
7
+ title: "Morning Bell",
8
+ artist: "Nature Sounds",
9
+ duration: "0:15",
10
+ thumbnail: "https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=300&h=300&fit=crop",
11
+ },
12
+ {
13
+ src: "https://www.soundjay.com/buttons/sounds/button-4.wav",
14
+ title: "Digital Click",
15
+ artist: "Tech Audio",
16
+ duration: "0:05",
17
+ thumbnail: "https://images.unsplash.com/photo-1514525253161-7a46d19cd819?w=300&h=300&fit=crop",
18
+ },
19
+ {
20
+ src: "https://www.soundjay.com/buttons/sounds/button-10.wav",
21
+ title: "Soft Chime",
22
+ artist: "Ambient Studio",
23
+ duration: "0:08",
24
+ thumbnail: "https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=300&h=300&fit=crop",
25
+ },
26
+ ];
27
+ export function AudioGallery({ tracks = defaultTracks, className = "", galleryTitle = "Audio Gallery", primaryColor = "#8b5cf6", backgroundColor = "#0f0f23", textColor = "#e4e4e7", secondaryColor = "#1a1a2e", border = 0, borderColor, maxWidth = 420, autoplay = false, loop = false, }) {
28
+ const [currentTrackIndex, setCurrentTrackIndex] = useState(null);
29
+ const [isPlaying, setIsPlaying] = useState(false);
30
+ const [currentTime, setCurrentTime] = useState(0);
31
+ const [duration, setDuration] = useState(0);
32
+ const [volume, setVolume] = useState(0.7);
33
+ const [isFullscreen, setIsFullscreen] = useState(false);
34
+ const [isLooping, setIsLooping] = useState(loop);
35
+ const [isShuffle, setIsShuffle] = useState(false);
36
+ const [isLiked, setIsLiked] = useState(false);
37
+ const [showVolumeSlider, setShowVolumeSlider] = useState(false);
38
+ const audioRef = useRef(null);
39
+ const playerRef = useRef(null);
40
+ const volumeTimeoutRef = useRef(null);
41
+ useEffect(() => {
42
+ if (audioRef.current) {
43
+ audioRef.current.volume = volume;
44
+ }
45
+ }, [volume]);
46
+ useEffect(() => {
47
+ if (audioRef.current) {
48
+ audioRef.current.loop = isLooping;
49
+ }
50
+ }, [isLooping]);
51
+ useEffect(() => {
52
+ if (currentTrackIndex === null && audioRef.current) {
53
+ audioRef.current.pause();
54
+ setIsPlaying(false);
55
+ setCurrentTime(0);
56
+ setDuration(0);
57
+ }
58
+ }, [currentTrackIndex]);
59
+ const playPauseTrack = (index) => {
60
+ if (currentTrackIndex === index) {
61
+ if (!audioRef.current)
62
+ return;
63
+ if (audioRef.current.paused) {
64
+ audioRef.current.play().catch(() => { });
65
+ setIsPlaying(true);
66
+ }
67
+ else {
68
+ audioRef.current.pause();
69
+ setIsPlaying(false);
70
+ }
71
+ }
72
+ else {
73
+ setCurrentTrackIndex(index);
74
+ setTimeout(() => {
75
+ if (audioRef.current) {
76
+ audioRef.current.play().catch(() => { });
77
+ setIsPlaying(true);
78
+ }
79
+ }, 100);
80
+ }
81
+ };
82
+ const handleTimeUpdate = () => {
83
+ if (!audioRef.current)
84
+ return;
85
+ setCurrentTime(audioRef.current.currentTime);
86
+ if (audioRef.current.duration) {
87
+ setDuration(audioRef.current.duration);
88
+ }
89
+ };
90
+ const handleSeek = (e) => {
91
+ if (!audioRef.current || !duration)
92
+ return;
93
+ const rect = e.currentTarget.getBoundingClientRect();
94
+ const clickPos = e.clientX - rect.left;
95
+ const percent = clickPos / rect.width;
96
+ const seekTime = percent * duration;
97
+ audioRef.current.currentTime = seekTime;
98
+ setCurrentTime(seekTime);
99
+ };
100
+ const handleVolumeChange = (e) => {
101
+ const rect = e.currentTarget.getBoundingClientRect();
102
+ const clickPos = e.clientX - rect.left;
103
+ const percent = clickPos / rect.width;
104
+ const newVolume = Math.max(0, Math.min(1, percent));
105
+ setVolume(newVolume);
106
+ };
107
+ const skip = (seconds) => {
108
+ if (audioRef.current && duration) {
109
+ let newTime = audioRef.current.currentTime + seconds;
110
+ newTime = Math.min(Math.max(newTime, 0), duration);
111
+ audioRef.current.currentTime = newTime;
112
+ setCurrentTime(newTime);
113
+ }
114
+ };
115
+ const nextTrack = () => {
116
+ if (currentTrackIndex === null)
117
+ return;
118
+ let nextIndex;
119
+ if (isShuffle) {
120
+ nextIndex = Math.floor(Math.random() * tracks.length);
121
+ }
122
+ else {
123
+ nextIndex = (currentTrackIndex + 1) % tracks.length;
124
+ }
125
+ playPauseTrack(nextIndex);
126
+ };
127
+ const prevTrack = () => {
128
+ if (currentTrackIndex === null)
129
+ return;
130
+ let prevIndex;
131
+ if (isShuffle) {
132
+ prevIndex = Math.floor(Math.random() * tracks.length);
133
+ }
134
+ else {
135
+ prevIndex = (currentTrackIndex - 1 + tracks.length) % tracks.length;
136
+ }
137
+ playPauseTrack(prevIndex);
138
+ };
139
+ const toggleMute = () => {
140
+ setVolume((prev) => (prev > 0 ? 0 : 0.7));
141
+ };
142
+ const showVolume = () => {
143
+ setShowVolumeSlider(true);
144
+ if (volumeTimeoutRef.current) {
145
+ clearTimeout(volumeTimeoutRef.current);
146
+ }
147
+ volumeTimeoutRef.current = setTimeout(() => {
148
+ setShowVolumeSlider(false);
149
+ }, 3000);
150
+ };
151
+ const formatTime = (time) => {
152
+ if (isNaN(time))
153
+ return "0:00";
154
+ const minutes = Math.floor(time / 60);
155
+ const seconds = Math.floor(time % 60);
156
+ return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
157
+ };
158
+ const currentTrack = currentTrackIndex !== null ? tracks[currentTrackIndex] : null;
159
+ return (_jsxs("div", { className: className, style: {
160
+ maxWidth: `${maxWidth}px`,
161
+ margin: "20px auto",
162
+ background: `linear-gradient(145deg, ${backgroundColor}, ${secondaryColor})`,
163
+ color: textColor,
164
+ borderRadius: "24px",
165
+ padding: "24px 16px",
166
+ fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
167
+ userSelect: "none",
168
+ backdropFilter: "blur(20px)",
169
+ border: `${border}px solid ${borderColor}40`,
170
+ }, children: [_jsxs("div", { style: {
171
+ display: "flex",
172
+ alignItems: "center",
173
+ justifyContent: "space-between",
174
+ marginBottom: "24px",
175
+ }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [_jsx("div", { style: {
176
+ width: "40px",
177
+ height: "40px",
178
+ borderRadius: "12px",
179
+ background: `linear-gradient(135deg, ${primaryColor}, ${secondaryColor})`,
180
+ display: "flex",
181
+ alignItems: "center",
182
+ justifyContent: "center",
183
+ }, children: _jsx(Music, { size: 20, color: "white" }) }), _jsxs("div", { children: [_jsx("h2", { style: {
184
+ margin: 0,
185
+ fontSize: "1.25rem",
186
+ fontWeight: "700",
187
+ color: primaryColor, // fallback for browsers without WebkitTextFillColor
188
+ }, children: galleryTitle }), _jsxs("p", { style: { margin: 0, fontSize: "0.875rem", opacity: 0.7 }, children: [tracks.length, " tracks"] })] })] }), _jsx("button", { onClick: () => setIsShuffle(!isShuffle), style: {
189
+ background: isShuffle ? primaryColor : "transparent",
190
+ border: "none",
191
+ borderRadius: "12px",
192
+ padding: "8px",
193
+ cursor: "pointer",
194
+ transition: "all 0.3s ease",
195
+ opacity: isShuffle ? 1 : 0.6,
196
+ }, "aria-label": "Toggle Shuffle", children: _jsx(Shuffle, { size: 18, color: isShuffle ? "white" : textColor }) })] }), _jsx("div", { style: {
197
+ display: "flex",
198
+ flexDirection: "column",
199
+ gap: "8px",
200
+ marginBottom: "20px",
201
+ maxHeight: "240px",
202
+ overflowY: "auto",
203
+ paddingRight: "4px",
204
+ }, children: tracks.map((track, index) => {
205
+ const isActive = currentTrackIndex === index;
206
+ const isCurrentlyPlaying = isActive && isPlaying;
207
+ return (_jsxs("div", { onClick: () => playPauseTrack(index), style: {
208
+ display: "flex",
209
+ alignItems: "center",
210
+ padding: "12px 16px",
211
+ borderRadius: "16px",
212
+ background: isActive
213
+ ? `linear-gradient(135deg, ${primaryColor}20, ${primaryColor}10)`
214
+ : "rgba(255,255,255,0.05)",
215
+ border: isActive
216
+ ? `1px solid ${primaryColor}40`
217
+ : "1px solid transparent",
218
+ cursor: "pointer",
219
+ transition: "all 0.3s ease",
220
+ backdropFilter: isActive ? `blur(20px)` : "none",
221
+ }, children: [_jsxs("div", { style: {
222
+ width: "48px",
223
+ height: "48px",
224
+ borderRadius: "12px",
225
+ background: track.thumbnail
226
+ ? `url(${track.thumbnail}) center/cover`
227
+ : primaryColor,
228
+ display: "flex",
229
+ alignItems: "center",
230
+ justifyContent: "center",
231
+ marginRight: "12px",
232
+ position: "relative",
233
+ overflow: "hidden",
234
+ }, children: [!track.thumbnail && _jsx(Music, { size: 20, color: "white" }), isCurrentlyPlaying && (_jsx("div", { style: {
235
+ position: "absolute",
236
+ top: 0,
237
+ left: 0,
238
+ right: 0,
239
+ bottom: 0,
240
+ background: "rgba(0,0,0,0.6)",
241
+ display: "flex",
242
+ alignItems: "center",
243
+ justifyContent: "center",
244
+ }, children: _jsx("div", { style: {
245
+ width: "16px",
246
+ height: "16px",
247
+ display: "flex",
248
+ gap: "2px",
249
+ alignItems: "end",
250
+ justifyContent: "center",
251
+ }, children: [0, 1, 2].map((i) => (_jsx("div", { style: {
252
+ width: "3px",
253
+ background: "white",
254
+ borderRadius: "2px",
255
+ animation: `equalizer 1s ease-in-out infinite ${i * 0.2}s`,
256
+ height: "12px",
257
+ } }, i))) }) }))] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: {
258
+ fontWeight: "600",
259
+ fontSize: "0.95rem",
260
+ color: isActive ? primaryColor : textColor,
261
+ whiteSpace: "nowrap",
262
+ overflow: "hidden",
263
+ textOverflow: "ellipsis",
264
+ }, children: track.title }), track.artist && (_jsx("div", { style: {
265
+ fontSize: "0.8rem",
266
+ opacity: 0.7,
267
+ whiteSpace: "nowrap",
268
+ overflow: "hidden",
269
+ textOverflow: "ellipsis",
270
+ }, children: track.artist }))] }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [track.duration && (_jsx("span", { style: { fontSize: "0.8rem", opacity: 0.6 }, children: track.duration })), _jsx("div", { style: {
271
+ width: "32px",
272
+ height: "32px",
273
+ borderRadius: "8px",
274
+ background: isActive
275
+ ? primaryColor
276
+ : "rgba(255,255,255,0.1)",
277
+ display: "flex",
278
+ alignItems: "center",
279
+ justifyContent: "center",
280
+ transition: "all 0.3s ease",
281
+ }, children: isCurrentlyPlaying ? (_jsx(Pause, { size: 16, color: "white" })) : (_jsx(Play, { size: 16, color: "white" })) })] })] }, index));
282
+ }) }), currentTrack && (_jsxs("div", { ref: playerRef, style: {
283
+ background: `linear-gradient(135deg, ${secondaryColor}, ${backgroundColor})`,
284
+ borderRadius: "20px",
285
+ padding: "20px",
286
+ border: `1px solid ${primaryColor}40`,
287
+ backdropFilter: "blur(20px)",
288
+ position: "relative",
289
+ overflow: "hidden",
290
+ }, children: [currentTrack.thumbnail && (_jsx("div", { style: {
291
+ position: "absolute",
292
+ top: 0,
293
+ left: 0,
294
+ right: 0,
295
+ bottom: 0,
296
+ backgroundImage: `url(${currentTrack.thumbnail})`,
297
+ backgroundSize: "cover",
298
+ backgroundPosition: "center",
299
+ filter: "blur(60px) opacity(0.1)",
300
+ transform: "scale(1.1)",
301
+ } })), _jsxs("div", { style: { position: "relative", zIndex: 1 }, children: [_jsxs("div", { style: { textAlign: "center", marginBottom: "20px" }, children: [_jsx("h3", { style: {
302
+ margin: "0 0 4px 0",
303
+ fontSize: "1.1rem",
304
+ fontWeight: "700",
305
+ color: primaryColor,
306
+ }, children: currentTrack.title }), currentTrack.artist && (_jsx("p", { style: { margin: 0, opacity: 0.7, fontSize: "0.9rem" }, children: currentTrack.artist }))] }), _jsxs("div", { style: {
307
+ display: "flex",
308
+ alignItems: "center",
309
+ justifyContent: "center",
310
+ gap: "20px",
311
+ marginBottom: "20px",
312
+ }, children: [_jsx("button", { onClick: prevTrack, style: controlButtonStyle(textColor, "rgba(255,255,255,0.1)"), "aria-label": "Previous Track", children: _jsx(SkipBack, { size: 20 }) }), _jsxs("button", { onClick: () => skip(-10), style: controlButtonStyle(textColor, "rgba(255,255,255,0.1)"), "aria-label": "Skip back 10 seconds", children: [_jsx(SkipBack, { size: 16 }), _jsx("span", { style: { fontSize: "0.7rem", marginLeft: "2px" }, children: "10" })] }), _jsx("button", { onClick: () => {
313
+ if (!audioRef.current)
314
+ return;
315
+ if (isPlaying) {
316
+ audioRef.current.pause();
317
+ setIsPlaying(false);
318
+ }
319
+ else {
320
+ audioRef.current.play().catch(() => { });
321
+ setIsPlaying(true);
322
+ }
323
+ }, style: {
324
+ ...controlButtonStyle("#fff", primaryColor),
325
+ width: "60px",
326
+ height: "60px",
327
+ borderRadius: "50%",
328
+ boxShadow: `0 8px 25px ${primaryColor}40`,
329
+ }, "aria-label": isPlaying ? "Pause" : "Play", children: isPlaying ? _jsx(Pause, { size: 24 }) : _jsx(Play, { size: 24 }) }), _jsxs("button", { onClick: () => skip(10), style: controlButtonStyle(textColor, "rgba(255,255,255,0.1)"), "aria-label": "Skip forward 10 seconds", children: [_jsx("span", { style: { fontSize: "0.7rem", marginRight: "2px" }, children: "10" }), _jsx(SkipForward, { size: 16 })] }), _jsx("button", { onClick: nextTrack, style: controlButtonStyle(textColor, "rgba(255,255,255,0.1)"), "aria-label": "Next Track", children: _jsx(SkipForward, { size: 20 }) })] }), _jsxs("div", { style: { marginBottom: "16px" }, children: [_jsxs("div", { style: {
330
+ display: "flex",
331
+ alignItems: "center",
332
+ justifyContent: "space-between",
333
+ marginBottom: "8px",
334
+ fontSize: "0.8rem",
335
+ opacity: 0.7,
336
+ }, children: [_jsx("span", { children: formatTime(currentTime) }), _jsx("span", { children: formatTime(duration) })] }), _jsx("div", { onClick: handleSeek, style: {
337
+ height: "6px",
338
+ background: "rgba(255,255,255,0.2)",
339
+ borderRadius: "3px",
340
+ cursor: "pointer",
341
+ position: "relative",
342
+ overflow: "hidden",
343
+ }, children: _jsx("div", { style: {
344
+ width: `${(currentTime / duration) * 100 || 0}%`,
345
+ height: "100%",
346
+ background: `linear-gradient(90deg, ${primaryColor}, #ec4899)`,
347
+ borderRadius: "3px",
348
+ position: "relative",
349
+ }, children: _jsx("div", { style: {
350
+ position: "absolute",
351
+ right: "-6px",
352
+ top: "50%",
353
+ transform: "translateY(-50%)",
354
+ width: "12px",
355
+ height: "12px",
356
+ background: primaryColor,
357
+ borderRadius: "50%",
358
+ boxShadow: `0 2px 8px ${primaryColor}60`,
359
+ } }) }) })] }), _jsxs("div", { style: {
360
+ display: "flex",
361
+ alignItems: "center",
362
+ justifyContent: "space-between",
363
+ }, children: [_jsxs("div", { style: { display: "flex", gap: "8px" }, children: [_jsx("button", { onClick: () => setIsLiked(!isLiked), style: controlButtonStyle(isLiked ? "#ec4899" : textColor, "rgba(255,255,255,0.1)"), "aria-label": "Like", children: _jsx(Heart, { size: 16, fill: isLiked ? "#ec4899" : "none" }) }), _jsx("button", { onClick: () => setIsLooping(!isLooping), style: controlButtonStyle(isLooping ? primaryColor : textColor, "rgba(255,255,255,0.1)"), "aria-label": "Toggle Loop", children: _jsx(RotateCcw, { size: 16 }) })] }), _jsxs("div", { style: {
364
+ display: "flex",
365
+ alignItems: "center",
366
+ gap: "8px",
367
+ position: "relative",
368
+ }, children: [showVolumeSlider && (_jsx("div", { onClick: handleVolumeChange, style: {
369
+ width: "80px",
370
+ height: "4px",
371
+ background: "rgba(255,255,255,0.2)",
372
+ borderRadius: "2px",
373
+ cursor: "pointer",
374
+ position: "relative",
375
+ }, children: _jsx("div", { style: {
376
+ width: `${volume * 100}%`,
377
+ height: "100%",
378
+ background: primaryColor,
379
+ borderRadius: "2px",
380
+ } }) })), _jsx("button", { onClick: toggleMute, onMouseEnter: showVolume, style: controlButtonStyle(textColor, "rgba(255,255,255,0.1)"), "aria-label": volume > 0 ? "Mute" : "Unmute", children: volume > 0 ? _jsx(Volume2, { size: 16 }) : _jsx(VolumeX, { size: 16 }) })] })] })] }), _jsx("audio", { ref: audioRef, src: currentTrack.src, autoPlay: autoplay, loop: isLooping, onTimeUpdate: handleTimeUpdate, onEnded: () => {
381
+ setIsPlaying(false);
382
+ if (!isLooping) {
383
+ nextTrack();
384
+ }
385
+ }, onLoadedMetadata: handleTimeUpdate, preload: "metadata", style: { display: "none" } })] })), _jsx("style", { children: `
386
+ @keyframes equalizer {
387
+ 0%, 100% { height: 4px; }
388
+ 50% { height: 12px; }
389
+ }
390
+
391
+ /* Custom Scrollbar */
392
+ div::-webkit-scrollbar {
393
+ width: 4px;
394
+ }
395
+
396
+ div::-webkit-scrollbar-track {
397
+ background: rgba(255,255,255,0.1);
398
+ border-radius: 2px;
399
+ }
400
+
401
+ div::-webkit-scrollbar-thumb {
402
+ background: ${primaryColor};
403
+ border-radius: 2px;
404
+ }
405
+
406
+ div::-webkit-scrollbar-thumb:hover {
407
+ background: ${primaryColor}dd;
408
+ }
409
+ ` })] }));
410
+ }
411
+ function controlButtonStyle(color, backgroundColor) {
412
+ return {
413
+ border: "none",
414
+ backgroundColor,
415
+ color,
416
+ cursor: "pointer",
417
+ padding: "10px",
418
+ borderRadius: "12px",
419
+ display: "flex",
420
+ alignItems: "center",
421
+ justifyContent: "center",
422
+ transition: "all 0.3s ease",
423
+ backdropFilter: "blur(10px)",
424
+ };
425
+ }