@neuctra/ui 0.2.2 → 0.2.4

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 (105) hide show
  1. package/dist/components/basic/Accordation.d.ts +27 -18
  2. package/dist/components/basic/Alert.d.ts +14 -2
  3. package/dist/components/basic/Avatar.d.ts +5 -3
  4. package/dist/components/basic/Badge.d.ts +3 -3
  5. package/dist/components/basic/Button.d.ts +15 -17
  6. package/dist/components/basic/Card.d.ts +7 -49
  7. package/dist/components/basic/{CheckRadioInput.d.ts → CheckboxGroup.d.ts} +4 -5
  8. package/dist/components/basic/Container.d.ts +28 -26
  9. package/dist/components/basic/Drawer.d.ts +20 -11
  10. package/dist/components/basic/DropDown.d.ts +24 -34
  11. package/dist/components/basic/Flexbox.d.ts +18 -10
  12. package/dist/components/basic/GridView.d.ts +7 -5
  13. package/dist/components/basic/Image.d.ts +31 -6
  14. package/dist/components/basic/Input.d.ts +18 -10
  15. package/dist/components/basic/List.d.ts +11 -3
  16. package/dist/components/basic/Modal.d.ts +8 -2
  17. package/dist/components/basic/RadioGroup.d.ts +25 -0
  18. package/dist/components/basic/Section.d.ts +36 -0
  19. package/dist/components/basic/Stack.d.ts +27 -0
  20. package/dist/components/basic/SwitchGroup.d.ts +25 -0
  21. package/dist/components/basic/Table.d.ts +18 -54
  22. package/dist/components/basic/Tabs.d.ts +28 -28
  23. package/dist/components/basic/Text.d.ts +19 -32
  24. package/dist/index.cjs.js +55 -176
  25. package/dist/index.cjs.js.map +1 -0
  26. package/dist/index.d.ts +19 -18
  27. package/dist/index.es.js +3501 -4738
  28. package/dist/index.es.js.map +1 -0
  29. package/dist/src/components/avatar/AvatarGroup.js +9 -0
  30. package/dist/src/components/avatar/AvatarWithStatus.js +18 -0
  31. package/dist/src/components/basic/Accordation.js +74 -0
  32. package/dist/src/components/basic/Alert.js +141 -0
  33. package/dist/src/components/basic/AudioGallery.js +425 -0
  34. package/dist/src/components/basic/AudioPlayer.js +116 -0
  35. package/dist/src/components/basic/Avatar.js +181 -0
  36. package/dist/src/components/basic/Badge.js +66 -0
  37. package/dist/src/components/basic/Button.js +101 -0
  38. package/dist/src/components/basic/Card.js +47 -0
  39. package/dist/src/components/basic/CheckboxGroup.js +40 -0
  40. package/dist/src/components/basic/Container.js +45 -0
  41. package/dist/src/components/basic/Drawer.js +94 -0
  42. package/dist/src/components/basic/DropDown.js +162 -0
  43. package/dist/src/components/basic/Flexbox.js +67 -0
  44. package/dist/src/components/basic/GridView.js +51 -0
  45. package/dist/src/components/basic/Image.js +95 -0
  46. package/dist/src/components/basic/Input.js +123 -0
  47. package/dist/src/components/basic/List.js +71 -0
  48. package/dist/src/components/basic/Modal.js +86 -0
  49. package/dist/src/components/basic/RadioGroup.js +37 -0
  50. package/dist/src/components/basic/Section.js +100 -0
  51. package/dist/src/components/basic/Stack.js +75 -0
  52. package/dist/src/components/basic/SwitchGroup.js +50 -0
  53. package/dist/src/components/basic/Table.js +32 -0
  54. package/dist/src/components/basic/Tabs.js +149 -0
  55. package/dist/src/components/basic/Text.js +117 -0
  56. package/dist/src/index.js +46 -0
  57. package/dist/types/src/components/basic/Accordation.d.ts +44 -0
  58. package/dist/types/{components → src/components}/basic/Alert.d.ts +14 -2
  59. package/dist/types/{components → src/components}/basic/Avatar.d.ts +5 -3
  60. package/dist/types/{components → src/components}/basic/Badge.d.ts +3 -3
  61. package/dist/types/src/components/basic/Button.d.ts +26 -0
  62. package/dist/types/src/components/basic/Card.d.ts +28 -0
  63. package/dist/types/{components/basic/CheckRadioInput.d.ts → src/components/basic/CheckboxGroup.d.ts} +4 -5
  64. package/dist/types/src/components/basic/Container.d.ts +32 -0
  65. package/dist/types/src/components/basic/Drawer.d.ts +33 -0
  66. package/dist/types/src/components/basic/DropDown.d.ts +53 -0
  67. package/dist/types/src/components/basic/Flexbox.d.ts +25 -0
  68. package/dist/types/{components → src/components}/basic/GridView.d.ts +7 -5
  69. package/dist/types/src/components/basic/Image.d.ts +58 -0
  70. package/dist/types/{components → src/components}/basic/Input.d.ts +18 -10
  71. package/dist/types/{components → src/components}/basic/List.d.ts +11 -3
  72. package/dist/types/{components → src/components}/basic/Modal.d.ts +8 -2
  73. package/dist/types/src/components/basic/RadioGroup.d.ts +25 -0
  74. package/dist/types/src/components/basic/Section.d.ts +36 -0
  75. package/dist/types/src/components/basic/Stack.d.ts +27 -0
  76. package/dist/types/src/components/basic/SwitchGroup.d.ts +25 -0
  77. package/dist/types/src/components/basic/Table.d.ts +23 -0
  78. package/dist/types/src/components/basic/Tabs.d.ts +47 -0
  79. package/dist/types/src/components/basic/Text.d.ts +26 -0
  80. package/dist/types/{index.d.ts → src/index.d.ts} +19 -18
  81. package/dist/types/vite.config.d.ts +2 -0
  82. package/dist/ui.css +1 -1
  83. package/dist/vite.config.js +34 -0
  84. package/package.json +2 -1
  85. package/dist/components/basic/ImageGallery.d.ts +0 -21
  86. package/dist/components/basic/VideoGallery.d.ts +0 -136
  87. package/dist/components/basic/VideoPlayer.d.ts +0 -36
  88. package/dist/types/components/basic/Accordation.d.ts +0 -35
  89. package/dist/types/components/basic/Button.d.ts +0 -28
  90. package/dist/types/components/basic/Card.d.ts +0 -70
  91. package/dist/types/components/basic/Container.d.ts +0 -30
  92. package/dist/types/components/basic/Drawer.d.ts +0 -24
  93. package/dist/types/components/basic/DropDown.d.ts +0 -63
  94. package/dist/types/components/basic/Flexbox.d.ts +0 -17
  95. package/dist/types/components/basic/Image.d.ts +0 -33
  96. package/dist/types/components/basic/ImageGallery.d.ts +0 -21
  97. package/dist/types/components/basic/Table.d.ts +0 -59
  98. package/dist/types/components/basic/Tabs.d.ts +0 -47
  99. package/dist/types/components/basic/Text.d.ts +0 -39
  100. package/dist/types/components/basic/VideoGallery.d.ts +0 -136
  101. package/dist/types/components/basic/VideoPlayer.d.ts +0 -36
  102. /package/dist/types/{components → src/components}/avatar/AvatarGroup.d.ts +0 -0
  103. /package/dist/types/{components → src/components}/avatar/AvatarWithStatus.d.ts +0 -0
  104. /package/dist/types/{components → src/components}/basic/AudioGallery.d.ts +0 -0
  105. /package/dist/types/{components → src/components}/basic/AudioPlayer.d.ts +0 -0
