@neuctra/ui 0.2.5 → 0.2.7

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 (80) hide show
  1. package/dist/components/basic/Accordation.d.ts +9 -13
  2. package/dist/components/basic/Alert.d.ts +10 -23
  3. package/dist/components/basic/Avatar.d.ts +7 -16
  4. package/dist/components/basic/Badge.d.ts +9 -14
  5. package/dist/components/basic/Button.d.ts +9 -19
  6. package/dist/components/basic/CheckboxGroup.d.ts +1 -0
  7. package/dist/components/basic/Container.d.ts +2 -19
  8. package/dist/components/basic/Drawer.d.ts +7 -18
  9. package/dist/components/basic/DropDown.d.ts +20 -40
  10. package/dist/components/basic/FlexView.d.ts +16 -0
  11. package/dist/components/basic/GridView.d.ts +4 -9
  12. package/dist/components/basic/Image.d.ts +10 -31
  13. package/dist/components/basic/Input.d.ts +22 -35
  14. package/dist/components/basic/List.d.ts +6 -20
  15. package/dist/components/basic/Modal.d.ts +8 -8
  16. package/dist/components/basic/RadioGroup.d.ts +1 -0
  17. package/dist/components/basic/Stack.d.ts +4 -14
  18. package/dist/components/basic/SwitchGroup.d.ts +1 -0
  19. package/dist/components/basic/Table.d.ts +6 -1
  20. package/dist/components/basic/Text.d.ts +1109 -14
  21. package/dist/index.cjs.js +82 -52
  22. package/dist/index.cjs.js.map +1 -1
  23. package/dist/index.d.ts +3 -4
  24. package/dist/index.es.js +2004 -2914
  25. package/dist/index.es.js.map +1 -1
  26. package/dist/src/components/basic/Accordation.js +25 -26
  27. package/dist/src/components/basic/Alert.js +33 -138
  28. package/dist/src/components/basic/AudioPlayer.js +54 -40
  29. package/dist/src/components/basic/Avatar.js +41 -154
  30. package/dist/src/components/basic/Badge.js +23 -62
  31. package/dist/src/components/basic/Button.js +24 -97
  32. package/dist/src/components/basic/CheckboxGroup.js +36 -13
  33. package/dist/src/components/basic/Container.js +19 -38
  34. package/dist/src/components/basic/Drawer.js +22 -73
  35. package/dist/src/components/basic/DropDown.js +94 -158
  36. package/dist/src/components/basic/FlexView.js +19 -0
  37. package/dist/src/components/basic/GridView.js +15 -48
  38. package/dist/src/components/basic/Image.js +39 -79
  39. package/dist/src/components/basic/Input.js +68 -109
  40. package/dist/src/components/basic/List.js +20 -62
  41. package/dist/src/components/basic/Modal.js +6 -58
  42. package/dist/src/components/basic/RadioGroup.js +35 -18
  43. package/dist/src/components/basic/Stack.js +19 -72
  44. package/dist/src/components/basic/SwitchGroup.js +42 -16
  45. package/dist/src/components/basic/Table.js +15 -36
  46. package/dist/src/components/basic/Tabs.js +3 -12
  47. package/dist/src/components/basic/Text.js +20 -112
  48. package/dist/src/index.js +3 -5
  49. package/dist/types/src/components/basic/Accordation.d.ts +9 -13
  50. package/dist/types/src/components/basic/Alert.d.ts +10 -23
  51. package/dist/types/src/components/basic/Avatar.d.ts +7 -16
  52. package/dist/types/src/components/basic/Badge.d.ts +9 -14
  53. package/dist/types/src/components/basic/Button.d.ts +9 -19
  54. package/dist/types/src/components/basic/CheckboxGroup.d.ts +1 -0
  55. package/dist/types/src/components/basic/Container.d.ts +2 -19
  56. package/dist/types/src/components/basic/Drawer.d.ts +7 -18
  57. package/dist/types/src/components/basic/DropDown.d.ts +20 -40
  58. package/dist/types/src/components/basic/FlexView.d.ts +16 -0
  59. package/dist/types/src/components/basic/GridView.d.ts +4 -9
  60. package/dist/types/src/components/basic/Image.d.ts +10 -31
  61. package/dist/types/src/components/basic/Input.d.ts +22 -35
  62. package/dist/types/src/components/basic/List.d.ts +6 -20
  63. package/dist/types/src/components/basic/Modal.d.ts +8 -8
  64. package/dist/types/src/components/basic/RadioGroup.d.ts +1 -0
  65. package/dist/types/src/components/basic/Stack.d.ts +4 -14
  66. package/dist/types/src/components/basic/SwitchGroup.d.ts +1 -0
  67. package/dist/types/src/components/basic/Table.d.ts +6 -1
  68. package/dist/types/src/components/basic/Text.d.ts +1109 -14
  69. package/dist/types/src/index.d.ts +3 -4
  70. package/dist/ui.css +1 -1
  71. package/package.json +2 -1
  72. package/dist/components/basic/Card.d.ts +0 -28
  73. package/dist/components/basic/Flexbox.d.ts +0 -25
  74. package/dist/components/basic/Section.d.ts +0 -36
  75. package/dist/src/components/basic/Card.js +0 -47
  76. package/dist/src/components/basic/Flexbox.js +0 -67
  77. package/dist/src/components/basic/Section.js +0 -100
  78. package/dist/types/src/components/basic/Card.d.ts +0 -28
  79. package/dist/types/src/components/basic/Flexbox.d.ts +0 -25
  80. package/dist/types/src/components/basic/Section.d.ts +0 -36
