social-masonry 1.0.0 → 1.0.1

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.
@@ -8,44 +8,54 @@ var react = require('react');
8
8
  /**
9
9
  * Social Masonry - Utility Functions
10
10
  */
11
+ // ============================================
12
+ // URL Parsing Utilities
13
+ // ============================================
11
14
  /**
12
- * Debounce function execution
15
+ * Extract tweet ID from Twitter/X URL
13
16
  */
14
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
- function debounce(fn, delay) {
16
- let timeoutId = null;
17
- return function (...args) {
18
- if (timeoutId) {
19
- clearTimeout(timeoutId);
20
- }
21
- timeoutId = setTimeout(() => {
22
- fn.apply(this, args);
23
- timeoutId = null;
24
- }, delay);
25
- };
17
+ function extractTweetId(url) {
18
+ if (!url)
19
+ return null;
20
+ const match = url.match(/(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/);
21
+ return match ? match[1] : null;
26
22
  }
27
23
  /**
28
- * Throttle function execution
24
+ * Extract post ID from Instagram URL
29
25
  */
30
- function throttle(fn, limit) {
31
- let inThrottle = false;
32
- let lastArgs = null;
33
- return function (...args) {
34
- if (!inThrottle) {
35
- fn.apply(this, args);
36
- inThrottle = true;
37
- setTimeout(() => {
38
- inThrottle = false;
39
- if (lastArgs) {
40
- fn.apply(this, lastArgs);
41
- lastArgs = null;
42
- }
43
- }, limit);
44
- }
45
- else {
46
- lastArgs = args;
47
- }
48
- };
26
+ function extractInstagramId(url) {
27
+ if (!url)
28
+ return null;
29
+ const match = url.match(/instagram\.com\/(?:p|reel)\/([A-Za-z0-9_-]+)/);
30
+ return match ? match[1] : null;
31
+ }
32
+ /**
33
+ * Generate unique ID from post URL
34
+ */
35
+ function generatePostId(post) {
36
+ if (post.id)
37
+ return post.id;
38
+ if (post.platform === 'twitter') {
39
+ const tweetId = extractTweetId(post.url);
40
+ return tweetId ? `tw-${tweetId}` : `tw-${hashString(post.url)}`;
41
+ }
42
+ if (post.platform === 'instagram') {
43
+ const igId = extractInstagramId(post.url);
44
+ return igId ? `ig-${igId}` : `ig-${hashString(post.url)}`;
45
+ }
46
+ return `post-${hashString(post.url)}`;
47
+ }
48
+ /**
49
+ * Simple string hash function
50
+ */
51
+ function hashString(str) {
52
+ let hash = 0;
53
+ for (let i = 0; i < str.length; i++) {
54
+ const char = str.charCodeAt(i);
55
+ hash = ((hash << 5) - hash) + char;
56
+ hash = hash & hash;
57
+ }
58
+ return Math.abs(hash).toString(36);
49
59
  }
50
60
  /**
51
61
  * Get number of columns based on viewport width
@@ -73,660 +83,279 @@ const defaultColumnConfig = [
73
83
  { columns: 2, minWidth: 600 },
74
84
  { columns: 1, minWidth: 0 },
75
85
  ];
76
- /**
77
- * Format number with abbreviations (1K, 1M, etc.)
78
- */
79
- function formatNumber(num) {
80
- if (num >= 1000000) {
81
- return `${(num / 1000000).toFixed(1).replace(/\.0$/, '')}M`;
82
- }
83
- if (num >= 1000) {
84
- return `${(num / 1000).toFixed(1).replace(/\.0$/, '')}K`;
85
- }
86
- return num.toString();
87
- }
88
- /**
89
- * Format relative time
90
- */
91
- function formatRelativeTime(date) {
92
- const now = new Date();
93
- const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
94
- if (diffInSeconds < 60) {
95
- return 'now';
96
- }
97
- const diffInMinutes = Math.floor(diffInSeconds / 60);
98
- if (diffInMinutes < 60) {
99
- return `${diffInMinutes}m`;
100
- }
101
- const diffInHours = Math.floor(diffInMinutes / 60);
102
- if (diffInHours < 24) {
103
- return `${diffInHours}h`;
104
- }
105
- const diffInDays = Math.floor(diffInHours / 24);
106
- if (diffInDays < 7) {
107
- return `${diffInDays}d`;
108
- }
109
- const diffInWeeks = Math.floor(diffInDays / 7);
110
- if (diffInWeeks < 4) {
111
- return `${diffInWeeks}w`;
112
- }
113
- // Format as date for older posts
114
- return date.toLocaleDateString('en-US', {
115
- month: 'short',
116
- day: 'numeric',
117
- });
118
- }
119
- /**
120
- * Parse CSS value to pixels
121
- */
122
- function parseCSSValue(value, containerWidth) {
123
- if (typeof value === 'number') {
124
- return value;
125
- }
126
- const numMatch = value.match(/^([\d.]+)(px|rem|em|%|vw)?$/);
127
- if (!numMatch) {
128
- return 0;
129
- }
130
- const num = parseFloat(numMatch[1]);
131
- const unit = numMatch[2] || 'px';
132
- switch (unit) {
133
- case 'px':
134
- return num;
135
- case 'rem':
136
- return num * 16; // Assume 16px base
137
- case 'em':
138
- return num * 16;
139
- case '%':
140
- return 0;
141
- case 'vw':
142
- return (num / 100) * window.innerWidth;
143
- default:
144
- return num;
145
- }
146
- }
147
- /**
148
- * Check if element is in viewport
149
- */
150
- function isInViewport(element, scrollTop, viewportHeight, overscan = 0) {
151
- const expandedTop = scrollTop - overscan;
152
- const expandedBottom = scrollTop + viewportHeight + overscan;
153
- return element.bottom >= expandedTop && element.top <= expandedBottom;
154
- }
155
- /**
156
- * Get scroll position
157
- */
158
- function getScrollPosition(scrollContainer) {
159
- if (!scrollContainer || scrollContainer === window) {
160
- return {
161
- scrollTop: window.scrollY || document.documentElement.scrollTop,
162
- viewportHeight: window.innerHeight,
163
- };
164
- }
165
- return {
166
- scrollTop: scrollContainer.scrollTop,
167
- viewportHeight: scrollContainer.clientHeight,
168
- };
169
- }
170
- /**
171
- * Type guard for Twitter posts
172
- */
173
- function isTwitterPost(post) {
174
- return post.platform === 'twitter';
175
- }
176
- /**
177
- * Type guard for Instagram posts
178
- */
179
- function isInstagramPost(post) {
180
- return post.platform === 'instagram';
181
- }
182
- /**
183
- * Request animation frame with fallback
184
- */
185
- const raf = typeof requestAnimationFrame !== 'undefined'
186
- ? requestAnimationFrame
187
- : (callback) => setTimeout(callback, 16);
188
- /**
189
- * Cancel animation frame with fallback
190
- */
191
- const cancelRaf = typeof cancelAnimationFrame !== 'undefined'
192
- ? cancelAnimationFrame
193
- : (id) => clearTimeout(id);
194
86
 
195
- /**
196
- * Social Masonry - Layout Engine
197
- * Calculates positions for masonry grid items
198
- */
199
- class LayoutEngine {
200
- constructor(options) {
201
- this.options = {
202
- gap: 16,
203
- columns: defaultColumnConfig,
204
- defaultColumns: 3,
205
- padding: 0,
206
- animationDuration: 300,
207
- animate: true,
208
- easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
209
- ...options,
210
- };
211
- this.state = {
212
- positions: new Map(),
213
- columnHeights: [],
214
- containerHeight: 0,
215
- columnWidth: 0,
216
- };
217
- }
218
- /**
219
- * Calculate layout for all posts
220
- */
221
- calculate(posts) {
222
- const { containerWidth, itemHeights } = this.options;
223
- const gap = parseCSSValue(this.options.gap);
224
- const padding = parseCSSValue(this.options.padding);
225
- // Calculate column count and width
226
- const columnCount = getColumnCount(this.options.columns, containerWidth);
227
- const availableWidth = containerWidth - padding * 2;
228
- const totalGapWidth = gap * (columnCount - 1);
229
- const columnWidth = (availableWidth - totalGapWidth) / columnCount;
230
- // Initialize column heights
231
- const columnHeights = new Array(columnCount).fill(0);
232
- const positions = new Map();
233
- // Place each item in the shortest column
234
- for (const post of posts) {
235
- const itemHeight = itemHeights.get(post.id) ?? this.estimateHeight(post, columnWidth);
236
- // Find shortest column
237
- const shortestColumn = columnHeights.indexOf(Math.min(...columnHeights));
238
- // Calculate position
239
- const x = padding + shortestColumn * (columnWidth + gap);
240
- const y = columnHeights[shortestColumn];
241
- positions.set(post.id, {
242
- id: post.id,
243
- x,
244
- y,
245
- width: columnWidth,
246
- height: itemHeight,
247
- column: shortestColumn,
248
- });
249
- // Update column height
250
- columnHeights[shortestColumn] = y + itemHeight + gap;
87
+ // ============================================
88
+ // Script Loading
89
+ // ============================================
90
+ let twitterScriptLoaded = false;
91
+ let instagramScriptLoaded = false;
92
+ const loadTwitterScript = () => {
93
+ if (twitterScriptLoaded)
94
+ return Promise.resolve();
95
+ if (typeof window === 'undefined')
96
+ return Promise.resolve();
97
+ return new Promise((resolve) => {
98
+ if (window.twttr) {
99
+ twitterScriptLoaded = true;
100
+ resolve();
101
+ return;
251
102
  }
252
- // Remove last gap from column heights
253
- const containerHeight = Math.max(...columnHeights.map(h => h - gap), 0);
254
- this.state = {
255
- positions,
256
- columnHeights,
257
- containerHeight,
258
- columnWidth,
103
+ const script = document.createElement('script');
104
+ script.src = 'https://platform.twitter.com/widgets.js';
105
+ script.async = true;
106
+ script.onload = () => {
107
+ twitterScriptLoaded = true;
108
+ resolve();
259
109
  };
260
- return this.state;
261
- }
262
- /**
263
- * Estimate item height based on content
264
- */
265
- estimateHeight(post, columnWidth) {
266
- let height = 0;
267
- // Header (avatar, name, etc.)
268
- height += 56;
269
- // Text content
270
- if (post.platform === 'twitter') {
271
- const textLength = post.content.text.length;
272
- const avgCharsPerLine = Math.floor(columnWidth / 8); // ~8px per char
273
- const lines = Math.ceil(textLength / avgCharsPerLine);
274
- height += lines * 24; // ~24px per line
275
- }
276
- else if (post.content.caption) {
277
- const captionLength = post.content.caption.length;
278
- const avgCharsPerLine = Math.floor(columnWidth / 8);
279
- const lines = Math.min(Math.ceil(captionLength / avgCharsPerLine), 4); // Max 4 lines
280
- height += lines * 20;
281
- }
282
- // Media
283
- if (post.platform === 'twitter' && post.media?.length) {
284
- const media = post.media[0];
285
- const aspectRatio = media.aspectRatio ?? 16 / 9;
286
- height += columnWidth / aspectRatio;
287
- }
288
- else if (post.platform === 'instagram') {
289
- const aspectRatio = post.media.aspectRatio ?? 1;
290
- height += columnWidth / aspectRatio;
110
+ document.head.appendChild(script);
111
+ });
112
+ };
113
+ const loadInstagramScript = () => {
114
+ if (instagramScriptLoaded)
115
+ return Promise.resolve();
116
+ if (typeof window === 'undefined')
117
+ return Promise.resolve();
118
+ return new Promise((resolve) => {
119
+ if (window.instgrm) {
120
+ instagramScriptLoaded = true;
121
+ resolve();
122
+ return;
291
123
  }
292
- // Footer (metrics, timestamp)
293
- height += 44;
294
- // Padding
295
- height += 24;
296
- return Math.round(height);
297
- }
298
- /**
299
- * Update single item height and recalculate affected items
300
- */
301
- updateItemHeight(id, height) {
302
- this.options.itemHeights.set(id, height);
303
- }
304
- /**
305
- * Get current layout state
306
- */
307
- getState() {
308
- return this.state;
309
- }
310
- /**
311
- * Get position for specific item
312
- */
313
- getPosition(id) {
314
- return this.state.positions.get(id);
315
- }
316
- /**
317
- * Update container width
318
- */
319
- setContainerWidth(width) {
320
- this.options.containerWidth = width;
321
- }
322
- /**
323
- * Get current column count
324
- */
325
- getColumnCount() {
326
- return getColumnCount(this.options.columns, this.options.containerWidth);
327
- }
328
- /**
329
- * Get CSS variables for animations
330
- */
331
- getCSSVariables() {
332
- return {
333
- '--sm-animation-duration': `${this.options.animationDuration}ms`,
334
- '--sm-easing': this.options.easing,
335
- '--sm-gap': typeof this.options.gap === 'number'
336
- ? `${this.options.gap}px`
337
- : this.options.gap,
338
- '--sm-column-width': `${this.state.columnWidth}px`,
339
- '--sm-container-height': `${this.state.containerHeight}px`,
124
+ const script = document.createElement('script');
125
+ script.src = 'https://www.instagram.com/embed.js';
126
+ script.async = true;
127
+ script.onload = () => {
128
+ instagramScriptLoaded = true;
129
+ resolve();
340
130
  };
341
- }
342
- }
343
-
344
- /**
345
- * Social Masonry - Virtualization Engine
346
- * Handles virtual scrolling for large lists
347
- */
348
- class VirtualizationEngine {
349
- constructor(options) {
350
- this.visibleItems = [];
351
- this.rafId = null;
352
- this.lastScrollTop = 0;
353
- this.isScrolling = false;
354
- this.scrollEndTimeout = null;
355
- this.options = {
356
- enabled: true,
357
- overscan: 3,
358
- estimatedItemHeight: 400,
359
- scrollContainer: null,
360
- ...options,
131
+ document.head.appendChild(script);
132
+ });
133
+ };
134
+ const TwitterEmbed = ({ url, theme = 'light', onLoad, onError, }) => {
135
+ const containerRef = react.useRef(null);
136
+ const embedRef = react.useRef(null);
137
+ const [loading, setLoading] = react.useState(true);
138
+ const mountedRef = react.useRef(true);
139
+ react.useEffect(() => {
140
+ mountedRef.current = true;
141
+ return () => {
142
+ mountedRef.current = false;
361
143
  };
362
- this.posts = options.posts;
363
- this.positions = options.positions;
364
- this.onVisibleItemsChange = options.onVisibleItemsChange;
365
- this.scrollHandler = throttle(this.handleScroll.bind(this), 16);
366
- }
367
- /**
368
- * Initialize scroll listener
369
- */
370
- init() {
371
- if (!this.options.enabled)
372
- return;
373
- const container = this.options.scrollContainer ?? window;
374
- container.addEventListener('scroll', this.scrollHandler, { passive: true });
375
- // Initial calculation
376
- this.calculateVisibleItems();
377
- }
378
- /**
379
- * Destroy and cleanup
380
- */
381
- destroy() {
382
- const container = this.options.scrollContainer ?? window;
383
- container.removeEventListener('scroll', this.scrollHandler);
384
- if (this.rafId !== null) {
385
- cancelRaf(this.rafId);
386
- }
387
- if (this.scrollEndTimeout) {
388
- clearTimeout(this.scrollEndTimeout);
389
- }
390
- }
391
- /**
392
- * Handle scroll event
393
- */
394
- handleScroll() {
395
- if (this.rafId !== null)
144
+ }, []);
145
+ react.useEffect(() => {
146
+ const embedContainer = embedRef.current;
147
+ if (!embedContainer)
396
148
  return;
397
- this.isScrolling = true;
398
- // Clear existing scroll end timeout
399
- if (this.scrollEndTimeout) {
400
- clearTimeout(this.scrollEndTimeout);
401
- }
402
- // Set scroll end detection
403
- this.scrollEndTimeout = setTimeout(() => {
404
- this.isScrolling = false;
405
- }, 150);
406
- this.rafId = raf(() => {
407
- this.rafId = null;
408
- this.calculateVisibleItems();
409
- });
410
- }
411
- /**
412
- * Calculate which items are visible
413
- */
414
- calculateVisibleItems() {
415
- const { scrollTop, viewportHeight } = getScrollPosition(this.options.scrollContainer);
416
- const overscanPx = this.options.overscan * this.options.estimatedItemHeight;
417
- this.lastScrollTop = scrollTop;
418
- const newVisibleItems = [];
419
- for (let i = 0; i < this.posts.length; i++) {
420
- const post = this.posts[i];
421
- const position = this.positions.get(post.id);
422
- if (!position)
423
- continue;
424
- const isVisible = isInViewport({
425
- top: position.y,
426
- bottom: position.y + position.height,
427
- }, scrollTop, viewportHeight, overscanPx);
428
- if (isVisible) {
429
- newVisibleItems.push({
430
- index: i,
431
- post,
432
- position,
433
- isVisible: true,
434
- });
149
+ // Create a fresh container for the widget
150
+ const widgetContainer = document.createElement('div');
151
+ embedContainer.appendChild(widgetContainer);
152
+ setLoading(true);
153
+ loadTwitterScript().then(() => {
154
+ if (!mountedRef.current)
155
+ return;
156
+ const twttr = window.twttr;
157
+ if (!twttr) {
158
+ onError?.(new Error('Twitter widgets not loaded'));
159
+ setLoading(false);
160
+ return;
435
161
  }
436
- }
437
- // Check if visible items changed
438
- const hasChanged = this.hasVisibleItemsChanged(newVisibleItems);
439
- if (hasChanged) {
440
- this.visibleItems = newVisibleItems;
441
- this.onVisibleItemsChange?.(newVisibleItems);
442
- }
443
- return this.visibleItems;
444
- }
445
- /**
446
- * Check if visible items have changed
447
- */
448
- hasVisibleItemsChanged(newItems) {
449
- if (newItems.length !== this.visibleItems.length) {
450
- return true;
451
- }
452
- for (let i = 0; i < newItems.length; i++) {
453
- if (newItems[i].post.id !== this.visibleItems[i].post.id) {
454
- return true;
162
+ // Extract tweet ID from URL
163
+ const match = url.match(/(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/);
164
+ if (!match) {
165
+ onError?.(new Error('Invalid Twitter URL'));
166
+ setLoading(false);
167
+ return;
455
168
  }
456
- }
457
- return false;
458
- }
459
- /**
460
- * Update posts and positions
461
- */
462
- update(posts, positions) {
463
- this.posts = posts;
464
- this.positions = positions;
465
- this.calculateVisibleItems();
466
- }
467
- /**
468
- * Get visible items
469
- */
470
- getVisibleItems() {
471
- return this.visibleItems;
472
- }
473
- /**
474
- * Get all items (for non-virtualized mode)
475
- */
476
- getAllItems() {
477
- return this.posts.map((post, index) => ({
478
- index,
479
- post,
480
- position: this.positions.get(post.id),
481
- isVisible: true,
482
- }));
483
- }
484
- /**
485
- * Check if scrolling
486
- */
487
- getIsScrolling() {
488
- return this.isScrolling;
489
- }
490
- /**
491
- * Get scroll direction
492
- */
493
- getScrollDirection() {
494
- const { scrollTop } = getScrollPosition(this.options.scrollContainer);
495
- if (scrollTop > this.lastScrollTop) {
496
- return 'down';
497
- }
498
- else if (scrollTop < this.lastScrollTop) {
499
- return 'up';
500
- }
501
- return 'none';
502
- }
503
- /**
504
- * Check if near bottom (for infinite scroll)
505
- */
506
- isNearBottom(threshold = 500) {
507
- const { scrollTop, viewportHeight } = getScrollPosition(this.options.scrollContainer);
508
- // Get container height from positions
509
- let maxBottom = 0;
510
- for (const position of this.positions.values()) {
511
- maxBottom = Math.max(maxBottom, position.y + position.height);
512
- }
513
- return scrollTop + viewportHeight >= maxBottom - threshold;
514
- }
515
- /**
516
- * Scroll to item
517
- */
518
- scrollToItem(id, behavior = 'smooth') {
519
- const position = this.positions.get(id);
520
- if (!position)
521
- return;
522
- const container = this.options.scrollContainer ?? window;
523
- if (container === window) {
524
- window.scrollTo({
525
- top: position.y,
526
- behavior,
527
- });
528
- }
529
- else {
530
- container.scrollTo({
531
- top: position.y,
532
- behavior,
169
+ twttr.widgets.createTweet(match[1], widgetContainer, {
170
+ theme,
171
+ conversation: 'none',
172
+ dnt: true,
173
+ }).then((el) => {
174
+ if (!mountedRef.current)
175
+ return;
176
+ setLoading(false);
177
+ if (el) {
178
+ onLoad?.();
179
+ }
180
+ else {
181
+ onError?.(new Error('Tweet not found or unavailable'));
182
+ }
183
+ }).catch((err) => {
184
+ if (!mountedRef.current)
185
+ return;
186
+ setLoading(false);
187
+ onError?.(err);
533
188
  });
534
- }
535
- }
536
- /**
537
- * Get render range for optimization
538
- */
539
- getRenderRange() {
540
- if (this.visibleItems.length === 0) {
541
- return { start: 0, end: 0 };
542
- }
543
- const indices = this.visibleItems.map(item => item.index);
544
- return {
545
- start: Math.min(...indices),
546
- end: Math.max(...indices) + 1,
189
+ });
190
+ return () => {
191
+ // Safely remove the widget container
192
+ if (embedContainer && widgetContainer.parentNode === embedContainer) {
193
+ embedContainer.removeChild(widgetContainer);
194
+ }
547
195
  };
548
- }
549
- }
550
-
551
- // ============================================
552
- // Icons
553
- // ============================================
554
- const TwitterIcon = ({ className }) => (jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: className, children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) }));
555
- const InstagramIcon = ({ className }) => (jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: className, children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M12 2c2.717 0 3.056.01 4.122.06 1.065.05 1.79.217 2.428.465.66.254 1.216.598 1.772 1.153a4.908 4.908 0 0 1 1.153 1.772c.247.637.415 1.363.465 2.428.047 1.066.06 1.405.06 4.122 0 2.717-.01 3.056-.06 4.122-.05 1.065-.218 1.79-.465 2.428a4.883 4.883 0 0 1-1.153 1.772 4.915 4.915 0 0 1-1.772 1.153c-.637.247-1.363.415-2.428.465-1.066.047-1.405.06-4.122.06-2.717 0-3.056-.01-4.122-.06-1.065-.05-1.79-.218-2.428-.465a4.89 4.89 0 0 1-1.772-1.153 4.904 4.904 0 0 1-1.153-1.772c-.248-.637-.415-1.363-.465-2.428C2.013 15.056 2 14.717 2 12c0-2.717.01-3.056.06-4.122.05-1.066.217-1.79.465-2.428a4.88 4.88 0 0 1 1.153-1.772A4.897 4.897 0 0 1 5.45 2.525c.638-.248 1.362-.415 2.428-.465C8.944 2.013 9.283 2 12 2zm0 1.802c-2.67 0-2.986.01-4.04.058-.976.045-1.505.207-1.858.344-.466.182-.8.398-1.15.748-.35.35-.566.684-.748 1.15-.137.353-.3.882-.344 1.857-.048 1.055-.058 1.37-.058 4.041 0 2.67.01 2.986.058 4.04.045.976.207 1.505.344 1.858.182.466.399.8.748 1.15.35.35.684.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058 2.67 0 2.987-.01 4.04-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.684.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041 0-2.67-.01-2.986-.058-4.04-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 0 0-.748-1.15 3.098 3.098 0 0 0-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.055-.048-1.37-.058-4.041-.058zm0 3.063a5.135 5.135 0 1 1 0 10.27 5.135 5.135 0 0 1 0-10.27zm0 8.468a3.333 3.333 0 1 0 0-6.666 3.333 3.333 0 0 0 0 6.666zm6.538-8.671a1.2 1.2 0 1 1-2.4 0 1.2 1.2 0 0 1 2.4 0z" }) }));
556
- const VerifiedIcon = ({ className }) => (jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: className, children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.34 2.19c-1.39-.46-2.9-.2-3.91.81s-1.27 2.52-.81 3.91C2.63 9.33 1.75 10.57 1.75 12s.88 2.67 2.19 3.34c-.46 1.39-.2 2.9.81 3.91s2.52 1.27 3.91.81c.66 1.31 1.91 2.19 3.34 2.19s2.67-.88 3.34-2.19c1.39.46 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z" }) }));
557
- const PlayIcon = ({ className }) => (jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: className, children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M8 5v14l11-7z" }) }));
558
- const Card = ({ post, position, variant = 'default', theme = 'auto', borderRadius = 12, showPlatformIcon = true, showAuthor = true, showMetrics = true, showTimestamp = true, formatDate = formatRelativeTime, formatNumber: formatNum = formatNumber, className = '', hoverEffect = true, imageLoading = 'lazy', fallbackImage = '', onPostClick, onAuthorClick, onMediaClick, onHeightChange, }) => {
559
- const cardRef = react.useRef(null);
196
+ }, [url, theme, onLoad, onError]);
197
+ return (jsxRuntime.jsxs("div", { ref: containerRef, className: "sm-twitter-embed", children: [loading && (jsxRuntime.jsx("div", { style: {
198
+ display: 'flex',
199
+ alignItems: 'center',
200
+ justifyContent: 'center',
201
+ minHeight: 200,
202
+ backgroundColor: theme === 'dark' ? '#15202b' : '#f5f5f5',
203
+ borderRadius: 12,
204
+ }, children: jsxRuntime.jsx("div", { style: { color: theme === 'dark' ? '#8899a6' : '#666' }, children: "Loading..." }) })), jsxRuntime.jsx("div", { ref: embedRef })] }));
205
+ };
206
+ const InstagramEmbed = ({ url, onLoad, onError, }) => {
207
+ const containerRef = react.useRef(null);
208
+ const embedRef = react.useRef(null);
209
+ const [loading, setLoading] = react.useState(true);
210
+ const mountedRef = react.useRef(true);
560
211
  react.useEffect(() => {
561
- if (cardRef.current && onHeightChange) {
562
- const height = cardRef.current.offsetHeight;
563
- if (height !== position.height) {
564
- onHeightChange(post.id, height);
565
- }
566
- }
567
- }, [post.id, position.height, onHeightChange]);
568
- const handlePostClick = react.useCallback((e) => {
569
- const target = e.target;
570
- if (target.tagName === 'A' || target.closest('a'))
212
+ mountedRef.current = true;
213
+ return () => {
214
+ mountedRef.current = false;
215
+ };
216
+ }, []);
217
+ react.useEffect(() => {
218
+ const embedContainer = embedRef.current;
219
+ if (!embedContainer)
220
+ return;
221
+ // Extract post ID from URL
222
+ const match = url.match(/instagram\.com\/(?:p|reel)\/([A-Za-z0-9_-]+)/);
223
+ if (!match) {
224
+ onError?.(new Error('Invalid Instagram URL'));
225
+ setLoading(false);
571
226
  return;
572
- onPostClick?.(post, e);
573
- }, [post, onPostClick]);
574
- const handleAuthorClick = react.useCallback((e) => {
575
- e.stopPropagation();
576
- onAuthorClick?.(post, e);
577
- }, [post, onAuthorClick]);
578
- const handleMediaClick = react.useCallback((index) => (e) => {
579
- e.stopPropagation();
580
- onMediaClick?.(post, index, e);
581
- }, [post, onMediaClick]);
582
- const classes = [
583
- 'sm-card',
584
- `sm-card--${post.platform}`,
585
- `sm-card--${variant}`,
586
- `sm-card--${theme}`,
587
- hoverEffect && 'sm-card--hover',
588
- className,
589
- ].filter(Boolean).join(' ');
590
- const style = {
591
- position: 'absolute',
592
- left: position.x,
593
- top: position.y,
594
- width: position.width,
595
- borderRadius: typeof borderRadius === 'number' ? borderRadius : borderRadius,
596
- cursor: onPostClick ? 'pointer' : undefined,
597
- };
598
- const date = post.createdAt instanceof Date ? post.createdAt : new Date(post.createdAt);
599
- return (jsxRuntime.jsxs("div", { ref: cardRef, className: classes, style: style, onClick: handlePostClick, "data-post-id": post.id, "data-platform": post.platform, children: [showPlatformIcon && (jsxRuntime.jsx("div", { className: "sm-card__platform-icon", children: post.platform === 'twitter' ? (jsxRuntime.jsx(TwitterIcon, { className: "sm-platform-icon sm-platform-icon--twitter" })) : (jsxRuntime.jsx(InstagramIcon, { className: "sm-platform-icon sm-platform-icon--instagram" })) })), showAuthor && (jsxRuntime.jsxs("div", { className: "sm-card__header", onClick: onAuthorClick ? handleAuthorClick : undefined, style: { cursor: onAuthorClick ? 'pointer' : undefined }, children: [jsxRuntime.jsx("img", { src: post.author.avatarUrl || fallbackImage, alt: post.author.displayName || post.author.username, className: "sm-card__avatar", loading: imageLoading, onError: (e) => {
600
- if (fallbackImage) {
601
- e.target.src = fallbackImage;
602
- }
603
- } }), jsxRuntime.jsxs("div", { className: "sm-card__author", children: [jsxRuntime.jsxs("span", { className: "sm-card__author-name", children: [post.author.displayName || post.author.username, post.author.verified && jsxRuntime.jsx(VerifiedIcon, { className: "sm-card__verified" })] }), jsxRuntime.jsxs("span", { className: "sm-card__author-handle", children: ["@", post.author.username] })] })] })), jsxRuntime.jsxs("div", { className: "sm-card__content", children: [isTwitterPost(post) && (jsxRuntime.jsx("p", { className: "sm-card__text", children: post.content.text })), isInstagramPost(post) && post.content.caption && (jsxRuntime.jsx("p", { className: "sm-card__caption", children: post.content.caption.length > 150
604
- ? `${post.content.caption.substring(0, 150)}...`
605
- : post.content.caption }))] }), isTwitterPost(post) && post.media && post.media.length > 0 && (jsxRuntime.jsx("div", { className: `sm-card__media ${post.media.length > 1 ? `sm-card__media-grid sm-card__media-grid--${Math.min(post.media.length, 4)}` : ''}`, children: post.media.slice(0, 4).map((media, index) => (jsxRuntime.jsxs("div", { className: `sm-card__media-item ${media.type !== 'image' ? 'sm-card__media-item--video' : ''}`, onClick: onMediaClick ? handleMediaClick(index) : undefined, style: { cursor: onMediaClick ? 'pointer' : undefined }, children: [jsxRuntime.jsx("img", { src: media.thumbnailUrl || media.url, alt: "Post media", loading: imageLoading, onError: (e) => {
606
- if (fallbackImage) {
607
- e.target.src = fallbackImage;
608
- }
609
- } }), (media.type === 'video' || media.type === 'gif') && (jsxRuntime.jsx("div", { className: "sm-card__play-button", children: jsxRuntime.jsx(PlayIcon, {}) }))] }, index))) })), isInstagramPost(post) && (jsxRuntime.jsxs("div", { className: "sm-card__media", style: { aspectRatio: post.media.aspectRatio || 1 }, children: [jsxRuntime.jsx("img", { src: post.media.thumbnailUrl || post.media.url, alt: "Post media", loading: imageLoading, onError: (e) => {
610
- if (fallbackImage) {
611
- e.target.src = fallbackImage;
612
- }
613
- } }), post.media.type === 'video' && (jsxRuntime.jsx("div", { className: "sm-card__play-button", children: jsxRuntime.jsx(PlayIcon, {}) })), post.media.type === 'carousel' && (jsxRuntime.jsx("div", { className: "sm-card__carousel-indicator", children: jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm12 0c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-6 0c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z" }) }) }))] })), (showMetrics || showTimestamp) && (jsxRuntime.jsxs("div", { className: "sm-card__footer", children: [showMetrics && post.metrics && (jsxRuntime.jsxs("div", { className: "sm-card__metrics", children: [isTwitterPost(post) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [post.metrics.replies !== undefined && (jsxRuntime.jsxs("span", { className: "sm-card__metric", children: [jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.01zm8.005-6c-3.317 0-6.005 2.69-6.005 6 0 3.37 2.77 6.08 6.138 6.01l.351-.01h1.761v2.3l5.087-2.81c1.951-1.08 3.163-3.13 3.163-5.36 0-3.39-2.744-6.13-6.129-6.13H9.756z" }) }), formatNum(post.metrics.replies)] })), post.metrics.retweets !== undefined && (jsxRuntime.jsxs("span", { className: "sm-card__metric", children: [jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M4.5 3.88l4.432 4.14-1.364 1.46L5.5 7.55V16c0 1.1.896 2 2 2H13v2H7.5c-2.209 0-4-1.79-4-4V7.55L1.432 9.48.068 8.02 4.5 3.88zM16.5 6H11V4h5.5c2.209 0 4 1.79 4 4v8.45l2.068-1.93 1.364 1.46-4.432 4.14-4.432-4.14 1.364-1.46 2.068 1.93V8c0-1.1-.896-2-2-2z" }) }), formatNum(post.metrics.retweets)] })), post.metrics.likes !== undefined && (jsxRuntime.jsxs("span", { className: "sm-card__metric", children: [jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M16.697 5.5c-1.222-.06-2.679.51-3.89 2.16l-.805 1.09-.806-1.09C9.984 6.01 8.526 5.44 7.304 5.5c-1.243.07-2.349.78-2.91 1.91-.552 1.12-.633 2.78.479 4.82 1.074 1.97 3.257 4.27 7.129 6.61 3.87-2.34 6.052-4.64 7.126-6.61 1.111-2.04 1.03-3.7.477-4.82-.561-1.13-1.666-1.84-2.908-1.91zm4.187 7.69c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z" }) }), formatNum(post.metrics.likes)] }))] })), isInstagramPost(post) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [post.metrics.likes !== undefined && (jsxRuntime.jsxs("span", { className: "sm-card__metric", children: [jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M16.792 3.904A4.989 4.989 0 0 1 21.5 9.122c0 3.072-2.652 4.959-5.197 7.222-2.512 2.243-3.865 3.469-4.303 3.752-.477-.309-2.143-1.823-4.303-3.752C5.141 14.072 2.5 12.167 2.5 9.122a4.989 4.989 0 0 1 4.708-5.218 4.21 4.21 0 0 1 3.675 1.941c.84 1.175.98 1.763 1.12 1.763s.278-.588 1.11-1.766a4.17 4.17 0 0 1 3.679-1.938m0-2a6.04 6.04 0 0 0-4.797 2.127 6.052 6.052 0 0 0-4.787-2.127A6.985 6.985 0 0 0 .5 9.122c0 3.61 2.55 5.827 5.015 7.97.283.246.569.494.853.747l1.027.918a44.998 44.998 0 0 0 3.518 3.018 2 2 0 0 0 2.174 0 45.263 45.263 0 0 0 3.626-3.115l.922-.824c.293-.26.59-.519.885-.774 2.334-2.025 4.98-4.32 4.98-7.94a6.985 6.985 0 0 0-6.708-7.218z" }) }), formatNum(post.metrics.likes)] })), post.metrics.comments !== undefined && (jsxRuntime.jsxs("span", { className: "sm-card__metric", children: [jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { fill: "currentColor", d: "M20.656 17.008a9.993 9.993 0 1 0-3.59 3.615L22 22l-1.344-4.992zM10 12a1 1 0 1 1 1 1 1 1 0 0 1-1-1zm-4 0a1 1 0 1 1 1 1 1 1 0 0 1-1-1zm8 0a1 1 0 1 1 1 1 1 1 0 0 1-1-1z" }) }), formatNum(post.metrics.comments)] }))] }))] })), showTimestamp && (jsxRuntime.jsx("time", { className: "sm-card__timestamp", dateTime: date.toISOString(), children: formatDate(date) }))] }))] }));
227
+ }
228
+ // Create a fresh container for the widget
229
+ const widgetContainer = document.createElement('div');
230
+ widgetContainer.innerHTML = `
231
+ <blockquote
232
+ class="instagram-media"
233
+ data-instgrm-captioned
234
+ data-instgrm-permalink="https://www.instagram.com/p/${match[1]}/"
235
+ data-instgrm-version="14"
236
+ style="
237
+ background:#FFF;
238
+ border:0;
239
+ border-radius:12px;
240
+ box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15);
241
+ margin: 0;
242
+ max-width:100%;
243
+ min-width:100%;
244
+ padding:0;
245
+ width:100%;
246
+ "
247
+ >
248
+ </blockquote>
249
+ `;
250
+ embedContainer.appendChild(widgetContainer);
251
+ loadInstagramScript().then(() => {
252
+ if (!mountedRef.current)
253
+ return;
254
+ const instgrm = window.instgrm;
255
+ if (instgrm) {
256
+ instgrm.Embeds.process();
257
+ setLoading(false);
258
+ onLoad?.();
259
+ }
260
+ });
261
+ return () => {
262
+ // Safely remove the widget container
263
+ if (embedContainer && widgetContainer.parentNode === embedContainer) {
264
+ embedContainer.removeChild(widgetContainer);
265
+ }
266
+ };
267
+ }, [url, onLoad, onError]);
268
+ return (jsxRuntime.jsxs("div", { ref: containerRef, className: "sm-instagram-embed", children: [loading && (jsxRuntime.jsx("div", { style: {
269
+ display: 'flex',
270
+ alignItems: 'center',
271
+ justifyContent: 'center',
272
+ minHeight: 400,
273
+ backgroundColor: '#fafafa',
274
+ borderRadius: 12,
275
+ }, children: jsxRuntime.jsx("div", { style: { color: '#666' }, children: "Loading..." }) })), jsxRuntime.jsx("div", { ref: embedRef })] }));
614
276
  };
615
277
  const SocialMasonry = react.forwardRef((props, ref) => {
616
- const { posts: initialPosts = [], gap = 16, columns = defaultColumnConfig, padding = 0, animationDuration = 300, animate = true, easing = 'cubic-bezier(0.4, 0, 0.2, 1)', virtualization, loadMoreThreshold: _loadMoreThreshold = 500, onLoadMore: _onLoadMore, onLayoutComplete, className, style, ...cardProps } = props;
278
+ const { posts: initialPosts = [], gap = 16, columns = defaultColumnConfig, theme = 'light', onEmbedLoad, onEmbedError, className, style, } = props;
617
279
  const containerRef = react.useRef(null);
618
280
  const [posts, setPosts] = react.useState(initialPosts);
619
- const [positions, setPositions] = react.useState(new Map());
620
- const [containerHeight, setContainerHeight] = react.useState(0);
621
- const [itemHeights] = react.useState(() => new Map());
622
- const [visibleItems, setVisibleItems] = react.useState([]);
623
- const layoutEngine = react.useMemo(() => {
624
- return new LayoutEngine({
625
- gap,
626
- columns,
627
- padding,
628
- animationDuration,
629
- animate,
630
- easing,
631
- containerWidth: containerRef.current?.clientWidth || 800,
632
- itemHeights,
633
- });
634
- }, [gap, columns, padding, animationDuration, animate, easing]);
635
- const virtualizationEngine = react.useMemo(() => {
636
- if (!virtualization?.enabled)
637
- return null;
638
- return new VirtualizationEngine({
639
- ...virtualization,
640
- posts,
641
- positions,
642
- onVisibleItemsChange: setVisibleItems,
643
- });
644
- }, [virtualization?.enabled]);
645
- // Calculate layout
646
- const calculateLayout = react.useCallback(() => {
647
- if (!containerRef.current)
648
- return;
649
- layoutEngine.setContainerWidth(containerRef.current.clientWidth);
650
- const state = layoutEngine.calculate(posts);
651
- setPositions(state.positions);
652
- setContainerHeight(state.containerHeight);
653
- if (virtualizationEngine) {
654
- virtualizationEngine.update(posts, state.positions);
655
- setVisibleItems(virtualizationEngine.calculateVisibleItems());
656
- }
657
- onLayoutComplete?.(Array.from(state.positions.values()));
658
- }, [posts, layoutEngine, virtualizationEngine, onLayoutComplete]);
659
- // Handle resize
281
+ const [columnCount, setColumnCount] = react.useState(3);
282
+ // Update posts when initialPosts changes
283
+ react.useEffect(() => {
284
+ setPosts(initialPosts);
285
+ }, [initialPosts]);
286
+ // Calculate column count based on container width
660
287
  react.useEffect(() => {
661
- const handleResize = debounce(() => {
662
- calculateLayout();
663
- }, 150);
664
- const resizeObserver = new ResizeObserver(handleResize);
288
+ const updateColumnCount = () => {
289
+ if (!containerRef.current)
290
+ return;
291
+ const width = containerRef.current.clientWidth;
292
+ const count = getColumnCount(columns, width);
293
+ setColumnCount(count);
294
+ };
295
+ updateColumnCount();
296
+ const resizeObserver = new ResizeObserver(updateColumnCount);
665
297
  if (containerRef.current) {
666
298
  resizeObserver.observe(containerRef.current);
667
299
  }
668
- return () => {
669
- resizeObserver.disconnect();
670
- };
671
- }, [calculateLayout]);
672
- // Initial layout
673
- react.useEffect(() => {
674
- calculateLayout();
675
- }, [posts, calculateLayout]);
676
- // Initialize virtualization
677
- react.useEffect(() => {
678
- if (virtualizationEngine) {
679
- virtualizationEngine.init();
680
- return () => virtualizationEngine.destroy();
681
- }
682
- return undefined;
683
- }, [virtualizationEngine]);
684
- // Handle height change
685
- const handleHeightChange = react.useCallback((id, height) => {
686
- itemHeights.set(id, height);
687
- calculateLayout();
688
- }, [itemHeights, calculateLayout]);
300
+ return () => resizeObserver.disconnect();
301
+ }, [columns]);
689
302
  // Expose methods via ref
690
303
  react.useImperativeHandle(ref, () => ({
691
304
  addPosts: (newPosts) => {
692
305
  setPosts(prev => [...prev, ...newPosts]);
693
306
  },
694
307
  setPosts: (newPosts) => {
695
- itemHeights.clear();
696
308
  setPosts(newPosts);
697
309
  },
698
310
  removePost: (id) => {
699
- itemHeights.delete(id);
700
- setPosts(prev => prev.filter(p => p.id !== id));
311
+ setPosts(prev => prev.filter(p => generatePostId(p) !== id));
701
312
  },
702
313
  refresh: () => {
703
- itemHeights.clear();
704
- calculateLayout();
705
- },
706
- scrollToPost: (id, behavior = 'smooth') => {
707
- const position = positions.get(id);
708
- if (position) {
709
- window.scrollTo({ top: position.y, behavior });
314
+ // Re-process embeds
315
+ const instgrm = window.instgrm;
316
+ if (instgrm) {
317
+ instgrm.Embeds.process();
710
318
  }
711
319
  },
712
- }), [positions, calculateLayout, itemHeights]);
713
- // Determine which posts to render
714
- const postsToRender = virtualizationEngine
715
- ? visibleItems.map(item => item.post)
716
- : posts;
320
+ }), []);
321
+ // Distribute posts into columns (CSS-based masonry simulation)
322
+ const distributeToColumns = react.useCallback(() => {
323
+ const cols = Array.from({ length: columnCount }, () => []);
324
+ posts.forEach((post, index) => {
325
+ cols[index % columnCount].push(post);
326
+ });
327
+ return cols;
328
+ }, [posts, columnCount]);
329
+ const postColumns = distributeToColumns();
717
330
  return (jsxRuntime.jsxs("div", { ref: containerRef, className: `sm-container ${className || ''}`, style: {
718
- position: 'relative',
331
+ display: 'flex',
332
+ gap,
719
333
  width: '100%',
720
- height: containerHeight,
721
334
  ...style,
722
- }, children: [postsToRender.map(post => {
723
- const position = positions.get(post.id);
724
- if (!position)
335
+ }, children: [postColumns.map((columnPosts, colIndex) => (jsxRuntime.jsx("div", { className: "sm-column", style: {
336
+ flex: 1,
337
+ display: 'flex',
338
+ flexDirection: 'column',
339
+ gap,
340
+ minWidth: 0,
341
+ }, children: columnPosts.map(post => {
342
+ const postId = generatePostId(post);
343
+ if (post.platform === 'twitter') {
344
+ return (jsxRuntime.jsx("div", { className: "sm-embed sm-embed--twitter", children: jsxRuntime.jsx(TwitterEmbed, { url: post.url, theme: theme, onLoad: () => onEmbedLoad?.(post), onError: (error) => onEmbedError?.(post, error) }) }, postId));
345
+ }
346
+ if (post.platform === 'instagram') {
347
+ return (jsxRuntime.jsx("div", { className: "sm-embed sm-embed--instagram", children: jsxRuntime.jsx(InstagramEmbed, { url: post.url, onLoad: () => onEmbedLoad?.(post), onError: (error) => onEmbedError?.(post, error) }) }, postId));
348
+ }
725
349
  return null;
726
- // Extract event handlers and adapt types
727
- const { onPostClick, onAuthorClick, onMediaClick, ...restCardProps } = cardProps;
728
- return (jsxRuntime.jsx(Card, { post: post, position: position, onHeightChange: handleHeightChange, onPostClick: onPostClick ? (p, e) => onPostClick(p, e.nativeEvent) : undefined, onAuthorClick: onAuthorClick ? (p, e) => onAuthorClick(p, e.nativeEvent) : undefined, onMediaClick: onMediaClick ? (p, i, e) => onMediaClick(p, i, e.nativeEvent) : undefined, ...restCardProps }, post.id));
729
- }), posts.length === 0 && (jsxRuntime.jsx("div", { className: "sm-empty", children: "No posts to display" }))] }));
350
+ }) }, colIndex))), posts.length === 0 && (jsxRuntime.jsx("div", { className: "sm-empty", style: {
351
+ display: 'flex',
352
+ alignItems: 'center',
353
+ justifyContent: 'center',
354
+ padding: 40,
355
+ color: '#666',
356
+ fontSize: 14,
357
+ width: '100%',
358
+ }, children: "No posts to display" }))] }));
730
359
  });
731
360
  SocialMasonry.displayName = 'SocialMasonry';
732
361