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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "l-min-components",
3
- "version": "1.7.1302",
3
+ "version": "1.7.1303",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/assets",
@@ -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
@@ -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";