@@ -1,9 +1,8 @@
1
+ "use client";
1
2
  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, }) => {
3
+ import { useState, useRef, useEffect, memo, } from "react";
4
+ import { ChevronDown, ChevronUp } from "lucide-react";
5
+ export const Accordion = memo(({ items, allowMultiple = false, defaultOpen = [], borderColor = "#e5e7eb", backgroundColor = "#fff", textColor = "#111827", hoverBgColor = "#f3f4f6", hoverTextColor = "#111827", contentBgColor = "#fff", contentTextColor = "#374151", paddingY = "1rem", paddingX = "1rem", marginY = "0.5rem", 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
6
  const [openIndexes, setOpenIndexes] = useState(defaultOpen);
8
7
  const contentRefs = useRef([]);
9
8
  useEffect(() => {
@@ -24,49 +23,49 @@ export const Accordion = memo(({ items, allowMultiple = false, defaultOpen = [],
24
23
  ? []
25
24
  : [index]);
26
25
  };
27
- return (_jsx("div", { className: className, style: { width: "100%", ...style }, children: items.map((item, index) => {
26
+ // Helper to normalize CSS values (number -> px)
27
+ const toCssValue = (value) => value !== undefined
28
+ ? typeof value === "number"
29
+ ? `${value}px`
30
+ : value
31
+ : undefined;
32
+ return (_jsx("div", { className: `space-y-2 ${className}`, style: { width: "100%", ...style }, children: items.map((item, index) => {
28
33
  const isOpen = openIndexes.includes(index);
29
- return (_jsxs("div", { style: {
30
- border: `1px solid ${borderColor}`,
31
- borderRadius,
32
- margin: `${marginY} 0`,
34
+ return (_jsxs("div", { className: "border shadow-sm overflow-hidden transition-all duration-300", style: {
35
+ borderColor,
36
+ borderRadius: toCssValue(borderRadius),
37
+ margin: `${toCssValue(marginY)} 0`,
33
38
  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",
39
+ }, children: [_jsxs("button", { onClick: () => toggleItem(index), className: "w-full flex justify-between items-center transition-colors duration-300", style: {
41
40
  backgroundColor,
42
41
  color: textColor,
43
- padding: `${paddingY} ${paddingX}`,
42
+ padding: `${toCssValue(paddingY)} ${toCssValue(paddingX)}`,
44
43
  fontWeight,
45
- fontSize,
44
+ fontSize: toCssValue(fontSize),
46
45
  cursor: "pointer",
47
46
  border: "none",
48
47
  outline: "none",
49
- transition: `all ${transitionDuration}`,
50
48
  }, onMouseEnter: (e) => {
51
49
  e.currentTarget.style.backgroundColor = hoverBgColor;
52
50
  e.currentTarget.style.color = hoverTextColor;
53
51
  }, onMouseLeave: (e) => {
54
52
  e.currentTarget.style.backgroundColor = backgroundColor;
55
53
  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) => {
54
+ }, children: [_jsx("span", { children: item.title }), _jsx("span", { style: { fontSize: toCssValue(iconSize) }, children: isOpen
55
+ ? iconOpen || _jsx(ChevronUp, { size: 16 })
56
+ : iconClose || _jsx(ChevronDown, { size: 16 }) })] }), _jsx("div", { ref: (el) => {
57
57
  contentRefs.current[index] = el;
58
- }, style: {
59
- overflow: "hidden",
58
+ }, className: "overflow-hidden transition-all duration-300", style: {
60
59
  maxHeight: isOpen
61
- ? `${contentRefs.current[index]?.scrollHeight}px`
60
+ ? `${contentRefs.current[index]?.scrollHeight ?? 0}px`
62
61
  : "0px",
63
62
  transition: `max-height ${transitionDuration} ease-in-out`,
64
63
  }, children: _jsx("div", { style: {
65
64
  borderTop: `1px solid ${borderColor}`,
66
65
  backgroundColor: contentBgColor,
67
66
  color: contentTextColor,
68
- padding: contentPadding,
69
- fontSize: contentFontSize,
67
+ padding: toCssValue(contentPadding),
68
+ fontSize: toCssValue(contentFontSize),
70
69
  fontWeight: contentFontWeight,
71
70
  }, children: item.content }) })] }, index));
72
71
  }) }));
@@ -1,141 +1,36 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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
- const getPositionStyle = (position) => {
31
- const base = {
32
- position: "fixed",
33
- zIndex: 9999,
34
- pointerEvents: "auto",
35
- };
36
- switch (position) {
37
- case "top-left":
38
- return { ...base, top: "1.25rem", left: "1.25rem" };
39
- case "top-center":
40
- return { ...base, top: "1.25rem", left: "50%", transform: "translateX(-50%)" };
41
- case "top-right":
42
- return { ...base, top: "1.25rem", right: "1.25rem" };
43
- case "bottom-left":
44
- return { ...base, bottom: "1.25rem", left: "1.25rem" };
45
- case "bottom-center":
46
- return { ...base, bottom: "1.25rem", left: "50%", transform: "translateX(-50%)" };
47
- case "bottom-right":
48
- default:
49
- return { ...base, bottom: "1.25rem", right: "1.25rem" };
50
- }
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useState, useCallback } from "react";
4
+ import { X, Info, CheckCircle, AlertCircle, AlertTriangle } from "lucide-react";
5
+ const ToastContext = createContext(undefined);
6
+ export const useToast = () => {
7
+ const context = useContext(ToastContext);
8
+ if (!context)
9
+ throw new Error("useToast must be used within ToastProvider");
10
+ return context;
51
11
  };
52
- 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, }) => {
53
- const [visible, setVisible] = useState(false);
54
- const [exiting, setExiting] = useState(false);
55
- useEffect(() => {
56
- setVisible(true);
57
- if (duration) {
58
- const timer = setTimeout(() => {
59
- setExiting(true);
60
- setTimeout(() => {
61
- setVisible(false);
62
- onClose?.();
63
- }, parseInt(animationDuration));
64
- }, duration);
65
- return () => clearTimeout(timer);
12
+ export const ToastProvider = ({ children }) => {
13
+ const [toasts, setToasts] = useState([]);
14
+ const removeToast = useCallback((id) => {
15
+ setToasts((prev) => prev.filter((t) => t.id !== id));
16
+ }, []);
17
+ const addToast = useCallback((toast) => {
18
+ const id = Date.now() + Math.random();
19
+ setToasts((prev) => [...prev, { ...toast, id }]);
20
+ if (toast.duration !== 0) {
21
+ setTimeout(() => removeToast(id), toast.duration ?? 4000);
66
22
  }
67
- }, [duration, onClose, animationDuration]);
68
- const { bg, border, iconColor, Icon } = typeStyles[type];
69
- const positionStyle = getPositionStyle(position);
70
- const containerStyle = useMemo(() => ({
71
- ...positionStyle,
72
- display: "flex",
73
- alignItems: "flex-start",
74
- gap: "0.75rem",
75
- backgroundColor: backgroundColor ?? bg,
76
- borderLeft: `4px solid ${borderColor ?? border}`,
77
- borderRadius,
78
- color: textColor,
79
- boxShadow: shadow,
80
- padding,
81
- maxWidth,
82
- width: "calc(100% - 2.5rem)",
83
- opacity: visible && !exiting ? 1 : 0,
84
- transform: visible && !exiting
85
- ? "translateY(0)"
86
- : position.includes("bottom")
87
- ? "translateY(20px)"
88
- : "translateY(-20px)",
89
- transition: `opacity ${animationDuration} ease, transform ${animationDuration} ease`,
90
- fontWeight,
91
- fontSize,
92
- ...style,
93
- }), [
94
- visible,
95
- exiting,
96
- bg,
97
- border,
98
- borderColor,
99
- borderRadius,
100
- position,
101
- shadow,
102
- padding,
103
- textColor,
104
- maxWidth,
105
- backgroundColor,
106
- animationDuration,
107
- style,
108
- fontSize,
109
- fontWeight,
110
- ]);
111
- if (!visible)
112
- return null;
113
- return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
114
- @keyframes alert-slide-in-top {
115
- from { opacity: 0; transform: translateY(-20px); }
116
- to { opacity: 1; transform: translateY(0); }
117
- }
118
- @keyframes alert-slide-in-bottom {
119
- from { opacity: 0; transform: translateY(20px); }
120
- to { opacity: 1; transform: translateY(0); }
121
- }
122
- ` }), _jsxs("div", { className: className, style: {
123
- ...containerStyle,
124
- animation: `${position.includes("bottom") ? "alert-slide-in-bottom" : "alert-slide-in-top"} ${animationDuration} ease`,
125
- }, 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: { fontSize: "0.875rem", color: descriptionColor, lineHeight: 1.4 }, children: description })), actionButton && _jsx("div", { style: { marginTop: "8px" }, children: actionButton })] }), dismissible && (_jsx("button", { onClick: () => {
126
- setExiting(true);
127
- setTimeout(() => {
128
- setVisible(false);
129
- onClose?.();
130
- }, parseInt(animationDuration));
131
- }, style: {
132
- background: "transparent",
133
- border: "none",
134
- color: "#6b7280",
135
- cursor: "pointer",
136
- marginLeft: "8px",
137
- padding: 0,
138
- lineHeight: 0,
139
- }, "aria-label": "Close alert", children: _jsx(X, { size: 16 }) }))] })] }));
23
+ }, [removeToast]);
24
+ return (_jsxs(ToastContext.Provider, { value: { addToast }, children: [children, _jsx("div", { className: "fixed top-5 right-5 flex flex-col gap-3 z-50", children: toasts.map((toast) => (_jsx(ToastItem, { toast: toast, onClose: () => removeToast(toast.id) }, toast.id))) })] }));
25
+ };
26
+ const typeStyles = {
27
+ success: { bg: "bg-green-50", border: "border-green-400", icon: _jsx(CheckCircle, { size: 20, className: "text-green-600" }) },
28
+ error: { bg: "bg-red-50", border: "border-red-400", icon: _jsx(AlertCircle, { size: 20, className: "text-red-600" }) },
29
+ warning: { bg: "bg-yellow-50", border: "border-yellow-400", icon: _jsx(AlertTriangle, { size: 20, className: "text-yellow-600" }) },
30
+ info: { bg: "bg-blue-50", border: "border-blue-400", icon: _jsx(Info, { size: 20, className: "text-blue-600" }) },
31
+ };
32
+ const ToastItem = ({ toast, onClose }) => {
33
+ const { title, description, type = "info" } = toast;
34
+ const { bg, border, icon } = typeStyles[type];
35
+ return (_jsxs("div", { className: `flex items-start gap-3 p-4 border-l-4 rounded shadow ${bg} ${border} animate-slide-in-right`, role: "alert", children: [_jsx("div", { children: icon }), _jsxs("div", { className: "flex-1 min-w-0", children: [title && _jsx("div", { className: "font-semibold text-sm mb-1", children: title }), description && _jsx("div", { className: "text-sm text-gray-700", children: description })] }), _jsx("button", { onClick: onClose, className: "text-gray-500 hover:text-gray-700", children: _jsx(X, { size: 16 }) })] }));
140
36
  };
141
- Alert.displayName = "Alert";
@@ -1,13 +1,14 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useRef, useState, useEffect } from "react";
3
4
  import { Play, Pause, Volume2, VolumeX, Maximize, Minimize, RotateCcw, SkipBack, SkipForward, } from "lucide-react";
4
- export const AudioPlayer = ({ src, thumbnail, autoPlay = false, loop = false, backgroundColor = "#000000", primaryColor = "#10b981", secondaryColor = "#ffffff", borderRadius = "12px", padding = "16px", width = "100%", className, }) => {
5
+ export const AudioPlayer = ({ src, thumbnail, autoPlay = false, loop = false, backgroundColor = "#0a0a0a", primaryColor = "#10b981", secondaryColor = "#ffffff", borderRadius = "12px", padding = "12px", width = "100%", className, }) => {
5
6
  const audioRef = useRef(null);
6
7
  const playerRef = useRef(null);
7
8
  const [isPlaying, setIsPlaying] = useState(autoPlay);
8
9
  const [currentTime, setCurrentTime] = useState(0);
9
10
  const [duration, setDuration] = useState(0);
10
- const [volume, setVolume] = useState(0.5);
11
+ const [volume, setVolume] = useState(0.7);
11
12
  const [isFullscreen, setIsFullscreen] = useState(false);
12
13
  const [isLooping, setIsLooping] = useState(loop);
13
14
  useEffect(() => {
@@ -31,13 +32,12 @@ export const AudioPlayer = ({ src, thumbnail, autoPlay = false, loop = false, ba
31
32
  setDuration(audioRef.current.duration);
32
33
  };
33
34
  const handleSeek = (e) => {
34
- if (!audioRef.current || !e.currentTarget)
35
+ if (!audioRef.current)
35
36
  return;
36
37
  const rect = e.currentTarget.getBoundingClientRect();
37
38
  const percent = (e.clientX - rect.left) / rect.width;
38
- const time = percent * duration;
39
- audioRef.current.currentTime = time;
40
- setCurrentTime(time);
39
+ audioRef.current.currentTime = percent * duration;
40
+ setCurrentTime(percent * duration);
41
41
  };
42
42
  const skip = (seconds) => {
43
43
  if (audioRef.current)
@@ -57,60 +57,74 @@ export const AudioPlayer = ({ src, thumbnail, autoPlay = false, loop = false, ba
57
57
  return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
58
58
  };
59
59
  return (_jsxs("div", { ref: playerRef, className: className, style: {
60
- position: "relative",
60
+ display: "flex",
61
+ alignItems: "center",
62
+ gap: "12px",
61
63
  width,
62
64
  backgroundColor,
63
65
  borderRadius,
64
- color: secondaryColor,
65
- boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
66
- overflow: "hidden",
67
66
  padding,
68
67
  boxSizing: "border-box",
69
- maxWidth: "100%",
68
+ color: secondaryColor,
69
+ boxShadow: "0 6px 16px rgba(0,0,0,0.2)",
70
+ flexWrap: "wrap",
70
71
  }, children: [_jsx("audio", { ref: audioRef, src: src, autoPlay: autoPlay, loop: loop, onTimeUpdate: handleTimeUpdate, onLoadedMetadata: handleTimeUpdate }), thumbnail && (_jsx("img", { src: thumbnail, alt: "Audio Thumbnail", style: {
71
- width: "100%",
72
+ width: "60px",
73
+ height: "60px",
74
+ borderRadius: "50%",
72
75
  objectFit: "cover",
73
- borderRadius,
74
- marginBottom: "16px",
75
- maxHeight: "150px",
76
+ flexShrink: 0,
76
77
  } })), _jsxs("div", { style: {
77
78
  display: "flex",
78
79
  alignItems: "center",
79
- justifyContent: "center",
80
- gap: "10px",
81
- flexShrink: 0,
82
- marginTop: "10px",
83
- }, children: [_jsx("button", { onClick: () => skip(-10), "aria-label": "Skip Back 10s", children: _jsx(SkipBack, { size: 20 }) }), _jsx("button", { onClick: togglePlayPause, style: {
80
+ gap: "12px",
81
+ flex: 1,
82
+ minWidth: "0",
83
+ }, children: [_jsx("button", { onClick: () => skip(-10), style: {
84
+ background: "transparent",
85
+ border: "none",
86
+ color: secondaryColor,
87
+ cursor: "pointer",
88
+ }, "aria-label": "Skip Back 10s", children: _jsx(SkipBack, { size: 20 }) }), _jsx("button", { onClick: togglePlayPause, style: {
84
89
  background: primaryColor,
85
- borderRadius: "9999px",
90
+ borderRadius: "50%",
86
91
  padding: "10px",
87
- color: "#fff",
88
92
  border: "none",
89
93
  cursor: "pointer",
90
- }, "aria-label": isPlaying ? "Pause" : "Play", children: isPlaying ? _jsx(Pause, { size: 20 }) : _jsx(Play, { size: 20 }) }), _jsx("button", { onClick: () => skip(10), "aria-label": "Skip Forward 10s", children: _jsx(SkipForward, { size: 20 }) })] }), _jsxs("div", { style: {
91
- display: "flex",
92
- flexWrap: "wrap",
93
- alignItems: "center",
94
- justifyContent: "space-between",
95
- gap: "12px",
96
- width: "100%",
97
- }, children: [_jsxs("div", { style: { flexShrink: 0, minWidth: "60px", textAlign: "center" }, children: [_jsx("span", { style: { fontSize: "14px", marginRight: "4px" }, children: formatTime(currentTime) }), _jsxs("span", { style: { fontSize: "14px", color: "#aaa" }, children: ["/ ", formatTime(duration)] })] }), _jsx("div", { onClick: handleSeek, style: {
94
+ display: "flex",
95
+ alignItems: "center",
96
+ justifyContent: "center",
97
+ }, "aria-label": isPlaying ? "Pause" : "Play", children: isPlaying ? _jsx(Pause, { size: 20 }) : _jsx(Play, { size: 20 }) }), _jsx("button", { onClick: () => skip(10), style: {
98
+ background: "transparent",
99
+ border: "none",
100
+ color: secondaryColor,
101
+ cursor: "pointer",
102
+ }, "aria-label": "Skip Forward 10s", children: _jsx(SkipForward, { size: 20 }) }), _jsx("div", { onClick: handleSeek, style: {
98
103
  flex: 1,
99
- height: "8px",
104
+ height: "6px",
100
105
  background: "#444",
101
- borderRadius: "4px",
106
+ borderRadius: "3px",
102
107
  cursor: "pointer",
103
108
  position: "relative",
104
- minWidth: "100px",
105
109
  }, children: _jsx("div", { style: {
106
110
  width: `${(currentTime / duration) * 100 || 0}%`,
107
111
  height: "100%",
108
112
  background: primaryColor,
109
- borderRadius: "4px",
110
- } }) }), _jsxs("div", { style: {
111
- display: "flex",
112
- alignItems: "center",
113
- gap: "10px",
114
- flexShrink: 0,
115
- }, children: [_jsx("button", { onClick: () => setIsLooping(!isLooping), "aria-label": "Toggle Loop", children: _jsx(RotateCcw, { size: 18, color: isLooping ? primaryColor : undefined }) }), _jsx("button", { onClick: () => setVolume(volume > 0 ? 0 : 0.5), "aria-label": "Toggle Mute", children: volume > 0 ? _jsx(Volume2, { size: 18 }) : _jsx(VolumeX, { size: 18 }) }), _jsx("button", { onClick: toggleFullscreen, "aria-label": "Toggle Fullscreen", children: isFullscreen ? _jsx(Minimize, { size: 18 }) : _jsx(Maximize, { size: 18 }) })] })] })] }));
113
+ borderRadius: "3px",
114
+ } }) }), _jsxs("div", { style: { minWidth: "70px", textAlign: "right", fontSize: "0.8rem" }, children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("button", { onClick: () => setIsLooping(!isLooping), style: {
115
+ background: "transparent",
116
+ border: "none",
117
+ cursor: "pointer",
118
+ color: isLooping ? primaryColor : secondaryColor,
119
+ }, "aria-label": "Toggle Loop", children: _jsx(RotateCcw, { size: 18 }) }), _jsx("button", { onClick: () => setVolume(volume > 0 ? 0 : 0.7), style: {
120
+ background: "transparent",
121
+ border: "none",
122
+ cursor: "pointer",
123
+ color: secondaryColor,
124
+ }, "aria-label": "Toggle Mute", children: volume > 0 ? _jsx(Volume2, { size: 18 }) : _jsx(VolumeX, { size: 18 }) }), _jsx("button", { onClick: toggleFullscreen, style: {
125
+ background: "transparent",
126
+ border: "none",
127
+ cursor: "pointer",
128
+ color: secondaryColor,
129
+ }, "aria-label": "Toggle Fullscreen", children: isFullscreen ? _jsx(Minimize, { size: 18 }) : _jsx(Maximize, { size: 18 }) })] })] })] }));
116
130
  };
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useState } from "react";
3
4
  import { User } from "lucide-react";
@@ -11,171 +12,57 @@ const sizeMap = {
11
12
  "2xl": 64,
12
13
  };
13
14
  const fontSizeMap = {
14
- xs: 10,
15
- sm: 12,
16
- md: 14,
17
- lg: 16,
18
- xl: 18,
19
- "2xl": 20,
15
+ xs: "text-[10px]",
16
+ sm: "text-[12px]",
17
+ md: "text-[14px]",
18
+ lg: "text-[16px]",
19
+ xl: "text-[18px]",
20
+ "2xl": "text-[20px]",
20
21
  };
21
- const statusSizeMap = {
22
- xs: 6,
23
- sm: 8,
24
- md: 10,
25
- lg: 12,
26
- xl: 14,
27
- "2xl": 16,
22
+ const dimensionMap = {
23
+ xs: "w-6 h-6",
24
+ sm: "w-8 h-8",
25
+ md: "w-10 h-10",
26
+ lg: "w-12 h-12",
27
+ xl: "w-14 h-14",
28
+ "2xl": "w-16 h-16",
28
29
  };
29
- // --- Helpers ---
30
- const getVariantStyles = (variant) => {
31
- switch (variant) {
32
- case "square":
33
- return "0px";
34
- case "rounded":
35
- return "8px";
36
- default:
37
- return "50%";
38
- }
30
+ const variantMap = {
31
+ circular: "rounded-full",
32
+ rounded: "rounded-lg",
33
+ square: "rounded-none",
39
34
  };
40
- const getStatusPositionStyle = (position, offset) => {
41
- switch (position) {
42
- case "top-left":
43
- return { top: 2, left: 2 };
44
- case "top-right":
45
- return { top: 2, right: 2 };
46
- case "bottom-left":
47
- return { bottom: 2, left: 2 };
48
- default:
49
- return { bottom: 2, right: 2 };
50
- }
51
- };
52
- const getSpacingOffset = (spacing, dim) => {
53
- switch (spacing) {
54
- case "tight":
55
- return -(dim * 0.5);
56
- case "loose":
57
- return -(dim * 0.15);
58
- default:
59
- return -(dim * 0.35);
60
- }
35
+ const statusPositionMap = {
36
+ "top-left": "top-0 left-0",
37
+ "top-right": "top-0 right-0",
38
+ "bottom-left": "bottom-0 left-0",
39
+ "bottom-right": "bottom-0 right-0",
61
40
  };
62
41
  // --- Avatar Component ---
63
- export const Avatar = ({ src, alt = "User avatar", size = "md", variant = "circular", isOnline = false, isOffline = false, className = "", style, statusClassName = "", statusStyle, statusPosition = "bottom-right", fallback, ring = false, ringColor = "#3b82f6", onClick, }) => {
42
+ export const Avatar = ({ src, alt = "User avatar", size = "md", variant = "circular", isOnline = false, isOffline = false, fallback, ring = false, ringColor = "#3b82f6", onClick, statusPosition = "bottom-right", className = "", statusClassName = "", style, statusStyle, }) => {
64
43
  const [imageError, setImageError] = useState(false);
65
- const [isHovered, setIsHovered] = useState(false);
66
- const isResponsive = size === "responsive";
67
- const dimension = !isResponsive ? sizeMap[size] : undefined;
68
- const fontSize = !isResponsive ? fontSizeMap[size] : "clamp(10px, 2vw, 16px)";
69
- const statusSize = !isResponsive ? statusSizeMap[size] : 10;
70
- const borderRadius = getVariantStyles(variant);
71
- const initials = fallback || alt
72
- .split(" ")
73
- .map((n) => n[0])
74
- .join("")
75
- .toUpperCase()
76
- .slice(0, 2);
77
- const statusColor = isOnline ? "#10b981" : isOffline ? "#6b7280" : "";
78
- const statusLabel = isOnline ? "Online" : isOffline ? "Offline" : "";
79
- const showImage = src && !imageError;
80
44
  const clickable = !!onClick;
81
- return (_jsxs("div", { className: className, role: clickable ? "button" : "img", tabIndex: clickable ? 0 : -1, "aria-label": alt, onClick: onClick, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onKeyDown: (e) => {
45
+ const initials = fallback ||
46
+ alt
47
+ .split(" ")
48
+ .map((n) => n[0])
49
+ .join("")
50
+ .toUpperCase()
51
+ .slice(0, 2);
52
+ const statusColor = isOnline ? "bg-green-500" : isOffline ? "bg-gray-400" : "";
53
+ // Width/height for inline styles
54
+ const dimensionPx = sizeMap[size];
55
+ return (_jsxs("div", { role: clickable ? "button" : "img", tabIndex: clickable ? 0 : -1, "aria-label": alt, onClick: onClick, className: `relative inline-flex items-center justify-center overflow-hidden ${dimensionMap[size]} ${variantMap[variant]} transition-all duration-200 ${clickable ? "cursor-pointer hover:scale-105" : ""} ${ring ? "ring-2" : ""} ${className}`, style: {
56
+ ...style,
57
+ ...(ring ? { boxShadow: `0 0 0 2px ${ringColor}` } : {}),
58
+ }, onKeyDown: (e) => {
82
59
  if (clickable && (e.key === "Enter" || e.key === " ")) {
83
60
  e.preventDefault();
84
61
  onClick?.();
85
62
  }
86
- }, style: {
87
- position: "relative",
88
- width: isResponsive ? "100%" : dimension,
89
- height: isResponsive ? "100%" : dimension,
90
- borderRadius,
91
- overflow: "visible",
92
- display: "inline-flex",
93
- alignItems: "center",
94
- justifyContent: "center",
95
- flexShrink: 0,
96
- cursor: clickable ? "pointer" : "default",
97
- transition: "all 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
98
- transform: isHovered && clickable ? "scale(1.05)" : "scale(1)",
99
- boxShadow: ring
100
- ? `0 0 0 3px ${ringColor}22, 0 0 0 1px ${ringColor}`
101
- : isHovered && clickable
102
- ? "0 6px 16px rgba(0, 0, 0, 0.15)"
103
- : "0 1px 3px rgba(0, 0, 0, 0.1)",
104
- ...style,
105
- }, children: [showImage ? (_jsx("img", { src: src, alt: alt, onError: () => setImageError(true), loading: "lazy", style: {
106
- width: "100%",
107
- height: "100%",
108
- objectFit: "cover",
109
- borderRadius,
110
- transition: "opacity 0.3s ease-in-out",
111
- } })) : (_jsx("div", { style: {
112
- width: "100%",
113
- height: "100%",
114
- background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
115
- color: "#fff",
116
- fontSize,
117
- fontWeight: 600,
118
- borderRadius,
119
- display: "flex",
120
- alignItems: "center",
121
- justifyContent: "center",
122
- }, children: initials || _jsx(User, { size: dimension ? dimension * 0.5 : 20 }) })), (isOnline || isOffline) && (_jsx("div", { className: statusClassName, "aria-label": statusLabel, title: statusLabel, style: {
123
- position: "absolute",
124
- width: statusSize,
125
- height: statusSize,
126
- borderRadius: "50%",
127
- backgroundColor: statusColor,
128
- border: "2px solid white",
129
- boxShadow: "0 1px 3px rgba(0, 0, 0, 0.12)",
130
- ...getStatusPositionStyle(statusPosition, statusSize),
63
+ }, children: [src && !imageError ? (_jsx("img", { src: src, alt: alt, onError: () => setImageError(true), className: `w-full h-full object-cover ${variantMap[variant]} transition-opacity duration-300` })) : (_jsx("div", { className: `w-full h-full flex items-center justify-center bg-gradient-to-tr from-purple-500 to-blue-500 text-white font-semibold ${fontSizeMap[size]} ${variantMap[variant]}`, children: initials || _jsx(User, { className: "text-white" }) })), (isOnline || isOffline) && (_jsx("span", { "aria-label": isOnline ? "Online" : "Offline", title: isOnline ? "Online" : "Offline", className: `absolute ${statusPositionMap[statusPosition]} ${statusColor} border-2 border-white rounded-full shadow-sm ${statusClassName}`, style: {
64
+ width: dimensionPx / 3,
65
+ height: dimensionPx / 3,
131
66
  ...statusStyle,
132
67
  } }))] }));
133
68
  };
134
- // --- AvatarGroup Component ---
135
- export const AvatarGroup = ({ avatars, max = 4, size = "md", className = "", style, spacing = "normal", direction = "left", }) => {
136
- const isResponsive = size === "responsive";
137
- const dimension = !isResponsive ? sizeMap[size] : 40;
138
- const fontSize = !isResponsive ? fontSizeMap[size] : "clamp(10px, 2vw, 14px)";
139
- const spacingOffset = getSpacingOffset(spacing, dimension);
140
- const visibleAvatars = avatars.slice(0, max);
141
- const extraCount = avatars.length - max;
142
- return (_jsxs("div", { className: className, style: {
143
- display: "flex",
144
- alignItems: "center",
145
- flexDirection: direction === "right" ? "row-reverse" : "row",
146
- flexWrap: "wrap",
147
- ...style,
148
- }, role: "group", "aria-label": `Avatar group with ${avatars.length} members`, children: [visibleAvatars.map((avatar, i) => {
149
- const isFirst = direction === "left" ? i === 0 : i === visibleAvatars.length - 1;
150
- const zIndex = direction === "left" ? visibleAvatars.length - i : i + 1;
151
- return (_jsx("div", { style: {
152
- marginLeft: direction === "left" && !isFirst ? spacingOffset : 0,
153
- marginRight: direction === "right" && !isFirst ? spacingOffset : 0,
154
- zIndex,
155
- position: "relative",
156
- transition: "transform 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
157
- }, onMouseEnter: (e) => {
158
- e.currentTarget.style.transform = `translateY(-3px) scale(1.05)`;
159
- e.currentTarget.style.zIndex = "100";
160
- }, onMouseLeave: (e) => {
161
- e.currentTarget.style.transform = "translateY(0) scale(1)";
162
- e.currentTarget.style.zIndex = zIndex.toString();
163
- }, children: _jsx(Avatar, { ...avatar, size: size }) }, i));
164
- }), extraCount > 0 && (_jsxs("div", { style: {
165
- marginLeft: direction === "left" ? spacingOffset : 0,
166
- marginRight: direction === "right" ? spacingOffset : 0,
167
- width: dimension,
168
- height: dimension,
169
- borderRadius: "50%",
170
- background: "linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%)",
171
- color: "#374151",
172
- fontSize,
173
- fontWeight: 600,
174
- display: "flex",
175
- justifyContent: "center",
176
- alignItems: "center",
177
- border: "3px solid white",
178
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
179
- userSelect: "none",
180
- }, title: `${extraCount} more members`, children: ["+", extraCount] }))] }));
181
- };