@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,66 +1,27 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { memo } from "react";
3
- export const Badge = memo(({ text, color = "#2563eb", textColor = "#fff", borderColor = "#2563eb", borderWidth = "0", icon, iconPosition = "left", rounded = false, borderRadius, fontSize = "13px", fontWeight = 500, horizontalPadding = "10px", verticalPadding = "4px", margin = "0", shadow = "0 1px 3px rgba(0,0,0,0.1)", notificationDot = false, dotColor = "#ef4444", count, pulse = false, style, className = "", onClick, }) => {
4
- const baseStyle = {
5
- display: "inline-flex",
6
- alignItems: "center",
7
- justifyContent: "center",
8
- backgroundColor: color,
9
- color: textColor,
10
- border: `${borderWidth} solid ${borderColor}`,
11
- borderRadius: borderRadius || (rounded ? "9999px" : "6px"),
12
- padding: `${verticalPadding} ${horizontalPadding}`,
13
- fontSize,
14
- fontWeight,
15
- margin,
16
- boxShadow: shadow,
17
- position: "relative",
18
- cursor: onClick ? "pointer" : "default",
19
- userSelect: "none",
20
- lineHeight: 1,
21
- transition: "all 0.2s ease",
22
- ...style,
4
+ import clsx from "clsx";
5
+ /* -------------------------------------------------------------------------- */
6
+ /* 🏷 Badge */
7
+ /* -------------------------------------------------------------------------- */
8
+ export const Badge = memo(({ text, icon, iconPosition = "left", primaryTheme = true, primaryColor = "#3b82f6", size = "md", rounded = true, notificationDot = false, dotColor = "#ef4444", count, pulse = false, className, onClick, }) => {
9
+ /** 📏 Sizes */
10
+ const sizes = {
11
+ sm: "px-2 py-0.5 text-xs",
12
+ md: "px-3 py-1 text-xs",
13
+ lg: "px-4 py-1.5 text-sm",
23
14
  };
24
- const dotStyle = {
25
- position: "absolute",
26
- top: "-4px",
27
- right: "-4px",
28
- height: "8px",
29
- width: "8px",
30
- backgroundColor: dotColor,
31
- borderRadius: "50%",
32
- animation: pulse ? "pulseAnim 1.2s infinite" : undefined,
33
- };
34
- const countStyle = {
35
- position: "absolute",
36
- top: "-8px",
37
- right: "-8px",
38
- backgroundColor: dotColor,
39
- color: "#fff",
40
- borderRadius: "50%",
41
- minWidth: "18px",
42
- height: "18px",
43
- fontSize: "11px",
44
- padding: "0 5px",
45
- display: "flex",
46
- alignItems: "center",
47
- justifyContent: "center",
48
- lineHeight: 1,
49
- };
50
- const iconStyle = {
51
- display: "flex",
52
- alignItems: "center",
53
- margin: icon && text
54
- ? iconPosition === "left"
55
- ? "0 6px 0 0"
56
- : "0 0 0 6px"
57
- : 0,
58
- };
59
- return (_jsxs("span", { style: baseStyle, className: className, onClick: onClick, children: [notificationDot && _jsx("span", { style: dotStyle }), typeof count !== "undefined" && _jsx("span", { style: countStyle, children: count }), icon && iconPosition === "left" && _jsx("span", { style: iconStyle, children: icon }), text && _jsx("span", { children: text }), icon && iconPosition === "right" && _jsx("span", { style: iconStyle, children: icon }), _jsx("style", { children: `
60
- @keyframes pulseAnim {
61
- 0% { transform: scale(1); opacity: 1; }
62
- 50% { transform: scale(1.5); opacity: 0.5; }
63
- 100% { transform: scale(1); opacity: 1; }
64
- }
65
- ` })] }));
15
+ /** 🎨 Theme */
16
+ const themeClasses = primaryTheme
17
+ ? "bg-[var(--primary)] text-white"
18
+ : "";
19
+ const dynamicStyle = !primaryTheme
20
+ ? {
21
+ backgroundColor: primaryColor,
22
+ color: "#fff",
23
+ }
24
+ : {};
25
+ return (_jsxs("span", { onClick: onClick, style: !primaryTheme ? dynamicStyle : undefined, className: clsx("relative inline-flex items-center justify-center gap-1 font-medium", "transition-all duration-200 select-none", rounded ? "rounded-full" : "rounded-md", sizes[size], themeClasses, onClick && "cursor-pointer hover:opacity-90", className), children: [notificationDot && (_jsx("span", { style: !primaryTheme ? { backgroundColor: dotColor } : undefined, className: clsx("absolute -top-1 -right-1 w-2 h-2 rounded-full", pulse && "animate-ping", primaryTheme ? "bg-red-500" : "") })), count !== undefined && (_jsx("span", { style: !primaryTheme ? { backgroundColor: dotColor } : undefined, className: clsx("absolute -top-2 -right-2 min-w-[18px] h-[18px] px-1 text-[10px]", "flex items-center justify-center rounded-full text-white", primaryTheme ? "bg-red-500" : ""), children: count })), icon && iconPosition === "left" && (_jsx("span", { className: "flex items-center", children: icon })), text && _jsx("span", { children: text }), icon && iconPosition === "right" && (_jsx("span", { className: "flex items-center", children: icon }))] }));
66
26
  });
27
+ Badge.displayName = "Badge";
@@ -1,101 +1,28 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useMemo, useState } from "react";
4
- /** 🌈 Default color palette */
5
- const defaultColors = {
6
- light: {
7
- default: "#111",
8
- primary: "#2563eb",
9
- success: "#16a34a",
10
- danger: "#dc2626",
11
- white: "#ffffff",
12
- muted: "#6b7280",
13
- border: "#d1d5db",
14
- hover: "#1d4ed8",
15
- text: "#ffffff",
16
- },
17
- dark: {
18
- default: "#f8fafc",
19
- primary: "#60a5fa",
20
- success: "#22c55e",
21
- danger: "#f87171",
22
- white: "#ffffff",
23
- muted: "#9ca3af",
24
- border: "#374151",
25
- hover: "#3b82f6",
26
- text: "#000000",
27
- },
28
- };
29
- /** 🎨 Adjust color shade utility */
30
- function adjustColor(color, amount) {
31
- if (!color.startsWith("#") || color.length !== 7)
32
- return color;
33
- return ("#" +
34
- color
35
- .replace(/^#/, "")
36
- .replace(/../g, (hex) => ("0" +
37
- Math.min(255, Math.max(0, parseInt(hex, 16) + amount)).toString(16)).slice(-2)));
38
- }
39
- /** 💎 Minimal customizable Button */
40
- export const Button = ({ children, type = "button", onClick, iconBefore, iconAfter, className = "", style, fullWidth = false, disabled = false, loading = false, loadingText = "Loading...", darkMode = false, baseColor, size = "md", rounded = true, showBorder = false, // ✅ default: no border
41
- }) => {
42
- const [hovered, setHovered] = useState(false);
43
- /** 🧠 Theme system */
44
- const theme = useMemo(() => {
45
- if (!baseColor)
46
- return defaultColors[darkMode ? "dark" : "light"];
47
- const light = {
48
- primary: baseColor,
49
- hover: adjustColor(baseColor, -40),
50
- text: "#ffffff",
51
- border: adjustColor(baseColor, -60),
52
- };
53
- const dark = {
54
- primary: adjustColor(baseColor, 60),
55
- hover: adjustColor(baseColor, 80),
56
- text: "#000000",
57
- border: adjustColor(baseColor, 40),
58
- };
59
- return darkMode ? dark : light;
60
- }, [baseColor, darkMode]);
3
+ import { forwardRef } from "react";
4
+ import clsx from "clsx";
5
+ /* -------------------------------------------------------------------------- */
6
+ /* 🔘 Button */
7
+ /* -------------------------------------------------------------------------- */
8
+ export const Button = forwardRef(({ children, iconBefore, iconAfter, loading = false, loadingText = "Loading...", fullWidth = false, primaryTheme = true, primaryColor = "#3b82f6", size = "md", disabled, className, ...props }, ref) => {
61
9
  /** 📏 Sizes */
62
- const sizes = {
63
- sm: { px: 16, py: 6, font: "0.85rem" },
64
- md: { px: 24, py: 10, font: "1rem" },
65
- lg: { px: 32, py: 14, font: "1.125rem" },
10
+ const sizeClasses = {
11
+ sm: "px-3 py-1.5 text-sm",
12
+ md: "px-5 py-2.5 text-sm",
13
+ lg: "px-6 py-3 text-base",
66
14
  };
67
- const s = sizes[size];
68
- /** 💅 Computed styles */
69
- const buttonStyle = {
70
- display: "inline-flex",
71
- alignItems: "center",
72
- justifyContent: "center",
73
- gap: "8px",
74
- padding: `${s.py}px ${s.px}px`,
75
- fontSize: s.font,
76
- fontWeight: 500,
77
- borderRadius: rounded ? 8 : 3,
78
- border: showBorder ? `1px solid ${theme.border}` : "none", // conditional border
79
- width: fullWidth ? "100%" : "auto",
80
- backgroundColor: hovered && !disabled ? theme.hover : theme.primary,
81
- color: theme.text,
82
- cursor: disabled ? "not-allowed" : "pointer",
83
- opacity: disabled ? 0.6 : 1,
84
- transition: "all 0.25s ease-in-out",
85
- boxShadow: "0 1px 3px rgba(0, 0, 0, 0.15)",
86
- ...style,
87
- };
88
- return (_jsxs("button", { type: type, className: className, style: buttonStyle, onClick: !disabled && !loading ? onClick : undefined, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), disabled: disabled, children: [loading ? (_jsxs(_Fragment, { children: [_jsx("span", { style: {
89
- width: "16px",
90
- height: "16px",
91
- border: "2px solid currentColor",
92
- borderTopColor: "transparent",
93
- borderRadius: "50%",
94
- animation: "spin 1s linear infinite",
95
- } }), _jsx("span", { children: loadingText })] })) : (_jsxs(_Fragment, { children: [iconBefore && _jsx("span", { children: iconBefore }), _jsx("span", { children: children }), iconAfter && _jsx("span", { children: iconAfter })] })), _jsx("style", { children: `
96
- @keyframes spin {
97
- to { transform: rotate(360deg); }
98
- }
99
- ` })] }));
100
- };
101
- export default Button;
15
+ /** 🎨 Theme styles */
16
+ const themeClasses = primaryTheme
17
+ ? "bg-[var(--primary)] text-white hover:bg-[var(--primary)]/90 focus:ring-[var(--primary)]/30"
18
+ : "";
19
+ /** 🎨 Dynamic fallback for non-primary theme */
20
+ const dynamicStyle = !primaryTheme
21
+ ? {
22
+ backgroundColor: primaryColor,
23
+ color: "#fff",
24
+ }
25
+ : {};
26
+ return (_jsx("button", { ref: ref, disabled: disabled || loading, style: !primaryTheme ? dynamicStyle : undefined, className: clsx("inline-flex items-center justify-center gap-2 font-medium rounded-lg transition-all duration-200", "focus:outline-none focus:ring-2 focus:ring-offset-1", sizeClasses[size], themeClasses, fullWidth && "w-full", (disabled || loading) && "opacity-60 cursor-not-allowed", className), ...props, children: loading ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" }), _jsx("span", { children: loadingText })] })) : (_jsxs(_Fragment, { children: [iconBefore && (_jsx("span", { className: "flex items-center justify-center", children: iconBefore })), _jsx("span", { children: children }), iconAfter && (_jsx("span", { className: "flex items-center justify-center", children: iconAfter }))] })) }));
27
+ });
28
+ Button.displayName = "Button";
@@ -1,26 +1,49 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- export const CheckboxGroup = ({ name, options, selectedValues = [], onChange, disabled = false, readOnly = false, required = false, error, className = "", customIcon, style, labelStyle, iconSize = 20, iconCheckedBgColor = "#2563eb", iconUncheckedBorderColor = "#9ca3af", textColor = "#374151", errorStyle, }) => {
3
+ import { useState, useEffect, useRef } from "react";
4
+ import clsx from "clsx";
5
+ export const CheckboxGroup = ({ name, options, selectedValues = [], onChange, disabled = false, readOnly = false, required = false, error, className, customIcon, style, labelStyle, iconSize = 20, iconCheckedBgColor = "#2563eb", iconUncheckedBorderColor = "#9ca3af", textColor = "#374151", errorStyle, darkMode = false, }) => {
6
+ const containerRef = useRef(null);
7
+ const [focusedIndex, setFocusedIndex] = useState(null);
4
8
  const handleChange = (value) => {
5
- if (!onChange)
9
+ if (!onChange || disabled || readOnly)
6
10
  return;
7
11
  const updatedValues = selectedValues.includes(value)
8
12
  ? selectedValues.filter((v) => v !== value)
9
13
  : [...selectedValues, value];
10
14
  onChange(updatedValues);
11
15
  };
12
- return (_jsxs("div", { className: className, style: { display: "flex", flexDirection: "column", gap: 8, ...style }, role: "group", "aria-disabled": disabled, children: [options.map((option) => {
16
+ // Keyboard navigation (space/enter toggle)
17
+ useEffect(() => {
18
+ const container = containerRef.current;
19
+ if (!container)
20
+ return;
21
+ const handleKeyDown = (e) => {
22
+ if (disabled)
23
+ return;
24
+ if (focusedIndex === null)
25
+ return;
26
+ const currentIndex = focusedIndex;
27
+ if (e.key === "ArrowDown" || e.key === "ArrowRight") {
28
+ e.preventDefault();
29
+ setFocusedIndex((currentIndex + 1) % options.length);
30
+ }
31
+ if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
32
+ e.preventDefault();
33
+ setFocusedIndex((currentIndex - 1 + options.length) % options.length);
34
+ }
35
+ if (e.key === " " || e.key === "Enter") {
36
+ e.preventDefault();
37
+ handleChange(options[currentIndex].value);
38
+ }
39
+ };
40
+ container.addEventListener("keydown", handleKeyDown);
41
+ return () => container.removeEventListener("keydown", handleKeyDown);
42
+ }, [focusedIndex, options, selectedValues, disabled]);
43
+ return (_jsxs("div", { ref: containerRef, role: "group", "aria-disabled": disabled, "aria-invalid": !!error, tabIndex: 0, className: clsx("flex flex-col gap-2", className), style: { ...style }, children: [options.map((option, index) => {
13
44
  const isChecked = selectedValues.includes(option.value);
14
- return (_jsxs("label", { style: {
15
- display: "flex",
16
- alignItems: "center",
17
- justifyContent: "space-between",
18
- cursor: disabled ? "not-allowed" : "pointer",
19
- opacity: disabled ? 0.6 : 1,
20
- gap: 8,
21
- userSelect: "none",
22
- ...labelStyle,
23
- }, children: [_jsx("span", { style: { color: textColor, fontSize: 14 }, children: option.label }), _jsx("input", { type: "checkbox", name: name, value: option.value, checked: isChecked, disabled: disabled || readOnly, required: required, onChange: () => handleChange(option.value), style: { display: "none" } }), customIcon ? (customIcon(isChecked)) : (_jsx("span", { style: {
45
+ const isFocused = focusedIndex === index;
46
+ return (_jsxs("label", { className: clsx("flex items-center justify-between cursor-pointer select-none transition-opacity", disabled ? "opacity-50 cursor-not-allowed" : "opacity-100", isFocused ? "ring-2 ring-blue-400" : ""), style: { ...labelStyle }, onFocus: () => setFocusedIndex(index), children: [_jsx("span", { style: { color: textColor, fontSize: 14 }, children: option.label }), _jsx("input", { type: "checkbox", name: name, value: option.value, checked: isChecked, disabled: disabled || readOnly, required: required, onChange: () => handleChange(option.value), style: { display: "none" } }), customIcon ? (customIcon(isChecked)) : (_jsx("span", { style: {
24
47
  display: "inline-flex",
25
48
  justifyContent: "center",
26
49
  alignItems: "center",
@@ -1,45 +1,26 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useMemo } from "react";
3
- /** Tailwind-like container widths */
4
- const containerWidths = {
5
- sm: "640px",
6
- md: "768px",
7
- lg: "1024px",
8
- xl: "1280px",
9
- "2xl": "1536px",
10
- full: "100%",
2
+ import clsx from "clsx";
3
+ const sizeMap = {
4
+ sm: "max-w-sm",
5
+ md: "max-w-md",
6
+ lg: "max-w-lg",
7
+ xl: "max-w-xl",
8
+ "2xl": "max-w-2xl",
9
+ full: "w-full",
10
+ };
11
+ const paddingMap = {
12
+ none: "p-0",
13
+ sm: "p-4",
14
+ md: "p-6",
15
+ lg: "p-8",
16
+ xl: "p-12",
11
17
  };
12
18
  /**
13
19
  * 🧱 Container Component
14
- * A flexible, responsive layout wrapper inspired by Tailwind's container.
20
+ * Fully responsive, Tailwind-first layout wrapper
15
21
  */
16
- export const Container = ({ size = "lg", padding = 0, margin = "0 auto", backgroundColor = "transparent", center = true, width, height = "auto", borderRadius = 0, style, className = "", children, }) => {
17
- const styles = useMemo(() => {
18
- const computedPadding = typeof padding === "number" ? `${padding}px` : padding;
19
- const computedMargin = center && margin === "0 auto" ? "0 auto" : margin;
20
- const computedBorderRadius = typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius;
21
- return {
22
- maxWidth: width ?? containerWidths[size],
23
- margin: computedMargin,
24
- padding: computedPadding,
25
- backgroundColor,
26
- height,
27
- borderRadius: computedBorderRadius,
28
- boxSizing: "border-box",
29
- width: width ?? "100%",
30
- ...style,
31
- };
32
- }, [
33
- size,
34
- padding,
35
- margin,
36
- backgroundColor,
37
- width,
38
- height,
39
- borderRadius,
40
- center,
41
- style,
42
- ]);
43
- return (_jsx("div", { className: className, style: styles, children: children }));
22
+ export const Container = ({ size = "lg", padding = "md", center = true, className = "", children, }) => {
23
+ const classes = clsx("w-full box-border", sizeMap[size], paddingMap[padding], center && "mx-auto", className);
24
+ return _jsx("div", { className: classes, children: children });
44
25
  };
45
26
  export default Container;
@@ -1,36 +1,21 @@
1
+ "use client";
1
2
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { useState, useEffect, useMemo } from "react";
3
4
  import { X } from "lucide-react";
4
- export const DrawerButton = ({ label = "Open Drawer", icon, iconPosition = "left", onClick, color = "#2563eb", textColor = "#fff", borderRadius = "6px", padding = "10px 16px", fontSize = "14px", gap = "8px", style, className = "", }) => (_jsxs("button", { onClick: onClick, style: {
5
- display: "inline-flex",
6
- alignItems: "center",
7
- justifyContent: "center",
8
- backgroundColor: color,
9
- color: textColor,
10
- border: "none",
11
- borderRadius,
12
- padding,
13
- fontSize,
14
- gap,
15
- cursor: "pointer",
16
- fontWeight: 500,
17
- transition: "all 0.2s ease",
18
- boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
19
- ...style,
20
- }, className: className, children: [icon && iconPosition === "left" && icon, label, icon && iconPosition === "right" && icon] }));
21
- export const Drawer = ({ open, onClose, position = "right", width = "320px", height = "320px", backgroundColor = "#fff", backdropColor = "rgba(0,0,0,0.5)", transitionDuration = 300, style, className = "", children, showCloseButton = true, closeIconColor = "#000", closeButtonStyle, }) => {
22
- const [visible, setVisible] = useState(open);
23
- // Handle mount/unmount delay for smooth fade-out
5
+ export const DrawerButton = ({ label = "Open Drawer", icon, iconPosition = "left", onClick, className = "", }) => {
6
+ return (_jsxs("button", { type: "button", onClick: onClick, className: `inline-flex items-center justify-center gap-2 px-4 py-2 rounded-md font-medium text-white bg-blue-600 hover:bg-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 ${className}`, children: [icon && iconPosition === "left" && icon, label, icon && iconPosition === "right" && icon] }));
7
+ };
8
+ export const Drawer = ({ open, onClose, position = "right", size = "320px", children, showCloseButton = true, className = "", overlayClassName = "", closeButtonClassName = "", }) => {
9
+ const [mounted, setMounted] = useState(open);
24
10
  useEffect(() => {
25
11
  if (open)
26
- setVisible(true);
12
+ setMounted(true);
27
13
  else
28
- setTimeout(() => setVisible(false), transitionDuration);
29
- }, [open, transitionDuration]);
30
- // Drawer transform direction
14
+ setTimeout(() => setMounted(false), 300); // smooth exit
15
+ }, [open]);
31
16
  const transform = useMemo(() => {
32
17
  if (open)
33
- return "translate(0, 0)";
18
+ return "translate(0,0)";
34
19
  switch (position) {
35
20
  case "left":
36
21
  return "translateX(-100%)";
@@ -41,54 +26,18 @@ export const Drawer = ({ open, onClose, position = "right", width = "320px", hei
41
26
  case "bottom":
42
27
  return "translateY(100%)";
43
28
  default:
44
- return "translate(0, 0)";
29
+ return "translate(0,0)";
45
30
  }
46
31
  }, [open, position]);
47
- const drawerStyle = {
48
- position: "fixed",
49
- backgroundColor,
50
- transition: `transform ${transitionDuration}ms ease, opacity ${transitionDuration}ms ease`,
51
- transform,
52
- opacity: open ? 1 : 0,
53
- zIndex: 1001,
54
- ...style,
55
- ...(position === "left" || position === "right"
56
- ? { top: 0, bottom: 0, [position]: 0, width, height: "100%" }
57
- : { left: 0, right: 0, [position]: 0, height, width: "100%" }),
58
- };
59
- const overlayStyle = {
60
- position: "fixed",
61
- inset: 0,
62
- backgroundColor: backdropColor,
63
- opacity: open ? 1 : 0,
64
- transition: `opacity ${transitionDuration}ms ease`,
65
- zIndex: 1000,
66
- display: visible ? "block" : "none",
67
- pointerEvents: open ? "auto" : "none",
68
- };
69
- const defaultCloseButtonStyle = {
70
- position: "absolute",
71
- top: "12px",
72
- right: "12px",
73
- background: "none",
74
- border: "none",
75
- cursor: "pointer",
76
- display: "flex",
77
- alignItems: "center",
78
- justifyContent: "center",
79
- transition: "transform 0.2s ease, opacity 0.2s ease",
80
- };
81
- return (_jsxs(_Fragment, { children: [_jsx("div", { style: overlayStyle, onClick: onClose }), _jsxs("div", { style: {
82
- ...drawerStyle,
83
- display: "flex",
84
- flexDirection: "column",
85
- visibility: visible ? "visible" : "hidden",
86
- pointerEvents: open ? "auto" : "none",
87
- boxShadow: "0 0 20px rgba(0,0,0,0.15)",
88
- }, className: className, children: [showCloseButton && (_jsx("button", { onClick: onClose, style: { ...defaultCloseButtonStyle, ...closeButtonStyle }, "aria-label": "Close drawer", children: _jsx(X, { size: 22, color: closeIconColor }) })), _jsx("div", { style: {
89
- flex: 1,
90
- overflowY: "auto",
91
- padding: "16px",
92
- scrollbarWidth: "thin",
93
- }, children: children })] })] }));
32
+ if (!mounted && !open)
33
+ return null;
34
+ return (_jsxs(_Fragment, { children: [_jsx("div", { onClick: onClose, className: `fixed inset-0 bg-black/50 transition-opacity ${open ? "opacity-100" : "opacity-0"} ${overlayClassName}` }), _jsxs("div", { className: `fixed bg-white shadow-lg flex flex-col transition-transform duration-300 ${className}`, style: {
35
+ width: position === "left" || position === "right" ? size : "100%",
36
+ height: position === "top" || position === "bottom" ? size : "100%",
37
+ top: position === "bottom" || position === "top" ? (position === "bottom" ? "auto" : 0) : 0,
38
+ bottom: position === "bottom" ? 0 : "auto",
39
+ left: position === "left" ? 0 : position === "right" ? "auto" : 0,
40
+ right: position === "right" ? 0 : position === "left" ? "auto" : 0,
41
+ transform,
42
+ }, children: [showCloseButton && (_jsx("button", { onClick: onClose, className: `absolute top-4 right-4 p-1 rounded-full hover:bg-gray-200 transition-colors ${closeButtonClassName}`, "aria-label": "Close drawer", children: _jsx(X, { size: 20 }) })), _jsx("div", { className: "flex-1 overflow-auto p-4", children: children })] })] }));
94
43
  };