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.
- package/README.md +97 -308
- package/dist/index.d.ts +62 -437
- package/dist/index.esm.js +93 -1354
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +101 -1366
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +35 -141
- package/dist/react/index.esm.js +280 -651
- package/dist/react/index.esm.js.map +1 -1
- package/dist/react/index.js +278 -649
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,291 +1,103 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/tkana-dev/social-masonry/main/assets/logo.svg" alt="Social Masonry" width="120" />
|
|
3
2
|
<h1>Social Masonry</h1>
|
|
4
|
-
<p><strong>
|
|
5
|
-
|
|
3
|
+
<p><strong>Masonry layout for X (Twitter) and Instagram embeds using official widgets</strong></p>
|
|
4
|
+
|
|
6
5
|
[](https://www.npmjs.com/package/social-masonry)
|
|
7
6
|
[](https://bundlephobia.com/package/social-masonry)
|
|
8
7
|
[](https://github.com/tkana-dev/social-masonry/blob/main/LICENSE)
|
|
9
8
|
[](https://www.typescriptlang.org/)
|
|
10
|
-
|
|
11
|
-
<a href="https://social-masonry.dev">Demo</a> · <a href="#installation">Installation</a> · <a href="#usage">Usage</a> · <a href="#api">API</a>
|
|
12
9
|
</div>
|
|
13
10
|
|
|
14
11
|
---
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
<img src="https://raw.githubusercontent.com/tkana-dev/social-masonry/main/assets/demo.gif" alt="Demo" width="100%" />
|
|
18
|
-
</p>
|
|
19
|
-
|
|
20
|
-
## ✨ Features
|
|
13
|
+
## Features
|
|
21
14
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- 🪶 **Lightweight** - ~8KB gzipped with zero dependencies
|
|
15
|
+
- **Official Widgets** - Uses Twitter's `widgets.js` and Instagram's `embed.js` for native embeds
|
|
16
|
+
- **Auto-sizing** - Embeds automatically adjust to their content height
|
|
17
|
+
- **Responsive** - Adaptive column layouts that look great on any screen size
|
|
18
|
+
- **Themeable** - Light and dark theme support for Twitter embeds
|
|
19
|
+
- **TypeScript** - Full type safety
|
|
20
|
+
- **React Ready** - First-class React component with ref support
|
|
21
|
+
- **Lightweight** - Minimal bundle size, widgets loaded on-demand
|
|
30
22
|
|
|
31
|
-
##
|
|
23
|
+
## Installation
|
|
32
24
|
|
|
33
25
|
```bash
|
|
34
|
-
# npm
|
|
35
26
|
npm install social-masonry
|
|
36
|
-
|
|
37
|
-
# yarn
|
|
38
|
-
yarn add social-masonry
|
|
39
|
-
|
|
40
|
-
# pnpm
|
|
41
|
-
pnpm add social-masonry
|
|
42
27
|
```
|
|
43
28
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
### Vanilla JavaScript
|
|
47
|
-
|
|
48
|
-
```javascript
|
|
49
|
-
import { createSocialMasonry } from 'social-masonry';
|
|
50
|
-
import 'social-masonry/styles';
|
|
51
|
-
|
|
52
|
-
const masonry = createSocialMasonry({
|
|
53
|
-
container: '#posts',
|
|
54
|
-
posts: [
|
|
55
|
-
{
|
|
56
|
-
platform: 'twitter',
|
|
57
|
-
id: '1',
|
|
58
|
-
url: 'https://twitter.com/user/status/123',
|
|
59
|
-
author: {
|
|
60
|
-
username: 'johndoe',
|
|
61
|
-
displayName: 'John Doe',
|
|
62
|
-
avatarUrl: 'https://...',
|
|
63
|
-
verified: true,
|
|
64
|
-
},
|
|
65
|
-
content: {
|
|
66
|
-
text: 'Hello, world! 🌍',
|
|
67
|
-
},
|
|
68
|
-
metrics: {
|
|
69
|
-
likes: 1234,
|
|
70
|
-
retweets: 567,
|
|
71
|
-
replies: 89,
|
|
72
|
-
},
|
|
73
|
-
createdAt: '2024-01-15T10:30:00Z',
|
|
74
|
-
},
|
|
75
|
-
// ... more posts
|
|
76
|
-
],
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Add more posts
|
|
80
|
-
masonry.addPosts(newPosts);
|
|
81
|
-
|
|
82
|
-
// Clean up
|
|
83
|
-
masonry.destroy();
|
|
84
|
-
```
|
|
29
|
+
## Quick Start
|
|
85
30
|
|
|
86
31
|
### React
|
|
87
32
|
|
|
88
33
|
```tsx
|
|
89
34
|
import { SocialMasonry } from 'social-masonry/react';
|
|
90
|
-
import 'social-masonry/styles';
|
|
91
35
|
|
|
92
36
|
function App() {
|
|
93
|
-
const posts =
|
|
94
|
-
|
|
37
|
+
const posts = [
|
|
38
|
+
{
|
|
39
|
+
id: '1',
|
|
40
|
+
platform: 'twitter',
|
|
41
|
+
url: 'https://twitter.com/username/status/1234567890',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: '2',
|
|
45
|
+
platform: 'instagram',
|
|
46
|
+
url: 'https://www.instagram.com/p/ABC123xyz/',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
95
50
|
return (
|
|
96
51
|
<SocialMasonry
|
|
97
52
|
posts={posts}
|
|
98
53
|
columns={[
|
|
99
|
-
{ columns: 4, minWidth:
|
|
100
|
-
{ columns: 3, minWidth:
|
|
101
|
-
{ columns: 2, minWidth:
|
|
54
|
+
{ columns: 4, minWidth: 1536 },
|
|
55
|
+
{ columns: 3, minWidth: 1024 },
|
|
56
|
+
{ columns: 2, minWidth: 640 },
|
|
102
57
|
{ columns: 1, minWidth: 0 },
|
|
103
58
|
]}
|
|
104
59
|
gap={16}
|
|
105
|
-
|
|
106
|
-
theme="auto"
|
|
107
|
-
onPostClick={(post) => window.open(post.url)}
|
|
60
|
+
theme="light"
|
|
108
61
|
/>
|
|
109
62
|
);
|
|
110
63
|
}
|
|
111
64
|
```
|
|
112
65
|
|
|
113
|
-
##
|
|
114
|
-
|
|
115
|
-
### Post Types
|
|
66
|
+
## API
|
|
116
67
|
|
|
117
|
-
|
|
68
|
+
### SocialMasonry Props
|
|
118
69
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
130
|
-
content: {
|
|
131
|
-
text: 'This is my tweet! #awesome',
|
|
132
|
-
html: 'This is my tweet! <a href="#">#awesome</a>', // Optional: pre-rendered HTML
|
|
133
|
-
},
|
|
134
|
-
media: [
|
|
135
|
-
{
|
|
136
|
-
type: 'image',
|
|
137
|
-
url: 'https://example.com/image.jpg',
|
|
138
|
-
aspectRatio: 16 / 9,
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
metrics: {
|
|
142
|
-
likes: 1234,
|
|
143
|
-
retweets: 567,
|
|
144
|
-
replies: 89,
|
|
145
|
-
views: 50000,
|
|
146
|
-
},
|
|
147
|
-
quotedPost: { /* nested TwitterPost */ }, // Optional
|
|
148
|
-
createdAt: '2024-01-15T10:30:00Z',
|
|
149
|
-
};
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
#### Instagram Post
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
const instagramPost: InstagramPost = {
|
|
156
|
-
platform: 'instagram',
|
|
157
|
-
id: 'unique-id',
|
|
158
|
-
url: 'https://instagram.com/p/ABC123',
|
|
159
|
-
author: {
|
|
160
|
-
username: 'janedoe',
|
|
161
|
-
displayName: 'Jane Doe',
|
|
162
|
-
avatarUrl: 'https://example.com/avatar.jpg',
|
|
163
|
-
verified: false,
|
|
164
|
-
},
|
|
165
|
-
content: {
|
|
166
|
-
caption: 'Beautiful sunset 🌅 #photography',
|
|
167
|
-
},
|
|
168
|
-
media: {
|
|
169
|
-
type: 'image', // 'image' | 'video' | 'carousel'
|
|
170
|
-
url: 'https://example.com/image.jpg',
|
|
171
|
-
aspectRatio: 1, // Square
|
|
172
|
-
carouselItems: [ /* for carousel type */ ],
|
|
173
|
-
},
|
|
174
|
-
metrics: {
|
|
175
|
-
likes: 5678,
|
|
176
|
-
comments: 123,
|
|
177
|
-
},
|
|
178
|
-
createdAt: '2024-01-15T10:30:00Z',
|
|
179
|
-
};
|
|
180
|
-
```
|
|
70
|
+
| Prop | Type | Default | Description |
|
|
71
|
+
|------|------|---------|-------------|
|
|
72
|
+
| `posts` | `SocialPost[]` | `[]` | Array of posts to display |
|
|
73
|
+
| `columns` | `number \| ColumnConfig[]` | `3` | Number of columns or responsive config |
|
|
74
|
+
| `gap` | `number` | `16` | Gap between items in pixels |
|
|
75
|
+
| `theme` | `'light' \| 'dark'` | `'light'` | Theme for Twitter embeds |
|
|
76
|
+
| `className` | `string` | - | Custom class for container |
|
|
77
|
+
| `style` | `CSSProperties` | - | Custom styles for container |
|
|
78
|
+
| `onEmbedLoad` | `(post: SocialPost) => void` | - | Called when an embed loads |
|
|
79
|
+
| `onEmbedError` | `(post: SocialPost, error: Error) => void` | - | Called on embed error |
|
|
181
80
|
|
|
182
|
-
###
|
|
81
|
+
### SocialPost Type
|
|
183
82
|
|
|
184
83
|
```typescript
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Layout
|
|
191
|
-
gap: 16, // Gap between cards (px)
|
|
192
|
-
columns: [ // Responsive breakpoints
|
|
193
|
-
{ columns: 4, minWidth: 1200 },
|
|
194
|
-
{ columns: 3, minWidth: 900 },
|
|
195
|
-
{ columns: 2, minWidth: 600 },
|
|
196
|
-
{ columns: 1, minWidth: 0 },
|
|
197
|
-
],
|
|
198
|
-
padding: 0, // Container padding
|
|
199
|
-
|
|
200
|
-
// Animation
|
|
201
|
-
animate: true,
|
|
202
|
-
animationDuration: 300,
|
|
203
|
-
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
204
|
-
|
|
205
|
-
// Card Styling
|
|
206
|
-
variant: 'default', // 'default' | 'minimal' | 'elevated' | 'bordered' | 'glass'
|
|
207
|
-
theme: 'auto', // 'light' | 'dark' | 'auto'
|
|
208
|
-
borderRadius: 12,
|
|
209
|
-
hoverEffect: true,
|
|
210
|
-
|
|
211
|
-
// Content
|
|
212
|
-
showPlatformIcon: true,
|
|
213
|
-
showAuthor: true,
|
|
214
|
-
showMetrics: true,
|
|
215
|
-
showTimestamp: true,
|
|
216
|
-
|
|
217
|
-
// Custom Formatters
|
|
218
|
-
formatDate: (date) => formatRelativeTime(date),
|
|
219
|
-
formatNumber: (num) => formatNumber(num),
|
|
220
|
-
|
|
221
|
-
// Image Loading
|
|
222
|
-
imageLoading: 'lazy', // 'lazy' | 'eager'
|
|
223
|
-
fallbackImage: 'https://...', // Fallback for broken images
|
|
224
|
-
|
|
225
|
-
// Virtualization (for large lists)
|
|
226
|
-
virtualization: {
|
|
227
|
-
enabled: false,
|
|
228
|
-
overscan: 3,
|
|
229
|
-
estimatedItemHeight: 400,
|
|
230
|
-
scrollContainer: null, // Default: window
|
|
231
|
-
},
|
|
232
|
-
|
|
233
|
-
// Infinite Scroll
|
|
234
|
-
loadMoreThreshold: 500,
|
|
235
|
-
showLoading: true,
|
|
236
|
-
loadingElement: '<div>Loading...</div>',
|
|
237
|
-
emptyMessage: 'No posts to display',
|
|
238
|
-
|
|
239
|
-
// Events
|
|
240
|
-
onPostClick: (post, event) => {},
|
|
241
|
-
onAuthorClick: (post, event) => {},
|
|
242
|
-
onMediaClick: (post, mediaIndex, event) => {},
|
|
243
|
-
onLayoutComplete: (positions) => {},
|
|
244
|
-
onLoadMore: async () => {},
|
|
245
|
-
onImageError: (post, error) => {},
|
|
246
|
-
});
|
|
84
|
+
interface SocialPost {
|
|
85
|
+
id?: string; // Optional unique identifier
|
|
86
|
+
platform: 'twitter' | 'instagram'; // Social platform
|
|
87
|
+
url: string; // Post URL
|
|
88
|
+
}
|
|
247
89
|
```
|
|
248
90
|
|
|
249
|
-
###
|
|
250
|
-
|
|
251
|
-
| Variant | Description |
|
|
252
|
-
|---------|-------------|
|
|
253
|
-
| `default` | Clean card with subtle border and shadow |
|
|
254
|
-
| `minimal` | No background, perfect for embedding |
|
|
255
|
-
| `elevated` | Floating card with prominent shadow |
|
|
256
|
-
| `bordered` | Strong border, no shadow |
|
|
257
|
-
| `glass` | Glassmorphism effect with blur |
|
|
258
|
-
|
|
259
|
-
### API Methods
|
|
91
|
+
### ColumnConfig Type
|
|
260
92
|
|
|
261
93
|
```typescript
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
masonry.setPosts(posts);
|
|
267
|
-
|
|
268
|
-
// Remove a post
|
|
269
|
-
masonry.removePost(postId);
|
|
270
|
-
|
|
271
|
-
// Update options
|
|
272
|
-
masonry.setOptions({ theme: 'dark' });
|
|
273
|
-
|
|
274
|
-
// Get current state
|
|
275
|
-
const state = masonry.getLayoutState();
|
|
276
|
-
const posts = masonry.getPosts();
|
|
277
|
-
|
|
278
|
-
// Scroll to a post
|
|
279
|
-
masonry.scrollToPost(postId, 'smooth');
|
|
280
|
-
|
|
281
|
-
// Recalculate layout
|
|
282
|
-
masonry.refresh();
|
|
283
|
-
|
|
284
|
-
// Clean up
|
|
285
|
-
masonry.destroy();
|
|
94
|
+
interface ColumnConfig {
|
|
95
|
+
columns: number; // Number of columns
|
|
96
|
+
minWidth: number; // Minimum container width for this config
|
|
97
|
+
}
|
|
286
98
|
```
|
|
287
99
|
|
|
288
|
-
###
|
|
100
|
+
### Ref Methods
|
|
289
101
|
|
|
290
102
|
```tsx
|
|
291
103
|
import { useRef } from 'react';
|
|
@@ -293,87 +105,71 @@ import { SocialMasonry, SocialMasonryRef } from 'social-masonry/react';
|
|
|
293
105
|
|
|
294
106
|
function App() {
|
|
295
107
|
const masonryRef = useRef<SocialMasonryRef>(null);
|
|
296
|
-
|
|
108
|
+
|
|
297
109
|
const handleAddPost = () => {
|
|
298
110
|
masonryRef.current?.addPosts([newPost]);
|
|
299
111
|
};
|
|
300
|
-
|
|
112
|
+
|
|
301
113
|
return (
|
|
302
114
|
<SocialMasonry
|
|
303
115
|
ref={masonryRef}
|
|
304
116
|
posts={posts}
|
|
305
|
-
// ...
|
|
306
117
|
/>
|
|
307
118
|
);
|
|
308
119
|
}
|
|
309
120
|
```
|
|
310
121
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
### CSS Variables
|
|
314
|
-
|
|
315
|
-
Override the default theme using CSS variables:
|
|
316
|
-
|
|
317
|
-
```css
|
|
318
|
-
:root {
|
|
319
|
-
/* Colors */
|
|
320
|
-
--sm-bg: #ffffff;
|
|
321
|
-
--sm-bg-hover: #f7f9fa;
|
|
322
|
-
--sm-text: #0f1419;
|
|
323
|
-
--sm-text-secondary: #536471;
|
|
324
|
-
--sm-text-muted: #8b98a5;
|
|
325
|
-
--sm-border: #eff3f4;
|
|
326
|
-
--sm-link: #1d9bf0;
|
|
327
|
-
|
|
328
|
-
/* Brand Colors */
|
|
329
|
-
--sm-twitter-primary: #1d9bf0;
|
|
330
|
-
--sm-instagram-primary: #e1306c;
|
|
331
|
-
|
|
332
|
-
/* Shadows */
|
|
333
|
-
--sm-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
334
|
-
--sm-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
335
|
-
--sm-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
336
|
-
|
|
337
|
-
/* Transitions */
|
|
338
|
-
--sm-transition-fast: 150ms ease;
|
|
339
|
-
--sm-transition-base: 200ms ease;
|
|
340
|
-
}
|
|
341
|
-
```
|
|
122
|
+
Available ref methods:
|
|
342
123
|
|
|
343
|
-
|
|
124
|
+
| Method | Description |
|
|
125
|
+
|--------|-------------|
|
|
126
|
+
| `addPosts(posts)` | Add posts to the end |
|
|
127
|
+
| `setPosts(posts)` | Replace all posts |
|
|
128
|
+
| `removePost(id)` | Remove a post by ID |
|
|
129
|
+
| `refresh()` | Re-process embeds |
|
|
344
130
|
|
|
345
|
-
|
|
131
|
+
## Supported URL Formats
|
|
346
132
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
});
|
|
352
|
-
```
|
|
133
|
+
### Twitter/X
|
|
134
|
+
|
|
135
|
+
- `https://twitter.com/username/status/1234567890`
|
|
136
|
+
- `https://x.com/username/status/1234567890`
|
|
353
137
|
|
|
354
|
-
|
|
138
|
+
### Instagram
|
|
355
139
|
|
|
356
|
-
|
|
140
|
+
- `https://www.instagram.com/p/ABC123xyz/`
|
|
141
|
+
- `https://www.instagram.com/reel/ABC123xyz/`
|
|
357
142
|
|
|
358
|
-
|
|
359
|
-
- **Efficient Layout**: Uses a columnar algorithm with O(n) complexity
|
|
360
|
-
- **Smart Updates**: Batched DOM updates and position caching
|
|
361
|
-
- **Lazy Loading**: Images load only when cards enter the viewport
|
|
143
|
+
## Utilities
|
|
362
144
|
|
|
363
|
-
|
|
145
|
+
The library exports utility functions for URL parsing:
|
|
364
146
|
|
|
365
147
|
```typescript
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
148
|
+
import {
|
|
149
|
+
extractTweetId,
|
|
150
|
+
extractInstagramId,
|
|
151
|
+
detectPlatform,
|
|
152
|
+
} from 'social-masonry';
|
|
153
|
+
|
|
154
|
+
// Extract tweet ID from URL
|
|
155
|
+
extractTweetId('https://x.com/user/status/123456'); // '123456'
|
|
156
|
+
|
|
157
|
+
// Extract Instagram post ID
|
|
158
|
+
extractInstagramId('https://instagram.com/p/ABC123'); // 'ABC123'
|
|
159
|
+
|
|
160
|
+
// Detect platform from URL
|
|
161
|
+
detectPlatform('https://x.com/user/status/123'); // 'twitter'
|
|
162
|
+
detectPlatform('https://instagram.com/p/ABC'); // 'instagram'
|
|
374
163
|
```
|
|
375
164
|
|
|
376
|
-
##
|
|
165
|
+
## How It Works
|
|
166
|
+
|
|
167
|
+
1. **Script Loading**: Twitter's `widgets.js` and Instagram's `embed.js` are loaded on-demand when needed
|
|
168
|
+
2. **Widget Creation**: Official APIs (`twttr.widgets.createTweet` and `instgrm.Embeds.process`) create native embeds
|
|
169
|
+
3. **Masonry Layout**: Posts are distributed across columns using a simple round-robin algorithm
|
|
170
|
+
4. **Auto-sizing**: Each embed automatically sizes to its content - no fixed heights
|
|
171
|
+
|
|
172
|
+
## Browser Support
|
|
377
173
|
|
|
378
174
|
| Browser | Version |
|
|
379
175
|
|---------|---------|
|
|
@@ -382,13 +178,6 @@ createSocialMasonry({
|
|
|
382
178
|
| Safari | 14+ |
|
|
383
179
|
| Edge | 80+ |
|
|
384
180
|
|
|
385
|
-
##
|
|
181
|
+
## License
|
|
386
182
|
|
|
387
|
-
MIT
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
<div align="center">
|
|
392
|
-
<p>If you find this project useful, please consider giving it a ⭐️</p>
|
|
393
|
-
<a href="https://github.com/tkana-dev/social-masonry">GitHub</a> · <a href="https://www.npmjs.com/package/social-masonry">npm</a> · <a href="https://social-masonry.dev">Demo</a>
|
|
394
|
-
</div>
|
|
183
|
+
MIT
|