react-mention-input 1.1.14 → 1.1.16

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.
@@ -16,6 +16,8 @@ interface MentionInputProps {
16
16
  sendBtnClassName?: string;
17
17
  suggestionListClassName?: string;
18
18
  suggestionItemClassName?: string;
19
+ attachedImageContainerClassName?: string;
20
+ attachedImageContainerStyle?: React.CSSProperties;
19
21
  imgClassName?: string; // New prop for image CSS class
20
22
  imgStyle?: React.CSSProperties; // New prop for inline image styles
21
23
  sendButtonIcon?: ReactNode; // Button icon (MUI icon or image path)
@@ -25,6 +27,7 @@ interface MentionInputProps {
25
27
  messageHTML: string;
26
28
  userSelectListWithIds: { id: number; name: string }[];
27
29
  userSelectListName: string[];
30
+ tags: string[];
28
31
  images?: File[];
29
32
  imageUrl?: string | null;
30
33
  }) => void;
@@ -43,6 +46,8 @@ const MentionInput: React.FC<MentionInputProps> = ({
43
46
  suggestionItemClassName,
44
47
  imgClassName,
45
48
  imgStyle,
49
+ attachedImageContainerClassName,
50
+ attachedImageContainerStyle,
46
51
  sendButtonIcon,
47
52
  attachmentButtonIcon,
48
53
  onSendMessage,
@@ -64,17 +69,29 @@ const MentionInput: React.FC<MentionInputProps> = ({
64
69
  const caretOffsetRef = useRef<number>(0);
65
70
  const userSelectListRef = useRef<string[]>([]); // Only unique names
66
71
  const userSelectListWithIdsRef = useRef<{ id: number; name: string }[]>([]); // Unique IDs with names
72
+ const tagsListRef = useRef<string[]>([]); // Store hashtags
67
73
  const fileInputRef = useRef<HTMLInputElement>(null);
68
74
 
69
75
  const highlightMentionsAndLinks = (text: string): string => {
70
76
  // Regular expression for detecting links
71
77
  const linkRegex = /(https?:\/\/[^\s]+)/g;
78
+
79
+ // Regular expression for detecting hashtags
80
+ const hashtagRegex = /#[\w]+/g;
72
81
 
73
82
  // Highlight links
74
83
  let highlightedText = text.replace(
75
84
  linkRegex,
76
85
  '<a href="$1" target="_blank" rel="noopener noreferrer" class="link-highlight">$1</a>'
77
86
  );
87
+
88
+ // Highlight hashtags
89
+ highlightedText = highlightedText.replace(
90
+ hashtagRegex,
91
+ (match) => {
92
+ return `<span class="hashtag-highlight">${match}</span>`;
93
+ }
94
+ );
78
95
 
79
96
  // Highlight mentions manually based on `userSelectListRef`
80
97
  userSelectListRef?.current.forEach((userName) => {
@@ -156,8 +173,17 @@ const MentionInput: React.FC<MentionInputProps> = ({
156
173
  setShowSuggestions(false);
157
174
  }
158
175
 
159
- // Only apply highlighting if we have mentions or links to highlight
160
- if (userSelectListRef.current.length > 0 || plainText.match(/(https?:\/\/[^\s]+)/g)) {
176
+ // Extract and store hashtags
177
+ const hashtagMatches = plainText.match(/#[\w]+/g);
178
+ if (hashtagMatches) {
179
+ const uniqueTags = Array.from(new Set(hashtagMatches));
180
+ tagsListRef.current = uniqueTags;
181
+ } else {
182
+ tagsListRef.current = [];
183
+ }
184
+
185
+ // Only apply highlighting if we have mentions, hashtags, or links to highlight
186
+ if (userSelectListRef.current.length > 0 || plainText.match(/(https?:\/\/[^\s]+)/g) || plainText.match(/#[\w]+/g)) {
161
187
  const currentHTML = inputRef.current.innerHTML;
162
188
  const htmlWithHighlights = highlightMentionsAndLinks(plainText);
163
189
 
@@ -369,6 +395,7 @@ const MentionInput: React.FC<MentionInputProps> = ({
369
395
  messageHTML,
370
396
  userSelectListWithIds: userSelectListWithIdsRef.current,
371
397
  userSelectListName: userSelectListRef.current,
398
+ tags: tagsListRef.current,
372
399
  images: selectedImage ? [selectedImage] : [],
373
400
  imageUrl: imageUrl
374
401
  });
@@ -379,6 +406,7 @@ const MentionInput: React.FC<MentionInputProps> = ({
379
406
  setImageUrl(null);
380
407
  userSelectListRef.current = [];
381
408
  userSelectListWithIdsRef.current = [];
409
+ tagsListRef.current = [];
382
410
  }
383
411
  }
384
412
  };
@@ -390,12 +418,10 @@ const MentionInput: React.FC<MentionInputProps> = ({
390
418
  }
391
419
  };
392
420
 
393
- console.log(inputValue, inputRef.current?.innerText.trim(), "inputValue====")
394
-
395
421
  return (
396
422
  <div className={`mention-container ${containerClassName || ""}`}>
397
423
  {imageUrl && selectedImage && (
398
- <div className="image-preview-card">
424
+ <div className={`image-preview-card ${attachedImageContainerClassName || ""}`} style={attachedImageContainerStyle}>
399
425
  <img
400
426
  src={imageUrl}
401
427
  alt="Preview"
@@ -21,9 +21,25 @@
21
21
  .message-card-header {
22
22
  display: flex;
23
23
  align-items: center;
24
+ justify-content: space-between;
24
25
  margin-bottom: 8px; /* Space between header and body */
25
26
  }
26
27
 
28
+ .message-card-header-left {
29
+ display: flex;
30
+ align-items: center;
31
+ }
32
+
33
+ .message-card-item-name {
34
+ font-size: 14px;
35
+ color: #666;
36
+ font-weight: 500;
37
+ background-color: #f5f5f5;
38
+ padding: 4px 8px;
39
+ border-radius: 6px;
40
+ border: 1px solid #e0e0e0;
41
+ }
42
+
27
43
  .message-card-img,
28
44
  .message-card-initials {
29
45
  width: 48px;
@@ -72,4 +88,40 @@
72
88
  color: #007bff;
73
89
  padding: 2px 4px;
74
90
  border-radius: 4px;
91
+ }
92
+
93
+ .hashtag-highlight {
94
+ background-color: rgba(255, 165, 0, 0.15);
95
+ color: #FF8C00;
96
+ padding: 2px 4px;
97
+ border-radius: 4px;
98
+ font-weight: 500;
99
+ }
100
+
101
+ /* Tag chips styling */
102
+ .message-card-tags {
103
+ display: flex;
104
+ flex-wrap: wrap;
105
+ gap: 8px;
106
+ margin-top: 12px;
107
+ }
108
+
109
+ .tag-chip {
110
+ padding: 6px 12px;
111
+ border-radius: 16px;
112
+ font-size: 12px;
113
+ font-weight: 500;
114
+ border: none;
115
+ display: inline-block;
116
+ white-space: nowrap;
117
+ }
118
+
119
+ .hashtag-chip {
120
+ background-color: #D4A574;
121
+ color: #FFFFFF;
122
+ }
123
+
124
+ .mention-chip {
125
+ background-color: #2684FF;
126
+ color: #FFFFFF;
75
127
  }
@@ -12,6 +12,7 @@ interface ShowMessageCardProps {
12
12
  commentKey?: string; // Custom key for comment
13
13
  imgSrcKey?: string; // Custom key for image source
14
14
  imageUrlKey?: string; // Custom key for attached image URL
15
+ itemNameKey?: string; // Custom key for item identifier (top-right)
15
16
  containerClassName?: string; // Class for the outermost container
16
17
  containerStyle?: CSSProperties; // Style for the outermost container
17
18
  cardClassName?: string; // Class for the card
@@ -32,6 +33,10 @@ interface ShowMessageCardProps {
32
33
  commentStyle?: CSSProperties; // Style for the comment text
33
34
  attachedImageClassName?: string; // Class for the attached image
34
35
  attachedImageStyle?: CSSProperties; // Style for the attached image
36
+ attachedImageContainerClassName?: string; // Class for the attached image container
37
+ attachedImageContainerStyle?: CSSProperties; // Style for the attached image container
38
+ itemNameClassName?: string; // Class for the item name (top-right)
39
+ itemNameStyle?: CSSProperties; // Style for the item name (top-right)
35
40
  renderItem?: (element: MessageCardProps) => ReactNode; // Custom render function
36
41
  }
37
42
 
@@ -42,6 +47,7 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
42
47
  commentKey = "comment",
43
48
  imgSrcKey = "imgSrc",
44
49
  imageUrlKey = "imageUrl", // Default key for attached image
50
+ itemNameKey = "name", // Default key for item identifier
45
51
  containerClassName,
46
52
  containerStyle,
47
53
  cardClassName,
@@ -62,6 +68,10 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
62
68
  commentStyle,
63
69
  attachedImageClassName,
64
70
  attachedImageStyle,
71
+ attachedImageContainerClassName,
72
+ attachedImageContainerStyle,
73
+ itemNameClassName,
74
+ itemNameStyle,
65
75
  renderItem, // Custom render function
66
76
  }) => {
67
77
  // State to manage initials for images that fail to load
@@ -87,6 +97,18 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
87
97
  return initials;
88
98
  };
89
99
 
100
+ // Helper function to extract hashtags and mentions from text
101
+ const extractTagsAndMentions = (text: string) => {
102
+ const plainText = text.replace(/<[^>]*>/g, ''); // Remove HTML tags to get plain text
103
+ const hashtags = plainText.match(/#[\w]+/g) || [];
104
+ const mentions = plainText.match(/@[\w\s-]+/g) || [];
105
+
106
+ return {
107
+ hashtags: Array.from(new Set(hashtags)), // Remove duplicates
108
+ mentions: Array.from(new Set(mentions.map(mention => mention.trim()))) // Remove duplicates and trim
109
+ };
110
+ };
111
+
90
112
  return (
91
113
  <div
92
114
  className={`message-card-container ${containerClassName || ""}`}
@@ -103,6 +125,9 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
103
125
  }
104
126
 
105
127
  const showInitials = initialsState[item.id || index] || !item[imgSrcKey]; // Decide whether to show initials
128
+
129
+ // Extract tags and mentions from the comment
130
+ const { hashtags, mentions } = extractTagsAndMentions(item[commentKey] || '');
106
131
 
107
132
  return (
108
133
  <div
@@ -114,39 +139,51 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
114
139
  className={`message-card-header ${headerClassName || ""}`}
115
140
  style={headerStyle}
116
141
  >
117
- {showInitials ? (
142
+ <div className="message-card-header-left">
143
+ {showInitials ? (
144
+ <div
145
+ className={`message-card-initials ${imgClassName || ""}`}
146
+ style={imgStyle}
147
+ >
148
+ {getInitials(item[nameKey])}
149
+ </div>
150
+ ) : (
151
+ <img
152
+ src={item[imgSrcKey]}
153
+ alt={item[nameKey]}
154
+ className={`message-card-img ${imgClassName || ""}`}
155
+ style={imgStyle}
156
+ onError={() => handleImageError(item.id || index)} // Pass card id or index
157
+ />
158
+ )}
118
159
  <div
119
- className={`message-card-initials ${imgClassName || ""}`}
120
- style={imgStyle}
160
+ className={`message-card-info ${infoClassName || ""}`}
161
+ style={infoStyle}
121
162
  >
122
- {getInitials(item[nameKey])}
163
+ <h3
164
+ className={`message-card-name ${nameClassName || ""}`}
165
+ style={nameStyle}
166
+ >
167
+ {item[nameKey]}
168
+ </h3>
169
+ <p
170
+ className={`message-card-date ${dateClassName || ""}`}
171
+ style={dateStyle}
172
+ >
173
+ {item[dateKey]}
174
+ </p>
123
175
  </div>
124
- ) : (
125
- <img
126
- src={item[imgSrcKey]}
127
- alt={item[nameKey]}
128
- className={`message-card-img ${imgClassName || ""}`}
129
- style={imgStyle}
130
- onError={() => handleImageError(item.id || index)} // Pass card id or index
131
- />
132
- )}
133
- <div
134
- className={`message-card-info ${infoClassName || ""}`}
135
- style={infoStyle}
136
- >
137
- <h3
138
- className={`message-card-name ${nameClassName || ""}`}
139
- style={nameStyle}
140
- >
141
- {item[nameKey]}
142
- </h3>
143
- <p
144
- className={`message-card-date ${dateClassName || ""}`}
145
- style={dateStyle}
146
- >
147
- {item[dateKey]}
148
- </p>
149
176
  </div>
177
+
178
+ {/* Item identifier in top-right corner */}
179
+ {item[itemNameKey] && (
180
+ <div
181
+ className={`message-card-item-name ${itemNameClassName || ""}`}
182
+ style={itemNameStyle}
183
+ >
184
+ {item[itemNameKey]}
185
+ </div>
186
+ )}
150
187
  </div>
151
188
  <div
152
189
  className={`message-card-body ${bodyClassName || ""}`}
@@ -160,7 +197,10 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
160
197
 
161
198
  {/* Display attached image if available */}
162
199
  {item?.[imageUrlKey] && (
163
- <div className="message-card-attached-image-container">
200
+ <div
201
+ className={`message-card-attached-image-container ${attachedImageContainerClassName || ""}`}
202
+ style={attachedImageContainerStyle}
203
+ >
164
204
  <img
165
205
  src={item[imageUrlKey]}
166
206
  alt="Attached"
@@ -169,6 +209,22 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
169
209
  />
170
210
  </div>
171
211
  )}
212
+
213
+ {/* Display hashtags and mentions as chips */}
214
+ {(hashtags.length > 0 || mentions.length > 0) && (
215
+ <div className="message-card-tags">
216
+ {hashtags.map((tag, tagIndex) => (
217
+ <span key={`hashtag-${tagIndex}`} className="tag-chip hashtag-chip">
218
+ {tag}
219
+ </span>
220
+ ))}
221
+ {mentions.map((mention, mentionIndex) => (
222
+ <span key={`mention-${mentionIndex}`} className="tag-chip mention-chip">
223
+ {mention}
224
+ </span>
225
+ ))}
226
+ </div>
227
+ )}
172
228
  </div>
173
229
  </div>
174
230
  );
package/vite.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 3000,
8
+ open: true
9
+ }
10
+ })