@neuctra/ui 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/basic/Accordation.d.ts +27 -18
- package/dist/components/basic/Alert.d.ts +15 -2
- package/dist/components/basic/Avatar.d.ts +5 -3
- package/dist/components/basic/Badge.d.ts +3 -3
- package/dist/components/basic/Button.d.ts +15 -17
- package/dist/components/basic/Card.d.ts +7 -49
- package/dist/components/basic/CheckRadioInput.d.ts +3 -1
- package/dist/components/basic/Container.d.ts +28 -26
- package/dist/components/basic/Drawer.d.ts +20 -11
- package/dist/components/basic/Flexbox.d.ts +18 -10
- package/dist/components/basic/GridView.d.ts +7 -5
- package/dist/components/basic/Image.d.ts +31 -6
- package/dist/components/basic/Input.d.ts +18 -10
- package/dist/components/basic/List.d.ts +11 -3
- package/dist/components/basic/Modal.d.ts +15 -2
- package/dist/components/basic/Section.d.ts +36 -0
- package/dist/components/basic/Stack.d.ts +27 -0
- package/dist/components/basic/Table.d.ts +18 -54
- package/dist/components/basic/Tabs.d.ts +28 -28
- package/dist/components/basic/Text.d.ts +19 -32
- package/dist/index.cjs.js +68 -223
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +17 -19
- package/dist/index.es.js +3169 -6313
- package/dist/index.es.js.map +1 -0
- package/dist/src/components/avatar/AvatarGroup.js +9 -0
- package/dist/src/components/avatar/AvatarWithStatus.js +18 -0
- package/dist/src/components/basic/Accordation.js +74 -0
- package/dist/src/components/basic/Alert.js +126 -0
- package/dist/src/components/basic/AudioGallery.js +425 -0
- package/dist/src/components/basic/AudioPlayer.js +116 -0
- package/dist/src/components/basic/Avatar.js +181 -0
- package/dist/src/components/basic/Badge.js +66 -0
- package/dist/src/components/basic/Button.js +101 -0
- package/dist/src/components/basic/Card.js +45 -0
- package/dist/src/components/basic/CheckRadioInput.js +83 -0
- package/dist/src/components/basic/Container.js +45 -0
- package/dist/src/components/basic/Drawer.js +94 -0
- package/dist/src/components/basic/DropDown.js +316 -0
- package/dist/src/components/basic/Flexbox.js +67 -0
- package/dist/src/components/basic/GridView.js +51 -0
- package/dist/src/components/basic/Image.js +95 -0
- package/dist/src/components/basic/Input.js +123 -0
- package/dist/src/components/basic/List.js +71 -0
- package/dist/src/components/basic/Modal.js +88 -0
- package/dist/src/components/basic/Section.js +100 -0
- package/dist/src/components/basic/Stack.js +75 -0
- package/dist/src/components/basic/Table.js +32 -0
- package/dist/src/components/basic/Tabs.js +149 -0
- package/dist/src/components/basic/Text.js +117 -0
- package/dist/src/index.js +44 -0
- package/dist/types/src/components/basic/Accordation.d.ts +44 -0
- package/dist/types/{components → src/components}/basic/Alert.d.ts +15 -2
- package/dist/types/{components → src/components}/basic/Avatar.d.ts +5 -3
- package/dist/types/{components → src/components}/basic/Badge.d.ts +3 -3
- package/dist/types/src/components/basic/Button.d.ts +26 -0
- package/dist/types/src/components/basic/Card.d.ts +28 -0
- package/dist/types/{components → src/components}/basic/CheckRadioInput.d.ts +3 -1
- package/dist/types/src/components/basic/Container.d.ts +32 -0
- package/dist/types/src/components/basic/Drawer.d.ts +33 -0
- package/dist/types/src/components/basic/Flexbox.d.ts +25 -0
- package/dist/types/{components → src/components}/basic/GridView.d.ts +7 -5
- package/dist/types/src/components/basic/Image.d.ts +58 -0
- package/dist/types/{components → src/components}/basic/Input.d.ts +18 -10
- package/dist/types/{components → src/components}/basic/List.d.ts +11 -3
- package/dist/types/src/components/basic/Modal.d.ts +24 -0
- package/dist/types/src/components/basic/Section.d.ts +36 -0
- package/dist/types/src/components/basic/Stack.d.ts +27 -0
- package/dist/types/src/components/basic/Table.d.ts +23 -0
- package/dist/types/src/components/basic/Tabs.d.ts +47 -0
- package/dist/types/src/components/basic/Text.d.ts +26 -0
- package/dist/types/{index.d.ts → src/index.d.ts} +17 -19
- package/dist/types/vite.config.d.ts +2 -0
- package/dist/ui.css +1 -1
- package/dist/vite.config.js +34 -0
- package/package.json +2 -1
- package/dist/components/basic/ImageGallery.d.ts +0 -21
- package/dist/components/basic/VideoGallery.d.ts +0 -136
- package/dist/components/basic/VideoPlayer.d.ts +0 -36
- package/dist/types/components/basic/Accordation.d.ts +0 -35
- package/dist/types/components/basic/Button.d.ts +0 -28
- package/dist/types/components/basic/Card.d.ts +0 -70
- package/dist/types/components/basic/Container.d.ts +0 -30
- package/dist/types/components/basic/Drawer.d.ts +0 -24
- package/dist/types/components/basic/Flexbox.d.ts +0 -17
- package/dist/types/components/basic/Image.d.ts +0 -33
- package/dist/types/components/basic/ImageGallery.d.ts +0 -21
- package/dist/types/components/basic/Modal.d.ts +0 -11
- package/dist/types/components/basic/Table.d.ts +0 -59
- package/dist/types/components/basic/Tabs.d.ts +0 -47
- package/dist/types/components/basic/Text.d.ts +0 -39
- package/dist/types/components/basic/VideoGallery.d.ts +0 -136
- package/dist/types/components/basic/VideoPlayer.d.ts +0 -36
- /package/dist/types/{components → src/components}/avatar/AvatarGroup.d.ts +0 -0
- /package/dist/types/{components → src/components}/avatar/AvatarWithStatus.d.ts +0 -0
- /package/dist/types/{components → src/components}/basic/AudioGallery.d.ts +0 -0
- /package/dist/types/{components → src/components}/basic/AudioPlayer.d.ts +0 -0
- /package/dist/types/{components → src/components}/basic/DropDown.d.ts +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo } from "react";
|
|
3
|
+
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
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (open)
|
|
26
|
+
setVisible(true);
|
|
27
|
+
else
|
|
28
|
+
setTimeout(() => setVisible(false), transitionDuration);
|
|
29
|
+
}, [open, transitionDuration]);
|
|
30
|
+
// Drawer transform direction
|
|
31
|
+
const transform = useMemo(() => {
|
|
32
|
+
if (open)
|
|
33
|
+
return "translate(0, 0)";
|
|
34
|
+
switch (position) {
|
|
35
|
+
case "left":
|
|
36
|
+
return "translateX(-100%)";
|
|
37
|
+
case "right":
|
|
38
|
+
return "translateX(100%)";
|
|
39
|
+
case "top":
|
|
40
|
+
return "translateY(-100%)";
|
|
41
|
+
case "bottom":
|
|
42
|
+
return "translateY(100%)";
|
|
43
|
+
default:
|
|
44
|
+
return "translate(0, 0)";
|
|
45
|
+
}
|
|
46
|
+
}, [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 })] })] }));
|
|
94
|
+
};
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
3
|
+
export const Dropdown = ({ options, value, defaultValue, onChange, placeholder = "Select an option", disabled = false, searchable = false, multiSelect = false, clearable = false, virtualized = false, optionHeight = 36, visibleOptions = 5,
|
|
4
|
+
// Styling defaults
|
|
5
|
+
width = "100%", height = "auto", borderColor = "#d1d5db", focusBorderColor = "#2563eb", errorBorderColor = "#dc2626", backgroundColor = "#ffffff", textColor = "#111827", placeholderColor = "#9ca3af", hoverColor = "#f3f4f6", selectedColor = "#eff6ff", disabledColor = "#f3f4f6", padding = "0.5rem 0.75rem", margin = "0", borderRadius = "0.375rem", boxShadow = "0 1px 2px 0 rgba(0, 0, 0, 0.05)", optionPadding = "0.5rem 0.75rem", optionGap = "0.5rem", transitionDuration = "200ms", dropdownMaxHeight = "300px", dropdownMinWidth = "100%",
|
|
6
|
+
// Custom classes
|
|
7
|
+
className = "", dropdownClassName = "", optionClassName = "", inputClassName = "",
|
|
8
|
+
// Custom styles
|
|
9
|
+
style, dropdownStyle, optionStyle, inputStyle,
|
|
10
|
+
// Icons
|
|
11
|
+
iconPrefix, iconSuffix, clearIcon = "×", dropdownIcon = "▼", checkIcon = "✓",
|
|
12
|
+
// Accessibility
|
|
13
|
+
ariaLabel, ariaLabelledby, ariaDescribedby,
|
|
14
|
+
// Callbacks
|
|
15
|
+
onFocus, onBlur, onOpen, onClose, }) => {
|
|
16
|
+
const [selectedValues, setSelectedValues] = useState([]);
|
|
17
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
18
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
19
|
+
const [focusedIndex, setFocusedIndex] = useState(null);
|
|
20
|
+
const dropdownRef = useRef(null);
|
|
21
|
+
const inputRef = useRef(null);
|
|
22
|
+
const optionsRef = useRef([]);
|
|
23
|
+
// Initialize selected values
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (value) {
|
|
26
|
+
setSelectedValues(multiSelect ? value.split(",") : [value]);
|
|
27
|
+
}
|
|
28
|
+
else if (defaultValue) {
|
|
29
|
+
setSelectedValues(multiSelect ? defaultValue.split(",") : [defaultValue]);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
setSelectedValues([]);
|
|
33
|
+
}
|
|
34
|
+
}, [value, defaultValue, multiSelect]);
|
|
35
|
+
// Filter options based on search term
|
|
36
|
+
const filteredOptions = searchable
|
|
37
|
+
? options.filter((option) => option.label.toLowerCase().includes(searchTerm.toLowerCase()))
|
|
38
|
+
: options;
|
|
39
|
+
// Handle click outside
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const handleClickOutside = (event) => {
|
|
42
|
+
if (dropdownRef.current &&
|
|
43
|
+
!dropdownRef.current.contains(event.target)) {
|
|
44
|
+
setIsOpen(false);
|
|
45
|
+
onClose?.();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
49
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
50
|
+
}, [onClose]);
|
|
51
|
+
// Keyboard navigation
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const handleKeyDown = (event) => {
|
|
54
|
+
if (!isOpen)
|
|
55
|
+
return;
|
|
56
|
+
switch (event.key) {
|
|
57
|
+
case "ArrowDown":
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
setFocusedIndex((prev) => {
|
|
60
|
+
const nextIndex = prev === null
|
|
61
|
+
? 0
|
|
62
|
+
: Math.min(prev + 1, filteredOptions.length - 1);
|
|
63
|
+
scrollToOption(nextIndex);
|
|
64
|
+
return nextIndex;
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
67
|
+
case "ArrowUp":
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
setFocusedIndex((prev) => {
|
|
70
|
+
const nextIndex = prev === null ? 0 : Math.max(prev - 1, 0);
|
|
71
|
+
scrollToOption(nextIndex);
|
|
72
|
+
return nextIndex;
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
case "Enter":
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
if (focusedIndex !== null) {
|
|
78
|
+
handleSelect(filteredOptions[focusedIndex].value);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case "Escape":
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
setIsOpen(false);
|
|
84
|
+
onClose?.();
|
|
85
|
+
break;
|
|
86
|
+
case "Tab":
|
|
87
|
+
setIsOpen(false);
|
|
88
|
+
onClose?.();
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
93
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
94
|
+
}, [isOpen, focusedIndex, filteredOptions]);
|
|
95
|
+
const scrollToOption = useCallback((index) => {
|
|
96
|
+
optionsRef.current[index]?.scrollIntoView({
|
|
97
|
+
block: "nearest",
|
|
98
|
+
behavior: "smooth",
|
|
99
|
+
});
|
|
100
|
+
}, []);
|
|
101
|
+
const handleSelect = (value) => {
|
|
102
|
+
let newSelectedValues;
|
|
103
|
+
if (multiSelect) {
|
|
104
|
+
newSelectedValues = selectedValues.includes(value)
|
|
105
|
+
? selectedValues.filter((v) => v !== value)
|
|
106
|
+
: [...selectedValues, value];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
newSelectedValues = [value];
|
|
110
|
+
setIsOpen(false);
|
|
111
|
+
onClose?.();
|
|
112
|
+
}
|
|
113
|
+
setSelectedValues(newSelectedValues);
|
|
114
|
+
onChange?.(multiSelect ? newSelectedValues.join(",") : value);
|
|
115
|
+
};
|
|
116
|
+
const handleClear = (e) => {
|
|
117
|
+
e.stopPropagation();
|
|
118
|
+
setSelectedValues([]);
|
|
119
|
+
onChange?.("");
|
|
120
|
+
setSearchTerm("");
|
|
121
|
+
};
|
|
122
|
+
const toggleDropdown = () => {
|
|
123
|
+
if (disabled)
|
|
124
|
+
return;
|
|
125
|
+
const newState = !isOpen;
|
|
126
|
+
setIsOpen(newState);
|
|
127
|
+
if (newState) {
|
|
128
|
+
onOpen?.();
|
|
129
|
+
if (searchable) {
|
|
130
|
+
setTimeout(() => inputRef.current?.focus(), 0);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
onClose?.();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const selectedOption = options.find((option) => option.value === selectedValues[0]);
|
|
138
|
+
const selectedOptions = options.filter((option) => selectedValues.includes(option.value));
|
|
139
|
+
// Virtualization setup
|
|
140
|
+
const [startIndex, setStartIndex] = useState(0);
|
|
141
|
+
const visibleOptionCount = Math.min(visibleOptions, filteredOptions.length);
|
|
142
|
+
const endIndex = Math.min(startIndex + visibleOptionCount, filteredOptions.length);
|
|
143
|
+
const visibleOptionsList = virtualized
|
|
144
|
+
? filteredOptions.slice(startIndex, endIndex)
|
|
145
|
+
: filteredOptions;
|
|
146
|
+
return (_jsxs("div", { ref: dropdownRef, className: `dropdown-container ${className}`, style: {
|
|
147
|
+
position: "relative",
|
|
148
|
+
width,
|
|
149
|
+
margin,
|
|
150
|
+
fontFamily: "'Inter', sans-serif",
|
|
151
|
+
...style,
|
|
152
|
+
}, children: [_jsx("style", { children: `
|
|
153
|
+
.dropdown-container {
|
|
154
|
+
--border-color: ${borderColor};
|
|
155
|
+
--focus-border-color: ${focusBorderColor};
|
|
156
|
+
--error-border-color: ${errorBorderColor};
|
|
157
|
+
--bg-color: ${backgroundColor};
|
|
158
|
+
--text-color: ${textColor};
|
|
159
|
+
--placeholder-color: ${placeholderColor};
|
|
160
|
+
--hover-color: ${hoverColor};
|
|
161
|
+
--selected-color: ${selectedColor};
|
|
162
|
+
--disabled-color: ${disabledColor};
|
|
163
|
+
--transition-duration: ${transitionDuration};
|
|
164
|
+
}
|
|
165
|
+
` }), _jsxs("div", { role: "button", onClick: toggleDropdown, "aria-disabled": disabled, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, className: `dropdown-control ${inputClassName}`, style: {
|
|
166
|
+
width: "100%",
|
|
167
|
+
minHeight: height,
|
|
168
|
+
padding,
|
|
169
|
+
backgroundColor: disabled ? disabledColor : backgroundColor,
|
|
170
|
+
color: textColor,
|
|
171
|
+
borderWidth: "1px",
|
|
172
|
+
borderStyle: "solid",
|
|
173
|
+
borderColor: isOpen ? focusBorderColor : borderColor,
|
|
174
|
+
borderRadius,
|
|
175
|
+
boxShadow,
|
|
176
|
+
display: "flex",
|
|
177
|
+
alignItems: "center",
|
|
178
|
+
justifyContent: "space-between",
|
|
179
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
180
|
+
opacity: disabled ? 0.7 : 1,
|
|
181
|
+
transition: `all ${transitionDuration} ease-in-out`,
|
|
182
|
+
textAlign: "left",
|
|
183
|
+
...inputStyle,
|
|
184
|
+
...(isOpen && {
|
|
185
|
+
boxShadow: `0 0 0 1px ${focusBorderColor}`,
|
|
186
|
+
}),
|
|
187
|
+
}, children: [_jsxs("div", { style: {
|
|
188
|
+
display: "flex",
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
gap: optionGap,
|
|
191
|
+
flex: 1,
|
|
192
|
+
overflow: "hidden",
|
|
193
|
+
}, children: [iconPrefix && (_jsx("span", { className: "dropdown-icon-prefix", style: { flexShrink: 0 }, children: iconPrefix })), multiSelect ? (_jsx("div", { style: {
|
|
194
|
+
display: "flex",
|
|
195
|
+
gap: "0.25rem",
|
|
196
|
+
flexWrap: "wrap",
|
|
197
|
+
flex: 1,
|
|
198
|
+
overflow: "hidden",
|
|
199
|
+
}, children: selectedOptions.length > 0 ? (selectedOptions.map((option) => (_jsxs("span", { style: {
|
|
200
|
+
backgroundColor: selectedColor,
|
|
201
|
+
padding: "0.25rem 0.5rem",
|
|
202
|
+
borderRadius: "0.25rem",
|
|
203
|
+
fontSize: "0.875rem",
|
|
204
|
+
display: "flex",
|
|
205
|
+
alignItems: "center",
|
|
206
|
+
gap: "0.25rem",
|
|
207
|
+
flexShrink: 0,
|
|
208
|
+
}, children: [option.icon && (_jsx("span", { style: { flexShrink: 0 }, children: option.icon })), _jsx("span", { style: {
|
|
209
|
+
whiteSpace: "nowrap",
|
|
210
|
+
overflow: "hidden",
|
|
211
|
+
textOverflow: "ellipsis",
|
|
212
|
+
}, children: option.label })] }, option.value)))) : (_jsx("span", { style: { color: placeholderColor }, children: placeholder })) })) : (_jsxs("span", { style: {
|
|
213
|
+
color: selectedOption ? textColor : placeholderColor,
|
|
214
|
+
overflow: "hidden",
|
|
215
|
+
textOverflow: "ellipsis",
|
|
216
|
+
whiteSpace: "nowrap",
|
|
217
|
+
display: "flex",
|
|
218
|
+
alignItems: "center",
|
|
219
|
+
gap: optionGap,
|
|
220
|
+
}, children: [selectedOption?.icon && (_jsx("span", { style: { flexShrink: 0 }, children: selectedOption.icon })), selectedOption ? selectedOption.label : placeholder] }))] }), _jsxs("div", { style: {
|
|
221
|
+
display: "flex",
|
|
222
|
+
alignItems: "center",
|
|
223
|
+
gap: "0.5rem",
|
|
224
|
+
marginLeft: "0.5rem",
|
|
225
|
+
flexShrink: 0,
|
|
226
|
+
}, children: [clearable && selectedValues.length > 0 && (_jsx("span", { onClick: handleClear, style: {
|
|
227
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
228
|
+
fontSize: "1rem",
|
|
229
|
+
color: textColor,
|
|
230
|
+
opacity: 0.7,
|
|
231
|
+
display: "flex",
|
|
232
|
+
alignItems: "center",
|
|
233
|
+
justifyContent: "center",
|
|
234
|
+
}, "aria-label": "Clear selection", children: clearIcon })), _jsx("span", { style: {
|
|
235
|
+
transition: `transform ${transitionDuration}`,
|
|
236
|
+
transform: isOpen ? "rotate(180deg)" : "rotate(0deg)",
|
|
237
|
+
fontSize: "0.75rem",
|
|
238
|
+
color: textColor,
|
|
239
|
+
opacity: 0.7,
|
|
240
|
+
}, children: dropdownIcon })] })] }), isOpen && (_jsxs("div", { className: `dropdown-menu ${dropdownClassName}`, style: {
|
|
241
|
+
position: "absolute",
|
|
242
|
+
top: "100%",
|
|
243
|
+
left: 0,
|
|
244
|
+
zIndex: 1000,
|
|
245
|
+
width: "100%",
|
|
246
|
+
minWidth: dropdownMinWidth,
|
|
247
|
+
maxHeight: dropdownMaxHeight,
|
|
248
|
+
overflowY: "auto",
|
|
249
|
+
backgroundColor,
|
|
250
|
+
border: `1px solid ${borderColor}`,
|
|
251
|
+
borderRadius,
|
|
252
|
+
boxShadow: `0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)`,
|
|
253
|
+
marginTop: "0.25rem",
|
|
254
|
+
transition: `opacity ${transitionDuration}, transform ${transitionDuration}`,
|
|
255
|
+
opacity: 0,
|
|
256
|
+
transform: "translateY(-0.5rem)",
|
|
257
|
+
animation: `dropdownFadeIn ${transitionDuration} ease-out forwards`,
|
|
258
|
+
...dropdownStyle,
|
|
259
|
+
}, role: "listbox", "aria-multiselectable": multiSelect, children: [searchable && (_jsx("div", { style: {
|
|
260
|
+
padding: "0.5rem",
|
|
261
|
+
borderBottom: `1px solid ${borderColor}`,
|
|
262
|
+
}, children: _jsx("input", { ref: inputRef, type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder: "Search...", style: {
|
|
263
|
+
width: "100%",
|
|
264
|
+
padding: "0.5rem 0.75rem",
|
|
265
|
+
borderWidth: "1px",
|
|
266
|
+
borderStyle: "solid",
|
|
267
|
+
color: textColor,
|
|
268
|
+
borderColor: isOpen ? focusBorderColor : borderColor,
|
|
269
|
+
borderRadius: "0.25rem",
|
|
270
|
+
outline: "none",
|
|
271
|
+
transition: `border-color ${transitionDuration}`,
|
|
272
|
+
...(isOpen && {
|
|
273
|
+
boxShadow: `0 0 0 1px ${focusBorderColor}`,
|
|
274
|
+
}),
|
|
275
|
+
}, onFocus: onFocus, onBlur: onBlur }) })), _jsx("ul", { style: { margin: 0, padding: "0.25rem 0", listStyle: "none" }, children: filteredOptions.length > 0 ? (visibleOptionsList.map((option, index) => {
|
|
276
|
+
const isSelected = selectedValues.includes(option.value);
|
|
277
|
+
const isFocused = focusedIndex === (virtualized ? startIndex + index : index);
|
|
278
|
+
const isDisabled = option.disabled;
|
|
279
|
+
return (_jsxs("li", { ref: (el) => {
|
|
280
|
+
if (el) {
|
|
281
|
+
const refIndex = virtualized
|
|
282
|
+
? startIndex + index
|
|
283
|
+
: index;
|
|
284
|
+
optionsRef.current[refIndex] = el;
|
|
285
|
+
}
|
|
286
|
+
}, onClick: () => !isDisabled && handleSelect(option.value), onMouseEnter: () => !isDisabled &&
|
|
287
|
+
setFocusedIndex(virtualized ? startIndex + index : index), className: `dropdown-option ${optionClassName} ${isDisabled ? "disabled" : ""}`, style: {
|
|
288
|
+
padding: optionPadding,
|
|
289
|
+
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
290
|
+
backgroundColor: isSelected
|
|
291
|
+
? selectedColor
|
|
292
|
+
: isFocused
|
|
293
|
+
? hoverColor
|
|
294
|
+
: backgroundColor,
|
|
295
|
+
color: isDisabled ? disabledColor : textColor,
|
|
296
|
+
display: "flex",
|
|
297
|
+
alignItems: "center",
|
|
298
|
+
gap: optionGap,
|
|
299
|
+
transition: `background-color ${transitionDuration}`,
|
|
300
|
+
...optionStyle,
|
|
301
|
+
}, role: "option", "aria-selected": isSelected, "aria-disabled": isDisabled, children: [multiSelect && (_jsx("span", { style: { flexShrink: 0 }, children: isSelected ? checkIcon : "○" })), option.icon && (_jsx("span", { style: { flexShrink: 0 }, children: option.icon })), _jsx("span", { style: { flex: 1 }, children: option.label })] }, option.value));
|
|
302
|
+
})) : (_jsx("li", { style: {
|
|
303
|
+
padding: optionPadding,
|
|
304
|
+
color: placeholderColor,
|
|
305
|
+
textAlign: "center",
|
|
306
|
+
}, children: "No options found" })) }), virtualized && filteredOptions.length > visibleOptionCount && (_jsx("div", { style: {
|
|
307
|
+
height: `${(filteredOptions.length - visibleOptionCount) * optionHeight}px`,
|
|
308
|
+
} }))] })), _jsx("style", { children: `
|
|
309
|
+
@keyframes dropdownFadeIn {
|
|
310
|
+
to {
|
|
311
|
+
opacity: 1;
|
|
312
|
+
transform: translateY(0);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
` })] }));
|
|
316
|
+
};
|
|
@@ -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
|
+
};
|