react-instagram-stories 1.0.5 → 1.1.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/README.md CHANGED
@@ -14,11 +14,14 @@ A high-performance, fully customizable Instagram-style Stories component for Rea
14
14
  - **Keyboard Navigation**: Full keyboard support for accessibility
15
15
  - **TypeScript**: Complete type definitions included
16
16
  - **Accessible**: ARIA labels and keyboard navigation
17
+ - **Tailwind CSS Support**: Customize every sub-element via `classNames` prop with Tailwind or custom CSS classes
18
+ - **3D Cube Transition**: Instagram-style 3D cube drag transition when switching between users
19
+ - **Story Resume**: Dragging back to a previous user resumes their story from where you left off
17
20
  - **Lightweight**: ~30 KB with zero runtime dependencies
18
21
  - **No Router Required**: Works with native browser history API
19
22
  - **URL Navigation**: Built-in query parameter support (`?user=userId&story=storyId`)
20
23
  - **Auto Progress**: Smart progress bar that pauses during video buffering
21
- - **Smooth Transitions**: Beautiful animations between stories and users
24
+ - **Smooth Transitions**: 3D cube drag transitions between users, smooth animations between stories
22
25
 
23
26
  ## Installation
24
27
 
@@ -37,8 +40,8 @@ pnpm add react-instagram-stories
37
40
  ### Simple Usage (Recommended)
38
41
 
39
42
  ```tsx
40
- import { Stories, demoUsers } from 'react-instagram-stories';
41
- import 'react-instagram-stories/styles.css';
43
+ import { Stories, demoUsers } from "react-instagram-stories";
44
+ import "react-instagram-stories/styles.css";
42
45
 
43
46
  function App() {
44
47
  return <Stories users={demoUsers} />;
@@ -52,8 +55,13 @@ That's it! Click on any avatar to open the story viewer. The URL automatically u
52
55
  ### Using Separate Components
53
56
 
54
57
  ```tsx
55
- import { AvatarList, StoryViewer, demoUsers, navigateWithParams } from 'react-instagram-stories';
56
- import 'react-instagram-stories/styles.css';
58
+ import {
59
+ AvatarList,
60
+ StoryViewer,
61
+ demoUsers,
62
+ navigateWithParams,
63
+ } from "react-instagram-stories";
64
+ import "react-instagram-stories/styles.css";
57
65
 
