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 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
+ [![NPM Version](https://img.shields.io/npm/v/react-instagram-stories.svg)](https://www.npmjs.com/package/react-instagram-stories)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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