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 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
 
@@ -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 { TextAreaDropDown } from './TextAreaDropDown';
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';
@@ -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 { TextAreaDropDown } from './TextAreaDropDown';
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
+ type Props = {
2
+ text: string;
3
+ };
4
+ export declare const TextElementStretch: React.FC<Props>;
5
+ export {};
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ehscan-react-components",
3
- "version": "0.1.50",
3
+ "version": "0.1.51",
4
4
  "description": "components",
5
5
  "main": "dist/Components.js",
6
6
  "types": "dist/Components.d.ts",