ehscan-react-components 0.1.50 → 0.1.51
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/README.md +4 -0
- package/dist/Components.d.ts +3 -2
- package/dist/Components.js +3 -2
- package/dist/style/text.module.css +10 -0
- package/dist/text/TextArea.d.ts +15 -0
- package/dist/text/TextArea.js +44 -0
- package/dist/text/TextAreaDropDown.d.ts +20 -0
- package/dist/text/TextAreaDropDown.js +134 -0
- package/dist/text/TextElementStretch.d.ts +5 -0
- package/dist/text/TextElementStretch.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ This library is ideal for dashboards, admin panels, internal tools, and feature-
|
|
|
14
14
|
- Button
|
|
15
15
|
- textarea
|
|
16
16
|
- TextAreaDropDown
|
|
17
|
+
- TextElementStretch
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
@@ -322,6 +323,9 @@ const WindowWrapper = ({ windowOpen, setWindowOpen }) => {
|
|
|
322
323
|
----
|
|
323
324
|
# Changelog
|
|
324
325
|
|
|
326
|
+
## [0.1.51] - 2025-12-16
|
|
327
|
+
- Added TextElementStretch
|
|
328
|
+
|
|
325
329
|
## [0.1.50] - 2025-12-16
|
|
326
330
|
- Button default style adjustments
|
|
327
331
|
|
package/dist/Components.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { Button } from './button/Button';
|
|
2
2
|
export { Window } from './window/Window';
|
|
3
3
|
export { DragAndDrop } from './dnd/DragAndDrop';
|
|
4
|
-
export { TextArea } from './TextArea';
|
|
5
|
-
export {
|
|
4
|
+
export { TextArea } from './text/TextArea';
|
|
5
|
+
export { TextElementStretch } from './text/TextElementStretch';
|
|
6
|
+
export { TextAreaDropDown } from './text/TextAreaDropDown';
|
|
6
7
|
export { AddBox } from './AddBox';
|
|
7
8
|
export { useChangeAddBox } from './tools/useChangeAddBox';
|
package/dist/Components.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
export { Button } from './button/Button';
|
|
3
3
|
export { Window } from './window/Window';
|
|
4
4
|
export { DragAndDrop } from './dnd/DragAndDrop';
|
|
5
|
-
export { TextArea } from './TextArea';
|
|
6
|
-
export {
|
|
5
|
+
export { TextArea } from './text/TextArea';
|
|
6
|
+
export { TextElementStretch } from './text/TextElementStretch';
|
|
7
|
+
export { TextAreaDropDown } from './text/TextAreaDropDown';
|
|
7
8
|
export { AddBox } from './AddBox';
|
|
8
9
|
export { useChangeAddBox } from './tools/useChangeAddBox';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
.textElementStretch{
|
|
2
|
+
flex: 1;
|
|
3
|
+
font-size: 100%;
|
|
4
|
+
line-height: var(--ext-text-stretch-line-height, 1);
|
|
5
|
+
display: -webkit-box;
|
|
6
|
+
-webkit-box-orient: vertical;
|
|
7
|
+
-webkit-line-clamp: var(--ext-text-stretch-clamp, 1);
|
|
8
|
+
line-clamp: var(--ext-text-stretch-clamp, 1);
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './style/input.css';
|
|
2
|
+
interface Props {
|
|
3
|
+
id?: string;
|
|
4
|
+
tabIndex?: number;
|
|
5
|
+
editable?: boolean;
|
|
6
|
+
label?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
value: string;
|
|
9
|
+
onChange: (value: string) => void;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
maxLength?: number;
|
|
12
|
+
addClass?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const TextArea: React.FC<Props>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState, useCallback, useId } from "react";
|
|
3
|
+
import './style/input.css';
|
|
4
|
+
export const TextArea = ({ id, tabIndex, label, value, editable = true, required = false, onChange, placeholder, maxLength = 500, addClass }) => {
|
|
5
|
+
const textareaRef = useRef(null);
|
|
6
|
+
const [charCount, setCharCount] = useState(value.length);
|
|
7
|
+
const generatedId = useId(); // unique fallback for aria linking
|
|
8
|
+
const textareaId = id || `textarea-${generatedId}`;
|
|
9
|
+
// 🔧 Resize and update char count whenever value changes
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
setHeight();
|
|
12
|
+
setCharCount(value.length);
|
|
13
|
+
}, [value]);
|
|
14
|
+
const setHeight = () => {
|
|
15
|
+
const el = textareaRef.current;
|
|
16
|
+
if (!el)
|
|
17
|
+
return;
|
|
18
|
+
el.style.height = "auto";
|
|
19
|
+
el.style.height = `${Math.max(el.scrollHeight, 20)}px`;
|
|
20
|
+
};
|
|
21
|
+
const handleInputChange = useCallback((event) => {
|
|
22
|
+
const newValue = event.target.value;
|
|
23
|
+
onChange(newValue);
|
|
24
|
+
setCharCount(newValue.length);
|
|
25
|
+
}, [onChange]);
|
|
26
|
+
const handleFocus = useCallback(() => {
|
|
27
|
+
const el = textareaRef.current;
|
|
28
|
+
if (!el)
|
|
29
|
+
return;
|
|
30
|
+
requestAnimationFrame(() => {
|
|
31
|
+
el.focus({ preventScroll: true });
|
|
32
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
33
|
+
});
|
|
34
|
+
setHeight();
|
|
35
|
+
}, []);
|
|
36
|
+
const clear = useCallback(() => {
|
|
37
|
+
onChange("");
|
|
38
|
+
setCharCount(0);
|
|
39
|
+
if (textareaRef.current) {
|
|
40
|
+
textareaRef.current.style.height = "auto";
|
|
41
|
+
}
|
|
42
|
+
}, [onChange]);
|
|
43
|
+
return (_jsxs("div", { className: `ext-textarea-wrapper ${addClass}`, children: [label && (_jsxs("div", { className: "ext-textarea-label", children: [_jsxs("label", { className: "ext-textarea-label-title", htmlFor: textareaId, children: [label, " ", required && _jsx("span", { className: "required", children: "*" })] }), _jsxs("div", { className: "ext-textarea-label-btns", children: [editable && charCount > 0 && (_jsxs("div", { className: "form-container-count", children: [charCount, " / ", maxLength] })), editable && charCount > 0 && (_jsx("div", { className: "ext-textarea-svg-close", onClick: clear, "aria-label": `Clear ${label !== null && label !== void 0 ? label : "text area"}`, children: _jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("line", { x1: "8", y1: "8", x2: "16", y2: "16", stroke: "#333", strokeWidth: "2", strokeLinecap: "round" }), _jsx("line", { x1: "16", y1: "8", x2: "8", y2: "16", stroke: "#333", strokeWidth: "2", strokeLinecap: "round" })] }) }))] })] })), _jsx("div", { className: "ext-textarea-box", children: _jsx("textarea", { id: textareaId, tabIndex: tabIndex, ref: textareaRef, value: value !== null && value !== void 0 ? value : "", placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : "...", maxLength: maxLength, onChange: handleInputChange, onFocus: handleFocus, onBlur: setHeight, className: `ext-textarea${required && value === "" ? " highlight" : ""}`, rows: 1, spellCheck: false, readOnly: !editable, "aria-required": required, "aria-label": label }) })] }));
|
|
44
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import './style/input-dropdown.css';
|
|
2
|
+
type Action = "add" | "remove";
|
|
3
|
+
interface Props {
|
|
4
|
+
id?: string;
|
|
5
|
+
tabIndex?: number;
|
|
6
|
+
alwaysOpenDropDown?: boolean;
|
|
7
|
+
closeCommand?: any;
|
|
8
|
+
editable?: boolean;
|
|
9
|
+
label?: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
value: string[];
|
|
12
|
+
dropdownValue: string[];
|
|
13
|
+
onChange: (value: string, action: Action) => void;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
addClass?: string;
|
|
17
|
+
maxDropDownHeight?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare const TextAreaDropDown: React.FC<Props>;
|
|
20
|
+
export default TextAreaDropDown;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useRef, useState, useId, useEffect } from "react";
|
|
12
|
+
import { DropDown } from "../DropDown";
|
|
13
|
+
import './style/input-dropdown.css';
|
|
14
|
+
export const TextAreaDropDown = ({ id, tabIndex, alwaysOpenDropDown, closeCommand, label, value, editable = true, required = false, dropdownValue, onChange, placeholder = 'select or create new entry', maxLength = 500, addClass, maxDropDownHeight = 200 }) => {
|
|
15
|
+
const childRef = useRef(null);
|
|
16
|
+
const textareaRef = useRef(null);
|
|
17
|
+
const searchInput = useRef(null);
|
|
18
|
+
const [charCount, setCharCount] = useState(value.length);
|
|
19
|
+
const generatedId = useId(); // unique fallback for aria linking
|
|
20
|
+
const textareaId = id || `textarea-${generatedId}`;
|
|
21
|
+
const [openDropDown, setOpenDropDown] = useState(false);
|
|
22
|
+
const [maxDropDownEntries, setMaxDropDownEntries] = useState(undefined);
|
|
23
|
+
const [tags, setTags] = useState(undefined);
|
|
24
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
25
|
+
const [filterItems, setFilterItem] = useState([]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setTags(value);
|
|
28
|
+
setCharCount(value.length);
|
|
29
|
+
if (dropdownValue === undefined)
|
|
30
|
+
return;
|
|
31
|
+
setMaxDropDownEntries(dropdownValue.length);
|
|
32
|
+
}, [value]);
|
|
33
|
+
const handleKeyDown = (e) => {
|
|
34
|
+
if (newBtn && e.key === 'Enter' && searchTerm !== '') {
|
|
35
|
+
createItem();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (e.key === "Backspace" && searchTerm === "" && value.length > 0) {
|
|
39
|
+
if (tags === null || tags === void 0 ? void 0 : tags.length) {
|
|
40
|
+
removeTag(tags[tags.length - 1]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (searchTerm === undefined || value === undefined)
|
|
46
|
+
return;
|
|
47
|
+
const dropDownEntry = dropdownValue.filter(tag => !value.includes(tag));
|
|
48
|
+
if (!searchTerm || searchTerm === '') {
|
|
49
|
+
setFilterItem(dropDownEntry);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const filterAndProcessItems = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
const searchWords = searchTerm
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.split(/\s+/) // split by whitespace
|
|
56
|
+
.filter(Boolean); // remove empty strings
|
|
57
|
+
const filtered = yield Promise.all(// some ✅ ANY search word should match (common) | All shall match -> every
|
|
58
|
+
dropDownEntry
|
|
59
|
+
.filter(item => searchWords.every(word => item.toLowerCase().includes(word.toLowerCase())))
|
|
60
|
+
.map((item) => __awaiter(void 0, void 0, void 0, function* () { return item; })));
|
|
61
|
+
setFilterItem(filtered);
|
|
62
|
+
});
|
|
63
|
+
filterAndProcessItems();
|
|
64
|
+
}, [searchTerm, value]);
|
|
65
|
+
const removeTag = (tagToRemove) => {
|
|
66
|
+
var _a;
|
|
67
|
+
onChange(tagToRemove, 'remove');
|
|
68
|
+
if (childRef.current)
|
|
69
|
+
(_a = childRef.current) === null || _a === void 0 ? void 0 : _a.calc();
|
|
70
|
+
};
|
|
71
|
+
const addItem = (entry) => {
|
|
72
|
+
var _a;
|
|
73
|
+
if (entry === undefined || !filterItems[entry])
|
|
74
|
+
return;
|
|
75
|
+
const newValue = filterItems[entry];
|
|
76
|
+
console.log(newValue);
|
|
77
|
+
onChange(newValue, 'add');
|
|
78
|
+
if (!searchInput.current)
|
|
79
|
+
return;
|
|
80
|
+
searchInput.current.focus();
|
|
81
|
+
if (childRef.current)
|
|
82
|
+
(_a = childRef.current) === null || _a === void 0 ? void 0 : _a.calc();
|
|
83
|
+
};
|
|
84
|
+
const createItem = () => {
|
|
85
|
+
var _a;
|
|
86
|
+
if (searchTerm === undefined || searchTerm === '')
|
|
87
|
+
return;
|
|
88
|
+
onChange(searchTerm, 'add');
|
|
89
|
+
if (!searchInput.current)
|
|
90
|
+
return;
|
|
91
|
+
searchInput.current.value = '';
|
|
92
|
+
searchInput.current.focus();
|
|
93
|
+
setSearchTerm("");
|
|
94
|
+
if (childRef.current)
|
|
95
|
+
(_a = childRef.current) === null || _a === void 0 ? void 0 : _a.calc();
|
|
96
|
+
};
|
|
97
|
+
const [newBtn, setNewBtn] = useState(false);
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (searchTerm === undefined)
|
|
100
|
+
return;
|
|
101
|
+
if (searchTerm !== "")
|
|
102
|
+
setOpenDropDown(true); //if closed on !alwaysOpenDropDown
|
|
103
|
+
if (dropdownValue.includes(searchTerm)) {
|
|
104
|
+
setNewBtn(false);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (searchTerm !== '')
|
|
108
|
+
setNewBtn(true);
|
|
109
|
+
}, [searchTerm]);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!closeCommand)
|
|
112
|
+
return;
|
|
113
|
+
setOpenDropDown(false);
|
|
114
|
+
}, [closeCommand]);
|
|
115
|
+
const focusInput = () => {
|
|
116
|
+
if (alwaysOpenDropDown) {
|
|
117
|
+
setOpenDropDown(true);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(searchTerm);
|
|
121
|
+
if (searchTerm) {
|
|
122
|
+
setOpenDropDown(true);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const blurInput = () => {
|
|
126
|
+
// setOpenDropDown(false)
|
|
127
|
+
// setNewBtn(false)
|
|
128
|
+
// setSearchTerm('')
|
|
129
|
+
};
|
|
130
|
+
if (!tags)
|
|
131
|
+
return null;
|
|
132
|
+
return (_jsxs("div", { className: `ext-textarea-wrapper-dropdown ${addClass}`, ref: textareaRef, children: [label && (_jsxs("div", { className: "ext-textarea-label", children: [_jsxs("label", { className: "ext-textarea-label-title", htmlFor: textareaId, children: [label, " ", required && _jsx("span", { className: "required", children: "*" })] }), _jsxs("div", { className: "ext-textarea-label-btns", children: [editable && charCount > 0 && (_jsxs("div", { className: "form-container-count", children: [charCount, " / ", maxLength] })), editable && charCount > 0 && (_jsx("div", { className: "ext-textarea-svg-close", "aria-label": `Clear ${label !== null && label !== void 0 ? label : "text area"}` }))] })] })), _jsx("div", { className: `ext-textarea-box-dropdown${openDropDown ? ' open' : ''}`, onClick: () => focusInput(), children: _jsxs("div", { className: "ext-textarea-dropdown-inner", children: [tags.map((item, index) => (_jsxs("div", { className: "textarea-tag loop", children: [_jsx("div", { children: item }), _jsx("div", { className: "textarea-tag-erase", onClick: () => removeTag(tags[index]), children: _jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("line", { x1: "8", y1: "8", x2: "16", y2: "16", stroke: "#333", strokeWidth: "1", strokeLinecap: "round" }), _jsx("line", { x1: "16", y1: "8", x2: "8", y2: "16", stroke: "#333", strokeWidth: "1", strokeLinecap: "round" })] }) })] }, index))), _jsx("div", { className: "search-x-wrapper", children: _jsxs("div", { className: "search-x", children: [_jsx("div", { className: "search-x-input", children: _jsx("input", { type: "text", tabIndex: tabIndex, ref: searchInput, onFocus: () => focusInput(), onBlur: () => blurInput(), placeholder: placeholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), onKeyDown: handleKeyDown }) }), _jsx("div", { className: `search-x-btn${newBtn ? ' show' : ''}`, onClick: () => createItem(), children: newBtn && 'create new one' })] }) })] }) }), _jsx(DropDown, { ref: childRef, maxDropDownHeight: maxDropDownHeight, openDropDown: openDropDown, display: filterItems, addItem: addItem, maxDropDownEntries: maxDropDownEntries, searchTerm: searchTerm })] }));
|
|
133
|
+
};
|
|
134
|
+
export default TextAreaDropDown;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import styles from '../style/text.module.css';
|
|
3
|
+
export const TextElementStretch = ({ text }) => {
|
|
4
|
+
return (_jsx(_Fragment, { children: _jsx("div", { className: styles.textElementStretch, children: text }) }));
|
|
5
|
+
};
|