@@ -0,0 +1,162 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useMemo, useState, forwardRef, } from "react";
4
+ /* ----------------------
5
+ * Default theme tokens
6
+ * ---------------------*/
7
+ const THEMES = {
8
+ light: {
9
+ controlBg: "#ffffff",
10
+ menuBg: "#ffffff",
11
+ textColor: "#111827",
12
+ placeholderColor: "#6b7280",
13
+ hoverBg: "#f9fafb",
14
+ selectedBg: "#eff6ff",
15
+ disabledBg: "#f3f4f6",
16
+ disabledTextColor: "#9ca3af",
17
+ borderColor: "#e5e7eb",
18
+ accentColor: "#3b82f6",
19
+ },
20
+ dark: {
21
+ controlBg: "#1f2937",
22
+ menuBg: "#111827",
23
+ textColor: "#f9fafb",
24
+ placeholderColor: "#9ca3af",
25
+ hoverBg: "#374151",
26
+ selectedBg: "#2563eb33",
27
+ disabledBg: "#374151",
28
+ disabledTextColor: "#6b7280",
29
+ borderColor: "#374151",
30
+ accentColor: "#60a5fa",
31
+ },
32
+ custom: {},
33
+ };
34
+ /* ----------------------
35
+ * Component
36
+ * ---------------------*/
37
+ const DropdownInner = (props, ref) => {
38
+ const id = useId();
39
+ const { options, value, values, defaultValue, defaultValues, onChange, placeholder = "Select...", disabled = false, searchable = false, multiSelect = false, clearable = false, virtualized = false, width = "100%", dropdownMaxHeight = "320px", borderRadius = "8px", boxShadow = "0 8px 28px rgba(0,0,0,0.1)", borderColor, accentColor, theme = "light", menuBg, controlBg, textColor, hoverBg, selectedBg, disabledBg, disabledTextColor, placeholderColor, transitionDuration = "180ms", className, controlClassName, menuClassName, optionClassName, style, controlStyle, menuStyle, optionStyle, iconPrefix, iconSuffix, clearIcon = "×", dropdownIcon = "▾", checkIcon = "✓", } = props;
40
+ // merge theme colors
41
+ const themeVars = {
42
+ ...THEMES[theme],
43
+ ...(theme === "custom" ? {} : {}),
44
+ };
45
+ const colors = {
46
+ borderColor: borderColor ?? themeVars.borderColor,
47
+ accentColor: accentColor ?? themeVars.accentColor,
48
+ controlBg: controlBg ?? themeVars.controlBg,
49
+ menuBg: menuBg ?? themeVars.menuBg,
50
+ textColor: textColor ?? themeVars.textColor,
51
+ hoverBg: hoverBg ?? themeVars.hoverBg,
52
+ selectedBg: selectedBg ?? themeVars.selectedBg,
53
+ disabledBg: disabledBg ?? themeVars.disabledBg,
54
+ disabledTextColor: disabledTextColor ?? themeVars.disabledTextColor,
55
+ placeholderColor: placeholderColor ?? themeVars.placeholderColor,
56
+ };
57
+ /* ----------------- State ----------------- */
58
+ const [isOpen, setIsOpen] = useState(false);
59
+ const [selected, setSelected] = useState(defaultValues ?? (defaultValue ? [defaultValue] : []));
60
+ useEffect(() => {
61
+ if (value)
62
+ setSelected([value]);
63
+ if (values)
64
+ setSelected(values);
65
+ }, [value, values]);
66
+ const selectedOptions = useMemo(() => options.filter((o) => selected.includes(o.value)), [options, selected]);
67
+ const toggle = () => !disabled && setIsOpen((s) => !s);
68
+ const selectValue = (val) => {
69
+ if (multiSelect) {
70
+ const exists = selected.includes(val);
71
+ const next = exists
72
+ ? selected.filter((v) => v !== val)
73
+ : [...selected, val];
74
+ setSelected(next);
75
+ onChange?.(next);
76
+ }
77
+ else {
78
+ setSelected([val]);
79
+ onChange?.(val);
80
+ setIsOpen(false);
81
+ }
82
+ };
83
+ const clearSelection = (e) => {
84
+ e?.stopPropagation();
85
+ setSelected([]);
86
+ onChange?.(multiSelect ? [] : "");
87
+ };
88
+ /* ----------------- Render ----------------- */
89
+ return (_jsxs("div", { ref: ref, className: className, style: {
90
+ position: "relative",
91
+ width,
92
+ fontFamily: "Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial",
93
+ ...style,
94
+ }, children: [_jsxs("div", { className: controlClassName, role: "button", tabIndex: 0, onClick: toggle, style: {
95
+ display: "flex",
96
+ justifyContent: "space-between",
97
+ alignItems: "center",
98
+ background: colors.controlBg,
99
+ color: colors.textColor,
100
+ padding: "0.5rem 0.75rem",
101
+ border: `1px solid ${isOpen ? colors.accentColor : colors.borderColor}`,
102
+ borderRadius,
103
+ cursor: disabled ? "not-allowed" : "pointer",
104
+ boxShadow: isOpen ? `0 0 0 3px ${colors.accentColor}33` : undefined,
105
+ transition: `all ${transitionDuration} ease`,
106
+ ...controlStyle,
107
+ }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [iconPrefix, selectedOptions.length ? (_jsx("span", { children: selectedOptions.map((s) => s.label).join(", ") })) : (_jsx("span", { style: { color: colors.placeholderColor }, children: placeholder }))] }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [clearable && selected.length > 0 && (_jsx("button", { onClick: clearSelection, style: {
108
+ background: "transparent",
109
+ border: "none",
110
+ color: colors.textColor,
111
+ cursor: "pointer",
112
+ }, children: clearIcon })), iconSuffix, _jsx("div", { style: {
113
+ transform: isOpen ? "rotate(180deg)" : "rotate(0deg)",
114
+ transition: `transform ${transitionDuration}`,
115
+ }, children: dropdownIcon })] })] }), isOpen && (_jsx("ul", { className: menuClassName, style: {
116
+ position: "absolute",
117
+ zIndex: 100,
118
+ top: "calc(100% + 4px)",
119
+ left: 0,
120
+ right: 0,
121
+ background: colors.menuBg,
122
+ border: `1px solid ${colors.borderColor}`,
123
+ borderRadius,
124
+ boxShadow,
125
+ maxHeight: dropdownMaxHeight,
126
+ overflowY: "auto",
127
+ transition: `opacity ${transitionDuration} ease`,
128
+ ...menuStyle,
129
+ }, children: options.map((opt) => {
130
+ const isSelected = selected.includes(opt.value);
131
+ return (_jsxs("li", { onClick: () => !opt.disabled && selectValue(opt.value), className: optionClassName, style: {
132
+ display: "flex",
133
+ alignItems: "center",
134
+ gap: 8,
135
+ padding: "0.5rem 0.75rem",
136
+ background: opt.disabled
137
+ ? colors.disabledBg
138
+ : isSelected
139
+ ? colors.selectedBg
140
+ : "transparent",
141
+ color: opt.disabled
142
+ ? colors.disabledTextColor
143
+ : colors.textColor,
144
+ cursor: opt.disabled ? "not-allowed" : "pointer",
145
+ borderRadius: 6,
146
+ userSelect: "none",
147
+ ...optionStyle,
148
+ }, onMouseEnter: (e) => {
149
+ if (!opt.disabled && !isSelected)
150
+ e.currentTarget.style.backgroundColor = colors.hoverBg;
151
+ }, onMouseLeave: (e) => {
152
+ if (!opt.disabled && !isSelected)
153
+ e.currentTarget.style.backgroundColor = "transparent";
154
+ }, children: [multiSelect && (_jsx("span", { style: { width: 18, textAlign: "center" }, children: isSelected ? checkIcon : "○" })), opt.icon && _jsx("span", { children: opt.icon }), _jsxs("div", { style: { flex: 1 }, children: [opt.label, opt.description && (_jsx("div", { style: {
155
+ fontSize: 12,
156
+ color: colors.placeholderColor,
157
+ marginTop: 2,
158
+ }, children: opt.description }))] })] }, opt.value));
159
+ }) }))] }));
160
+ };
161
+ export const Dropdown = forwardRef(DropdownInner);
162
+ export default Dropdown;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
+ const getScreenSize = (width) => {
4
+ if (width < 768)
5
+ return "sm";
6
+ if (width < 1024)
7
+ return "md";
8
+ return "lg";
9
+ };
10
+ const resolveResponsive = (prop, screen, fallback) => {
11
+ if (prop == null)
12
+ return fallback;
13
+ if (typeof prop !== "object")
14
+ return prop;
15
+ return prop[screen] ?? fallback;
16
+ };
17
+ export const Flexbox = ({ direction = { sm: "column", md: "row", lg: "row" }, align = "center", justify = "space-between", wrap = "wrap", gap = 16, padding, margin, backgroundColor = "transparent", width = "100%", maxWidth = "100%", height = "auto", borderRadius, border, boxShadow, overflow, children, style, className, }) => {
18
+ const [screen, setScreen] = useState("lg");
19
+ useEffect(() => {
20
+ const updateScreen = () => setScreen(getScreenSize(window.innerWidth));
21
+ updateScreen();
22
+ window.addEventListener("resize", updateScreen);
23
+ return () => window.removeEventListener("resize", updateScreen);
24
+ }, []);
25
+ const computedStyle = useMemo(() => {
26
+ const toCssValue = (val) => typeof val === "number" ? `${val}px` : val;
27
+ return {
28
+ display: "flex",
29
+ flexDirection: resolveResponsive(direction, screen, "row"),
30
+ alignItems: resolveResponsive(align, screen, "center"),
31
+ justifyContent: resolveResponsive(justify, screen, "flex-start"),
32
+ flexWrap: resolveResponsive(wrap, screen, "wrap"),
33
+ gap: toCssValue(resolveResponsive(gap, screen, undefined)),
34
+ padding: toCssValue(resolveResponsive(padding, screen, undefined)),
35
+ margin: toCssValue(resolveResponsive(margin, screen, undefined)),
36
+ backgroundColor: resolveResponsive(backgroundColor, screen, undefined),
37
+ width: resolveResponsive(width, screen, "100%"),
38
+ maxWidth: resolveResponsive(maxWidth, screen, undefined),
39
+ height: resolveResponsive(height, screen, undefined),
40
+ borderRadius: resolveResponsive(borderRadius, screen, undefined),
41
+ border: resolveResponsive(border, screen, undefined),
42
+ boxShadow: resolveResponsive(boxShadow, screen, undefined),
43
+ overflow: resolveResponsive(overflow, screen, undefined),
44
+ boxSizing: "border-box",
45
+ ...style,
46
+ };
47
+ }, [
48
+ direction,
49
+ align,
50
+ justify,
51
+ wrap,
52
+ gap,
53
+ padding,
54
+ margin,
55
+ backgroundColor,
56
+ width,
57
+ maxWidth,
58
+ height,
59
+ borderRadius,
60
+ border,
61
+ boxShadow,
62
+ overflow,
63
+ screen,
64
+ style,
65
+ ]);
66
+ return (_jsx("div", { className: className, style: computedStyle, children: children }));
67
+ };
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
+ const getScreenSize = (width) => {
4
+ if (width < 768)
5
+ return "sm";
6
+ if (width < 1024)
7
+ return "md";
8
+ return "lg";
9
+ };
10
+ export const GridView = ({ columns = { sm: 1, md: 2, lg: 3 }, gap = 16, padding = 0, alignItems = "stretch", justifyItems = "stretch", backgroundColor = "transparent", width = "100%", maxWidth = "100%", height = "auto", margin = 0, style, className = "", children, }) => {
11
+ const [screenSize, setScreenSize] = useState("lg");
12
+ useEffect(() => {
13
+ setScreenSize(getScreenSize(window.innerWidth));
14
+ const onResize = () => setScreenSize(getScreenSize(window.innerWidth));
15
+ window.addEventListener("resize", onResize);
16
+ return () => window.removeEventListener("resize", onResize);
17
+ }, []);
18
+ const resolvedColumns = useMemo(() => {
19
+ if (typeof columns === "number")
20
+ return columns;
21
+ return columns[screenSize] ?? 1;
22
+ }, [columns, screenSize]);
23
+ const styles = useMemo(() => ({
24
+ display: "grid",
25
+ gridTemplateColumns: `repeat(${resolvedColumns}, 1fr)`,
26
+ gap: typeof gap === "number" ? `${gap}px` : gap,
27
+ padding: typeof padding === "number" ? `${padding}px` : padding,
28
+ margin: typeof margin === "number" ? `${margin}px` : margin,
29
+ alignItems,
30
+ justifyItems,
31
+ backgroundColor,
32
+ width,
33
+ maxWidth,
34
+ height,
35
+ boxSizing: "border-box",
36
+ ...style,
37
+ }), [
38
+ resolvedColumns,
39
+ gap,
40
+ padding,
41
+ margin,
42
+ alignItems,
43
+ justifyItems,
44
+ backgroundColor,
45
+ width,
46
+ maxWidth,
47
+ height,
48
+ style,
49
+ ]);
50
+ return (_jsx("div", { style: styles, className: className, children: children }));
51
+ };
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useCallback } from "react";
3
+ /**
4
+ * ✅ Industry-Standard Image Component
5
+ * - SEO & accessibility optimized
6
+ * - Lazy loading & responsive
7
+ * - Fully customizable styling
8
+ * - Smooth hover transitions
9
+ * - Overlay and SVG support
10
+ */
11
+ export const Image = ({ src, alt = "Image", title, width = "100%", height = "auto", borderRadius = "8px", borderColor = "transparent", borderStyle = "solid", borderWidth = "0px", shadow = false, boxShadow, opacity = 1, objectFit = "cover", overlayText, overlayColor = "rgba(0,0,0,0.5)", svgIcon, responsive = false, padding, margin, lazyLoad = true, hoverOpacity, hoverShadow = false, hoverScale = 1, hoverRotate = 0, transitionDuration = "0.3s", overflow = "hidden", className, style, onClick, onLoad, onError, }) => {
12
+ // ✅ Memoized base style for performance
13
+ const baseStyle = useMemo(() => ({
14
+ width: responsive ? "100%" : width,
15
+ height: responsive ? "auto" : height,
16
+ borderRadius,
17
+ border: `${borderWidth} ${borderStyle} ${borderColor}`,
18
+ objectFit,
19
+ opacity,
20
+ boxShadow: shadow
21
+ ? boxShadow || "0 4px 12px rgba(0,0,0,0.15)"
22
+ : "none",
23
+ transition: `all ${transitionDuration} ease`,
24
+ display: "block",
25
+ }), [
26
+ responsive,
27
+ width,
28
+ height,
29
+ borderRadius,
30
+ borderColor,
31
+ borderStyle,
32
+ borderWidth,
33
+ objectFit,
34
+ opacity,
35
+ shadow,
36
+ boxShadow,
37
+ transitionDuration,
38
+ ]);
39
+ // ✅ Hover effects
40
+ const handleMouseEnter = useCallback((e) => {
41
+ e.currentTarget.style.opacity =
42
+ hoverOpacity !== undefined ? hoverOpacity.toString() : "1";
43
+ e.currentTarget.style.boxShadow = hoverShadow
44
+ ? "0 8px 20px rgba(0,0,0,0.3)"
45
+ : baseStyle.boxShadow || "none";
46
+ e.currentTarget.style.transform = `scale(${hoverScale}) rotate(${hoverRotate}deg)`;
47
+ }, [hoverOpacity, hoverShadow, hoverScale, hoverRotate, baseStyle.boxShadow]);
48
+ const handleMouseLeave = useCallback((e) => {
49
+ e.currentTarget.style.opacity = baseStyle.opacity?.toString() || "1";
50
+ e.currentTarget.style.boxShadow = baseStyle.boxShadow || "none";
51
+ e.currentTarget.style.transform = "scale(1) rotate(0deg)";
52
+ }, [baseStyle]);
53
+ // ✅ Overflow control
54
+ const overflowStyles = useMemo(() => {
55
+ switch (overflow) {
56
+ case "x":
57
+ return { overflowX: "hidden" };
58
+ case "y":
59
+ return { overflowY: "hidden" };
60
+ default:
61
+ return { overflow };
62
+ }
63
+ }, [overflow]);
64
+ return (_jsxs("div", { className: className, role: "img", "aria-label": alt, title: title || alt, onClick: onClick, style: {
65
+ width: responsive ? "100%" : width,
66
+ height: responsive ? "auto" : height,
67
+ padding,
68
+ margin,
69
+ position: "relative",
70
+ cursor: onClick ? "pointer" : "default",
71
+ display: "inline-block",
72
+ transition: `all ${transitionDuration} ease`,
73
+ ...overflowStyles,
74
+ ...style,
75
+ }, children: [svgIcon ? (_jsx("div", { style: {
76
+ width: "100%",
77
+ height: "100%",
78
+ display: "flex",
79
+ alignItems: "center",
80
+ justifyContent: "center",
81
+ }, children: svgIcon })) : (_jsx("img", { src: src, alt: alt, title: title || alt, loading: lazyLoad ? "lazy" : "eager", style: baseStyle, onClick: onClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onLoad: onLoad, onError: onError, decoding: "async", fetchPriority: "high" })), overlayText && (_jsx("div", { style: {
82
+ position: "absolute",
83
+ inset: 0,
84
+ backgroundColor: overlayColor,
85
+ color: "#fff",
86
+ display: "flex",
87
+ alignItems: "center",
88
+ justifyContent: "center",
89
+ fontWeight: "bold",
90
+ fontSize: "1.1rem",
91
+ textAlign: "center",
92
+ padding: "1rem",
93
+ boxSizing: "border-box",
94
+ }, children: overlayText }))] }));
95
+ };
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef, useImperativeHandle, forwardRef, } from "react";
4
+ import { Eye, EyeOff } from "lucide-react";
5
+ export const Input = forwardRef((props, ref) => {
6
+ const { type = "text", label, placeholder = "", name = "", value, defaultValue, onChange, disabled = false, readOnly = false, required = false, error, success = false, autoFocus = false, iconLeft, iconRight,
7
+ /** 🎨 Styling props */
8
+ labelColor = "#374151", placeholderColor = "#9ca3af", backgroundColor = "#ffffff", textColor = "#111827", borderColor = "#d1d5db", hoverBorderColor = "#9ca3af", focusBorderColor = "#2563eb", errorColor = "#dc2626", successColor = "#16a34a", iconColor = "#6b7280", shadow = "0 1px 2px rgba(0,0,0,0.05)",
9
+ /** 📏 Layout + size */
10
+ size = "md", fontSize = "14px", fontFamily = "Inter, system-ui, sans-serif", radius = "8px", rows = 4, cols, maxLength, resize = true, showCharacterCount = true, paddingX, paddingY, className, style, } = props;
11
+ const inputRef = useRef(null);
12
+ const [localValue, setLocalValue] = useState(defaultValue || "");
13
+ const [visible, setVisible] = useState(false);
14
+ useImperativeHandle(ref, () => inputRef.current);
15
+ /** ✅ Make it controlled properly */
16
+ const handleChange = (e) => {
17
+ const newValue = e.target.value;
18
+ if (maxLength && newValue.length > maxLength)
19
+ return;
20
+ setLocalValue(newValue);
21
+ if (onChange)
22
+ onChange(name, newValue);
23
+ };
24
+ const currentValue = value !== undefined ? value : localValue;
25
+ /** 🎯 Dynamic border colors */
26
+ const currentBorderColor = error
27
+ ? errorColor
28
+ : success
29
+ ? successColor
30
+ : borderColor;
31
+ /** 🧠 Size tokens */
32
+ const sizes = {
33
+ sm: { paddingY: "6px", paddingX: "10px", font: "13px" },
34
+ md: { paddingY: "10px", paddingX: "14px", font: "14px" },
35
+ lg: { paddingY: "14px", paddingX: "18px", font: "16px" },
36
+ }[size];
37
+ const px = paddingX || sizes.paddingX;
38
+ const py = paddingY || sizes.paddingY;
39
+ /** 💅 Base input styles */
40
+ const baseInputStyle = {
41
+ width: "100%",
42
+ border: `1px solid ${currentBorderColor}`,
43
+ borderRadius: radius,
44
+ backgroundColor,
45
+ color: textColor,
46
+ fontFamily,
47
+ fontSize,
48
+ padding: `${py} ${px}`,
49
+ paddingLeft: iconLeft ? "40px" : px,
50
+ paddingRight: iconRight || type === "password" ? "40px" : px,
51
+ outline: "none",
52
+ transition: "border-color 0.25s ease, box-shadow 0.25s ease",
53
+ resize: type === "textarea" && !resize ? "none" : undefined,
54
+ boxShadow: shadow,
55
+ ...style,
56
+ };
57
+ /** 🧠 Placeholder dynamic color */
58
+ const dynamicPlaceholder = {
59
+ "::placeholder": {
60
+ color: placeholderColor,
61
+ opacity: 1,
62
+ },
63
+ };
64
+ /** 🎨 Dynamic border behavior */
65
+ const applyDynamicBorder = (el, color) => {
66
+ if (el)
67
+ el.style.borderColor = color;
68
+ };
69
+ const commonEvents = {
70
+ onFocus: (e) => applyDynamicBorder(e.currentTarget, focusBorderColor),
71
+ onBlur: (e) => applyDynamicBorder(e.currentTarget, currentBorderColor),
72
+ onMouseEnter: (e) => applyDynamicBorder(e.currentTarget, hoverBorderColor),
73
+ onMouseLeave: (e) => applyDynamicBorder(e.currentTarget, currentBorderColor),
74
+ };
75
+ return (_jsxs("div", { className: className, style: {
76
+ width: "100%",
77
+ display: "flex",
78
+ flexDirection: "column",
79
+ fontFamily,
80
+ }, children: [label && (_jsxs("label", { htmlFor: name, style: {
81
+ marginBottom: 6,
82
+ color: labelColor,
83
+ fontWeight: 500,
84
+ fontSize: "14px",
85
+ }, children: [label, required && _jsx("span", { style: { color: errorColor }, children: " *" })] })), _jsxs("div", { style: { position: "relative", width: "100%" }, children: [iconLeft && (_jsx("span", { style: {
86
+ position: "absolute",
87
+ left: 12,
88
+ top: "50%",
89
+ transform: "translateY(-50%)",
90
+ color: iconColor,
91
+ pointerEvents: "none",
92
+ }, children: iconLeft })), type === "textarea" ? (_jsx("textarea", { ref: inputRef, name: name, value: currentValue, placeholder: placeholder, disabled: disabled, readOnly: readOnly, rows: rows, cols: cols, maxLength: maxLength, autoFocus: autoFocus, style: { ...baseInputStyle, ...dynamicPlaceholder }, onChange: handleChange, ...commonEvents })) : (_jsx("input", { ref: inputRef, id: name, type: type === "password"
93
+ ? visible
94
+ ? "text"
95
+ : "password"
96
+ : type, name: name, value: currentValue, placeholder: placeholder, disabled: disabled, readOnly: readOnly, autoFocus: autoFocus, style: { ...baseInputStyle, ...dynamicPlaceholder }, onChange: handleChange, ...commonEvents })), type === "password" && (_jsx("button", { type: "button", onClick: () => setVisible(!visible), style: {
97
+ position: "absolute",
98
+ right: 10,
99
+ top: "50%",
100
+ transform: "translateY(-50%)",
101
+ background: "transparent",
102
+ border: "none",
103
+ cursor: "pointer",
104
+ color: iconColor,
105
+ padding: 0,
106
+ }, children: visible ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), iconRight && type !== "password" && (_jsx("span", { style: {
107
+ position: "absolute",
108
+ right: 12,
109
+ top: "50%",
110
+ transform: "translateY(-50%)",
111
+ color: iconColor,
112
+ pointerEvents: "none",
113
+ }, children: iconRight }))] }), type === "textarea" && showCharacterCount && maxLength && (_jsxs("div", { style: {
114
+ textAlign: "right",
115
+ fontSize: "12px",
116
+ color: "#6b7280",
117
+ marginTop: 4,
118
+ }, children: [currentValue.length, "/", maxLength] })), error && (_jsx("div", { style: {
119
+ color: errorColor,
120
+ fontSize: "12px",
121
+ marginTop: 4,
122
+ }, children: error }))] }));
123
+ });
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const ListItem = ({ text, icon, onClick, subItems, bulletColor = "#2563eb", textColor = "#111827", fontSize = "15px", fontWeight = 500, spacing = "12px", isInline = false, }) => {
3
+ const itemContainerStyle = {
4
+ display: "flex",
5
+ flexDirection: "column",
6
+ gap: "6px",
7
+ marginBottom: isInline ? "0" : spacing,
8
+ };
9
+ const contentStyle = {
10
+ display: "flex",
11
+ alignItems: "center",
12
+ gap: "8px",
13
+ fontSize,
14
+ fontWeight,
15
+ color: textColor,
16
+ cursor: onClick ? "pointer" : "default",
17
+ transition: "color 0.2s ease, transform 0.2s ease",
18
+ };
19
+ const bulletStyle = {
20
+ width: "8px",
21
+ height: "8px",
22
+ backgroundColor: bulletColor,
23
+ borderRadius: "50%",
24
+ flexShrink: 0,
25
+ };
26
+ const subListStyle = {
27
+ listStyleType: "disc",
28
+ paddingLeft: "20px",
29
+ margin: 0,
30
+ };
31
+ return (_jsxs("li", { style: itemContainerStyle, children: [_jsxs("div", { style: contentStyle, onClick: onClick, onMouseEnter: (e) => (e.currentTarget.style.color = bulletColor), onMouseLeave: (e) => (e.currentTarget.style.color = textColor), children: [icon ? (_jsx("span", { style: { fontSize: "16px", color: textColor }, children: icon })) : (!isInline && _jsx("span", { style: bulletStyle })), _jsx("span", { children: text })] }), subItems && subItems.length > 0 && (_jsx("ul", { style: subListStyle, children: subItems.map((sub, index) => (_jsx(ListItem, { ...sub, bulletColor: bulletColor, textColor: textColor, fontSize: fontSize, fontWeight: fontWeight, spacing: spacing, isInline: false }, index))) }))] }));
32
+ };
33
+ /* -------------------------------------------------------------------------- */
34
+ /* 📋 List */
35
+ /* -------------------------------------------------------------------------- */
36
+ export const List = ({ title, titleIcon, items, type = "unordered", bulletColor = "#2563eb", textColor = "#111827", backgroundColor = "#fff", borderColor = "#e5e7eb", fontSize = "15px", fontWeight = 500, borderRadius = "12px", padding = "16px", spacing = "12px", className, style, }) => {
37
+ const isOrdered = type === "ordered";
38
+ const isInline = type === "inline";
39
+ const containerStyle = {
40
+ backgroundColor,
41
+ borderColor,
42
+ color: textColor,
43
+ borderWidth: borderColor ? "1px" : "0px",
44
+ borderStyle: "solid",
45
+ borderRadius,
46
+ padding,
47
+ ...style,
48
+ };
49
+ const listStyle = isInline
50
+ ? {
51
+ display: "flex",
52
+ gap: spacing,
53
+ paddingLeft: 0,
54
+ listStyleType: "none",
55
+ margin: 0,
56
+ }
57
+ : {
58
+ listStyleType: isOrdered ? "decimal" : "none",
59
+ paddingLeft: isOrdered ? "20px" : "0",
60
+ margin: 0,
61
+ };
62
+ const ListTag = isOrdered ? "ol" : "ul";
63
+ return (_jsxs("div", { className: className, style: containerStyle, children: [title && (_jsxs("div", { style: {
64
+ display: "flex",
65
+ alignItems: "center",
66
+ fontSize: "17px",
67
+ fontWeight: 600,
68
+ marginBottom: "10px",
69
+ gap: "8px",
70
+ }, children: [titleIcon && _jsx("span", { style: { fontSize: "18px" }, children: titleIcon }), _jsx("span", { children: title })] })), _jsx(ListTag, { style: listStyle, children: items.map((item, index) => (_jsx(ListItem, { ...item, bulletColor: bulletColor, textColor: textColor, fontSize: fontSize, fontWeight: fontWeight, spacing: spacing, isInline: isInline }, index))) })] }));
71
+ };
@@ -0,0 +1,86 @@
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
+ export const Modal = ({ isOpen, onClose, children, ariaLabel, title, overlayStyle, modalStyle, closeButtonStyle, disableOverlayClose = false, transitionDuration = 200, className, darkMode = false, }) => {
5
+ const modalRef = useRef(null);
6
+ /* ------------------------------ Escape Close ----------------------------- */
7
+ useEffect(() => {
8
+ const handleEsc = (e) => {
9
+ if (e.key === "Escape")
10
+ onClose();
11
+ };
12
+ if (isOpen)
13
+ document.addEventListener("keydown", handleEsc);
14
+ return () => document.removeEventListener("keydown", handleEsc);
15
+ }, [isOpen, onClose]);
16
+ /* ------------------------------ Scroll Lock ------------------------------ */
17
+ useEffect(() => {
18
+ if (isOpen) {
19
+ const prev = document.body.style.overflow;
20
+ document.body.style.overflow = "hidden";
21
+ return () => {
22
+ document.body.style.overflow = prev;
23
+ };
24
+ }
25
+ }, [isOpen]);
26
+ /* ------------------------------ Click Outside ---------------------------- */
27
+ const handleOverlayClick = useCallback(() => {
28
+ if (!disableOverlayClose)
29
+ onClose();
30
+ }, [disableOverlayClose, onClose]);
31
+ if (!isOpen)
32
+ return null;
33
+ /* ------------------------------- Base Styles ----------------------------- */
34
+ const baseOverlay = {
35
+ position: "fixed",
36
+ inset: 0,
37
+ backgroundColor: darkMode ? "rgba(0,0,0,0.8)" : "rgba(0,0,0,0.6)",
38
+ display: "flex",
39
+ justifyContent: "center",
40
+ alignItems: "center",
41
+ zIndex: 1000,
42
+ opacity: isOpen ? 1 : 0,
43
+ transition: `opacity ${transitionDuration}ms ease`,
44
+ ...overlayStyle,
45
+ };
46
+ const baseModal = {
47
+ position: "relative",
48
+ backgroundColor: darkMode ? "#1f1f1f" : "#fff",
49
+ color: darkMode ? "#f5f5f5" : "#111",
50
+ borderRadius: 12,
51
+ width: "90vw",
52
+ maxWidth: 700,
53
+ maxHeight: "90vh",
54
+ overflowY: "auto",
55
+ padding: 24,
56
+ boxShadow: darkMode
57
+ ? "0 10px 40px rgba(0,0,0,0.7)"
58
+ : "0 10px 40px rgba(0,0,0,0.25)",
59
+ transform: isOpen ? "scale(1)" : "scale(0.95)",
60
+ transition: `transform ${transitionDuration}ms ease, opacity ${transitionDuration}ms ease`,
61
+ ...modalStyle,
62
+ };
63
+ const baseCloseBtn = {
64
+ position: "absolute",
65
+ top: 16,
66
+ right: 16,
67
+ background: "transparent",
68
+ border: "none",
69
+ cursor: "pointer",
70
+ padding: 4,
71
+ color: darkMode ? "#f5f5f5" : "#444",
72
+ transition: "color 0.2s ease, transform 0.2s ease",
73
+ ...closeButtonStyle,
74
+ };
75
+ return (_jsx("div", { role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title || "Modal", style: baseOverlay, onClick: handleOverlayClick, className: className, children: _jsxs("div", { ref: modalRef, style: baseModal, onClick: (e) => e.stopPropagation(), children: [_jsx("button", { onClick: onClose, "aria-label": "Close modal", style: baseCloseBtn, onMouseEnter: (e) => {
76
+ e.currentTarget.style.color = darkMode ? "#fff" : "#000";
77
+ e.currentTarget.style.transform = "scale(1.1)";
78
+ }, onMouseLeave: (e) => {
79
+ e.currentTarget.style.color = darkMode ? "#f5f5f5" : "#444";
80
+ e.currentTarget.style.transform = "scale(1)";
81
+ }, children: _jsx(X, { size: 24 }) }), title && (_jsx("h2", { style: {
82
+ fontSize: "1.25rem",
83
+ fontWeight: 600,
84
+ marginBottom: "1rem",
85
+ }, children: title })), children] }) }));
86
+ };