react-mention-input 1.0.18 → 1.1.0
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/MentionInput.css +28 -1
- package/dist/MentionInput.d.ts +2 -2
- package/dist/MentionInput.js +126 -26
- package/package.json +1 -1
- package/src/MentionInput.css +28 -1
- package/src/MentionInput.tsx +177 -63
package/dist/MentionInput.css
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
justify-content: center;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
.suggestion-list {
|
|
45
|
+
/* .suggestion-list {
|
|
46
46
|
position: absolute;
|
|
47
47
|
top: 100%;
|
|
48
48
|
left: 0;
|
|
@@ -57,8 +57,21 @@
|
|
|
57
57
|
overflow-y: auto;
|
|
58
58
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
59
59
|
z-index: 1;
|
|
60
|
+
} */
|
|
61
|
+
|
|
62
|
+
.suggestion-list {
|
|
63
|
+
max-height: 150px;
|
|
64
|
+
overflow-y: auto;
|
|
65
|
+
background: #fff;
|
|
66
|
+
border: 1px solid #ddd;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
z-index: 1000;
|
|
69
|
+
width: auto;
|
|
70
|
+
white-space: nowrap;
|
|
71
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
60
72
|
}
|
|
61
73
|
|
|
74
|
+
|
|
62
75
|
.suggestion-item {
|
|
63
76
|
padding: 8px 12px;
|
|
64
77
|
cursor: pointer;
|
|
@@ -69,3 +82,17 @@
|
|
|
69
82
|
.suggestion-item:hover {
|
|
70
83
|
background-color: #f5f5f5;
|
|
71
84
|
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
.mention-highlight {
|
|
88
|
+
background-color: #e0f7fa;
|
|
89
|
+
color: #007bff;
|
|
90
|
+
padding: 2px 4px;
|
|
91
|
+
border-radius: 4px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.mention-input .placeholder {
|
|
95
|
+
color: #aaa;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
position: absolute;
|
|
98
|
+
}
|
package/dist/MentionInput.d.ts
CHANGED
package/dist/MentionInput.js
CHANGED
|
@@ -1,48 +1,148 @@
|
|
|
1
|
-
import React, { useState } from
|
|
2
|
-
import
|
|
1
|
+
import React, { useState, useRef } from "react";
|
|
2
|
+
import ReactDOM from "react-dom";
|
|
3
|
+
import "./MentionInput.css";
|
|
3
4
|
var MentionInput = function (_a) {
|
|
4
|
-
var users = _a.users,
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
var users = _a.users, _b = _a.placeholder, placeholder = _b === void 0 ? "Type a message..." : _b, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, sendButtonIcon = _a.sendButtonIcon, onSendMessage = _a.onSendMessage;
|
|
6
|
+
var _c = useState(""), inputValue = _c[0], setInputValue = _c[1]; // Plain text
|
|
7
|
+
var _d = useState([]), suggestions = _d[0], setSuggestions = _d[1];
|
|
8
|
+
var _e = useState(false), showSuggestions = _e[0], setShowSuggestions = _e[1];
|
|
9
|
+
var _f = useState('bottom'), tooltipPosition = _f[0], setTooltipPosition = _f[1];
|
|
10
|
+
var inputRef = useRef(null);
|
|
11
|
+
var suggestionListRef = useRef(null);
|
|
12
|
+
var caretOffsetRef = useRef(0);
|
|
13
|
+
var highlightMentions = function (text) {
|
|
14
|
+
return text.replace(/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g, // Match @ followed by one or two words, ending with a space or end of string
|
|
15
|
+
'<span class="mention-highlight">@$1</span>');
|
|
16
|
+
};
|
|
17
|
+
var restoreCaretPosition = function (node, caretOffset) {
|
|
18
|
+
var range = document.createRange();
|
|
19
|
+
var sel = window.getSelection();
|
|
20
|
+
var charCount = 0;
|
|
21
|
+
var findCaret = function (currentNode) {
|
|
22
|
+
var _a;
|
|
23
|
+
for (var _i = 0, _b = Array.from(currentNode.childNodes); _i < _b.length; _i++) {
|
|
24
|
+
var child = _b[_i];
|
|
25
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
26
|
+
var textLength = ((_a = child.textContent) === null || _a === void 0 ? void 0 : _a.length) || 0;
|
|
27
|
+
if (charCount + textLength >= caretOffset) {
|
|
28
|
+
range.setStart(child, caretOffset - charCount);
|
|
29
|
+
range.collapse(true);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
charCount += textLength;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
37
|
+
if (findCaret(child))
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
findCaret(node);
|
|
44
|
+
if (sel) {
|
|
45
|
+
sel.removeAllRanges();
|
|
46
|
+
sel.addRange(range);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var handleInputChange = function () {
|
|
50
|
+
if (!inputRef.current)
|
|
51
|
+
return;
|
|
52
|
+
var selection = window.getSelection();
|
|
53
|
+
var range = selection === null || selection === void 0 ? void 0 : selection.getRangeAt(0);
|
|
54
|
+
var newCaretOffset = 0;
|
|
55
|
+
if (range && inputRef.current.contains(range.startContainer)) {
|
|
56
|
+
var preCaretRange = range.cloneRange();
|
|
57
|
+
preCaretRange.selectNodeContents(inputRef.current);
|
|
58
|
+
preCaretRange.setEnd(range.startContainer, range.startOffset);
|
|
59
|
+
newCaretOffset = preCaretRange.toString().length; // Calculate caret offset
|
|
60
|
+
}
|
|
61
|
+
caretOffsetRef.current = newCaretOffset; // Update ref directly
|
|
62
|
+
var plainText = inputRef.current.innerText;
|
|
63
|
+
setInputValue(plainText);
|
|
64
|
+
// Handle mentions and suggestions
|
|
65
|
+
var mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
|
|
12
66
|
if (mentionMatch) {
|
|
13
67
|
var query_1 = mentionMatch[1].toLowerCase();
|
|
14
|
-
var filteredUsers = users.filter(function (user) {
|
|
15
|
-
return user.name.toLowerCase().startsWith(query_1);
|
|
16
|
-
});
|
|
68
|
+
var filteredUsers = query_1 === "" ? users : users.filter(function (user) { return user.name.toLowerCase().startsWith(query_1); });
|
|
17
69
|
setSuggestions(filteredUsers);
|
|
18
|
-
setShowSuggestions(
|
|
70
|
+
setShowSuggestions(filteredUsers.length > 0);
|
|
19
71
|
}
|
|
20
72
|
else {
|
|
21
73
|
setShowSuggestions(false);
|
|
22
74
|
}
|
|
75
|
+
var previousHTML = inputRef.current.innerHTML;
|
|
76
|
+
var htmlWithHighlights = highlightMentions(plainText);
|
|
77
|
+
if (previousHTML !== htmlWithHighlights) {
|
|
78
|
+
inputRef.current.innerHTML = htmlWithHighlights;
|
|
79
|
+
}
|
|
80
|
+
restoreCaretPosition(inputRef.current, newCaretOffset);
|
|
23
81
|
};
|
|
24
82
|
var handleSuggestionClick = function (user) {
|
|
25
|
-
|
|
26
|
-
|
|
83
|
+
if (!inputRef.current)
|
|
84
|
+
return;
|
|
85
|
+
var plainText = inputValue; // Current input value
|
|
86
|
+
var caretOffset = caretOffsetRef.current; // Use ref value for caret offset
|
|
87
|
+
var mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
|
|
88
|
+
if (!mentionMatch)
|
|
89
|
+
return;
|
|
90
|
+
var mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
|
|
91
|
+
var newValue = plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
|
|
27
92
|
setInputValue(newValue);
|
|
93
|
+
inputRef.current.innerText = newValue;
|
|
94
|
+
var htmlWithHighlights = highlightMentions(newValue);
|
|
95
|
+
inputRef.current.innerHTML = htmlWithHighlights;
|
|
28
96
|
setShowSuggestions(false);
|
|
97
|
+
var mentionEnd = mentionIndex + user.name.length + 1;
|
|
98
|
+
restoreCaretPosition(inputRef.current, mentionEnd);
|
|
29
99
|
};
|
|
30
100
|
var handleSendMessage = function () {
|
|
31
101
|
if (inputValue.trim() && onSendMessage) {
|
|
32
102
|
onSendMessage(inputValue.trim());
|
|
33
|
-
setInputValue(
|
|
103
|
+
setInputValue("");
|
|
104
|
+
setShowSuggestions(false);
|
|
105
|
+
if (inputRef.current)
|
|
106
|
+
inputRef.current.innerText = "";
|
|
34
107
|
}
|
|
35
108
|
};
|
|
36
|
-
var
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
109
|
+
var renderSuggestions = function () {
|
|
110
|
+
if (!showSuggestions || !inputRef.current)
|
|
111
|
+
return null;
|
|
112
|
+
var inputRect = inputRef.current.getBoundingClientRect();
|
|
113
|
+
var styles = {
|
|
114
|
+
position: 'absolute',
|
|
115
|
+
zIndex: 1000
|
|
116
|
+
};
|
|
117
|
+
// Adjust position based on calculated tooltip position
|
|
118
|
+
switch (tooltipPosition) {
|
|
119
|
+
case 'top':
|
|
120
|
+
styles.left = "".concat(inputRect.left, "px");
|
|
121
|
+
styles.top = "".concat(inputRect.top - 150, "px");
|
|
122
|
+
break;
|
|
123
|
+
case 'bottom':
|
|
124
|
+
styles.left = "".concat(inputRect.left, "px");
|
|
125
|
+
styles.top = "".concat(inputRect.bottom, "px");
|
|
126
|
+
break;
|
|
127
|
+
case 'left':
|
|
128
|
+
styles.left = "".concat(inputRect.left - 150, "px");
|
|
129
|
+
styles.top = "".concat(inputRect.top, "px");
|
|
130
|
+
break;
|
|
131
|
+
case 'right':
|
|
132
|
+
styles.left = "".concat(inputRect.right, "px");
|
|
133
|
+
styles.top = "".concat(inputRect.top, "px");
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
break;
|
|
40
137
|
}
|
|
138
|
+
console.log("document.body", document.body);
|
|
139
|
+
return ReactDOM.createPortal(React.createElement("ul", { className: "suggestion-list ".concat(suggestionListClassName || ''), ref: suggestionListRef, style: styles }, suggestions.map(function (user) { return (React.createElement("li", { key: user.id, onClick: function () { return handleSuggestionClick(user); }, className: "suggestion-item ".concat(suggestionItemClassName || ''), role: "option", tabIndex: 0, "aria-selected": "false" }, user.name)); })), document.body // Render in portal
|
|
140
|
+
);
|
|
41
141
|
};
|
|
42
|
-
return (React.createElement("div", { className: "mention-container ".concat(containerClassName ||
|
|
43
|
-
React.createElement("div", { className: "mention-input-container ".concat(inputContainerClassName ||
|
|
44
|
-
React.createElement("
|
|
45
|
-
React.createElement("button", { onClick: handleSendMessage, className: "send-button ".concat(sendBtnClassName ||
|
|
46
|
-
|
|
142
|
+
return (React.createElement("div", { className: "mention-container ".concat(containerClassName || "") },
|
|
143
|
+
React.createElement("div", { className: "mention-input-container ".concat(inputContainerClassName || "") },
|
|
144
|
+
React.createElement("div", { ref: inputRef, contentEditable: true, suppressContentEditableWarning: true, className: "mention-input ".concat(inputClassName || ""), onInput: handleInputChange }),
|
|
145
|
+
React.createElement("button", { onClick: handleSendMessage, className: "send-button ".concat(sendBtnClassName || "") }, sendButtonIcon || "➤")),
|
|
146
|
+
renderSuggestions()));
|
|
47
147
|
};
|
|
48
148
|
export default MentionInput;
|
package/package.json
CHANGED
package/src/MentionInput.css
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
justify-content: center;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
.suggestion-list {
|
|
45
|
+
/* .suggestion-list {
|
|
46
46
|
position: absolute;
|
|
47
47
|
top: 100%;
|
|
48
48
|
left: 0;
|
|
@@ -57,8 +57,21 @@
|
|
|
57
57
|
overflow-y: auto;
|
|
58
58
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
59
59
|
z-index: 1;
|
|
60
|
+
} */
|
|
61
|
+
|
|
62
|
+
.suggestion-list {
|
|
63
|
+
max-height: 150px;
|
|
64
|
+
overflow-y: auto;
|
|
65
|
+
background: #fff;
|
|
66
|
+
border: 1px solid #ddd;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
z-index: 1000;
|
|
69
|
+
width: auto;
|
|
70
|
+
white-space: nowrap;
|
|
71
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
60
72
|
}
|
|
61
73
|
|
|
74
|
+
|
|
62
75
|
.suggestion-item {
|
|
63
76
|
padding: 8px 12px;
|
|
64
77
|
cursor: pointer;
|
|
@@ -69,3 +82,17 @@
|
|
|
69
82
|
.suggestion-item:hover {
|
|
70
83
|
background-color: #f5f5f5;
|
|
71
84
|
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
.mention-highlight {
|
|
88
|
+
background-color: #e0f7fa;
|
|
89
|
+
color: #007bff;
|
|
90
|
+
padding: 2px 4px;
|
|
91
|
+
border-radius: 4px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.mention-input .placeholder {
|
|
95
|
+
color: #aaa;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
position: absolute;
|
|
98
|
+
}
|
package/src/MentionInput.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React, { useState,
|
|
2
|
-
import
|
|
1
|
+
import React, { useState, useRef, useEffect, ReactNode } from "react";
|
|
2
|
+
import ReactDOM from "react-dom";
|
|
3
|
+
import "./MentionInput.css";
|
|
3
4
|
|
|
4
5
|
interface User {
|
|
5
6
|
id: number;
|
|
6
|
-
name: string;
|
|
7
|
+
name: string; // Full name, e.g., "John Heart"
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
interface MentionInputProps {
|
|
@@ -19,9 +20,9 @@ interface MentionInputProps {
|
|
|
19
20
|
onSendMessage?: (message: string) => void;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
const MentionInput: React.FC<MentionInputProps> = ({
|
|
23
24
|
users,
|
|
24
|
-
placeholder,
|
|
25
|
+
placeholder = "Type a message...",
|
|
25
26
|
containerClassName,
|
|
26
27
|
inputContainerClassName,
|
|
27
28
|
inputClassName,
|
|
@@ -31,92 +32,205 @@ interface MentionInputProps {
|
|
|
31
32
|
sendButtonIcon,
|
|
32
33
|
onSendMessage,
|
|
33
34
|
}) => {
|
|
34
|
-
const [inputValue, setInputValue] = useState<string>(
|
|
35
|
+
const [inputValue, setInputValue] = useState<string>(""); // Plain text
|
|
35
36
|
const [suggestions, setSuggestions] = useState<User[]>([]);
|
|
36
37
|
const [showSuggestions, setShowSuggestions] = useState<boolean>(false);
|
|
38
|
+
const [tooltipPosition, setTooltipPosition] = useState<'top' | 'bottom' | 'left' | 'right'>(
|
|
39
|
+
'bottom'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const inputRef = useRef<HTMLDivElement>(null);
|
|
43
|
+
const suggestionListRef = useRef<HTMLUListElement>(null);
|
|
44
|
+
const caretOffsetRef = useRef<number>(0);
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const highlightMentions = (text: string): string => {
|
|
48
|
+
return text.replace(
|
|
49
|
+
/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g, // Match @ followed by one or two words, ending with a space or end of string
|
|
50
|
+
'<span class="mention-highlight">@$1</span>'
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const restoreCaretPosition = (node: HTMLElement, caretOffset: number) => {
|
|
55
|
+
const range = document.createRange();
|
|
56
|
+
const sel = window.getSelection();
|
|
57
|
+
let charCount = 0;
|
|
37
58
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
const findCaret = (currentNode: Node) => {
|
|
60
|
+
for (const child of Array.from(currentNode.childNodes)) {
|
|
61
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
62
|
+
const textLength = child.textContent?.length || 0;
|
|
63
|
+
if (charCount + textLength >= caretOffset) {
|
|
64
|
+
range.setStart(child, caretOffset - charCount);
|
|
65
|
+
range.collapse(true);
|
|
66
|
+
return true;
|
|
67
|
+
} else {
|
|
68
|
+
charCount += textLength;
|
|
69
|
+
}
|
|
70
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
71
|
+
if (findCaret(child)) return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
41
76
|
|
|
42
|
-
|
|
77
|
+
findCaret(node);
|
|
78
|
+
|
|
79
|
+
if (sel) {
|
|
80
|
+
sel.removeAllRanges();
|
|
81
|
+
sel.addRange(range);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleInputChange = () => {
|
|
86
|
+
if (!inputRef.current) return;
|
|
87
|
+
|
|
88
|
+
const selection = window.getSelection();
|
|
89
|
+
const range = selection?.getRangeAt(0);
|
|
90
|
+
|
|
91
|
+
let newCaretOffset = 0;
|
|
92
|
+
if (range && inputRef.current.contains(range.startContainer)) {
|
|
93
|
+
const preCaretRange = range.cloneRange();
|
|
94
|
+
preCaretRange.selectNodeContents(inputRef.current);
|
|
95
|
+
preCaretRange.setEnd(range.startContainer, range.startOffset);
|
|
96
|
+
newCaretOffset = preCaretRange.toString().length; // Calculate caret offset
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
caretOffsetRef.current = newCaretOffset; // Update ref directly
|
|
100
|
+
|
|
101
|
+
const plainText = inputRef.current.innerText;
|
|
102
|
+
setInputValue(plainText);
|
|
103
|
+
|
|
104
|
+
// Handle mentions and suggestions
|
|
105
|
+
const mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
|
|
43
106
|
if (mentionMatch) {
|
|
44
107
|
const query = mentionMatch[1].toLowerCase();
|
|
45
|
-
const filteredUsers = users.filter((user) =>
|
|
46
|
-
|
|
47
|
-
);
|
|
108
|
+
const filteredUsers = query === "" ? users : users.filter((user) => user.name.toLowerCase().startsWith(query));
|
|
109
|
+
|
|
48
110
|
setSuggestions(filteredUsers);
|
|
49
|
-
setShowSuggestions(
|
|
111
|
+
setShowSuggestions(filteredUsers.length > 0);
|
|
50
112
|
} else {
|
|
51
113
|
setShowSuggestions(false);
|
|
52
114
|
}
|
|
115
|
+
|
|
116
|
+
const previousHTML = inputRef.current.innerHTML;
|
|
117
|
+
const htmlWithHighlights = highlightMentions(plainText);
|
|
118
|
+
if (previousHTML !== htmlWithHighlights) {
|
|
119
|
+
inputRef.current.innerHTML = htmlWithHighlights;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
restoreCaretPosition(inputRef.current, newCaretOffset);
|
|
53
123
|
};
|
|
54
124
|
|
|
55
125
|
const handleSuggestionClick = (user: User) => {
|
|
56
|
-
|
|
57
|
-
|
|
126
|
+
if (!inputRef.current) return;
|
|
127
|
+
|
|
128
|
+
const plainText = inputValue; // Current input value
|
|
129
|
+
const caretOffset = caretOffsetRef.current; // Use ref value for caret offset
|
|
130
|
+
const mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
|
|
131
|
+
|
|
132
|
+
if (!mentionMatch) return;
|
|
133
|
+
|
|
134
|
+
const mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
|
|
135
|
+
|
|
136
|
+
const newValue =
|
|
137
|
+
plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
|
|
138
|
+
|
|
58
139
|
setInputValue(newValue);
|
|
140
|
+
inputRef.current.innerText = newValue;
|
|
141
|
+
|
|
142
|
+
const htmlWithHighlights = highlightMentions(newValue);
|
|
143
|
+
inputRef.current.innerHTML = htmlWithHighlights;
|
|
144
|
+
|
|
59
145
|
setShowSuggestions(false);
|
|
146
|
+
|
|
147
|
+
const mentionEnd = mentionIndex + user.name.length + 1;
|
|
148
|
+
restoreCaretPosition(inputRef.current, mentionEnd);
|
|
60
149
|
};
|
|
61
|
-
|
|
62
150
|
const handleSendMessage = () => {
|
|
63
151
|
if (inputValue.trim() && onSendMessage) {
|
|
64
152
|
onSendMessage(inputValue.trim());
|
|
65
|
-
setInputValue(
|
|
153
|
+
setInputValue("");
|
|
154
|
+
setShowSuggestions(false);
|
|
155
|
+
if (inputRef.current) inputRef.current.innerText = "";
|
|
66
156
|
}
|
|
67
157
|
};
|
|
68
158
|
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
159
|
+
const renderSuggestions = () => {
|
|
160
|
+
if (!showSuggestions || !inputRef.current) return null;
|
|
161
|
+
|
|
162
|
+
const inputRect = inputRef.current.getBoundingClientRect();
|
|
163
|
+
const styles: React.CSSProperties = {
|
|
164
|
+
position: 'absolute',
|
|
165
|
+
zIndex: 1000
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Adjust position based on calculated tooltip position
|
|
169
|
+
switch (tooltipPosition) {
|
|
170
|
+
case 'top':
|
|
171
|
+
styles.left = `${inputRect.left}px`;
|
|
172
|
+
styles.top = `${inputRect.top - 150}px`;
|
|
173
|
+
break;
|
|
174
|
+
case 'bottom':
|
|
175
|
+
styles.left = `${inputRect.left}px`;
|
|
176
|
+
styles.top = `${inputRect.bottom}px`;
|
|
177
|
+
break;
|
|
178
|
+
case 'left':
|
|
179
|
+
styles.left = `${inputRect.left - 150}px`;
|
|
180
|
+
styles.top = `${inputRect.top}px`;
|
|
181
|
+
break;
|
|
182
|
+
case 'right':
|
|
183
|
+
styles.left = `${inputRect.right}px`;
|
|
184
|
+
styles.top = `${inputRect.top}px`;
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
break;
|
|
73
188
|
}
|
|
74
|
-
};
|
|
75
189
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
value={inputValue}
|
|
83
|
-
onChange={handleInputChange}
|
|
84
|
-
onKeyPress={handleKeyPress}
|
|
85
|
-
placeholder={placeholder || 'Type a message...'}
|
|
86
|
-
className={`mention-input ${inputClassName || ''}`}
|
|
87
|
-
/>
|
|
88
|
-
<button
|
|
89
|
-
onClick={handleSendMessage}
|
|
90
|
-
className={`send-button ${sendBtnClassName || ''}`}
|
|
190
|
+
console.log("document.body",document.body);
|
|
191
|
+
return ReactDOM.createPortal(
|
|
192
|
+
<ul
|
|
193
|
+
className={`suggestion-list ${suggestionListClassName || ''}`}
|
|
194
|
+
ref={suggestionListRef}
|
|
195
|
+
style={styles}
|
|
91
196
|
>
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
197
|
+
{suggestions.map((user) => (
|
|
198
|
+
<li
|
|
199
|
+
key={user.id}
|
|
200
|
+
onClick={() => handleSuggestionClick(user)}
|
|
201
|
+
className={`suggestion-item ${suggestionItemClassName || ''}`}
|
|
202
|
+
role="option"
|
|
203
|
+
tabIndex={0}
|
|
204
|
+
aria-selected="false"
|
|
205
|
+
>
|
|
206
|
+
{user.name}
|
|
207
|
+
</li>
|
|
208
|
+
))}
|
|
209
|
+
</ul>,
|
|
210
|
+
document.body // Render in portal
|
|
211
|
+
);
|
|
212
|
+
};
|
|
102
213
|
|
|
214
|
+
return (
|
|
215
|
+
<div className={`mention-container ${containerClassName || ""}`}>
|
|
216
|
+
<div className={`mention-input-container ${inputContainerClassName || ""}`}>
|
|
217
|
+
<div
|
|
218
|
+
ref={inputRef}
|
|
219
|
+
contentEditable
|
|
220
|
+
suppressContentEditableWarning
|
|
221
|
+
className={`mention-input ${inputClassName || ""}`}
|
|
222
|
+
onInput={handleInputChange}
|
|
223
|
+
></div>
|
|
224
|
+
<button
|
|
225
|
+
onClick={handleSendMessage}
|
|
226
|
+
className={`send-button ${sendBtnClassName || ""}`}
|
|
227
|
+
>
|
|
228
|
+
{sendButtonIcon || "➤"}
|
|
229
|
+
</button>
|
|
103
230
|
</div>
|
|
104
|
-
{
|
|
105
|
-
<ul className={`suggestion-list ${suggestionListClassName || ''}`}>
|
|
106
|
-
{suggestions.map((user) => (
|
|
107
|
-
<li
|
|
108
|
-
key={user.id}
|
|
109
|
-
onClick={() => handleSuggestionClick(user)}
|
|
110
|
-
className={`suggestion-item ${suggestionItemClassName || ''}`}
|
|
111
|
-
>
|
|
112
|
-
{user.name}
|
|
113
|
-
</li>
|
|
114
|
-
))}
|
|
115
|
-
</ul>
|
|
116
|
-
)}
|
|
231
|
+
{renderSuggestions()}
|
|
117
232
|
</div>
|
|
118
233
|
);
|
|
119
234
|
};
|
|
120
235
|
|
|
121
|
-
|
|
122
|
-
export default MentionInput
|
|
236
|
+
export default MentionInput;
|