l-min-components 1.7.1302 โ 1.7.1303
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/package.json
CHANGED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
forwardRef,
|
|
6
|
+
useImperativeHandle,
|
|
7
|
+
} from "react";
|
|
8
|
+
|
|
9
|
+
// Main React component for the Emoji Input
|
|
10
|
+
// Manages its own state internally (uncontrolled behavior)
|
|
11
|
+
const EmojiInput = forwardRef(
|
|
12
|
+
(
|
|
13
|
+
{
|
|
14
|
+
containerStyle,
|
|
15
|
+
inputStyle,
|
|
16
|
+
buttonStyle,
|
|
17
|
+
pickerStyle,
|
|
18
|
+
onChange, // Exposed onChange prop
|
|
19
|
+
onPaste, // Exposed onPaste prop
|
|
20
|
+
onFocus, // Exposed onFocus prop
|
|
21
|
+
onBlur, // Exposed onBlur prop
|
|
22
|
+
onKeyDown, // Exposed onKeyDown prop
|
|
23
|
+
onKeyUp, // Exposed onKeyUp prop
|
|
24
|
+
onKeyPress, // Exposed onKeyPress prop
|
|
25
|
+
onEnter, // Exposed onEnter prop for handling single Enter key press
|
|
26
|
+
disabled = false, // Disabled prop
|
|
27
|
+
readOnly = false, // ReadOnly prop
|
|
28
|
+
fontSize = "16px", // Font size prop
|
|
29
|
+
lineHeight = "20px", // Line height prop
|
|
30
|
+
// Removed 'value' prop for uncontrolled behavior
|
|
31
|
+
},
|
|
32
|
+
ref
|
|
33
|
+
) => {
|
|
34
|
+
// Internal state is the source of truth for the input value
|
|
35
|
+
const [internalValue, setInternalValue] = useState("");
|
|
36
|
+
|
|
37
|
+
// State to manage the visibility of the emoji picker
|
|
38
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
39
|
+
// State to manage the position of the emoji picker ('top' or 'bottom')
|
|
40
|
+
const [pickerPosition, setPickerPosition] = useState("bottom"); // Default to bottom (above input)
|
|
41
|
+
// State to manage the emoji search term
|
|
42
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
43
|
+
|
|
44
|
+
// Ref to access the textarea element directly
|
|
45
|
+
const textareaRef = useRef(null);
|
|
46
|
+
// Ref to access the input container element
|
|
47
|
+
const containerRef = useRef(null);
|
|
48
|
+
// Ref to access the emoji picker element
|
|
49
|
+
const emojiPickerRef = useRef(null);
|
|
50
|
+
// Ref to access the emoji toggle button element
|
|
51
|
+
const emojiToggleRef = useRef(null);
|
|
52
|
+
|
|
53
|
+
// Expose imperative methods to the parent component via the ref
|
|
54
|
+
useImperativeHandle(ref, () => ({
|
|
55
|
+
// Method to get the current input value (from internal state)
|
|
56
|
+
getValue: () => internalValue,
|
|
57
|
+
// Method to set the input value (updates internal state and calls onChange)
|
|
58
|
+
setValue: (newValue) => {
|
|
59
|
+
setInternalValue(newValue);
|
|
60
|
+
// Manually trigger onChange with the new value
|
|
61
|
+
if (onChange) {
|
|
62
|
+
const event = { target: { value: newValue } };
|
|
63
|
+
onChange(event);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
// Method to focus the textarea
|
|
67
|
+
focus: () => {
|
|
68
|
+
if (textareaRef.current) {
|
|
69
|
+
textareaRef.current.focus();
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
// Method to clear the input (updates internal state to empty and calls onChange)
|
|
73
|
+
clear: () => {
|
|
74
|
+
setInternalValue("");
|
|
75
|
+
// Manually trigger onChange with empty string
|
|
76
|
+
if (onChange) {
|
|
77
|
+
const event = { target: { value: "" } };
|
|
78
|
+
onChange(event);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
// Method to toggle the emoji picker visibility
|
|
82
|
+
toggleEmojiPicker: () => togglePicker(),
|
|
83
|
+
// Method to show the emoji picker
|
|
84
|
+
showEmojiPicker: () => setShowPicker(true),
|
|
85
|
+
// Method to hide the emoji picker
|
|
86
|
+
hideEmojiPicker: () => setShowPicker(false),
|
|
87
|
+
// You can expose other methods as needed
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
// Expanded list of emojis with names/keywords for search
|
|
91
|
+
const allEmojisWithNames = [
|
|
92
|
+
{ emoji: "๐", name: "Smiling Face with Smiling Eyes" },
|
|
93
|
+
{ emoji: "๐", name: "Face with Tears of Joy" },
|
|
94
|
+
{ emoji: "๐", name: "Smiling Face with Heart-Eyes" },
|
|
95
|
+
{ emoji: "๐ฅฐ", name: "Smiling Face with Hearts" },
|
|
96
|
+
{ emoji: "๐", name: "Face Blowing a Kiss" },
|
|
97
|
+
{ emoji: "๐", name: "Kissing Face" },
|
|
98
|
+
{ emoji: "๐", name: "Kissing Face with Smiling Eyes" },
|
|
99
|
+
{ emoji: "๐", name: "Kissing Face with Closed Eyes" },
|
|
100
|
+
{ emoji: "๐", name: "Face Savoring Food" },
|
|
101
|
+
{ emoji: "๐", name: "Face with Tongue" },
|
|
102
|
+
{ emoji: "๐", name: "Winking Face with Tongue" },
|
|
103
|
+
{ emoji: "๐คช", name: "Zany Face" },
|
|
104
|
+
{ emoji: "๐คจ", name: "Face with Raised Eyebrow" },
|
|
105
|
+
{ emoji: "๐ง", name: "Face with Monocle" },
|
|
106
|
+
{ emoji: "๐ค", name: "Nerd Face" },
|
|
107
|
+
{ emoji: "๐", name: "Smiling Face with Sunglasses" },
|
|
108
|
+
{ emoji: "๐คฉ", name: "Star-Struck" },
|
|
109
|
+
{ emoji: "๐ฅณ", name: "Partying Face" },
|
|
110
|
+
{ emoji: "๐", name: "Smirking Face" },
|
|
111
|
+
{ emoji: "๐", name: "Unamused Face" },
|
|
112
|
+
{ emoji: "๐", name: "Disappointed Face" },
|
|
113
|
+
{ emoji: "๐", name: "Pensive Face" },
|
|
114
|
+
{ emoji: "๐", name: "Worried Face" },
|
|
115
|
+
{ emoji: "๐", name: "Confused Face" },
|
|
116
|
+
{ emoji: "๐", name: "Slightly Frowning Face" },
|
|
117
|
+
{ emoji: "โน๏ธ", name: "Frowning Face" },
|
|
118
|
+
{ emoji: "๐ฃ", name: "Persevering Face" },
|
|
119
|
+
{ emoji: "๐", name: "Confounded Face" },
|
|
120
|
+
{ emoji: "๐ซ", name: "Tired Face" },
|
|
121
|
+
{ emoji: "๐ฉ", name: "Weary Face" },
|
|
122
|
+
{ emoji: "๐ฅบ", name: "Pleading Face" },
|
|
123
|
+
{ emoji: "๐ข", name: "Crying Face" },
|
|
124
|
+
{ emoji: "๐ญ", name: "Loudly Crying Face" },
|
|
125
|
+
{ emoji: "๐ค", name: "Face with Steam From Nose" },
|
|
126
|
+
{ emoji: "๐ ", name: "Angry Face" },
|
|
127
|
+
{ emoji: "๐ก", name: "Pouting Face" },
|
|
128
|
+
{ emoji: "๐คฌ", name: "Face with Symbols on Mouth" },
|
|
129
|
+
{ emoji: "๐", name: "Smiling Face with Horns" },
|
|
130
|
+
{ emoji: "๐ฟ", name: "Angry Face with Horns" },
|
|
131
|
+
{ emoji: "๐", name: "Skull" },
|
|
132
|
+
{ emoji: "๐ป", name: "Ghost" },
|
|
133
|
+
{ emoji: "๐ฝ", name: "Alien" },
|
|
134
|
+
{ emoji: "๐ค", name: "Robot" },
|
|
135
|
+
{ emoji: "๐", name: "Jack-O-Lantern" },
|
|
136
|
+
{ emoji: "๐บ", name: "Grinning Cat" },
|
|
137
|
+
{ emoji: "๐ธ", name: "Grinning Cat with Smiling Eyes" },
|
|
138
|
+
{ emoji: "๐น", name: "Cat with Tears of Joy" },
|
|
139
|
+
{ emoji: "๐ป", name: "Smiling Cat with Heart-Eyes" },
|
|
140
|
+
{ emoji: "๐ผ", name: "Cat with Wry Smile" },
|
|
141
|
+
{ emoji: "๐ฝ", name: "Kissing Cat" },
|
|
142
|
+
{ emoji: "๐", name: "Weary Cat" },
|
|
143
|
+
{ emoji: "๐ฟ", name: "Crying Cat" },
|
|
144
|
+
{ emoji: "๐พ", name: "Pouting Cat" },
|
|
145
|
+
{ emoji: "๐", name: "Thumbs Up" },
|
|
146
|
+
{ emoji: "๐", name: "Thumbs Down" },
|
|
147
|
+
{ emoji: "๐", name: "Clapping Hands" },
|
|
148
|
+
{ emoji: "๐", name: "Raising Hands" },
|
|
149
|
+
{ emoji: "๐คฒ", name: "Palms Up Together" },
|
|
150
|
+
{ emoji: "๐ค", name: "Handshake" },
|
|
151
|
+
{ emoji: "๐", name: "Folded Hands" },
|
|
152
|
+
{ emoji: "๐ช", name: "Flexed Biceps" },
|
|
153
|
+
{ emoji: "๐", name: "Backhand Index Pointing Left" },
|
|
154
|
+
{ emoji: "๐", name: "Backhand Index Pointing Right" },
|
|
155
|
+
{ emoji: "โ๏ธ", name: "Index Pointing Up" },
|
|
156
|
+
{ emoji: "๐", name: "Index Pointing Up" }, // Duplicate, can be refined
|
|
157
|
+
{ emoji: "๐", name: "Index Pointing Down" },
|
|
158
|
+
{ emoji: "โ๏ธ", name: "Victory Hand" },
|
|
159
|
+
{ emoji: "๐ค", name: "Crossed Fingers" },
|
|
160
|
+
{ emoji: "๐", name: "Vulcan Salute" },
|
|
161
|
+
{ emoji: "๐ค", name: "Sign of the Horns" },
|
|
162
|
+
{ emoji: "๐ค", name: "Call Me Hand" },
|
|
163
|
+
{ emoji: "๐๏ธ", name: "Hand with Fingers Splayed" },
|
|
164
|
+
{ emoji: "โ", name: "Raised Hand" },
|
|
165
|
+
{ emoji: "๐", name: "OK Hand" },
|
|
166
|
+
{ emoji: "๐ค", name: "Pinched Fingers" },
|
|
167
|
+
{ emoji: "๐", name: "Waving Hand" },
|
|
168
|
+
{ emoji: "๐ค", name: "Raised Back of Hand" },
|
|
169
|
+
{ emoji: "โค๏ธ", name: "Red Heart" },
|
|
170
|
+
{ emoji: "๐งก", name: "Orange Heart" },
|
|
171
|
+
{ emoji: "๐", name: "Yellow Heart" },
|
|
172
|
+
{ emoji: "๐", name: "Green Heart" },
|
|
173
|
+
{ emoji: "๐", name: "Blue Heart" },
|
|
174
|
+
{ emoji: "๐", name: "Purple Heart" },
|
|
175
|
+
{ emoji: "๐ค", name: "Black Heart" },
|
|
176
|
+
{ emoji: "๐ค", name: "White Heart" },
|
|
177
|
+
{ emoji: "๐ค", name: "Brown Heart" },
|
|
178
|
+
{ emoji: "๐", name: "Broken Heart" },
|
|
179
|
+
{ emoji: "โฃ๏ธ", name: "Heart Exclamation" },
|
|
180
|
+
{ emoji: "๐", name: "Two Hearts" },
|
|
181
|
+
{ emoji: "๐", name: "Revolving Hearts" },
|
|
182
|
+
{ emoji: "๐", name: "Beating Heart" },
|
|
183
|
+
{ emoji: "๐", name: "Growing Heart" },
|
|
184
|
+
{ emoji: "๐", name: "Sparkling Heart" },
|
|
185
|
+
{ emoji: "๐", name: "Heart with Arrow" },
|
|
186
|
+
{ emoji: "๐", name: "Heart with Ribbon" },
|
|
187
|
+
{ emoji: "๐ฏ", name: "Hundred Points" },
|
|
188
|
+
{ emoji: "๐ข", name: "Anger Symbol" },
|
|
189
|
+
{ emoji: "๐ฅ", name: "Collision" },
|
|
190
|
+
{ emoji: "๐ซ", name: "Dizzy" },
|
|
191
|
+
{ emoji: "๐ฆ", name: "Sweat Droplets" },
|
|
192
|
+
{ emoji: "๐จ", name: "Dashing Away" },
|
|
193
|
+
{ emoji: "๐ฉ", name: "Pile of Poo" },
|
|
194
|
+
{ emoji: "๐", name: "Red Apple" },
|
|
195
|
+
{ emoji: "๐", name: "Banana" },
|
|
196
|
+
{ emoji: "๐", name: "Cherries" },
|
|
197
|
+
{ emoji: "๐", name: "Strawberry" },
|
|
198
|
+
{ emoji: "๐", name: "Hamburger" },
|
|
199
|
+
{ emoji: "๐", name: "Pizza" },
|
|
200
|
+
{ emoji: "๐", name: "French Fries" },
|
|
201
|
+
{ emoji: "๐ฆ", name: "Soft Ice Cream" },
|
|
202
|
+
{ emoji: "๐ฉ", name: "Doughnut" },
|
|
203
|
+
{ emoji: "โ", name: "Hot Beverage" },
|
|
204
|
+
{ emoji: "๐ต", name: "Teacup Without Handle" },
|
|
205
|
+
{ emoji: "๐บ", name: "Beer Bottle" },
|
|
206
|
+
{ emoji: "๐ท", name: "Wine Glass" },
|
|
207
|
+
{ emoji: "๐ธ", name: "Cocktail Glass" },
|
|
208
|
+
{ emoji: "๐น", name: "Tropical Drink" },
|
|
209
|
+
{ emoji: "๐พ", name: "Bottle with Popping Cork" },
|
|
210
|
+
{ emoji: "๐", name: "Earth Globe Europe-Africa" },
|
|
211
|
+
{ emoji: "๐", name: "Earth Globe Americas" },
|
|
212
|
+
{ emoji: "๐", name: "Earth Globe Asia-Australia" },
|
|
213
|
+
{ emoji: "๐", name: "Sun with Face" },
|
|
214
|
+
{ emoji: "๐", name: "Full Moon Face" },
|
|
215
|
+
{ emoji: "๐", name: "New Moon Face" },
|
|
216
|
+
{ emoji: "๐", name: "Crescent Moon" },
|
|
217
|
+
{ emoji: "โญ", name: "Star" },
|
|
218
|
+
{ emoji: "๐", name: "Glowing Star" },
|
|
219
|
+
{ emoji: "โจ", name: "Sparkles" },
|
|
220
|
+
{ emoji: "โก", name: "High Voltage" },
|
|
221
|
+
{ emoji: "๐ฅ", name: "Fire" },
|
|
222
|
+
{ emoji: "๐ง", name: "Droplet" },
|
|
223
|
+
{ emoji: "๐", name: "Water Wave" },
|
|
224
|
+
{ emoji: "๐", name: "Rainbow" },
|
|
225
|
+
{ emoji: "โ๏ธ", name: "Cloud" },
|
|
226
|
+
{ emoji: "๐ถ", name: "Dog Face" },
|
|
227
|
+
{ emoji: "๐ฑ", name: "Cat Face" },
|
|
228
|
+
{ emoji: "๐ญ", name: "Mouse Face" },
|
|
229
|
+
{ emoji: "๐น", name: "Hamster Face" },
|
|
230
|
+
{ emoji: "๐ฐ", name: "Rabbit Face" },
|
|
231
|
+
{ emoji: "๐ฆ", name: "Fox Face" },
|
|
232
|
+
{ emoji: "๐ป", name: "Bear" },
|
|
233
|
+
{ emoji: "๐ผ", name: "Panda" },
|
|
234
|
+
{ emoji: "๐จ", name: "Koala" },
|
|
235
|
+
{ emoji: "๐ฏ", name: "Tiger Face" },
|
|
236
|
+
{ emoji: "๐ฆ", name: "Lion" },
|
|
237
|
+
{ emoji: "๐ฎ", name: "Cow Face" },
|
|
238
|
+
{ emoji: "๐ท", name: "Pig Face" },
|
|
239
|
+
{ emoji: "๐ธ", name: "Frog" },
|
|
240
|
+
{ emoji: "๐ต", name: "Monkey Face" },
|
|
241
|
+
{ emoji: "๐", name: "Chicken" },
|
|
242
|
+
{ emoji: "โฝ", name: "Soccer Ball" },
|
|
243
|
+
{ emoji: "๐", name: "Basketball" },
|
|
244
|
+
{ emoji: "๐", name: "American Football" },
|
|
245
|
+
{ emoji: "โพ", name: "Baseball" },
|
|
246
|
+
{ emoji: "๐พ", name: "Tennis Racquet and Ball" },
|
|
247
|
+
{ emoji: "๐", name: "Volleyball" },
|
|
248
|
+
{ emoji: "๐", name: "Rugby Football" },
|
|
249
|
+
{ emoji: "๐ฑ", name: "Pool 8 Ball" },
|
|
250
|
+
{ emoji: "๐", name: "Table Tennis Paddle and Ball" },
|
|
251
|
+
{ emoji: "๐ธ", name: "Badminton Racquet and Shuttlecock" },
|
|
252
|
+
{ emoji: "๐", name: "Ice Hockey Stick and Puck" },
|
|
253
|
+
{ emoji: "๐ฅ
", name: "Goal Net" },
|
|
254
|
+
{ emoji: "โณ", name: "Flag in Hole" },
|
|
255
|
+
{ emoji: "๐น", name: "Bow and Arrow" },
|
|
256
|
+
{ emoji: "๐ฃ", name: "Fishing Pole" },
|
|
257
|
+
{ emoji: "๐ฅ", name: "Boxing Glove" },
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
// Filter emojis based on the search term (case-insensitive)
|
|
261
|
+
const filteredEmojis = allEmojisWithNames.filter((item) => {
|
|
262
|
+
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
|
263
|
+
// Check if the search term is included in the emoji's name
|
|
264
|
+
return (
|
|
265
|
+
searchTerm === "" ||
|
|
266
|
+
item.name.toLowerCase().includes(lowerCaseSearchTerm)
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Function to insert an emoji at the current cursor position
|
|
271
|
+
const insertEmoji = (emoji) => {
|
|
272
|
+
const textarea = textareaRef.current;
|
|
273
|
+
if (!textarea) return; // Ensure textarea is available
|
|
274
|
+
|
|
275
|
+
const cursorPosition = textarea.selectionStart;
|
|
276
|
+
const textBefore = internalValue.substring(0, cursorPosition);
|
|
277
|
+
const textAfter = internalValue.substring(cursorPosition);
|
|
278
|
+
|
|
279
|
+
// Calculate the new value
|
|
280
|
+
const newValue = textBefore + emoji + textAfter;
|
|
281
|
+
|
|
282
|
+
// Update internal state
|
|
283
|
+
setInternalValue(newValue);
|
|
284
|
+
|
|
285
|
+
// Manually trigger onChange with the new value
|
|
286
|
+
if (onChange) {
|
|
287
|
+
const event = { target: { value: newValue } };
|
|
288
|
+
onChange(event);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Move cursor to after the inserted emoji
|
|
292
|
+
const newCursorPosition = cursorPosition + emoji.length;
|
|
293
|
+
|
|
294
|
+
// Use a timeout to ensure the state updates before setting selection
|
|
295
|
+
// This is a common pattern in React for DOM manipulations after state updates
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
textarea.selectionStart = newCursorPosition;
|
|
298
|
+
textarea.selectionEnd = newCursorPosition;
|
|
299
|
+
textarea.focus(); // Focus the textarea after inserting
|
|
300
|
+
}, 0);
|
|
301
|
+
|
|
302
|
+
// Optionally hide the picker after selection
|
|
303
|
+
// setShowPicker(false);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Effect to handle clicks outside the emoji picker to close it
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
const handleClickOutside = (event) => {
|
|
309
|
+
// Check if the click was outside the picker and not on the toggle button or the picker itself
|
|
310
|
+
if (
|
|
311
|
+
emojiPickerRef.current &&
|
|
312
|
+
!emojiPickerRef.current.contains(event.target) &&
|
|
313
|
+
event.target !== emojiToggleRef.current &&
|
|
314
|
+
!emojiToggleRef.current.contains(event.target)
|
|
315
|
+
) {
|
|
316
|
+
setShowPicker(false);
|
|
317
|
+
setSearchTerm(""); // Clear search term when closing
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Add the event listener
|
|
322
|
+
document.addEventListener("click", handleClickOutside);
|
|
323
|
+
|
|
324
|
+
// Cleanup the event listener on component unmount
|
|
325
|
+
return () => {
|
|
326
|
+
document.removeEventListener("click", handleClickOutside);
|
|
327
|
+
};
|
|
328
|
+
}, [showPicker]); // Re-run effect if showPicker changes
|
|
329
|
+
|
|
330
|
+
// Function to determine picker position based on available space
|
|
331
|
+
const determinePickerPosition = () => {
|
|
332
|
+
const container = containerRef.current;
|
|
333
|
+
if (!container) return "bottom"; // Default to bottom if container not found
|
|
334
|
+
|
|
335
|
+
const rect = container.getBoundingClientRect();
|
|
336
|
+
const viewportHeight = window.innerHeight;
|
|
337
|
+
|
|
338
|
+
// Calculate space above and below the input container
|
|
339
|
+
const spaceAbove = rect.top;
|
|
340
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
341
|
+
|
|
342
|
+
// Assume picker needs at least 200px height (based on max-height)
|
|
343
|
+
const requiredSpace = 200; // Match max-height of picker
|
|
344
|
+
|
|
345
|
+
// If there's enough space below, position below. Otherwise, position above.
|
|
346
|
+
// Prioritize below if space is sufficient, otherwise check above.
|
|
347
|
+
if (spaceBelow >= requiredSpace) {
|
|
348
|
+
return "top"; // Position below the input (top: 100%)
|
|
349
|
+
} else if (spaceAbove >= requiredSpace) {
|
|
350
|
+
return "bottom"; // Position above the input (bottom: 100%)
|
|
351
|
+
} else {
|
|
352
|
+
// If neither has enough space, position where there is more space
|
|
353
|
+
return spaceBelow > spaceAbove ? "top" : "bottom";
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// Toggle emoji picker visibility and determine position
|
|
358
|
+
const togglePicker = () => {
|
|
359
|
+
if (!showPicker) {
|
|
360
|
+
// If about to show, determine the best position
|
|
361
|
+
setPickerPosition(determinePickerPosition());
|
|
362
|
+
} else {
|
|
363
|
+
setSearchTerm(""); // Clear search term when hiding
|
|
364
|
+
}
|
|
365
|
+
setShowPicker(!showPicker);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
// Apply containerStyle prop
|
|
370
|
+
<div
|
|
371
|
+
className="input-container"
|
|
372
|
+
style={containerStyle}
|
|
373
|
+
ref={containerRef}>
|
|
374
|
+
{/* Textarea for input */}
|
|
375
|
+
{/* Apply inputStyle prop */}
|
|
376
|
+
<textarea
|
|
377
|
+
ref={textareaRef} // Attach ref to textarea
|
|
378
|
+
className="text-input"
|
|
379
|
+
rows="1" // Start with 1 row, max-height will control size
|
|
380
|
+
placeholder="Start typing..." // Updated placeholder text
|
|
381
|
+
// Use internal state as the source of truth
|
|
382
|
+
value={internalValue}
|
|
383
|
+
onChange={(e) => {
|
|
384
|
+
// Update internal state
|
|
385
|
+
setInternalValue(e.target.value);
|
|
386
|
+
// Always call the passed-in onChange handler if it exists
|
|
387
|
+
if (onChange) {
|
|
388
|
+
onChange(e);
|
|
389
|
+
}
|
|
390
|
+
}}
|
|
391
|
+
onPaste={(e) => {
|
|
392
|
+
if (onPaste) {
|
|
393
|
+
onPaste(e); // Call the passed-in onPaste handler
|
|
394
|
+
}
|
|
395
|
+
// onChange handler will be triggered by paste, updating internal state
|
|
396
|
+
}}
|
|
397
|
+
onFocus={(e) => {
|
|
398
|
+
// Added onFocus handler
|
|
399
|
+
if (onFocus) {
|
|
400
|
+
onFocus(e);
|
|
401
|
+
}
|
|
402
|
+
}}
|
|
403
|
+
onBlur={(e) => {
|
|
404
|
+
// Added onBlur handler
|
|
405
|
+
if (onBlur) {
|
|
406
|
+
onBlur(e);
|
|
407
|
+
}
|
|
408
|
+
}}
|
|
409
|
+
onKeyDown={(e) => {
|
|
410
|
+
// Added onKeyDown handler
|
|
411
|
+
// Check for Enter key press
|
|
412
|
+
if (e.key === "Enter") {
|
|
413
|
+
// If Shift is NOT pressed, prevent default and call onEnter
|
|
414
|
+
if (!e.shiftKey) {
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
if (onEnter) {
|
|
417
|
+
onEnter(internalValue); // Pass current internal value to onEnter
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// If Shift IS pressed, allow default (new line)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Always call the passed-in onKeyDown handler if it exists
|
|
424
|
+
if (onKeyDown) {
|
|
425
|
+
onKeyDown(e);
|
|
426
|
+
}
|
|
427
|
+
}}
|
|
428
|
+
onKeyUp={(e) => {
|
|
429
|
+
// Added onKeyUp handler
|
|
430
|
+
if (onKeyUp) {
|
|
431
|
+
onKeyUp(e);
|
|
432
|
+
}
|
|
433
|
+
}}
|
|
434
|
+
onKeyPress={(e) => {
|
|
435
|
+
// Added onKeyPress handler
|
|
436
|
+
if (onKeyPress) {
|
|
437
|
+
onKeyPress(e);
|
|
438
|
+
}
|
|
439
|
+
}}
|
|
440
|
+
disabled={disabled} // Apply disabled prop
|
|
441
|
+
readOnly={readOnly} // Apply readOnly prop
|
|
442
|
+
style={{
|
|
443
|
+
maxHeight: "52px",
|
|
444
|
+
overflowY: "auto",
|
|
445
|
+
fontSize: fontSize, // Apply fontSize prop
|
|
446
|
+
lineHeight: lineHeight, // Apply lineHeight prop
|
|
447
|
+
...inputStyle, // Apply custom inputStyle prop
|
|
448
|
+
}} // Set max height and overflow, apply inputStyle
|
|
449
|
+
></textarea>
|
|
450
|
+
|
|
451
|
+
{/* Button to toggle the emoji picker */}
|
|
452
|
+
{/* Apply buttonStyle prop */}
|
|
453
|
+
<button
|
|
454
|
+
ref={emojiToggleRef} // Attach ref to toggle button
|
|
455
|
+
className="emoji-toggle-button"
|
|
456
|
+
onClick={togglePicker} // Use the new togglePicker function
|
|
457
|
+
// Disable button if input is disabled or readOnly
|
|
458
|
+
disabled={disabled || readOnly}
|
|
459
|
+
style={buttonStyle} // Apply buttonStyle
|
|
460
|
+
>
|
|
461
|
+
{/* SVG Icon */}
|
|
462
|
+
<svg
|
|
463
|
+
width="24"
|
|
464
|
+
height="25"
|
|
465
|
+
viewBox="0 0 24 25"
|
|
466
|
+
fill="none"
|
|
467
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
468
|
+
<path
|
|
469
|
+
d="M19.0668 5.51844C23.0368 9.48844 22.9668 15.9684 18.8668 19.8584C15.0768 23.4484 8.92679 23.4484 5.12679 19.8584C1.01679 15.9684 0.946776 9.48844 4.92678 5.51844C8.82678 1.60844 15.1668 1.60844 19.0668 5.51844Z"
|
|
470
|
+
stroke="currentColor"
|
|
471
|
+
strokeWidth="1.5"
|
|
472
|
+
strokeLinecap="round"
|
|
473
|
+
strokeLinejoin="round"
|
|
474
|
+
/>
|
|
475
|
+
<path
|
|
476
|
+
d="M15.834 16.6367C13.714 18.6367 10.2741 18.6367 8.16406 16.6367"
|
|
477
|
+
stroke="currentColor"
|
|
478
|
+
strokeWidth="1.5"
|
|
479
|
+
strokeLinecap="round"
|
|
480
|
+
strokeLinejoin="round"
|
|
481
|
+
/>
|
|
482
|
+
<path
|
|
483
|
+
d="M8.66638 9.55859H8.67536"
|
|
484
|
+
stroke="currentColor"
|
|
485
|
+
strokeWidth="2.5"
|
|
486
|
+
strokeLinecap="round"
|
|
487
|
+
strokeLinejoin="round"
|
|
488
|
+
/>
|
|
489
|
+
<path
|
|
490
|
+
d="M15.8383 9.55859H15.8472"
|
|
491
|
+
stroke="currentColor"
|
|
492
|
+
strokeWidth="2.5"
|
|
493
|
+
strokeLinecap="round"
|
|
494
|
+
strokeLinejoin="round"
|
|
495
|
+
/>
|
|
496
|
+
</svg>
|
|
497
|
+
</button>
|
|
498
|
+
|
|
499
|
+
{/* Emoji picker container, shown conditionally */}
|
|
500
|
+
{showPicker && (
|
|
501
|
+
// Apply pickerStyle prop and dynamic position class
|
|
502
|
+
<div
|
|
503
|
+
ref={emojiPickerRef} // Attach ref to picker container
|
|
504
|
+
className={`emoji-picker-container show ${pickerPosition}`} // Add pickerPosition class
|
|
505
|
+
style={pickerStyle} // Apply pickerStyle
|
|
506
|
+
>
|
|
507
|
+
{/* Emoji Search Input */}
|
|
508
|
+
<input
|
|
509
|
+
type="text"
|
|
510
|
+
placeholder="Search emojis..."
|
|
511
|
+
className="emoji-search-input"
|
|
512
|
+
value={searchTerm}
|
|
513
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
514
|
+
/>
|
|
515
|
+
|
|
516
|
+
{/* Emoji Grid */}
|
|
517
|
+
<div className="emoji-grid">
|
|
518
|
+
{/* Map over filtered emojis and create buttons */}
|
|
519
|
+
{filteredEmojis.map((item, index) => (
|
|
520
|
+
<button
|
|
521
|
+
key={index} // Use index as key (safe for static list)
|
|
522
|
+
className="emoji-button"
|
|
523
|
+
onClick={() => insertEmoji(item.emoji)} // Insert emoji on click
|
|
524
|
+
title={item.name} // Add title for accessibility
|
|
525
|
+
>
|
|
526
|
+
{item.emoji}
|
|
527
|
+
</button>
|
|
528
|
+
))}
|
|
529
|
+
{filteredEmojis.length === 0 && (
|
|
530
|
+
<p className="no-results">No emojis found.</p>
|
|
531
|
+
)}
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
)}
|
|
535
|
+
{/* Plain CSS styles */}
|
|
536
|
+
<style>{`
|
|
537
|
+
.input-container {
|
|
538
|
+
position: relative;
|
|
539
|
+
width: 100%;
|
|
540
|
+
max-width: 400px; /* Adjusted max-width slightly for a common input size */
|
|
541
|
+
margin: 20px auto;
|
|
542
|
+
/* Updated default border color */
|
|
543
|
+
border: 1px solid rgba(229, 229, 234, 1);
|
|
544
|
+
border-radius: 0.375rem; /* Default rounded-md */
|
|
545
|
+
/* Removed overflow: hidden; */
|
|
546
|
+
/* Default background, can be overridden by prop */
|
|
547
|
+
background-color: #ffffff;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.text-input {
|
|
551
|
+
display: block; /* Make it a block element */
|
|
552
|
+
width: 100%;
|
|
553
|
+
padding: 8px; /* p-2 */
|
|
554
|
+
padding-right: 40px; /* Adjust padding to make space for the button */
|
|
555
|
+
border: none; /* Removed border */
|
|
556
|
+
outline: none; /* Removed default outline */
|
|
557
|
+
resize: none; /* Disable resize handle */
|
|
558
|
+
max-height: 52px; /* Set the maximum height */
|
|
559
|
+
overflow-y: auto; /* Add scrollbar if content exceeds max-height */
|
|
560
|
+
font-family: sans-serif; /* Use a common sans-serif font */
|
|
561
|
+
box-sizing: border-box; /* Include padding and border in element's total width and height */
|
|
562
|
+
/* Default background, can be overridden by prop */
|
|
563
|
+
background-color: transparent; /* Make textarea background transparent to show container background */
|
|
564
|
+
color: #374151; /* Set text color to a shade of gray */
|
|
565
|
+
/* Removed hardcoded font-size and line-height here */
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.text-input:disabled,
|
|
569
|
+
.text-input[readOnly] {
|
|
570
|
+
background-color: #f3f4f6; /* gray-100 */
|
|
571
|
+
cursor: not-allowed;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
.emoji-toggle-button {
|
|
576
|
+
position: absolute;
|
|
577
|
+
top: 50%; /* Center vertically */
|
|
578
|
+
right: 8px; /* Position from the right */
|
|
579
|
+
transform: translateY(-50%); /* Adjust for vertical centering */
|
|
580
|
+
display: inline-flex;
|
|
581
|
+
align-items: center;
|
|
582
|
+
justify-content: center;
|
|
583
|
+
padding: 4px; /* p-1 */
|
|
584
|
+
color: #6b7280; /* gray-500 */
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
background: none; /* No background */
|
|
587
|
+
border: none; /* No border */
|
|
588
|
+
transition: color 0.2s ease-in-out;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.emoji-toggle-button:hover:not(:disabled) {
|
|
592
|
+
color: #4b5563; /* gray-700 */
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.emoji-toggle-button:disabled {
|
|
596
|
+
cursor: not-allowed;
|
|
597
|
+
opacity: 0.6;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
.emoji-toggle-button svg {
|
|
602
|
+
stroke: currentColor; /* Make SVG stroke color match text color */
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.emoji-picker-container {
|
|
606
|
+
display: none; /* Hidden by default */
|
|
607
|
+
position: absolute;
|
|
608
|
+
left: 0;
|
|
609
|
+
right: 0;
|
|
610
|
+
z-index: 10;
|
|
611
|
+
max-height: 192px; /* max-h-48 */
|
|
612
|
+
overflow-y: hidden; /* Hide overflow for the container itself */
|
|
613
|
+
background-color: #ffffff; /* bg-white */
|
|
614
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* shadow-md */
|
|
615
|
+
border: 1px solid #e5e7eb; /* gray-200 */
|
|
616
|
+
border-radius: 0.375rem; /* rounded-md */
|
|
617
|
+
padding: 8px; /* p-2 */
|
|
618
|
+
flex-direction: column; /* Stack search and grid vertically */
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.emoji-picker-container.show {
|
|
622
|
+
display: flex; /* Show as flex container when 'show' class is present */
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.emoji-search-input {
|
|
626
|
+
width: 100%;
|
|
627
|
+
padding: 8px;
|
|
628
|
+
margin-bottom: 8px; /* Space below search input */
|
|
629
|
+
border: 1px solid #d1d5db;
|
|
630
|
+
border-radius: 0.25rem;
|
|
631
|
+
outline: none;
|
|
632
|
+
box-sizing: border-box;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.emoji-grid {
|
|
636
|
+
display: grid; /* Use grid for layout */
|
|
637
|
+
grid-template-columns: repeat(auto-fill, minmax(30px, 1fr)); /* Responsive columns */
|
|
638
|
+
gap: 4px; /* gap-1 */
|
|
639
|
+
overflow-y: auto; /* Add scrollbar to the grid */
|
|
640
|
+
max-height: calc(100% - 40px); /* Calculate remaining height for grid (adjust 40px based on search input height + margin) */
|
|
641
|
+
padding-right: 8px; /* Add padding for scrollbar */
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
/* Positioning classes */
|
|
646
|
+
.emoji-picker-container.bottom {
|
|
647
|
+
bottom: 100%; /* Position above the input */
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.emoji-picker-container.top {
|
|
651
|
+
top: 100%; /* Position below the input */
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
.emoji-button {
|
|
656
|
+
cursor: pointer;
|
|
657
|
+
font-size: 1.25rem; /* text-xl */
|
|
658
|
+
text-align: center;
|
|
659
|
+
padding: 4px; /* p-1 */
|
|
660
|
+
border-radius: 0.25rem; /* rounded */
|
|
661
|
+
background: none; /* No background */
|
|
662
|
+
border: none; /* No border */
|
|
663
|
+
transition: background-color 0.2s ease-in-out;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.emoji-button:hover {
|
|
667
|
+
background-color: #f3f4f6; /* gray-100 */
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.no-results {
|
|
671
|
+
text-align: center;
|
|
672
|
+
color: #6b7280; /* gray-500 */
|
|
673
|
+
grid-column: 1 / -1; /* Span across all columns in the grid */
|
|
674
|
+
}
|
|
675
|
+
`}</style>
|
|
676
|
+
</div>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
export default EmojiInput; // Export the component with the new name
|
package/src/components/index.js
CHANGED
|
@@ -56,3 +56,4 @@ export { default as DeveloperBanner } from "./banner/developerBanner";
|
|
|
56
56
|
export { default as VideoPlayer } from "./video-player";
|
|
57
57
|
export { default as useAudioPlayer } from "./useAudioPlayer";
|
|
58
58
|
export { default as AudioWaveComponent } from "./useAudioPlayer/audioWave";
|
|
59
|
+
export { default as InputEmoji } from "./InputEmoji";
|