@neuctra/ui 0.2.8 → 0.2.10

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 (81) hide show
  1. package/dist/index.cjs.js +107 -138
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.es.js +1555 -2367
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/{components → types/components}/basic/Alert.d.ts +3 -2
  6. package/dist/{components → types/components}/basic/Avatar.d.ts +1 -0
  7. package/dist/{components → types/components}/basic/Badge.d.ts +1 -2
  8. package/dist/{components → types/components}/basic/Button.d.ts +2 -2
  9. package/dist/{components → types/components}/basic/Input.d.ts +1 -1
  10. package/dist/types/components/basic/Tabs.d.ts +76 -0
  11. package/dist/{components → types/components}/basic/Text.d.ts +25 -20
  12. package/dist/{index.d.ts → types/index.d.ts} +3 -7
  13. package/dist/ui.css +1 -1
  14. package/package.json +9 -2
  15. package/dist/components/basic/Tabs.d.ts +0 -47
  16. package/dist/src/components/basic/Accordation.js +0 -73
  17. package/dist/src/components/basic/Alert.js +0 -36
  18. package/dist/src/components/basic/AudioGallery.js +0 -425
  19. package/dist/src/components/basic/AudioPlayer.js +0 -130
  20. package/dist/src/components/basic/Avatar.js +0 -68
  21. package/dist/src/components/basic/Badge.js +0 -27
  22. package/dist/src/components/basic/Button.js +0 -25
  23. package/dist/src/components/basic/CheckboxGroup.js +0 -63
  24. package/dist/src/components/basic/Container.js +0 -26
  25. package/dist/src/components/basic/Drawer.js +0 -43
  26. package/dist/src/components/basic/DropDown.js +0 -98
  27. package/dist/src/components/basic/FlexView.js +0 -19
  28. package/dist/src/components/basic/GridView.js +0 -18
  29. package/dist/src/components/basic/Image.js +0 -55
  30. package/dist/src/components/basic/Input.js +0 -82
  31. package/dist/src/components/basic/List.js +0 -29
  32. package/dist/src/components/basic/Modal.js +0 -34
  33. package/dist/src/components/basic/RadioGroup.js +0 -54
  34. package/dist/src/components/basic/Stack.js +0 -22
  35. package/dist/src/components/basic/SwitchGroup.js +0 -76
  36. package/dist/src/components/basic/Table.js +0 -30
  37. package/dist/src/components/basic/Tabs.js +0 -140
  38. package/dist/src/components/basic/Text.js +0 -25
  39. package/dist/src/index.js +0 -44
  40. package/dist/types/src/components/basic/Accordation.d.ts +0 -40
  41. package/dist/types/src/components/basic/Alert.d.ts +0 -17
  42. package/dist/types/src/components/basic/AudioGallery.d.ts +0 -23
  43. package/dist/types/src/components/basic/AudioPlayer.d.ts +0 -16
  44. package/dist/types/src/components/basic/Avatar.d.ts +0 -23
  45. package/dist/types/src/components/basic/Badge.d.ts +0 -21
  46. package/dist/types/src/components/basic/Button.d.ts +0 -15
  47. package/dist/types/src/components/basic/CheckboxGroup.d.ts +0 -27
  48. package/dist/types/src/components/basic/Container.d.ts +0 -15
  49. package/dist/types/src/components/basic/Drawer.d.ts +0 -22
  50. package/dist/types/src/components/basic/DropDown.d.ts +0 -33
  51. package/dist/types/src/components/basic/FlexView.d.ts +0 -16
  52. package/dist/types/src/components/basic/GridView.d.ts +0 -14
  53. package/dist/types/src/components/basic/Image.d.ts +0 -37
  54. package/dist/types/src/components/basic/Input.d.ts +0 -33
  55. package/dist/types/src/components/basic/List.d.ts +0 -20
  56. package/dist/types/src/components/basic/Modal.d.ts +0 -17
  57. package/dist/types/src/components/basic/RadioGroup.d.ts +0 -26
  58. package/dist/types/src/components/basic/Stack.d.ts +0 -17
  59. package/dist/types/src/components/basic/SwitchGroup.d.ts +0 -26
  60. package/dist/types/src/components/basic/Table.d.ts +0 -28
  61. package/dist/types/src/components/basic/Tabs.d.ts +0 -47
  62. package/dist/types/src/components/basic/Text.d.ts +0 -1121
  63. package/dist/types/src/index.d.ts +0 -24
  64. package/dist/types/vite.config.d.ts +0 -2
  65. package/dist/vite.config.js +0 -34
  66. /package/dist/{components/basic/Accordation.d.ts → types/components/basic/Accordion.d.ts} +0 -0
  67. /package/dist/{components → types/components}/basic/AudioGallery.d.ts +0 -0
  68. /package/dist/{components → types/components}/basic/AudioPlayer.d.ts +0 -0
  69. /package/dist/{components → types/components}/basic/CheckboxGroup.d.ts +0 -0
  70. /package/dist/{components → types/components}/basic/Container.d.ts +0 -0
  71. /package/dist/{components → types/components}/basic/Drawer.d.ts +0 -0
  72. /package/dist/{components → types/components}/basic/DropDown.d.ts +0 -0
  73. /package/dist/{components → types/components}/basic/FlexView.d.ts +0 -0
  74. /package/dist/{components → types/components}/basic/GridView.d.ts +0 -0
  75. /package/dist/{components → types/components}/basic/Image.d.ts +0 -0
  76. /package/dist/{components → types/components}/basic/List.d.ts +0 -0
  77. /package/dist/{components → types/components}/basic/Modal.d.ts +0 -0
  78. /package/dist/{components → types/components}/basic/RadioGroup.d.ts +0 -0
  79. /package/dist/{components → types/components}/basic/Stack.d.ts +0 -0
  80. /package/dist/{components → types/components}/basic/SwitchGroup.d.ts +0 -0
  81. /package/dist/{components → types/components}/basic/Table.d.ts +0 -0