58
66
  function App() {
59
67
  const handleAvatarClick = (userIndex: number) => {
@@ -76,20 +84,20 @@ function App() {
76
84
 
77
85
  The StoryViewer supports URL-based navigation using query parameters with **user ID** and **story ID**:
78
86
 
79
- | URL | Result |
80
- |-----|--------|
81
- | `?user=user-travel&story=travel-1` | Opens Travel user's first story |
82
- | `?user=user-polls&story=poll-1` | Opens Interactive user's poll story |
83
- | `?user=user-launch&story=launch-2` | Opens Events user's second story |
84
- | No query params | Viewer stays closed |
87
+ | URL | Result |
88
+ | ---------------------------------- | ----------------------------------- |
89
+ | `?user=user-travel&story=travel-1` | Opens Travel user's first story |
90
+ | `?user=user-polls&story=poll-1` | Opens Interactive user's poll story |
91
+ | `?user=user-launch&story=launch-2` | Opens Events user's second story |
92
+ | No query params | Viewer stays closed |
85
93
 
86
94
  ### Navigation Helpers
87
95
 
88
96
  ```tsx
89
- import { navigateWithParams, clearQueryParams } from 'react-instagram-stories';
97
+ import { navigateWithParams, clearQueryParams } from "react-instagram-stories";
90
98
 
91
99
  // Open story viewer with user ID and story ID
92
- navigateWithParams({ user: 'user-travel', story: 'travel-1' });
100
+ navigateWithParams({ user: "user-travel", story: "travel-1" });
93
101
 
94
102
  // Close story viewer (clear params)
95
103
  clearQueryParams();
@@ -102,20 +110,22 @@ clearQueryParams();
102
110
  The all-in-one component with avatar list and story viewer.
103
111
 
104
112
  ```tsx
105
- import { Stories } from 'react-instagram-stories';
113
+ import { Stories } from "react-instagram-stories";
106
114
 
107
- <Stories users={myUsers} />
115
+ <Stories users={myUsers} />;
108
116
  ```
109
117
 
110
- | Prop | Type | Default | Description |
111
- |------|------|---------|-------------|
112
- | `users` | `User[]` | **required** | Array of user objects with their stories |
118
+ | Prop | Type | Default | Description |
119
+ | ------------ | ------------------- | ------------ | ---------------------------------------------------------------- |
120
+ | `users` | `User[]` | **required** | Array of user objects with their stories |
121
+ | `classNames` | `StoriesClassNames` | - | Object to customize sub-element CSS classes (Tailwind or custom) |
113
122
 
114
123
  ### `<StoryViewer />` Component
115
124
 
116
125
  The story viewer component. Supports two modes:
117
126
 
118
127
  #### Query Param Mode (Default)
128
+
119
129
  When `isOpen` is not provided, reads from URL query params:
120
130
 
121
131
  ```tsx
@@ -123,6 +133,7 @@ When `isOpen` is not provided, reads from URL query params:
123
133
  ```
124
134
 
125
135
  #### Controlled Mode
136
+
126
137
  When `isOpen` is provided, you control the viewer state:
127
138
 
128
139
  ```tsx
@@ -131,19 +142,20 @@ When `isOpen` is provided, you control the viewer state:
131
142
  isOpen={true}
132
143
  initialUserIndex={0}
133
144
  initialStoryIndex={0}
134
- onClose={() => console.log('closed')}
145
+ onClose={() => console.log("closed")}
135
146
  onStoryChange={(userIndex, storyIndex) => console.log(userIndex, storyIndex)}
136
147
  />
137
148
  ```
138
149
 
139
- | Prop | Type | Default | Description |
140
- |------|------|---------|-------------|
141
- | `users` | `User[]` | **required** | Array of user objects |
142
- | `isOpen` | `boolean` | - | Controls viewer visibility (enables controlled mode) |
143
- | `initialUserIndex` | `number` | `0` | Starting user index |
144
- | `initialStoryIndex` | `number` | `0` | Starting story index |
145
- | `onClose` | `() => void` | - | Called when viewer closes |
146
- | `onStoryChange` | `(userIndex, storyIndex) => void` | - | Called when story changes |
150
+ | Prop | Type | Default | Description |
151
+ | ------------------- | --------------------------------- | ------------ | ---------------------------------------------------------------- |
152
+ | `users` | `User[]` | **required** | Array of user objects |
153
+ | `isOpen` | `boolean` | - | Controls viewer visibility (enables controlled mode) |
154
+ | `initialUserIndex` | `number` | `0` | Starting user index |
155
+ | `initialStoryIndex` | `number` | `0` | Starting story index |
156
+ | `onClose` | `() => void` | - | Called when viewer closes |
157
+ | `onStoryChange` | `(userIndex, storyIndex) => void` | - | Called when story changes |
158
+ | `classNames` | `StoryViewerClassNames` | - | Object to customize sub-element CSS classes (Tailwind or custom) |
147
159
 
148
160
  ### `<AvatarList />` Component
149
161
 
@@ -156,10 +168,11 @@ The horizontal scrollable avatar list.
156
168
  />
157
169
  ```
158
170
 
159
- | Prop | Type | Description |
160
- |------|------|-------------|
161
- | `users` | `User[]` | Array of user objects |
162
- | `onAvatarClick` | `(userIndex: number) => void` | Called when avatar is clicked |
171
+ | Prop | Type | Description |
172
+ | --------------- | ----------------------------- | ---------------------------------------------------------------- |
173
+ | `users` | `User[]` | Array of user objects |
174
+ | `onAvatarClick` | `(userIndex: number) => void` | Called when avatar is clicked |
175
+ | `classNames` | `AvatarListClassNames` | Object to customize sub-element CSS classes (Tailwind or custom) |
163
176
 
164
177
  ## Story Types
165
178
 
@@ -187,6 +200,7 @@ The horizontal scrollable avatar list.
187
200
  ```
188
201
 
189
202
  **Features:**
203
+
190
204
  - Audio enabled by default
191
205
  - Progress bar pauses during buffering
192
206
  - Auto-detects video duration
@@ -234,6 +248,7 @@ const MyCustomStory: React.FC<StoryItemControls> = ({
234
248
  ```
235
249
 
236
250
  **Control Methods Available:**
251
+
237
252
  - `pause()` - Pause the story timer
238
253
  - `resume()` - Resume the story timer
239
254
  - `next()` - Go to next story
@@ -247,10 +262,14 @@ Build interactive experiences! Here are examples included in `demoUsers`:
247
262
  ### Poll Component
248
263
 
249
264
  ```tsx
250
- const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) => {
265
+ const PollComponent: React.FC<StoryItemControls> = ({
266
+ pause,
267
+ resume,
268
+ next,
269
+ }) => {
251
270
  const [selected, setSelected] = React.useState<number | null>(null);
252
271
  const [votes, setVotes] = React.useState([42, 28, 18, 12]);
253
- const options = ['React', 'Vue', 'Angular', 'Svelte'];
272
+ const options = ["React", "Vue", "Angular", "Svelte"];
254
273
 
255
274
  React.useEffect(() => {
256
275
  pause(); // Pause timer during interaction
@@ -272,14 +291,16 @@ const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) =>
272
291
  const total = votes.reduce((a, b) => a + b, 0);
273
292
 
274
293
  return (
275
- <div style={{
276
- display: 'flex',
277
- flexDirection: 'column',
278
- height: '100%',
279
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
280
- padding: '20px'
281
- }}>
282
- <h2 style={{ color: 'white', marginBottom: '20px' }}>
294
+ <div
295
+ style={{
296
+ display: "flex",
297
+ flexDirection: "column",
298
+ height: "100%",
299
+ background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
300
+ padding: "20px",
301
+ }}
302
+ >
303
+ <h2 style={{ color: "white", marginBottom: "20px" }}>
283
304
  What's your favorite framework?
284
305
  </h2>
285
306
  {options.map((option, index) => (
@@ -288,17 +309,18 @@ const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) =>
288
309
  onClick={() => handleVote(index)}
289
310
  disabled={selected !== null}
290
311
  style={{
291
- margin: '8px 0',
292
- padding: '15px',
293
- background: selected === index ? '#4CAF50' : 'rgba(255,255,255,0.2)',
294
- color: 'white',
295
- border: 'none',
296
- borderRadius: '12px',
297
- fontSize: '16px',
298
- cursor: selected !== null ? 'default' : 'pointer'
312
+ margin: "8px 0",
313
+ padding: "15px",
314
+ background:
315
+ selected === index ? "#4CAF50" : "rgba(255,255,255,0.2)",
316
+ color: "white",
317
+ border: "none",
318
+ borderRadius: "12px",
319
+ fontSize: "16px",
320
+ cursor: selected !== null ? "default" : "pointer",
299
321
  }}
300
322
  >
301
- <div style={{ display: 'flex', justifyContent: 'space-between' }}>
323
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
302
324
  <span>{option}</span>
303
325
  {selected !== null && (
304
326
  <span>{((votes[index] / total) * 100).toFixed(0)}%</span>
@@ -318,7 +340,26 @@ const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) =>
318
340
  Import the default styles:
319
341
 
320
342
  ```tsx
321
- import 'react-instagram-stories/styles.css';
343
+ import "react-instagram-stories/styles.css";
344
+ ```
345
+
346
+ **With Tailwind CSS**: Import the library styles **before** your Tailwind CSS so that utility classes can override them:
347
+
348
+ ```css
349
+ /* Your global CSS file */
350
+ @import "react-instagram-stories/styles.css";
351
+
352
+ @tailwind base;
353
+ @tailwind components;
354
+ @tailwind utilities;
355
+ ```
356
+
357
+ Or in JS, import the library CSS before your app's CSS:
358
+
359
+ ```tsx
360
+ // main.tsx
361
+ import "react-instagram-stories/styles.css"; // first
362
+ import "./index.css"; // your Tailwind CSS — after
322
363
  ```
323
364
 
324
365
  Override with custom CSS:
@@ -350,13 +391,60 @@ Override with custom CSS:
350
391
  }
351
392
  ```
352
393
 
394
+ ## Customization with classNames
395
+
396
+ Every component accepts a `classNames` prop for fine-grained styling using Tailwind CSS or custom CSS classes. The prop is a typed object that maps to specific sub-elements.
397
+
398
+ ```tsx
399
+ <Stories
400
+ users={users}
401
+ classNames={{
402
+ avatarList: {
403
+ root: "bg-gray-900 p-4",
404
+ avatar: { ring: "border-pink-500", username: "text-xs text-gray-400" },
405
+ },
406
+ storyViewer: {
407
+ overlay: "bg-black/90",
408
+ closeButton: "hover:text-gray-300",
409
+ progressBars: {
410
+ bar: { fill: "bg-gradient-to-r from-pink-500 to-purple-500" },
411
+ },
412
+ },
413
+ }}
414
+ />
415
+ ```
416
+
417
+ You can also pass `classNames` directly to `StoryViewer` or `AvatarList` when using them separately:
418
+
419
+ ```tsx
420
+ <AvatarList
421
+ users={users}
422
+ onAvatarClick={handleClick}
423
+ classNames={{
424
+ root: "bg-gray-900 p-4",
425
+ avatar: { ring: "border-pink-500", username: "text-xs text-gray-400" },
426
+ }}
427
+ />
428
+
429
+ <StoryViewer
430
+ users={users}
431
+ classNames={{
432
+ overlay: "bg-black/90",
433
+ closeButton: "hover:text-gray-300",
434
+ progressBars: { bar: { fill: "bg-gradient-to-r from-pink-500 to-purple-500" } },
435
+ }}
436
+ />
437
+ ```
438
+
439
+ All `classNames` types are exported from the package. See the [TypeScript Types](#typescript-types) section for the full list.
440
+
353
441
  ## Advanced Usage
354
442
 
355
443
  ### Using Demo Data
356
444
 
357
445
  ```tsx
358
- import { Stories, demoUsers } from 'react-instagram-stories';
359
- import 'react-instagram-stories/styles.css';
446
+ import { Stories, demoUsers } from "react-instagram-stories";
447
+ import "react-instagram-stories/styles.css";
360
448
 
361
449
  function App() {
362
450
  return <Stories users={demoUsers} />;
@@ -368,7 +456,7 @@ The `demoUsers` includes examples of all story types including interactive polls
368
456
  ### Generate Demo Users
369
457
 
370
458
  ```tsx
371
- import { generateDemoUsers } from 'react-instagram-stories';
459
+ import { generateDemoUsers } from "react-instagram-stories";
372
460
 
373
461
  const users = generateDemoUsers(10); // 10 users with random stories
374
462
  ```
@@ -376,39 +464,39 @@ const users = generateDemoUsers(10); // 10 users with random stories
376
464
  ### Create Custom Stories
377
465
 
378
466
  ```tsx
379
- import type { User } from 'react-instagram-stories';
467
+ import type { User } from "react-instagram-stories";
380
468
 
381
469
  const myUsers: User[] = [
382
470
  {
383
- id: '1',
384
- username: 'johndoe',
385
- avatarUrl: 'https://example.com/avatar.jpg',
471
+ id: "1",
472
+ username: "johndoe",
473
+ avatarUrl: "https://example.com/avatar.jpg",
386
474
  hasUnreadStories: true, // Shows ring around avatar
387
475
  stories: [
388
476
  {
389
- id: 'story-1',
390
- type: 'image',
391
- src: 'https://example.com/photo.jpg',
392
- alt: 'Beach sunset',
477
+ id: "story-1",
478
+ type: "image",
479
+ src: "https://example.com/photo.jpg",
480
+ alt: "Beach sunset",
393
481
  duration: 5000,
394
482
  },
395
483
  {
396
- id: 'story-2',
397
- type: 'video',
398
- src: 'https://example.com/video.mp4',
484
+ id: "story-2",
485
+ type: "video",
486
+ src: "https://example.com/video.mp4",
399
487
  // duration auto-detected
400
488
  },
401
489
  {
402
- id: 'story-3',
403
- type: 'text',
404
- text: 'Hello from my story!',
405
- backgroundColor: '#FF6B6B',
406
- textColor: '#FFFFFF',
490
+ id: "story-3",
491
+ type: "text",
492
+ text: "Hello from my story!",
493
+ backgroundColor: "#FF6B6B",
494
+ textColor: "#FFFFFF",
407
495
  duration: 5000,
408
496
  },
409
497
  {
410
- id: 'story-4',
411
- type: 'custom_component',
498
+ id: "story-4",
499
+ type: "custom_component",
412
500
  component: MyPollComponent,
413
501
  duration: 10000,
414
502
  },
@@ -422,8 +510,8 @@ const myUsers: User[] = [
422
510
  If you don't want URL navigation, use controlled mode:
423
511
 
424
512
  ```tsx
425
- import { useState } from 'react';
426
- import { AvatarList, StoryViewer } from 'react-instagram-stories';
513
+ import { useState } from "react";
514
+ import { AvatarList, StoryViewer } from "react-instagram-stories";
427
515
 
428
516
  function App() {
429
517
  const [viewerState, setViewerState] = useState({
@@ -435,7 +523,9 @@ function App() {
435
523
  <>
436
524
  <AvatarList
437
525
  users={myUsers}
438
- onAvatarClick={(index) => setViewerState({ isOpen: true, userIndex: index })}
526
+ onAvatarClick={(index) =>
527
+ setViewerState({ isOpen: true, userIndex: index })
528
+ }
439
529
  />
440
530
  <StoryViewer
441
531
  users={myUsers}
@@ -458,6 +548,7 @@ function App() {
458
548
 
459
549
  - **Tap Left/Right** - Navigate stories
460
550
  - **Swipe Left/Right** - Change users
551
+ - **Drag Left/Right** - 3D cube transition between users (peek at next/previous user, snaps on release)
461
552
  - **Swipe Down** - Close
462
553
  - **Hold/Hover** - Pause
463
554
 
@@ -472,11 +563,18 @@ import type {
472
563
  ImageStoryItem,
473
564
  VideoStoryItem,
474
565
  TextStoryItem,
475
- CustomComponentStoryItem
476
- } from 'react-instagram-stories';
566
+ CustomComponentStoryItem,
567
+ StoriesClassNames,
568
+ StoryViewerClassNames,
569
+ AvatarListClassNames,
570
+ AvatarClassNames,
571
+ StoryProgressBarsClassNames,
572
+ ProgressBarClassNames,
573
+ StoryItemClassNames,
574
+ } from "react-instagram-stories";
477
575
 
478
576
  // Core Types
479
- type StoryItemType = 'image' | 'video' | 'text' | 'custom_component';
577
+ type StoryItemType = "image" | "video" | "text" | "custom_component";
480
578
 
481
579
  interface StoryItemControls {
482
580
  pause: () => void;
@@ -493,19 +591,58 @@ interface User {
493
591
  stories: StoryItem[];
494
592
  hasUnreadStories?: boolean;
495
593
  }
594
+
595
+ // ClassNames Types (for Tailwind / custom CSS customization)
596
+ interface StoriesClassNames {
597
+ avatarList?: AvatarListClassNames;
598
+ storyViewer?: StoryViewerClassNames;
599
+ }
600
+
601
+ interface AvatarListClassNames {
602
+ root?: string;
603
+ avatar?: AvatarClassNames;
604
+ }
605
+
606
+ interface AvatarClassNames {
607
+ root?: string;
608
+ ring?: string;
609
+ image?: string;
610
+ username?: string;
611
+ }
612
+
613
+ interface StoryViewerClassNames {
614
+ overlay?: string;
615
+ closeButton?: string;
616
+ progressBars?: StoryProgressBarsClassNames;
617
+ storyItem?: StoryItemClassNames;
618
+ }
619
+
620
+ interface StoryProgressBarsClassNames {
621
+ root?: string;
622
+ bar?: ProgressBarClassNames;
623
+ }
624
+
625
+ interface ProgressBarClassNames {
626
+ root?: string;
627
+ fill?: string;
628
+ }
629
+
630
+ interface StoryItemClassNames {
631
+ root?: string;
632
+ }
496
633
  ```
497
634
 
498
635
  ## Package Exports
499
636
 
500
637
  ```tsx
501
638
  // Components
502
- import { Stories, StoryViewer, AvatarList } from 'react-instagram-stories';
639
+ import { Stories, StoryViewer, AvatarList } from "react-instagram-stories";
503
640
 
504
641
  // Navigation helpers
505
- import { navigateWithParams, clearQueryParams } from 'react-instagram-stories';
642
+ import { navigateWithParams, clearQueryParams } from "react-instagram-stories";
506
643
 
507
644
  // Demo data
508
- import { demoUsers, generateDemoUsers } from 'react-instagram-stories';
645
+ import { demoUsers, generateDemoUsers } from "react-instagram-stories";
509
646
 
510
647
  // Types
511
648
  import type {
@@ -516,18 +653,25 @@ import type {
516
653
  ImageStoryItem,
517
654
  VideoStoryItem,
518
655
  TextStoryItem,
519
- CustomComponentStoryItem
520
- } from 'react-instagram-stories';
656
+ CustomComponentStoryItem,
657
+ StoriesClassNames,
658
+ StoryViewerClassNames,
659
+ AvatarListClassNames,
660
+ AvatarClassNames,
661
+ StoryProgressBarsClassNames,
662
+ ProgressBarClassNames,
663
+ StoryItemClassNames,
664
+ } from "react-instagram-stories";
521
665
 
522
666
  // Styles
523
- import 'react-instagram-stories/styles.css';
667
+ import "react-instagram-stories/styles.css";
524
668
  ```
525
669
 
526
670
  ## Performance
527
671
 
528
672
  - **Bundle Size**: ~30 KB (minified)
529
673
  - **Gzipped**: ~10 KB
530
- - **Zero Runtime Dependencies**
674
+ - **Zero Runtime Dependencies**: No production dependencies at all (framer-motion and tailwindcss-animate are dev-only)
531
675
  - **Smart Preloading**: Preloads adjacent stories
532
676
  - **Optimized Rendering**: Uses React.memo
533
677
  - **Video Buffering Detection**: Pauses progress during buffering
@@ -536,6 +680,7 @@ import 'react-instagram-stories/styles.css';
536
680
 
537
681
  - React 18+
538
682
  - TypeScript
683
+ - CSS 3D Transforms (cube transition between users)
539
684
  - Native Browser History API (no router needed)
540
685
  - tsup (bundler)
541
686