react-instagram-stories 0.0.1 → 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 +647 -0
- package/dist/index.cjs +18 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -10
- package/index.js +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
# React Instagram Stories
|
|
2
|
+
|
|
3
|
+
A high-performance, fully customizable Instagram-style Stories component for React with TypeScript support. Build engaging story experiences with images, videos, text, and custom components.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/react-instagram-stories)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- 🎬 **Multiple Content Types**: Images, videos (with audio), text, and fully custom components
|
|
11
|
+
- 🎨 **Fully Customizable**: Style every aspect of the stories
|
|
12
|
+
- ⚡ **High Performance**: Optimized rendering with intelligent preloading
|
|
13
|
+
- 📱 **Touch & Gestures**: Tap, swipe, and hold interactions
|
|
14
|
+
- ⌨️ **Keyboard Navigation**: Full keyboard support for accessibility
|
|
15
|
+
- 🎯 **TypeScript**: Complete type definitions included
|
|
16
|
+
- ♿ **Accessible**: ARIA labels and keyboard navigation
|
|
17
|
+
- 📦 **Lightweight**: Only **74.8 KB** with zero runtime dependencies
|
|
18
|
+
- 🔄 **Auto Progress**: Smart progress bar that pauses during video buffering
|
|
19
|
+
- 🎭 **Smooth Transitions**: Beautiful animations between stories and users
|
|
20
|
+
- 🔌 **React Router Integration**: Built-in URL-based navigation support
|
|
21
|
+
|
|
22
|
+
## 📦 Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install react-instagram-stories react-router-dom
|
|
26
|
+
# or
|
|
27
|
+
yarn add react-instagram-stories react-router-dom
|
|
28
|
+
# or
|
|
29
|
+
pnpm add react-instagram-stories react-router-dom
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Note**: `react-router-dom` is required for URL-based story navigation.
|
|
33
|
+
|
|
34
|
+
## 🚀 Quick Start
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
38
|
+
import { Stories, demoUsers } from 'react-instagram-stories';
|
|
39
|
+
import 'react-instagram-stories/styles.css';
|
|
40
|
+
|
|
41
|
+
function App() {
|
|
42
|
+
return (
|
|
43
|
+
<BrowserRouter>
|
|
44
|
+
<Routes>
|
|
45
|
+
<Route path="/" element={<Stories users={demoUsers} />} />
|
|
46
|
+
<Route path="/story/:storyId" element={<Stories users={demoUsers} />} />
|
|
47
|
+
</Routes>
|
|
48
|
+
</BrowserRouter>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default App;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 📖 API Reference
|
|
56
|
+
|
|
57
|
+
### `<Stories />` Component
|
|
58
|
+
|
|
59
|
+
The main component for displaying stories with avatar list and viewer.
|
|
60
|
+
|
|
61
|
+
#### Props
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Default | Description |
|
|
64
|
+
|------|------|---------|-------------|
|
|
65
|
+
| `users` | `User[]` | **required** | Array of user objects with their stories |
|
|
66
|
+
| `closeNavigateTo` | `string` | `'/'` | Navigation path when stories viewer is closed |
|
|
67
|
+
|
|
68
|
+
### Story Types
|
|
69
|
+
|
|
70
|
+
The component supports **4 core story types**:
|
|
71
|
+
|
|
72
|
+
#### 1. Image Story
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
{
|
|
76
|
+
id: 'unique-id',
|
|
77
|
+
type: 'image',
|
|
78
|
+
src: 'https://example.com/image.jpg',
|
|
79
|
+
alt: 'Description', // Optional
|
|
80
|
+
duration: 5000, // Optional, default: 5000ms
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### 2. Video Story
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
{
|
|
88
|
+
id: 'unique-id',
|
|
89
|
+
type: 'video',
|
|
90
|
+
src: 'https://example.com/video.mp4',
|
|
91
|
+
duration: 10000, // Optional, auto-detected from video
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Features:**
|
|
96
|
+
- ✅ Audio enabled by default
|
|
97
|
+
- ✅ Progress bar pauses during buffering
|
|
98
|
+
- ✅ Auto-detects video duration
|
|
99
|
+
|
|
100
|
+
#### 3. Text Story
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
{
|
|
104
|
+
id: 'unique-id',
|
|
105
|
+
type: 'text',
|
|
106
|
+
text: 'Hello World!',
|
|
107
|
+
backgroundColor: '#FF6B6B', // Optional, default: '#000'
|
|
108
|
+
textColor: '#FFFFFF', // Optional, default: '#fff'
|
|
109
|
+
duration: 5000,
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### 4. Custom Component Story
|
|
114
|
+
|
|
115
|
+
The most powerful feature - add ANY custom React component as a story!
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
const MyCustomStory: React.FC<StoryItemControls> = ({
|
|
119
|
+
pause,
|
|
120
|
+
resume,
|
|
121
|
+
next,
|
|
122
|
+
prev,
|
|
123
|
+
setDuration
|
|
124
|
+
}) => {
|
|
125
|
+
return (
|
|
126
|
+
<div style={{ height: '100%', background: '#667eea', padding: '20px' }}>
|
|
127
|
+
<h1>Custom Content</h1>
|
|
128
|
+
<button onClick={next}>Next Story</button>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// In your stories array:
|
|
134
|
+
{
|
|
135
|
+
id: 'unique-id',
|
|
136
|
+
type: 'custom_component',
|
|
137
|
+
component: MyCustomStory,
|
|
138
|
+
duration: 5000,
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Control Methods Available:**
|
|
143
|
+
- `pause()` - Pause the story timer
|
|
144
|
+
- `resume()` - Resume the story timer
|
|
145
|
+
- `next()` - Go to next story
|
|
146
|
+
- `prev()` - Go to previous story
|
|
147
|
+
- `setDuration(ms: number)` - Update story duration dynamically
|
|
148
|
+
|
|
149
|
+
## 💡 Custom Component Examples
|
|
150
|
+
|
|
151
|
+
Build interactive experiences! Here are examples included in `demoUsers`:
|
|
152
|
+
|
|
153
|
+
### 📊 Poll Component
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) => {
|
|
157
|
+
const [selected, setSelected] = React.useState<number | null>(null);
|
|
158
|
+
const [votes, setVotes] = React.useState([42, 28, 18, 12]);
|
|
159
|
+
const options = ['React', 'Vue', 'Angular', 'Svelte'];
|
|
160
|
+
|
|
161
|
+
React.useEffect(() => {
|
|
162
|
+
pause(); // Pause timer during interaction
|
|
163
|
+
return () => resume();
|
|
164
|
+
}, [pause, resume]);
|
|
165
|
+
|
|
166
|
+
const handleVote = (index: number) => {
|
|
167
|
+
setSelected(index);
|
|
168
|
+
const newVotes = [...votes];
|
|
169
|
+
newVotes[index] += 1;
|
|
170
|
+
setVotes(newVotes);
|
|
171
|
+
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
resume();
|
|
174
|
+
next();
|
|
175
|
+
}, 2000);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const total = votes.reduce((a, b) => a + b, 0);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div style={{
|
|
182
|
+
display: 'flex',
|
|
183
|
+
flexDirection: 'column',
|
|
184
|
+
height: '100%',
|
|
185
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
186
|
+
padding: '20px'
|
|
187
|
+
}}>
|
|
188
|
+
<h2 style={{ color: 'white', marginBottom: '20px' }}>
|
|
189
|
+
What's your favorite framework?
|
|
190
|
+
</h2>
|
|
191
|
+
{options.map((option, index) => (
|
|
192
|
+
<button
|
|
193
|
+
key={index}
|
|
194
|
+
onClick={() => handleVote(index)}
|
|
195
|
+
disabled={selected !== null}
|
|
196
|
+
style={{
|
|
197
|
+
margin: '8px 0',
|
|
198
|
+
padding: '15px',
|
|
199
|
+
background: selected === index ? '#4CAF50' : 'rgba(255,255,255,0.2)',
|
|
200
|
+
color: 'white',
|
|
201
|
+
border: 'none',
|
|
202
|
+
borderRadius: '12px',
|
|
203
|
+
fontSize: '16px',
|
|
204
|
+
cursor: selected !== null ? 'default' : 'pointer'
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
208
|
+
<span>{option}</span>
|
|
209
|
+
{selected !== null && (
|
|
210
|
+
<span>{((votes[index] / total) * 100).toFixed(0)}%</span>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
</button>
|
|
214
|
+
))}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Use it in your stories:
|
|
220
|
+
{
|
|
221
|
+
id: 'poll-1',
|
|
222
|
+
type: 'custom_component',
|
|
223
|
+
component: PollComponent,
|
|
224
|
+
duration: 15000, // Extended duration for interaction
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 🧠 Quiz Component
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
const QuizComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) => {
|
|
232
|
+
const [selected, setSelected] = React.useState<number | null>(null);
|
|
233
|
+
const correctAnswer = 2; // Jupiter
|
|
234
|
+
const options = ['Mars', 'Saturn', 'Jupiter', 'Neptune'];
|
|
235
|
+
|
|
236
|
+
React.useEffect(() => {
|
|
237
|
+
pause();
|
|
238
|
+
return () => resume();
|
|
239
|
+
}, [pause, resume]);
|
|
240
|
+
|
|
241
|
+
const handleAnswer = (index: number) => {
|
|
242
|
+
setSelected(index);
|
|
243
|
+
setTimeout(() => {
|
|
244
|
+
resume();
|
|
245
|
+
next();
|
|
246
|
+
}, 2500);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div style={{ /* styles */ }}>
|
|
251
|
+
<h2>Which planet is the largest in our solar system?</h2>
|
|
252
|
+
{options.map((option, index) => (
|
|
253
|
+
<button
|
|
254
|
+
key={index}
|
|
255
|
+
onClick={() => handleAnswer(index)}
|
|
256
|
+
disabled={selected !== null}
|
|
257
|
+
style={{
|
|
258
|
+
background: selected === index
|
|
259
|
+
? (index === correctAnswer ? '#4CAF50' : '#f44336')
|
|
260
|
+
: 'rgba(255,255,255,0.2)'
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{option}
|
|
264
|
+
{selected !== null && index === correctAnswer && ' ✓'}
|
|
265
|
+
</button>
|
|
266
|
+
))}
|
|
267
|
+
{selected !== null && (
|
|
268
|
+
<p style={{ marginTop: '20px', fontWeight: 'bold' }}>
|
|
269
|
+
{selected === correctAnswer ? '🎉 Correct!' : '❌ Wrong! Jupiter is the largest.'}
|
|
270
|
+
</p>
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### ⏱️ Countdown Component
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
const CountdownComponent: React.FC<StoryItemControls> = () => {
|
|
281
|
+
const [timeLeft, setTimeLeft] = React.useState({
|
|
282
|
+
days: 12,
|
|
283
|
+
hours: 8,
|
|
284
|
+
minutes: 45,
|
|
285
|
+
seconds: 30,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
React.useEffect(() => {
|
|
289
|
+
const timer = setInterval(() => {
|
|
290
|
+
setTimeLeft((prev) => {
|
|
291
|
+
let { days, hours, minutes, seconds } = prev;
|
|
292
|
+
seconds--;
|
|
293
|
+
if (seconds < 0) {
|
|
294
|
+
seconds = 59;
|
|
295
|
+
minutes--;
|
|
296
|
+
}
|
|
297
|
+
if (minutes < 0) {
|
|
298
|
+
minutes = 59;
|
|
299
|
+
hours--;
|
|
300
|
+
}
|
|
301
|
+
if (hours < 0) {
|
|
302
|
+
hours = 23;
|
|
303
|
+
days--;
|
|
304
|
+
}
|
|
305
|
+
return { days, hours, minutes, seconds };
|
|
306
|
+
});
|
|
307
|
+
}, 1000);
|
|
308
|
+
|
|
309
|
+
return () => clearInterval(timer);
|
|
310
|
+
}, []);
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<div style={{
|
|
314
|
+
display: 'flex',
|
|
315
|
+
flexDirection: 'column',
|
|
316
|
+
alignItems: 'center',
|
|
317
|
+
justifyContent: 'center',
|
|
318
|
+
height: '100%',
|
|
319
|
+
background: 'linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)',
|
|
320
|
+
padding: '20px'
|
|
321
|
+
}}>
|
|
322
|
+
<div style={{ fontSize: '48px', marginBottom: '15px' }}>🚀</div>
|
|
323
|
+
<h2 style={{ color: 'white', fontSize: '24px' }}>Product Launch</h2>
|
|
324
|
+
<p style={{ color: 'rgba(255,255,255,0.7)', marginBottom: '30px' }}>
|
|
325
|
+
Something amazing is coming...
|
|
326
|
+
</p>
|
|
327
|
+
|
|
328
|
+
<div style={{ display: 'flex', gap: '12px' }}>
|
|
329
|
+
{Object.entries(timeLeft).map(([key, value]) => (
|
|
330
|
+
<div key={key} style={{ textAlign: 'center' }}>
|
|
331
|
+
<div style={{
|
|
332
|
+
background: 'rgba(255,255,255,0.2)',
|
|
333
|
+
borderRadius: '12px',
|
|
334
|
+
padding: '15px 20px',
|
|
335
|
+
minWidth: '70px'
|
|
336
|
+
}}>
|
|
337
|
+
<div style={{ fontSize: '32px', fontWeight: 'bold', color: 'white' }}>
|
|
338
|
+
{String(value).padStart(2, '0')}
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
<div style={{ color: 'rgba(255,255,255,0.8)', fontSize: '12px', marginTop: '8px' }}>
|
|
342
|
+
{key.toUpperCase()}
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### 🎚️ Slider Component
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
const SliderComponent: React.FC<StoryItemControls> = ({ pause, resume }) => {
|
|
356
|
+
const [value, setValue] = React.useState(50);
|
|
357
|
+
|
|
358
|
+
React.useEffect(() => {
|
|
359
|
+
pause();
|
|
360
|
+
return () => resume();
|
|
361
|
+
}, [pause, resume]);
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<div style={{
|
|
365
|
+
display: 'flex',
|
|
366
|
+
flexDirection: 'column',
|
|
367
|
+
alignItems: 'center',
|
|
368
|
+
justifyContent: 'center',
|
|
369
|
+
height: '100%',
|
|
370
|
+
background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
|
|
371
|
+
padding: '40px'
|
|
372
|
+
}}>
|
|
373
|
+
<div style={{ fontSize: '48px', marginBottom: '20px' }}>🔥</div>
|
|
374
|
+
<h2 style={{ color: 'white', fontSize: '24px', marginBottom: '10px' }}>
|
|
375
|
+
How excited are you?
|
|
376
|
+
</h2>
|
|
377
|
+
|
|
378
|
+
<div style={{ fontSize: '64px', margin: '30px 0' }}>{value}</div>
|
|
379
|
+
|
|
380
|
+
<input
|
|
381
|
+
type="range"
|
|
382
|
+
min="0"
|
|
383
|
+
max="100"
|
|
384
|
+
value={value}
|
|
385
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
386
|
+
style={{
|
|
387
|
+
width: '80%',
|
|
388
|
+
height: '8px',
|
|
389
|
+
borderRadius: '4px',
|
|
390
|
+
appearance: 'none',
|
|
391
|
+
background: 'rgba(255,255,255,0.3)',
|
|
392
|
+
outline: 'none'
|
|
393
|
+
}}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**💡 Tip**: All these examples are included in `demoUsers`! Import and use them to see how they work.
|
|
401
|
+
|
|
402
|
+
## 🎨 Styling
|
|
403
|
+
|
|
404
|
+
Import the default styles:
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import 'react-instagram-stories/styles.css';
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Override with custom CSS:
|
|
411
|
+
|
|
412
|
+
```css
|
|
413
|
+
/* Override story viewer background */
|
|
414
|
+
.story-viewer {
|
|
415
|
+
background: rgba(0, 0, 0, 0.95);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* Customize progress bars */
|
|
419
|
+
.story-progress-bar {
|
|
420
|
+
background: rgba(255, 255, 255, 0.3);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.story-progress-bar-fill {
|
|
424
|
+
background: linear-gradient(to right, #ff6b6b, #ee5a6f);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* Style avatars */
|
|
428
|
+
.avatar-list {
|
|
429
|
+
padding: 20px;
|
|
430
|
+
gap: 16px;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.avatar {
|
|
434
|
+
width: 80px;
|
|
435
|
+
height: 80px;
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## 💡 Advanced Usage
|
|
440
|
+
|
|
441
|
+
### Using Demo Data
|
|
442
|
+
|
|
443
|
+
```tsx
|
|
444
|
+
import { Stories, demoUsers } from 'react-instagram-stories';
|
|
445
|
+
import 'react-instagram-stories/styles.css';
|
|
446
|
+
|
|
447
|
+
function App() {
|
|
448
|
+
return <Stories users={demoUsers} />;
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
The `demoUsers` includes examples of all story types including interactive polls, quizzes, countdowns, and sliders!
|
|
453
|
+
|
|
454
|
+
### Generate Demo Users
|
|
455
|
+
|
|
456
|
+
```tsx
|
|
457
|
+
import { generateDemoUsers } from 'react-instagram-stories';
|
|
458
|
+
|
|
459
|
+
const users = generateDemoUsers(10); // 10 users with random stories
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Create Custom Stories
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
import type { User } from 'react-instagram-stories';
|
|
466
|
+
|
|
467
|
+
const myUsers: User[] = [
|
|
468
|
+
{
|
|
469
|
+
id: '1',
|
|
470
|
+
username: 'johndoe',
|
|
471
|
+
avatarUrl: 'https://example.com/avatar.jpg',
|
|
472
|
+
hasUnreadStories: true, // Shows ring around avatar
|
|
473
|
+
stories: [
|
|
474
|
+
{
|
|
475
|
+
id: 'story-1',
|
|
476
|
+
type: 'image',
|
|
477
|
+
src: 'https://example.com/photo.jpg',
|
|
478
|
+
alt: 'Beach sunset',
|
|
479
|
+
duration: 5000,
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: 'story-2',
|
|
483
|
+
type: 'video',
|
|
484
|
+
src: 'https://example.com/video.mp4',
|
|
485
|
+
// duration auto-detected
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
id: 'story-3',
|
|
489
|
+
type: 'text',
|
|
490
|
+
text: 'Hello from my story!',
|
|
491
|
+
backgroundColor: '#FF6B6B',
|
|
492
|
+
textColor: '#FFFFFF',
|
|
493
|
+
duration: 5000,
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: 'story-4',
|
|
497
|
+
type: 'custom_component',
|
|
498
|
+
component: MyPollComponent,
|
|
499
|
+
duration: 10000,
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
},
|
|
503
|
+
];
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Without React Router
|
|
507
|
+
|
|
508
|
+
If you don't need URL navigation, use components directly:
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
import { useState } from 'react';
|
|
512
|
+
import { AvatarList, StoryViewer } from 'react-instagram-stories';
|
|
513
|
+
|
|
514
|
+
function App() {
|
|
515
|
+
const [viewerState, setViewerState] = useState({
|
|
516
|
+
isOpen: false,
|
|
517
|
+
userIndex: 0,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<>
|
|
522
|
+
<AvatarList
|
|
523
|
+
users={myUsers}
|
|
524
|
+
onAvatarClick={(index) => setViewerState({ isOpen: true, userIndex: index })}
|
|
525
|
+
/>
|
|
526
|
+
<StoryViewer
|
|
527
|
+
users={myUsers}
|
|
528
|
+
initialUserIndex={viewerState.userIndex}
|
|
529
|
+
isOpen={viewerState.isOpen}
|
|
530
|
+
onClose={() => setViewerState({ isOpen: false, userIndex: 0 })}
|
|
531
|
+
/>
|
|
532
|
+
</>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## ⌨️ Keyboard Controls
|
|
538
|
+
|
|
539
|
+
- `←` `→` - Navigate stories
|
|
540
|
+
- `Space` - Pause/Resume
|
|
541
|
+
- `Esc` - Close viewer
|
|
542
|
+
|
|
543
|
+
## 🖱️ Mouse & Touch
|
|
544
|
+
|
|
545
|
+
- **Tap Left/Right** - Navigate stories
|
|
546
|
+
- **Swipe Left/Right** - Change users
|
|
547
|
+
- **Swipe Down** - Close
|
|
548
|
+
- **Hold/Hover** - Pause
|
|
549
|
+
|
|
550
|
+
## 🎯 TypeScript Types
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
import type {
|
|
554
|
+
User,
|
|
555
|
+
StoryItem,
|
|
556
|
+
StoryItemType,
|
|
557
|
+
StoryItemControls,
|
|
558
|
+
ImageStoryItem,
|
|
559
|
+
VideoStoryItem,
|
|
560
|
+
TextStoryItem,
|
|
561
|
+
CustomComponentStoryItem
|
|
562
|
+
} from 'react-instagram-stories';
|
|
563
|
+
|
|
564
|
+
// Core Types
|
|
565
|
+
type StoryItemType = 'image' | 'video' | 'text' | 'custom_component';
|
|
566
|
+
|
|
567
|
+
interface StoryItemControls {
|
|
568
|
+
pause: () => void;
|
|
569
|
+
resume: () => void;
|
|
570
|
+
next: () => void;
|
|
571
|
+
prev: () => void;
|
|
572
|
+
setDuration: (ms: number) => void;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
interface User {
|
|
576
|
+
id: string;
|
|
577
|
+
username: string;
|
|
578
|
+
avatarUrl: string;
|
|
579
|
+
stories: StoryItem[];
|
|
580
|
+
hasUnreadStories?: boolean;
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## 📦 Package Exports
|
|
585
|
+
|
|
586
|
+
```tsx
|
|
587
|
+
// Main component
|
|
588
|
+
export { Stories } from 'react-instagram-stories';
|
|
589
|
+
|
|
590
|
+
// Types
|
|
591
|
+
export type {
|
|
592
|
+
User,
|
|
593
|
+
StoryItem,
|
|
594
|
+
StoryItemControls,
|
|
595
|
+
StoryItemType,
|
|
596
|
+
ImageStoryItem,
|
|
597
|
+
VideoStoryItem,
|
|
598
|
+
TextStoryItem,
|
|
599
|
+
CustomComponentStoryItem
|
|
600
|
+
} from 'react-instagram-stories';
|
|
601
|
+
|
|
602
|
+
// Utilities
|
|
603
|
+
export { generateDemoUsers, demoUsers } from 'react-instagram-stories';
|
|
604
|
+
|
|
605
|
+
// Styles
|
|
606
|
+
import 'react-instagram-stories/styles.css';
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
## 🚀 Performance
|
|
610
|
+
|
|
611
|
+
- **Bundle Size**: 74.8 KB (20 KB gzipped)
|
|
612
|
+
- **Zero Runtime Dependencies**
|
|
613
|
+
- **Smart Preloading**: Preloads adjacent stories
|
|
614
|
+
- **Optimized Rendering**: Uses React.memo
|
|
615
|
+
- **Video Buffering Detection**: Pauses progress during buffering
|
|
616
|
+
|
|
617
|
+
## 📊 Package Info
|
|
618
|
+
|
|
619
|
+
- **ESM**: 28.77 KB
|
|
620
|
+
- **CJS**: 30.44 KB
|
|
621
|
+
- **Gzipped**: ~20 KB
|
|
622
|
+
- **Dependencies**: 0 (React is peer dep)
|
|
623
|
+
|
|
624
|
+
## 🛠️ Tech Stack
|
|
625
|
+
|
|
626
|
+
- React 18+
|
|
627
|
+
- TypeScript
|
|
628
|
+
- React Router DOM (peer dependency)
|
|
629
|
+
- tsup (bundler)
|
|
630
|
+
|
|
631
|
+
## 🤝 Contributing
|
|
632
|
+
|
|
633
|
+
Contributions welcome! Open an issue or PR.
|
|
634
|
+
|
|
635
|
+
## 📄 License
|
|
636
|
+
|
|
637
|
+
MIT © [Ankit Jangir](https://github.com/ankit64jangir)
|
|
638
|
+
|
|
639
|
+
## 📞 Support
|
|
640
|
+
|
|
641
|
+
- 🐛 [Issues](https://github.com/ankit64jangir/react-instagram-stories/issues)
|
|
642
|
+
- 💬 [Discussions](https://github.com/ankit64jangir/react-instagram-stories/discussions)
|
|
643
|
+
- ⭐ [Star](https://github.com/ankit64jangir/react-instagram-stories)
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
Made with ❤️ by Ankit Jangir
|