social-masonry 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +394 -0
- package/dist/index.d.ts +550 -0
- package/dist/index.esm.js +1495 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +1512 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +195 -0
- package/dist/react/index.esm.js +730 -0
- package/dist/react/index.esm.js.map +1 -0
- package/dist/react/index.js +735 -0
- package/dist/react/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/styles.css.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { forwardRef, useRef, useState, useMemo, useCallback, useEffect, useImperativeHandle } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Social Masonry - Utility Functions
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Debounce function execution
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
function debounce(fn, delay) {
|
|
12
|
+
let timeoutId = null;
|
|
13
|
+
return function (...args) {
|
|
14
|
+
if (timeoutId) {
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
}
|
|
17
|
+
timeoutId = setTimeout(() => {
|
|
18
|
+
fn.apply(this, args);
|
|
19
|
+
timeoutId = null;
|
|
20
|
+
}, delay);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Throttle function execution
|
|
25
|
+
*/
|
|
26
|
+
function throttle(fn, limit) {
|
|
27
|
+
let inThrottle = false;
|
|
28
|
+
let lastArgs = null;
|
|
29
|
+
return function (...args) {
|
|
30
|
+
if (!inThrottle) {
|
|
31
|
+
fn.apply(this, args);
|
|
32
|
+
inThrottle = true;
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
inThrottle = false;
|
|
35
|
+
if (lastArgs) {
|
|
36
|
+
fn.apply(this, lastArgs);
|
|
37
|
+
lastArgs = null;
|
|
38
|
+
}
|
|
39
|
+
}, limit);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
lastArgs = args;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get number of columns based on viewport width
|
|
48
|
+
*/
|
|
49
|
+
function getColumnCount(columns, containerWidth) {
|
|
50
|
+
if (typeof columns === 'number') {
|
|
51
|
+
return columns;
|
|
52
|
+
}
|
|
53
|
+
// Sort by minWidth descending
|
|
54
|
+
const sorted = [...columns].sort((a, b) => b.minWidth - a.minWidth);
|
|
55
|
+
for (const config of sorted) {
|
|
56
|
+
if (containerWidth >= config.minWidth) {
|
|
57
|
+
return config.columns;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Return smallest breakpoint's columns or default to 1
|
|
61
|
+
return sorted[sorted.length - 1]?.columns ?? 1;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Default responsive column configuration
|
|
65
|
+
*/
|
|
66
|
+
const defaultColumnConfig = [
|
|
67
|
+
{ columns: 4, minWidth: 1200 },
|
|
68
|
+
{ columns: 3, minWidth: 900 },
|
|
69
|
+
{ columns: 2, minWidth: 600 },
|
|
70
|
+
{ columns: 1, minWidth: 0 },
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* Format number with abbreviations (1K, 1M, etc.)
|
|
74
|
+
*/
|
|
75
|
+
function formatNumber(num) {
|
|
76
|
+
if (num >= 1000000) {
|
|
77
|
+
return `${(num / 1000000).toFixed(1).replace(/\.0$/, '')}M`;
|
|
78
|
+
}
|
|
79
|
+
if (num >= 1000) {
|
|
80
|
+
return `${(num / 1000).toFixed(1).replace(/\.0$/, '')}K`;
|
|
81
|
+
}
|
|
82
|
+
return num.toString();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Format relative time
|
|
86
|
+
*/
|
|
87
|
+
function formatRelativeTime(date) {
|
|
88
|
+
const now = new Date();
|
|
89
|
+
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
|
90
|
+
if (diffInSeconds < 60) {
|
|
91
|
+
return 'now';
|
|
92
|
+
}
|
|
93
|
+
const diffInMinutes = Math.floor(diffInSeconds / 60);
|
|
94
|
+
if (diffInMinutes < 60) {
|
|
95
|
+
return `${diffInMinutes}m`;
|
|
96
|
+
}
|
|
97
|
+
const diffInHours = Math.floor(diffInMinutes / 60);
|
|
98
|
+
if (diffInHours < 24) {
|
|
99
|
+
return `${diffInHours}h`;
|
|
100
|
+
}
|
|
101
|
+
const diffInDays = Math.floor(diffInHours / 24);
|
|
102
|
+
if (diffInDays < 7) {
|
|
103
|
+
return `${diffInDays}d`;
|
|
104
|
+
}
|
|
105
|
+
const diffInWeeks = Math.floor(diffInDays / 7);
|
|
106
|
+
if (diffInWeeks < 4) {
|
|
107
|
+
return `${diffInWeeks}w`;
|
|
108
|
+
}
|
|
109
|
+
// Format as date for older posts
|
|
110
|
+
return date.toLocaleDateString('en-US', {
|
|
111
|
+
month: 'short',
|
|
112
|
+
day: 'numeric',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Parse CSS value to pixels
|
|
117
|
+
*/
|
|
118
|
+
function parseCSSValue(value, containerWidth) {
|
|
119
|
+
if (typeof value === 'number') {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
const numMatch = value.match(/^([\d.]+)(px|rem|em|%|vw)?$/);
|
|
123
|
+
if (!numMatch) {
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
const num = parseFloat(numMatch[1]);
|
|
127
|
+
const unit = numMatch[2] || 'px';
|
|
128
|
+
switch (unit) {
|
|
129
|
+
case 'px':
|
|
130
|
+
return num;
|
|
131
|
+
case 'rem':
|
|
132
|
+
return num * 16; // Assume 16px base
|
|
133
|
+
case 'em':
|
|
134
|
+
return num * 16;
|
|
135
|
+
case '%':
|
|
136
|
+
return 0;
|
|
137
|
+
case 'vw':
|
|
138
|
+
return (num / 100) * window.innerWidth;
|
|
139
|
+
default:
|
|
140
|
+
return num;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if element is in viewport
|
|
145
|
+
*/
|
|
146
|
+
function isInViewport(element, scrollTop, viewportHeight, overscan = 0) {
|
|
147
|
+
const expandedTop = scrollTop - overscan;
|
|
148
|
+
const expandedBottom = scrollTop + viewportHeight + overscan;
|
|
149
|
+
return element.bottom >= expandedTop && element.top <= expandedBottom;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get scroll position
|
|
153
|
+
*/
|
|
154
|
+
function getScrollPosition(scrollContainer) {
|
|
155
|
+
if (!scrollContainer || scrollContainer === window) {
|
|
156
|
+
return {
|
|
157
|
+
scrollTop: window.scrollY || document.documentElement.scrollTop,
|
|
158
|
+
viewportHeight: window.innerHeight,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
scrollTop: scrollContainer.scrollTop,
|
|
163
|
+
viewportHeight: scrollContainer.clientHeight,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Type guard for Twitter posts
|
|
168
|
+
*/
|
|
169
|
+
function isTwitterPost(post) {
|
|
170
|
+
return post.platform === 'twitter';
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Type guard for Instagram posts
|
|
174
|
+
*/
|
|
175
|
+
function isInstagramPost(post) {
|
|
176
|
+
return post.platform === 'instagram';
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Request animation frame with fallback
|
|
180
|
+
*/
|
|
181
|
+
const raf = typeof requestAnimationFrame !== 'undefined'
|
|
182
|
+
? requestAnimationFrame
|
|
183
|
+
: (callback) => setTimeout(callback, 16);
|
|
184
|
+
/**
|
|
185
|
+
* Cancel animation frame with fallback
|
|
186
|
+
*/
|
|
187
|
+
const cancelRaf = typeof cancelAnimationFrame !== 'undefined'
|
|
188
|
+
? cancelAnimationFrame
|
|
189
|
+
: (id) => clearTimeout(id);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Social Masonry - Layout Engine
|
|
193
|
+
* Calculates positions for masonry grid items
|
|
194
|
+
*/
|
|
195
|
+
class LayoutEngine {
|
|
196
|
+
constructor(options) {
|
|
197
|
+
this.options = {
|
|
198
|
+
gap: 16,
|
|
199
|
+
columns: defaultColumnConfig,
|
|
200
|
+
defaultColumns: 3,
|
|
201
|
+
padding: 0,
|
|
202
|
+
animationDuration: 300,
|
|
203
|
+
animate: true,
|
|
204
|
+
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
205
|
+
...options,
|
|
206
|
+
};
|
|
207
|
+
this.state = {
|
|
208
|
+
positions: new Map(),
|
|
209
|
+
columnHeights: [],
|
|
210
|
+
containerHeight: 0,
|
|
211
|
+
columnWidth: 0,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Calculate layout for all posts
|
|
216
|
+
*/
|
|
217
|
+
calculate(posts) {
|
|
218
|
+
const { containerWidth, itemHeights } = this.options;
|
|
219
|
+
const gap = parseCSSValue(this.options.gap);
|
|
220
|
+
const padding = parseCSSValue(this.options.padding);
|
|
221
|
+
// Calculate column count and width
|
|
222
|
+
const columnCount = getColumnCount(this.options.columns, containerWidth);
|
|
223
|
+
const availableWidth = containerWidth - padding * 2;
|
|
224
|
+
const totalGapWidth = gap * (columnCount - 1);
|
|
225
|
+
const columnWidth = (availableWidth - totalGapWidth) / columnCount;
|
|
226
|
+
// Initialize column heights
|
|
227
|
+
const columnHeights = new Array(columnCount).fill(0);
|
|
228
|
+
const positions = new Map();
|
|
229
|
+
// Place each item in the shortest column
|
|
230
|
+
for (const post of posts) {
|
|
231
|
+
const itemHeight = itemHeights.get(post.id) ?? this.estimateHeight(post, columnWidth);
|
|
232
|
+
// Find shortest column
|
|
233
|
+
const shortestColumn = columnHeights.indexOf(Math.min(...columnHeights));
|
|
234
|
+
// Calculate position
|
|
235
|
+
const x = padding + shortestColumn * (columnWidth + gap);
|
|
236
|
+
const y = columnHeights[shortestColumn];
|
|
237
|
+
positions.set(post.id, {
|
|
238
|
+
id: post.id,
|
|
239
|
+
x,
|
|
240
|
+
y,
|
|
241
|
+
width: columnWidth,
|
|
242
|
+
height: itemHeight,
|
|
243
|
+
column: shortestColumn,
|
|
244
|
+
});
|
|
245
|
+
// Update column height
|
|
246
|
+
columnHeights[shortestColumn] = y + itemHeight + gap;
|
|
247
|
+
}
|
|
248
|
+
// Remove last gap from column heights
|
|
249
|
+
const containerHeight = Math.max(...columnHeights.map(h => h - gap), 0);
|
|
250
|
+
this.state = {
|
|
251
|
+
positions,
|
|
252
|
+
columnHeights,
|
|
253
|
+
containerHeight,
|
|
254
|
+
columnWidth,
|
|
255
|
+
};
|
|
256
|
+
return this.state;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Estimate item height based on content
|
|
260
|
+
*/
|
|
261
|
+
estimateHeight(post, columnWidth) {
|
|
262
|
+
let height = 0;
|
|
263
|
+
// Header (avatar, name, etc.)
|
|
264
|
+
height += 56;
|
|
265
|
+
// Text content
|
|
266
|
+
if (post.platform === 'twitter') {
|
|
267
|
+
const textLength = post.content.text.length;
|
|
268
|
+
const avgCharsPerLine = Math.floor(columnWidth / 8); // ~8px per char
|
|
269
|
+
const lines = Math.ceil(textLength / avgCharsPerLine);
|
|
270
|
+
height += lines * 24; // ~24px per line
|
|
271
|
+
}
|
|
272
|
+
else if (post.content.caption) {
|
|
273
|
+
const captionLength = post.content.caption.length;
|
|
274
|
+
const avgCharsPerLine = Math.floor(columnWidth / 8);
|
|
275
|
+
const lines = Math.min(Math.ceil(captionLength / avgCharsPerLine), 4); // Max 4 lines
|
|
276
|
+
height += lines * 20;
|
|
277
|
+
}
|
|
278
|
+
// Media
|
|
279
|
+
if (post.platform === 'twitter' && post.media?.length) {
|
|
280
|
+
const media = post.media[0];
|
|
281
|
+
const aspectRatio = media.aspectRatio ?? 16 / 9;
|
|
282
|
+
height += columnWidth / aspectRatio;
|
|
283
|
+
}
|
|
284
|
+
else if (post.platform === 'instagram') {
|
|
285
|
+
const aspectRatio = post.media.aspectRatio ?? 1;
|
|
286
|
+
height += columnWidth / aspectRatio;
|
|
287
|
+
}
|
|
288
|
+
// Footer (metrics, timestamp)
|
|
289
|
+
height += 44;
|
|
290
|
+
// Padding
|
|
291
|
+
height += 24;
|
|
292
|
+
return Math.round(height);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Update single item height and recalculate affected items
|
|
296
|
+
*/
|
|
297
|
+
updateItemHeight(id, height) {
|
|
298
|
+
this.options.itemHeights.set(id, height);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get current layout state
|
|
302
|
+
*/
|
|
303
|
+
getState() {
|
|
304
|
+
return this.state;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get position for specific item
|
|
308
|
+
*/
|
|
309
|
+
getPosition(id) {
|
|
310
|
+
return this.state.positions.get(id);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Update container width
|
|
314
|
+
*/
|
|
315
|
+
setContainerWidth(width) {
|
|
316
|
+
this.options.containerWidth = width;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Get current column count
|
|
320
|
+
*/
|
|
321
|
+
getColumnCount() {
|
|
322
|
+
return getColumnCount(this.options.columns, this.options.containerWidth);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get CSS variables for animations
|
|
326
|
+
*/
|
|
327
|
+
getCSSVariables() {
|
|
328
|
+
return {
|
|
329
|
+
'--sm-animation-duration': `${this.options.animationDuration}ms`,
|
|
330
|
+
'--sm-easing': this.options.easing,
|
|
331
|
+
'--sm-gap': typeof this.options.gap === 'number'
|
|
332
|
+
? `${this.options.gap}px`
|
|
333
|
+
: this.options.gap,
|
|
334
|
+
'--sm-column-width': `${this.state.columnWidth}px`,
|
|
335
|
+
'--sm-container-height': `${this.state.containerHeight}px`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Social Masonry - Virtualization Engine
|
|
342
|
+
* Handles virtual scrolling for large lists
|
|
343
|
+
*/
|
|
344
|
+
class VirtualizationEngine {
|
|
345
|
+
constructor(options) {
|
|
346
|
+
this.visibleItems = [];
|
|
347
|
+
this.rafId = null;
|
|
348
|
+
this.lastScrollTop = 0;
|
|
349
|
+
this.isScrolling = false;
|
|
350
|
+
this.scrollEndTimeout = null;
|
|
351
|
+
this.options = {
|
|
352
|
+
enabled: true,
|
|
353
|
+
overscan: 3,
|
|
354
|
+
estimatedItemHeight: 400,
|
|
355
|
+
scrollContainer: null,
|
|
356
|
+
...options,
|
|
357
|
+
};
|
|
358
|
+
this.posts = options.posts;
|
|
359
|
+
this.positions = options.positions;
|
|
360
|
+
this.onVisibleItemsChange = options.onVisibleItemsChange;
|
|
361
|
+
this.scrollHandler = throttle(this.handleScroll.bind(this), 16);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Initialize scroll listener
|
|
365
|
+
*/
|
|
366
|
+
init() {
|
|
367
|
+
if (!this.options.enabled)
|
|
368
|
+
return;
|
|
369
|
+
const container = this.options.scrollContainer ?? window;
|
|
370
|
+
container.addEventListener('scroll', this.scrollHandler, { passive: true });
|
|
371
|
+
// Initial calculation
|
|
372
|
+
this.calculateVisibleItems();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Destroy and cleanup
|
|
376
|
+
*/
|
|
377
|
+
destroy() {
|
|
378
|
+
const container = this.options.scrollContainer ?? window;
|
|
379
|
+
container.removeEventListener('scroll', this.scrollHandler);
|
|
380
|
+
if (this.rafId !== null) {
|
|
381
|
+
cancelRaf(this.rafId);
|
|
382
|
+
}
|
|
383
|
+
if (this.scrollEndTimeout) {
|
|
384
|
+
clearTimeout(this.scrollEndTimeout);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Handle scroll event
|
|
389
|
+
*/
|
|
390
|
+
handleScroll() {
|
|
391
|
+
if (this.rafId !== null)
|
|
392
|
+
return;
|
|
393
|
+
this.isScrolling = true;
|
|
394
|
+
// Clear existing scroll end timeout
|
|
395
|
+
if (this.scrollEndTimeout) {
|
|
396
|
+
clearTimeout(this.scrollEndTimeout);
|
|
397
|
+
}
|
|
398
|
+
// Set scroll end detection
|
|
399
|
+
this.scrollEndTimeout = setTimeout(() => {
|
|
400
|
+
this.isScrolling = false;
|
|
401
|
+
}, 150);
|
|
402
|
+
this.rafId = raf(() => {
|
|
403
|
+
this.rafId = null;
|
|
404
|
+
this.calculateVisibleItems();
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Calculate which items are visible
|
|
409
|
+
*/
|
|
410
|
+
calculateVisibleItems() {
|
|
411
|
+
const { scrollTop, viewportHeight } = getScrollPosition(this.options.scrollContainer);
|
|
412
|
+
const overscanPx = this.options.overscan * this.options.estimatedItemHeight;
|
|
413
|
+
this.lastScrollTop = scrollTop;
|
|
414
|
+
const newVisibleItems = [];
|
|
415
|
+
for (let i = 0; i < this.posts.length; i++) {
|
|
416
|
+
const post = this.posts[i];
|
|
417
|
+
const position = this.positions.get(post.id);
|
|
418
|
+
if (!position)
|
|
419
|
+
continue;
|
|
420
|
+
const isVisible = isInViewport({
|
|
421
|
+
top: position.y,
|
|
422
|
+
bottom: position.y + position.height,
|
|
423
|
+
}, scrollTop, viewportHeight, overscanPx);
|
|
424
|
+
if (isVisible) {
|
|
425
|
+
newVisibleItems.push({
|
|
426
|
+
index: i,
|
|
427
|
+
post,
|
|
428
|
+
position,
|
|
429
|
+
isVisible: true,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Check if visible items changed
|
|
434
|
+
const hasChanged = this.hasVisibleItemsChanged(newVisibleItems);
|
|
435
|
+
if (hasChanged) {
|
|
436
|
+
this.visibleItems = newVisibleItems;
|
|
437
|
+
this.onVisibleItemsChange?.(newVisibleItems);
|
|
438
|
+
}
|
|
439
|
+
return this.visibleItems;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Check if visible items have changed
|
|
443
|
+
*/
|
|
444
|
+
hasVisibleItemsChanged(newItems) {
|
|
445
|
+
if (newItems.length !== this.visibleItems.length) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
for (let i = 0; i < newItems.length; i++) {
|
|
449
|
+
if (newItems[i].post.id !== this.visibleItems[i].post.id) {
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Update posts and positions
|
|
457
|
+
*/
|
|
458
|
+
update(posts, positions) {
|
|
459
|
+
this.posts = posts;
|
|
460
|
+
this.positions = positions;
|
|
461
|
+
this.calculateVisibleItems();
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Get visible items
|
|
465
|
+
*/
|
|
466
|
+
getVisibleItems() {
|
|
467
|
+
return this.visibleItems;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Get all items (for non-virtualized mode)
|
|
471
|
+
*/
|
|
472
|
+
getAllItems() {
|
|
473
|
+
return this.posts.map((post, index) => ({
|
|
474
|
+
index,
|
|
475
|
+
post,
|
|
476
|
+
position: this.positions.get(post.id),
|
|
477
|
+
isVisible: true,
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Check if scrolling
|
|
482
|
+
*/
|
|
483
|
+
getIsScrolling() {
|
|
484
|
+
return this.isScrolling;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Get scroll direction
|
|
488
|
+
*/
|
|
489
|
+
getScrollDirection() {
|
|
490
|
+
const { scrollTop } = getScrollPosition(this.options.scrollContainer);
|
|
491
|
+
if (scrollTop > this.lastScrollTop) {
|
|
492
|
+
return 'down';
|
|
493
|
+
}
|
|
494
|
+
else if (scrollTop < this.lastScrollTop) {
|
|
495
|
+
return 'up';
|
|
496
|
+
}
|
|
497
|
+
return 'none';
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Check if near bottom (for infinite scroll)
|
|
501
|
+
*/
|
|
502
|
+
isNearBottom(threshold = 500) {
|
|
503
|
+
const { scrollTop, viewportHeight } = getScrollPosition(this.options.scrollContainer);
|
|
504
|
+
// Get container height from positions
|
|
505
|
+
let maxBottom = 0;
|
|
506
|
+
for (const position of this.positions.values()) {
|
|
507
|
+
maxBottom = Math.max(maxBottom, position.y + position.height);
|
|
508
|
+
}
|
|
509
|
+
return scrollTop + viewportHeight >= maxBottom - threshold;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Scroll to item
|
|
513
|
+
*/
|
|
514
|
+
scrollToItem(id, behavior = 'smooth') {
|
|
515
|
+
const position = this.positions.get(id);
|
|
516
|
+
if (!position)
|
|
517
|
+
return;
|
|
518
|
+
const container = this.options.scrollContainer ?? window;
|
|
519
|
+
if (container === window) {
|
|
520
|
+
window.scrollTo({
|
|
521
|
+
top: position.y,
|
|
522
|
+
behavior,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
container.scrollTo({
|
|
527
|
+
top: position.y,
|
|
528
|
+
behavior,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Get render range for optimization
|
|
534
|
+
*/
|
|
535
|
+
getRenderRange() {
|
|
536
|
+
if (this.visibleItems.length === 0) {
|
|
537
|
+
return { start: 0, end: 0 };
|
|
538
|
+
}
|
|
539
|
+
const indices = this.visibleItems.map(item => item.index);
|
|
540
|
+
return {
|
|
541
|
+
start: Math.min(...indices),
|
|
542
|
+
end: Math.max(...indices) + 1,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ============================================
|
|
548
|
+
// Icons
|
|
549
|
+
// ============================================
|
|
550
|
+
const TwitterIcon = ({ className }) => (jsx("svg", { viewBox: "0 0 24 24", className: className, children: 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" }) }));
|
|
551
|
+
const InstagramIcon = ({ className }) => (jsx("svg", { viewBox: "0 0 24 24", className: className, children: 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" }) }));
|
|
552
|
+
const VerifiedIcon = ({ className }) => (jsx("svg", { viewBox: "0 0 24 24", className: className, children: 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" }) }));
|
|
553
|
+
const PlayIcon = ({ className }) => (jsx("svg", { viewBox: "0 0 24 24", className: className, children: jsx("path", { fill: "currentColor", d: "M8 5v14l11-7z" }) }));
|
|
554
|
+
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, }) => {
|
|
555
|
+
const cardRef = useRef(null);
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
if (cardRef.current && onHeightChange) {
|
|
558
|
+
const height = cardRef.current.offsetHeight;
|
|
559
|
+
if (height !== position.height) {
|
|
560
|
+
onHeightChange(post.id, height);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}, [post.id, position.height, onHeightChange]);
|
|
564
|
+
const handlePostClick = useCallback((e) => {
|
|
565
|
+
const target = e.target;
|
|
566
|
+
if (target.tagName === 'A' || target.closest('a'))
|
|
567
|
+
return;
|
|
568
|
+
onPostClick?.(post, e);
|
|
569
|
+
}, [post, onPostClick]);
|
|
570
|
+
const handleAuthorClick = useCallback((e) => {
|
|
571
|
+
e.stopPropagation();
|
|
572
|
+
onAuthorClick?.(post, e);
|
|
573
|
+
}, [post, onAuthorClick]);
|
|
574
|
+
const handleMediaClick = useCallback((index) => (e) => {
|
|
575
|
+
e.stopPropagation();
|
|
576
|
+
onMediaClick?.(post, index, e);
|
|
577
|
+
}, [post, onMediaClick]);
|
|
578
|
+
const classes = [
|
|
579
|
+
'sm-card',
|
|
580
|
+
`sm-card--${post.platform}`,
|
|
581
|
+
`sm-card--${variant}`,
|
|
582
|
+
`sm-card--${theme}`,
|
|
583
|
+
hoverEffect && 'sm-card--hover',
|
|
584
|
+
className,
|
|
585
|
+
].filter(Boolean).join(' ');
|
|
586
|
+
const style = {
|
|
587
|
+
position: 'absolute',
|
|
588
|
+
left: position.x,
|
|
589
|
+
top: position.y,
|
|
590
|
+
width: position.width,
|
|
591
|
+
borderRadius: typeof borderRadius === 'number' ? borderRadius : borderRadius,
|
|
592
|
+
cursor: onPostClick ? 'pointer' : undefined,
|
|
593
|
+
};
|
|
594
|
+
const date = post.createdAt instanceof Date ? post.createdAt : new Date(post.createdAt);
|
|
595
|
+
return (jsxs("div", { ref: cardRef, className: classes, style: style, onClick: handlePostClick, "data-post-id": post.id, "data-platform": post.platform, children: [showPlatformIcon && (jsx("div", { className: "sm-card__platform-icon", children: post.platform === 'twitter' ? (jsx(TwitterIcon, { className: "sm-platform-icon sm-platform-icon--twitter" })) : (jsx(InstagramIcon, { className: "sm-platform-icon sm-platform-icon--instagram" })) })), showAuthor && (jsxs("div", { className: "sm-card__header", onClick: onAuthorClick ? handleAuthorClick : undefined, style: { cursor: onAuthorClick ? 'pointer' : undefined }, children: [jsx("img", { src: post.author.avatarUrl || fallbackImage, alt: post.author.displayName || post.author.username, className: "sm-card__avatar", loading: imageLoading, onError: (e) => {
|
|
596
|
+
if (fallbackImage) {
|
|
597
|
+
e.target.src = fallbackImage;
|
|
598
|
+
}
|
|
599
|
+
} }), jsxs("div", { className: "sm-card__author", children: [jsxs("span", { className: "sm-card__author-name", children: [post.author.displayName || post.author.username, post.author.verified && jsx(VerifiedIcon, { className: "sm-card__verified" })] }), jsxs("span", { className: "sm-card__author-handle", children: ["@", post.author.username] })] })] })), jsxs("div", { className: "sm-card__content", children: [isTwitterPost(post) && (jsx("p", { className: "sm-card__text", children: post.content.text })), isInstagramPost(post) && post.content.caption && (jsx("p", { className: "sm-card__caption", children: post.content.caption.length > 150
|
|
600
|
+
? `${post.content.caption.substring(0, 150)}...`
|
|
601
|
+
: post.content.caption }))] }), isTwitterPost(post) && post.media && post.media.length > 0 && (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) => (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: [jsx("img", { src: media.thumbnailUrl || media.url, alt: "Post media", loading: imageLoading, onError: (e) => {
|
|
602
|
+
if (fallbackImage) {
|
|
603
|
+
e.target.src = fallbackImage;
|
|
604
|
+
}
|
|
605
|
+
} }), (media.type === 'video' || media.type === 'gif') && (jsx("div", { className: "sm-card__play-button", children: jsx(PlayIcon, {}) }))] }, index))) })), isInstagramPost(post) && (jsxs("div", { className: "sm-card__media", style: { aspectRatio: post.media.aspectRatio || 1 }, children: [jsx("img", { src: post.media.thumbnailUrl || post.media.url, alt: "Post media", loading: imageLoading, onError: (e) => {
|
|
606
|
+
if (fallbackImage) {
|
|
607
|
+
e.target.src = fallbackImage;
|
|
608
|
+
}
|
|
609
|
+
} }), post.media.type === 'video' && (jsx("div", { className: "sm-card__play-button", children: jsx(PlayIcon, {}) })), post.media.type === 'carousel' && (jsx("div", { className: "sm-card__carousel-indicator", children: jsx("svg", { viewBox: "0 0 24 24", children: 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) && (jsxs("div", { className: "sm-card__footer", children: [showMetrics && post.metrics && (jsxs("div", { className: "sm-card__metrics", children: [isTwitterPost(post) && (jsxs(Fragment, { children: [post.metrics.replies !== undefined && (jsxs("span", { className: "sm-card__metric", children: [jsx("svg", { viewBox: "0 0 24 24", children: 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 && (jsxs("span", { className: "sm-card__metric", children: [jsx("svg", { viewBox: "0 0 24 24", children: 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 && (jsxs("span", { className: "sm-card__metric", children: [jsx("svg", { viewBox: "0 0 24 24", children: 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) && (jsxs(Fragment, { children: [post.metrics.likes !== undefined && (jsxs("span", { className: "sm-card__metric", children: [jsx("svg", { viewBox: "0 0 24 24", children: 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 && (jsxs("span", { className: "sm-card__metric", children: [jsx("svg", { viewBox: "0 0 24 24", children: 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 && (jsx("time", { className: "sm-card__timestamp", dateTime: date.toISOString(), children: formatDate(date) }))] }))] }));
|
|
610
|
+
};
|
|
611
|
+
const SocialMasonry = forwardRef((props, ref) => {
|
|
612
|
+
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;
|
|
613
|
+
const containerRef = useRef(null);
|
|
614
|
+
const [posts, setPosts] = useState(initialPosts);
|
|
615
|
+
const [positions, setPositions] = useState(new Map());
|
|
616
|
+
const [containerHeight, setContainerHeight] = useState(0);
|
|
617
|
+
const [itemHeights] = useState(() => new Map());
|
|
618
|
+
const [visibleItems, setVisibleItems] = useState([]);
|
|
619
|
+
const layoutEngine = useMemo(() => {
|
|
620
|
+
return new LayoutEngine({
|
|
621
|
+
gap,
|
|
622
|
+
columns,
|
|
623
|
+
padding,
|
|
624
|
+
animationDuration,
|
|
625
|
+
animate,
|
|
626
|
+
easing,
|
|
627
|
+
containerWidth: containerRef.current?.clientWidth || 800,
|
|
628
|
+
itemHeights,
|
|
629
|
+
});
|
|
630
|
+
}, [gap, columns, padding, animationDuration, animate, easing]);
|
|
631
|
+
const virtualizationEngine = useMemo(() => {
|
|
632
|
+
if (!virtualization?.enabled)
|
|
633
|
+
return null;
|
|
634
|
+
return new VirtualizationEngine({
|
|
635
|
+
...virtualization,
|
|
636
|
+
posts,
|
|
637
|
+
positions,
|
|
638
|
+
onVisibleItemsChange: setVisibleItems,
|
|
639
|
+
});
|
|
640
|
+
}, [virtualization?.enabled]);
|
|
641
|
+
// Calculate layout
|
|
642
|
+
const calculateLayout = useCallback(() => {
|
|
643
|
+
if (!containerRef.current)
|
|
644
|
+
return;
|
|
645
|
+
layoutEngine.setContainerWidth(containerRef.current.clientWidth);
|
|
646
|
+
const state = layoutEngine.calculate(posts);
|
|
647
|
+
setPositions(state.positions);
|
|
648
|
+
setContainerHeight(state.containerHeight);
|
|
649
|
+
if (virtualizationEngine) {
|
|
650
|
+
virtualizationEngine.update(posts, state.positions);
|
|
651
|
+
setVisibleItems(virtualizationEngine.calculateVisibleItems());
|
|
652
|
+
}
|
|
653
|
+
onLayoutComplete?.(Array.from(state.positions.values()));
|
|
654
|
+
}, [posts, layoutEngine, virtualizationEngine, onLayoutComplete]);
|
|
655
|
+
// Handle resize
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
const handleResize = debounce(() => {
|
|
658
|
+
calculateLayout();
|
|
659
|
+
}, 150);
|
|
660
|
+
const resizeObserver = new ResizeObserver(handleResize);
|
|
661
|
+
if (containerRef.current) {
|
|
662
|
+
resizeObserver.observe(containerRef.current);
|
|
663
|
+
}
|
|
664
|
+
return () => {
|
|
665
|
+
resizeObserver.disconnect();
|
|
666
|
+
};
|
|
667
|
+
}, [calculateLayout]);
|
|
668
|
+
// Initial layout
|
|
669
|
+
useEffect(() => {
|
|
670
|
+
calculateLayout();
|
|
671
|
+
}, [posts, calculateLayout]);
|
|
672
|
+
// Initialize virtualization
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
if (virtualizationEngine) {
|
|
675
|
+
virtualizationEngine.init();
|
|
676
|
+
return () => virtualizationEngine.destroy();
|
|
677
|
+
}
|
|
678
|
+
return undefined;
|
|
679
|
+
}, [virtualizationEngine]);
|
|
680
|
+
// Handle height change
|
|
681
|
+
const handleHeightChange = useCallback((id, height) => {
|
|
682
|
+
itemHeights.set(id, height);
|
|
683
|
+
calculateLayout();
|
|
684
|
+
}, [itemHeights, calculateLayout]);
|
|
685
|
+
// Expose methods via ref
|
|
686
|
+
useImperativeHandle(ref, () => ({
|
|
687
|
+
addPosts: (newPosts) => {
|
|
688
|
+
setPosts(prev => [...prev, ...newPosts]);
|
|
689
|
+
},
|
|
690
|
+
setPosts: (newPosts) => {
|
|
691
|
+
itemHeights.clear();
|
|
692
|
+
setPosts(newPosts);
|
|
693
|
+
},
|
|
694
|
+
removePost: (id) => {
|
|
695
|
+
itemHeights.delete(id);
|
|
696
|
+
setPosts(prev => prev.filter(p => p.id !== id));
|
|
697
|
+
},
|
|
698
|
+
refresh: () => {
|
|
699
|
+
itemHeights.clear();
|
|
700
|
+
calculateLayout();
|
|
701
|
+
},
|
|
702
|
+
scrollToPost: (id, behavior = 'smooth') => {
|
|
703
|
+
const position = positions.get(id);
|
|
704
|
+
if (position) {
|
|
705
|
+
window.scrollTo({ top: position.y, behavior });
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
}), [positions, calculateLayout, itemHeights]);
|
|
709
|
+
// Determine which posts to render
|
|
710
|
+
const postsToRender = virtualizationEngine
|
|
711
|
+
? visibleItems.map(item => item.post)
|
|
712
|
+
: posts;
|
|
713
|
+
return (jsxs("div", { ref: containerRef, className: `sm-container ${className || ''}`, style: {
|
|
714
|
+
position: 'relative',
|
|
715
|
+
width: '100%',
|
|
716
|
+
height: containerHeight,
|
|
717
|
+
...style,
|
|
718
|
+
}, children: [postsToRender.map(post => {
|
|
719
|
+
const position = positions.get(post.id);
|
|
720
|
+
if (!position)
|
|
721
|
+
return null;
|
|
722
|
+
// Extract event handlers and adapt types
|
|
723
|
+
const { onPostClick, onAuthorClick, onMediaClick, ...restCardProps } = cardProps;
|
|
724
|
+
return (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));
|
|
725
|
+
}), posts.length === 0 && (jsx("div", { className: "sm-empty", children: "No posts to display" }))] }));
|
|
726
|
+
});
|
|
727
|
+
SocialMasonry.displayName = 'SocialMasonry';
|
|
728
|
+
|
|
729
|
+
export { SocialMasonry, SocialMasonry as default };
|
|
730
|
+
//# sourceMappingURL=index.esm.js.map
|