@@ -1,25 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
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, variant = "default", size = "md", disabled, className, type = "button", // ✅ important fix
9
- ...props }, ref) => {
10
- const isDisabled = disabled || loading;
11
- /** 📏 Sizes */
12
- const sizeClasses = {
13
- sm: "h-8 px-3 text-sm",
14
- md: "h-10 px-4 text-sm",
15
- lg: "h-12 px-6 text-base",
16
- };
17
- /** 🎨 Variants */
18
- const variantClasses = {
19
- default: "bg-blue-600 text-white hover:bg-blue-600/90",
20
- outline: "border border-zinc-300 text-zinc-900 hover:bg-zinc-100 dark:border-zinc-700 dark:text-white dark:hover:bg-zinc-800",
21
- ghost: "text-zinc-900 hover:bg-zinc-100 dark:text-white dark:hover:bg-zinc-800",
22
- };
23
- return (_jsx("button", { ref: ref, type: type, disabled: isDisabled, className: clsx("inline-flex items-center justify-center gap-2 rounded-lg font-medium transition-all duration-200", "focus:outline-none focus:ring-2 focus:ring-blue-500/30", "active:scale-[0.98]", sizeClasses[size], variantClasses[variant], fullWidth && "w-full", isDisabled && "opacity-60 cursor-not-allowed pointer-events-none", 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", children: iconBefore }), _jsx("span", { children: children }), iconAfter && _jsx("span", { className: "flex", children: iconAfter })] })) }));
24
- });
25
- Button.displayName = "Button";
@@ -1,63 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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);
8
- const handleChange = (value) => {
9
- if (!onChange || disabled || readOnly)
10
- return;
11
- const updatedValues = selectedValues.includes(value)
12
- ? selectedValues.filter((v) => v !== value)
13
- : [...selectedValues, value];
14
- onChange(updatedValues);
15
- };
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) => {
44
- const isChecked = selectedValues.includes(option.value);
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: {
47
- display: "inline-flex",
48
- justifyContent: "center",
49
- alignItems: "center",
50
- width: iconSize,
51
- height: iconSize,
52
- borderRadius: 4,
53
- border: `2px solid ${isChecked ? iconCheckedBgColor : iconUncheckedBorderColor}`,
54
- backgroundColor: isChecked ? iconCheckedBgColor : "transparent",
55
- transition: "all 0.25s ease",
56
- }, children: isChecked && (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", style: { width: iconSize * 0.6, height: iconSize * 0.6 }, children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) }))] }, option.value));
57
- }), error && (_jsx("p", { role: "alert", style: {
58
- color: "#dc2626",
59
- fontSize: 12,
60
- marginTop: 4,
61
- ...errorStyle,
62
- }, children: error }))] }));
63
- };
@@ -1,26 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
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",
17
- };
18
- /**
19
- * 🧱 Container Component
20
- * Fully responsive, Tailwind-first layout wrapper
21
- */
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 });
25
- };
26
- export default Container;
@@ -1,43 +0,0 @@
1
- "use client";
2
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState, useEffect, useMemo } from "react";
4
- import { X } from "lucide-react";
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);
10
- useEffect(() => {
11
- if (open)
12
- setMounted(true);
13
- else
14
- setTimeout(() => setMounted(false), 300); // smooth exit
15
- }, [open]);
16
- const transform = useMemo(() => {
17
- if (open)
18
- return "translate(0,0)";
19
- switch (position) {
20
- case "left":
21
- return "translateX(-100%)";
22
- case "right":
23
- return "translateX(100%)";
24
- case "top":
25
- return "translateY(-100%)";
26
- case "bottom":
27
- return "translateY(100%)";
28
- default:
29
- return "translate(0,0)";
30
- }
31
- }, [open, position]);
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 })] })] }));
43
- };
@@ -1,98 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState, forwardRef, useImperativeHandle, } from "react";
4
- import { ChevronDown } from "lucide-react";
5
- export const Dropdown = forwardRef((props, ref) => {
6
- const { label, name = "", value, defaultValue, onChange, options = [], placeholder = "Select an option", required, disabled, error, success, helperText, labelIcon: LabelIcon, prefixIcon: PrefixIcon, variant = "dark", primaryTheme = true, primaryColor = "#3b82f6", containerClassName = "", labelClassName = "", triggerClassName = "", dropdownClassName = "", optionClassName = "", className = "", } = props;
7
- const containerRef = useRef(null);
8
- useImperativeHandle(ref, () => containerRef.current);
9
- const [open, setOpen] = useState(false);
10
- const [localValue, setLocalValue] = useState(defaultValue || "");
11
- const currentValue = value !== undefined ? value : localValue;
12
- const selectedOption = options.find((o) => o.value === currentValue);
13
- /** Close outside */
14
- useEffect(() => {
15
- const handler = (e) => {
16
- if (containerRef.current &&
17
- !containerRef.current.contains(e.target)) {
18
- setOpen(false);
19
- }
20
- };
21
- document.addEventListener("mousedown", handler);
22
- return () => document.removeEventListener("mousedown", handler);
23
- }, []);
24
- const handleSelect = (option) => {
25
- setLocalValue(option.value);
26
- onChange?.(name, option.value);
27
- setOpen(false);
28
- };
29
- /** 🎨 Theme base */
30
- const theme = {
31
- dark: {
32
- bg: "bg-zinc-900",
33
- text: "text-white",
34
- border: "border-zinc-800",
35
- dropdown: "bg-black border-zinc-800",
36
- option: "text-zinc-300 hover:bg-zinc-800",
37
- },
38
- light: {
39
- bg: "bg-white",
40
- text: "text-gray-900",
41
- border: "border-gray-300",
42
- dropdown: "bg-white border-gray-200",
43
- option: "text-gray-700 hover:bg-gray-100",
44
- },
45
- }[variant];
46
- /** 🎨 Dynamic styles */
47
- const dynamicPrimary = !primaryTheme
48
- ? { color: primaryColor }
49
- : {};
50
- const dynamicBorder = !primaryTheme
51
- ? { borderColor: primaryColor }
52
- : {};
53
- const dynamicBgActive = !primaryTheme
54
- ? { backgroundColor: `${primaryColor}20` } // light tint
55
- : {};
56
- /** Border state */
57
- const borderState = error
58
- ? "border-rose-500"
59
- : success
60
- ? "border-emerald-500"
61
- : theme.border;
62
- const paddingLeft = PrefixIcon ? "pl-12 pr-10" : "px-4";
63
- return (_jsxs("div", { ref: containerRef, className: `w-full space-y-1 ${containerClassName} ${className}`, children: [label && (_jsxs("label", { className: `flex items-center gap-2 text-[13px] font-medium ${labelClassName}`, children: [LabelIcon && (_jsx(LabelIcon, { className: primaryTheme ? "w-4 h-4 text-[var(--primary)]" : "w-4 h-4", style: !primaryTheme ? dynamicPrimary : undefined })), label, required && _jsx("span", { className: "text-rose-500", children: "*" })] })), _jsxs("div", { className: "relative group", children: [PrefixIcon && (_jsx("div", { className: "absolute left-4 top-1/2 -translate-y-1/2 z-10", children: _jsx(PrefixIcon, { className: primaryTheme
64
- ? "w-4 h-4 text-zinc-500 group-focus-within:text-[var(--primary)]"
65
- : "w-4 h-4 text-zinc-500", style: !primaryTheme ? dynamicPrimary : undefined }) })), _jsxs("button", { type: "button", disabled: disabled, onClick: () => setOpen((p) => !p), style: !primaryTheme ? dynamicBorder : undefined, className: `
66
- w-full py-2.5 rounded-lg text-left text-sm
67
- border transition-all duration-200
68
- ${theme.bg} ${theme.text}
69
- ${paddingLeft}
70
- ${borderState}
71
- focus:ring-2
72
- ${primaryTheme
73
- ? "focus:ring-[var(--primary)]/30 focus:border-[var(--primary)]"
74
- : ""}
75
- disabled:opacity-50 disabled:cursor-not-allowed
76
- ${triggerClassName}
77
- `, children: [_jsx("span", { className: selectedOption ? theme.text : "text-zinc-500", children: selectedOption ? selectedOption.label : placeholder }), _jsx(ChevronDown, { className: `absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 transition-transform ${open ? "rotate-180" : ""} text-zinc-400` })] }), open && (_jsx("div", { className: `
78
- absolute z-50 mt-2 w-full rounded-xl border shadow-xl overflow-hidden
79
- ${theme.dropdown}
80
- ${dropdownClassName}
81
- `, children: _jsx("ul", { className: "max-h-60 overflow-y-auto", children: options.map((opt) => {
82
- const isActive = currentValue === opt.value;
83
- return (_jsxs("li", { onClick: () => handleSelect(opt), style: !primaryTheme && isActive ? dynamicBgActive : undefined, className: `
84
- px-4 py-3 cursor-pointer text-sm flex items-center gap-2
85
- ${isActive
86
- ? primaryTheme
87
- ? "bg-[var(--primary)]/15 text-[var(--primary)]"
88
- : ""
89
- : theme.option}
90
- ${optionClassName}
91
- `, children: [opt.icon && _jsx("span", { children: opt.icon }), opt.label] }, opt.value));
92
- }) }) }))] }), (helperText || error) && (_jsx("p", { className: `text-xs ${error
93
- ? "text-rose-500"
94
- : success
95
- ? "text-emerald-500"
96
- : "text-zinc-500"}`, children: typeof error === "string" ? error : helperText }))] }));
97
- });
98
- Dropdown.displayName = "Dropdown";
@@ -1,19 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import clsx from "clsx";
3
- /**
4
- * Converts responsive prop to Tailwind class string
5
- */
6
- const toResponsiveClass = (prefix, value) => {
7
- if (value == null)
8
- return "";
9
- if (typeof value !== "object")
10
- return `${prefix}-${value}`;
11
- return Object.entries(value)
12
- .map(([bp, val]) => `${bp}:${prefix}-${val}`)
13
- .join(" ");
14
- };
15
- export const FlexView = ({ direction = { sm: "column", md: "row" }, align = "center", justify = "between", wrap = "wrap", gap, padding, margin, className, children, }) => {
16
- const classes = clsx("flex", toResponsiveClass("flex", direction), toResponsiveClass("items", align), toResponsiveClass("justify", justify), toResponsiveClass("flex", wrap), // wrap classes: flex-wrap, flex-nowrap, flex-wrap-reverse
17
- gap ? toResponsiveClass("gap", gap) : "", padding ? toResponsiveClass("p", padding) : "", margin ? toResponsiveClass("m", margin) : "", className);
18
- return _jsx("div", { className: classes, children: children });
19
- };
@@ -1,18 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import clsx from "clsx";
3
- /**
4
- * Converts responsive prop to Tailwind class string
5
- */
6
- const toResponsiveClass = (prefix, value) => {
7
- if (value == null)
8
- return "";
9
- if (typeof value !== "object")
10
- return `${prefix}-${value}`;
11
- return Object.entries(value)
12
- .map(([bp, val]) => `${bp}:${prefix}-${val}`)
13
- .join(" ");
14
- };
15
- export const GridView = ({ columns = { sm: 1, md: 2, lg: 3 }, gap = 4, padding = 4, alignItems = "stretch", justifyItems = "stretch", className, children, }) => {
16
- const classes = clsx("grid", toResponsiveClass("grid-cols", columns), toResponsiveClass("gap", gap), toResponsiveClass("p", padding), alignItems !== "stretch" ? `items-${alignItems}` : "items-stretch", justifyItems !== "stretch" ? `justify-items-${justifyItems}` : "justify-items-stretch", className);
17
- return _jsx("div", { className: classes, children: children });
18
- };
@@ -1,55 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useMemo } from "react";
4
- export const Image = ({ src, alt = "Image", title, width = "w-full", height = "h-auto", rounded = "rounded-lg", borderColor = "transparent", borderWidth = "0", shadow = false, opacity = 1, objectFit = "cover", overlayText, overlayColor = "rgba(0,0,0,0.5)", svgIcon, responsive = false, p, m, hoverScale = 1.05, hoverRotate = 0, hoverOpacity, hoverShadow = false, transitionDuration = "300ms", overflow = "hidden", className = "", style, onClick, onLoad, onError, }) => {
5
- /** Base Tailwind classes */
6
- const baseClasses = useMemo(() => {
7
- const classes = [
8
- "relative inline-block transition-all",
9
- shadow ? "shadow-md" : "",
10
- rounded,
11
- p ?? "",
12
- m ?? "",
13
- responsive ? "w-full h-auto" : "",
14
- className,
15
- ];
16
- return classes.join(" ");
17
- }, [shadow, rounded, p, m, responsive, className]);
18
- /** Inline styles for custom widths, hover, borders, etc. */
19
- const inlineStyles = useMemo(() => ({
20
- width: typeof width === "number" ? width : undefined,
21
- height: typeof height === "number" ? height : undefined,
22
- objectFit,
23
- opacity,
24
- borderColor,
25
- borderWidth,
26
- transition: `all ${transitionDuration} ease`,
27
- ...style,
28
- }), [width, height, objectFit, opacity, borderColor, borderWidth, transitionDuration, style]);
29
- /** Overflow handling */
30
- const overflowStyles = useMemo(() => {
31
- switch (overflow) {
32
- case "x":
33
- return { overflowX: "hidden" };
34
- case "y":
35
- return { overflowY: "hidden" };
36
- default:
37
- return { overflow };
38
- }
39
- }, [overflow]);
40
- /** Hover effects */
41
- const handleMouseEnter = (e) => {
42
- if (hoverOpacity !== undefined)
43
- e.currentTarget.style.opacity = hoverOpacity.toString();
44
- e.currentTarget.style.transform = `scale(${hoverScale}) rotate(${hoverRotate}deg)`;
45
- if (hoverShadow)
46
- e.currentTarget.style.boxShadow = "0 8px 20px rgba(0,0,0,0.3)";
47
- };
48
- const handleMouseLeave = (e) => {
49
- e.currentTarget.style.opacity = opacity.toString();
50
- e.currentTarget.style.transform = "scale(1) rotate(0deg)";
51
- if (hoverShadow)
52
- e.currentTarget.style.boxShadow = "";
53
- };
54
- return (_jsxs("div", { role: "img", "aria-label": alt, title: title || alt, onClick: onClick, className: baseClasses, style: { position: "relative", cursor: onClick ? "pointer" : "default", ...overflowStyles }, children: [svgIcon ? (_jsx("div", { className: "w-full h-full flex items-center justify-center", children: svgIcon })) : (_jsx("img", { src: src, alt: alt, title: title || alt, loading: "lazy", style: inlineStyles, className: "block", onLoad: onLoad, onError: onError, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave })), overlayText && (_jsx("div", { style: { backgroundColor: overlayColor }, className: "absolute inset-0 flex items-center justify-center text-white font-bold text-center p-4", children: overlayText }))] }));
55
- };
@@ -1,82 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { forwardRef, useState, useImperativeHandle, useRef, } from "react";
4
- import { Eye, EyeOff } from "lucide-react";
5
- export const Input = forwardRef((props, ref) => {
6
- const { label, name = "", type = "text", placeholder = "", value, defaultValue, onChange, required, disabled, readOnly, error, success, helperText, icon: LabelIcon, prefix, prefixIcon: PrefixIcon, suffixIcon, min, max, step, rows = 4, primaryTheme = true, primaryColor = "#3b82f6", className = "", } = props;
7
- const inputRef = useRef(null);
8
- useImperativeHandle(ref, () => inputRef.current);
9
- const [localValue, setLocalValue] = useState(defaultValue || "");
10
- const [visible, setVisible] = useState(false);
11
- const hasPrefixIcon = Boolean(PrefixIcon);
12
- const hasPrefixText = Boolean(prefix);
13
- const hasPrefix = hasPrefixIcon || hasPrefixText;
14
- const currentValue = value !== undefined ? value : localValue;
15
- const handleChange = (e) => {
16
- const val = e.target.value;
17
- setLocalValue(val);
18
- onChange?.(name, val);
19
- };
20
- const handleKeyDown = (e) => {
21
- if (type === "number" && e.key === "-")
22
- e.preventDefault();
23
- };
24
- /** Padding logic */
25
- const getPadding = () => {
26
- if (!hasPrefix)
27
- return "px-4";
28
- if (hasPrefixIcon && hasPrefixText)
29
- return "pl-20 pr-4";
30
- if (hasPrefixText)
31
- return "pl-14 pr-4";
32
- if (hasPrefixIcon)
33
- return "pl-10 pr-4";
34
- return "px-4";
35
- };
36
- /** 🔥 Theme Styles */
37
- const dynamicFocusStyle = !primaryTheme
38
- ? {
39
- borderColor: primaryColor,
40
- boxShadow: `0 0 0 1px ${primaryColor}`,
41
- }
42
- : {};
43
- const dynamicColorStyle = !primaryTheme
44
- ? { color: primaryColor }
45
- : {};
46
- /** Border */
47
- const borderStyle = error
48
- ? "border-red-500"
49
- : success
50
- ? "border-emerald-500"
51
- : "border-zinc-300 dark:border-zinc-800";
52
- return (_jsxs("div", { className: `w-full space-y-1 ${className}`, children: [label && (_jsxs("label", { className: "flex items-center gap-2 text-[12px] font-medium text-gray-700 dark:text-zinc-100", children: [LabelIcon && (_jsx(LabelIcon, { size: 16, className: primaryTheme ? "text-[var(--primary)]" : "", style: !primaryTheme ? dynamicColorStyle : undefined })), label, required && _jsx("span", { className: "text-red-500", children: "*" })] })), _jsxs("div", { className: "relative", children: [hasPrefix && (_jsxs("div", { className: "absolute inset-y-0 left-0 flex items-center pl-3 gap-2 text-sm text-zinc-400 pointer-events-none", children: [PrefixIcon && _jsx(PrefixIcon, { size: 14 }), hasPrefixText && (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium text-zinc-600 dark:text-zinc-200", children: prefix }), _jsx("span", { className: "h-4 w-px bg-zinc-300 dark:bg-zinc-700" })] }))] })), type === "textarea" ? (_jsx("textarea", { ref: inputRef, name: name, value: currentValue, onChange: handleChange, placeholder: placeholder, required: required, disabled: disabled, readOnly: readOnly, rows: rows, style: !primaryTheme ? dynamicFocusStyle : undefined, className: `
53
- w-full rounded-lg text-sm
54
- bg-white dark:bg-zinc-900 border
55
- text-gray-900 dark:text-white
56
- placeholder:text-zinc-400
57
- py-2.5 outline-none
58
- focus:ring-1
59
- ${primaryTheme && "focus:ring-[var(--primary)] focus:border-[var(--primary)]"}
60
- ${getPadding()}
61
- ${borderStyle}
62
- ` })) : (_jsx("input", { ref: inputRef, type: type === "password"
63
- ? visible
64
- ? "text"
65
- : "password"
66
- : type, name: name, value: currentValue, onChange: handleChange, onKeyDown: handleKeyDown, placeholder: placeholder, required: required, disabled: disabled, readOnly: readOnly, min: type === "number" ? min ?? 0 : undefined, max: max, step: step, style: !primaryTheme ? dynamicFocusStyle : undefined, className: `
67
- w-full rounded-lg text-sm
68
- bg-white dark:bg-zinc-900 border
69
- text-gray-900 dark:text-white
70
- placeholder:text-zinc-400
71
- py-2.5 outline-none
72
- focus:ring-1
73
- disabled:opacity-50 disabled:cursor-not-allowed
74
- ${primaryTheme && "focus:ring-[var(--primary)] focus:border-[var(--primary)]"}
75
- ${getPadding()}
76
- ${borderStyle}
77
- ` })), type === "password" && (_jsx("button", { type: "button", onClick: () => setVisible(!visible), className: "absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400", children: visible ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), suffixIcon && type !== "password" && (_jsx("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400", children: suffixIcon }))] }), (helperText || error) && (_jsx("p", { className: `text-xs ${error
78
- ? "text-red-500"
79
- : success
80
- ? "text-emerald-500"
81
- : "text-zinc-500"}`, children: error && typeof error === "string" ? error : helperText }))] }));
82
- });
@@ -1,29 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import clsx from "clsx";
3
- const ListItem = ({ text, icon, onClick, subItems, isInline, isOrdered, primaryTheme = true, primaryColor = "#3b82f6", itemClassName, bulletClassName, }) => {
4
- // ✅ Dynamic style for primary color
5
- const dynamicStyle = !primaryTheme
6
- ? { color: primaryColor }
7
- : {};
8
- const bulletStyle = !primaryTheme
9
- ? { backgroundColor: primaryColor }
10
- : {};
11
- return (_jsxs("li", { className: clsx(!isInline && "mb-3"), children: [_jsxs("div", { onClick: onClick, style: !primaryTheme ? dynamicStyle : undefined, className: clsx("flex items-center gap-2 text-sm text-zinc-800 dark:text-zinc-200 transition-all", onClick &&
12
- (primaryTheme
13
- ? "cursor-pointer hover:text-[var(--primary)]"
14
- : "cursor-pointer"), itemClassName), children: [icon ? (_jsx("span", { className: "text-base", children: icon })) : (!isInline &&
15
- !isOrdered && (_jsx("span", { style: !primaryTheme ? bulletStyle : undefined, className: clsx("w-2 h-2 rounded-full", primaryTheme && "bg-[var(--primary)]", bulletClassName) }))), _jsx("span", { children: text })] }), subItems && subItems.length > 0 && (_jsx("ul", { className: clsx("pl-5 mt-2 space-y-2", isOrdered ? "list-decimal" : "list-none"), children: subItems.map((sub, i) => (_jsx(ListItem, { ...sub, isInline: false, isOrdered: isOrdered, primaryTheme: primaryTheme, primaryColor: primaryColor, itemClassName: itemClassName, bulletClassName: bulletClassName }, i))) }))] }));
16
- };
17
- /* -------------------------------------------------------------------------- */
18
- /* 📋 List */
19
- /* -------------------------------------------------------------------------- */
20
- export const List = ({ title, titleIcon, items, type = "unordered", primaryTheme = true, primaryColor = "#3b82f6", className, itemClassName, titleClassName, bulletClassName, }) => {
21
- const isOrdered = type === "ordered";
22
- const isInline = type === "inline";
23
- const ListTag = isOrdered ? "ol" : "ul";
24
- return (_jsxs("div", { className: clsx("bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl p-4", className), children: [title && (_jsxs("div", { className: clsx("flex items-center gap-2 mb-3 text-base font-semibold text-zinc-900 dark:text-white", titleClassName), children: [titleIcon && _jsx("span", { children: titleIcon }), _jsx("span", { children: title })] })), _jsx(ListTag, { className: clsx(isInline
25
- ? "flex flex-wrap gap-4"
26
- : isOrdered
27
- ? "list-decimal pl-5 space-y-2"
28
- : "list-none p-0"), children: items.map((item, i) => (_jsx(ListItem, { ...item, isInline: isInline, isOrdered: isOrdered, primaryTheme: primaryTheme, primaryColor: primaryColor, itemClassName: itemClassName, bulletClassName: bulletClassName }, i))) })] }));
29
- };
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useCallback } from "react";
3
- import { X } from "lucide-react";
4
- import clsx from "clsx";
5
- export const Modal = ({ isOpen, onClose, children, title, icon, ariaLabel, className, overlayClassName, contentClassName, closeButtonClassName, disableOverlayClose = false, darkMode = false, transitionDuration = 200, }) => {
6
- const modalRef = useRef(null);
7
- /* Escape key close */
8
- useEffect(() => {
9
- const handleEsc = (e) => {
10
- if (e.key === "Escape")
11
- onClose();
12
- };
13
- if (isOpen)
14
- document.addEventListener("keydown", handleEsc);
15
- return () => document.removeEventListener("keydown", handleEsc);
16
- }, [isOpen, onClose]);
17
- /* Scroll lock */
18
- useEffect(() => {
19
- if (isOpen) {
20
- const prev = document.body.style.overflow;
21
- document.body.style.overflow = "hidden";
22
- return () => {
23
- document.body.style.overflow = prev;
24
- };
25
- }
26
- }, [isOpen]);
27
- const handleOverlayClick = useCallback(() => {
28
- if (!disableOverlayClose)
29
- onClose();
30
- }, [disableOverlayClose, onClose]);
31
- if (!isOpen)
32
- return null;
33
- return (_jsx("div", { role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title || "Modal", className: clsx("fixed inset-0 flex items-center justify-center z-50 transition-opacity", darkMode ? "bg-black/80" : "bg-black/60", overlayClassName), style: { transitionDuration: `${transitionDuration}ms` }, onClick: handleOverlayClick, children: _jsxs("div", { ref: modalRef, className: clsx("relative w-[90vw] max-w-2xl max-h-[90vh] overflow-y-auto p-6 rounded-lg shadow-lg transition-transform", darkMode ? "bg-zinc-900 text-white" : "bg-white text-gray-900", "scale-100", contentClassName), style: { transitionDuration: `${transitionDuration}ms` }, onClick: (e) => e.stopPropagation(), children: [_jsx("button", { onClick: onClose, "aria-label": "Close modal", className: clsx("absolute top-4 right-4 p-1 rounded-full hover:scale-110 transition-transform", darkMode ? "text-white hover:text-gray-200" : "text-gray-700 hover:text-gray-900", closeButtonClassName), children: _jsx(X, { size: 24 }) }), title && (_jsxs("div", { className: "flex items-center gap-2 mb-4", children: [icon && _jsx("span", { className: "flex-shrink-0", children: icon }), _jsx("h2", { className: "text-xl font-semibold", children: title })] })), children] }) }));
34
- };
@@ -1,54 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect, useRef } from "react";
4
- import clsx from "clsx";
5
- export const RadioGroup = ({ name, options, selectedValue, onChange, disabled = false, readOnly = false, required = false, error, className, style, labelStyle, iconSize = 20, iconCheckedBgColor = "#2563eb", iconUncheckedBorderColor = "#9ca3af", textColor = "#374151", errorStyle, darkMode = false, }) => {
6
- const containerRef = useRef(null);
7
- const [focusedIndex, setFocusedIndex] = useState(null);
8
- // Keyboard navigation
9
- useEffect(() => {
10
- const container = containerRef.current;
11
- if (!container)
12
- return;
13
- const handleKeyDown = (e) => {
14
- if (disabled)
15
- return;
16
- const currentIndex = focusedIndex ?? options.findIndex((o) => o.value === selectedValue) ?? 0;
17
- if (e.key === "ArrowDown" || e.key === "ArrowRight") {
18
- e.preventDefault();
19
- const nextIndex = (currentIndex + 1) % options.length;
20
- onChange && onChange(options[nextIndex].value);
21
- setFocusedIndex(nextIndex);
22
- }
23
- if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
24
- e.preventDefault();
25
- const prevIndex = (currentIndex - 1 + options.length) % options.length;
26
- onChange && onChange(options[prevIndex].value);
27
- setFocusedIndex(prevIndex);
28
- }
29
- };
30
- container.addEventListener("keydown", handleKeyDown);
31
- return () => container.removeEventListener("keydown", handleKeyDown);
32
- }, [focusedIndex, options, selectedValue, onChange, disabled]);
33
- return (_jsxs("div", { ref: containerRef, role: "radiogroup", "aria-disabled": disabled, "aria-invalid": !!error, className: clsx("flex flex-col gap-2", className), style: { ...style }, tabIndex: 0, children: [options.map((option, index) => {
34
- const isChecked = selectedValue === option.value;
35
- const isFocused = focusedIndex === index;
36
- return (_jsxs("label", { className: clsx("flex items-center justify-between cursor-pointer select-none gap-2", disabled ? "opacity-50 cursor-not-allowed" : "opacity-100", isFocused ? "ring-2 ring-blue-400" : ""), style: { ...labelStyle }, children: [_jsx("span", { style: { color: textColor, fontSize: 14 }, children: option.label }), _jsx("input", { type: "radio", name: name, value: option.value, checked: isChecked, disabled: disabled || readOnly, required: required, onChange: () => onChange && onChange(option.value), style: { display: "none" } }), _jsx("span", { className: clsx("inline-flex items-center justify-center rounded-full transition-all"), style: {
37
- width: iconSize,
38
- height: iconSize,
39
- border: `2px solid ${isChecked ? iconCheckedBgColor : iconUncheckedBorderColor}`,
40
- backgroundColor: isChecked ? iconCheckedBgColor : "transparent",
41
- transition: "all 0.2s ease",
42
- }, children: isChecked && (_jsx("span", { style: {
43
- width: iconSize / 2,
44
- height: iconSize / 2,
45
- borderRadius: "50%",
46
- backgroundColor: darkMode ? "#fff" : "#fff",
47
- } })) })] }, option.value));
48
- }), error && (_jsx("p", { role: "alert", style: {
49
- color: "#dc2626",
50
- fontSize: 12,
51
- marginTop: 4,
52
- ...errorStyle,
53
- }, children: error }))] }));
54
- };
@@ -1,22 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import clsx from "clsx";
3
- /**
4
- * Convert responsive prop to Tailwind class string
5
- */
6
- const toResponsiveClass = (prefix, value) => {
7
- if (value == null)
8
- return "";
9
- if (typeof value !== "object")
10
- return `${prefix}-${value}`;
11
- return Object.entries(value)
12
- .map(([bp, val]) => `${bp}:${prefix}-${val}`)
13
- .join(" ");
14
- };
15
- export const Stack = ({ direction = { sm: "vertical", md: "horizontal" }, gap = 4, align = "center", justify = "start", wrap = "nowrap", className, children, }) => {
16
- const classes = clsx("flex", toResponsiveClass("flex", direction), // flex-col or flex-row
17
- toResponsiveClass("gap", gap), align !== "stretch" ? `items-${align}` : "items-stretch", justify !== "start" ? `justify-${justify}` : "justify-start", wrap !== "nowrap" ? `flex-${wrap}` : "flex-nowrap", className);
18
- return _jsx("div", { className: classes, children: children });
19
- };
20
- /** Shortcuts for horizontal & vertical stacks */
21
- export const HStack = (props) => (_jsx(Stack, { direction: "horizontal", ...props }));
22
- export const VStack = (props) => (_jsx(Stack, { direction: "vertical", ...props }));