react-instagram-stories 1.0.4 → 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
@@ -5,71 +5,178 @@ A high-performance, fully customizable Instagram-style Stories component for Rea
5
5
  [![NPM Version](https://img.shields.io/npm/v/react-instagram-stories.svg)](https://www.npmjs.com/package/react-instagram-stories)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
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
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
+ - **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
20
+ - **Lightweight**: ~30 KB with zero runtime dependencies
21
+ - **No Router Required**: Works with native browser history API
22
+ - **URL Navigation**: Built-in query parameter support (`?user=userId&story=storyId`)
23
+ - **Auto Progress**: Smart progress bar that pauses during video buffering
24
+ - **Smooth Transitions**: 3D cube drag transitions between users, smooth animations between stories
25
+
26
+ ## Installation
23
27
 
24
28
  ```bash
25
- npm install react-instagram-stories react-router-dom
29
+ npm install react-instagram-stories
26
30
  # or
27
- yarn add react-instagram-stories react-router-dom
31
+ yarn add react-instagram-stories
28
32
  # or
29
- pnpm add react-instagram-stories react-router-dom
33
+ pnpm add react-instagram-stories
30
34
  ```
31
35
 
32
- **Note**: `react-router-dom` is required for URL-based story navigation.
36
+ **Note**: No additional dependencies required! Works without react-router-dom.
33
37
 
34
- ## 🚀 Quick Start
38
+ ## Quick Start
39
+
40
+ ### Simple Usage (Recommended)
41
+
42
+ ```tsx
43
+ import { Stories, demoUsers } from "react-instagram-stories";
44
+ import "react-instagram-stories/styles.css";
45
+
46
+ function App() {
47
+ return <Stories users={demoUsers} />;
48
+ }
49
+
50
+ export default App;
51
+ ```
52
+
53
+ That's it! Click on any avatar to open the story viewer. The URL automatically updates to `?user=userId&story=storyId` format.
54
+
55
+ ### Using Separate Components
35
56
 
36
57
  ```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';
58
+ import {
59
+ AvatarList,
60
+ StoryViewer,
61
+ demoUsers,
62
+ navigateWithParams,
63
+ } from "react-instagram-stories";
64
+ import "react-instagram-stories/styles.css";
40
65
 
41
66
  function App() {
67
+ const handleAvatarClick = (userIndex: number) => {
68
+ const user = demoUsers[userIndex];
69
+ // Navigate using user ID and story ID
70
+ navigateWithParams({ user: user.id, story: user.stories[0].id });
71
+ };
72
+
42
73
  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>
74
+ <div>
75
+ <h1>My App</h1>
76
+ <AvatarList users={demoUsers} onAvatarClick={handleAvatarClick} />
77
+ <StoryViewer users={demoUsers} />
78
+ </div>
49
79
  );
50
80
  }
81
+ ```
51
82
 
52
- export default App;
83
+ ## URL Navigation
84
+
85
+ The StoryViewer supports URL-based navigation using query parameters with **user ID** and **story ID**:
86
+
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 |
93
+
94
+ ### Navigation Helpers
95
+
96
+ ```tsx
97
+ import { navigateWithParams, clearQueryParams } from "react-instagram-stories";
98
+
99
+ // Open story viewer with user ID and story ID
100
+ navigateWithParams({ user: "user-travel", story: "travel-1" });
101
+
102
+ // Close story viewer (clear params)
103
+ clearQueryParams();
53
104
  ```
54
105
 
55
- ## 📖 API Reference
106
+ ## API Reference
56
107
 
57
108
  ### `<Stories />` Component
58
109
 
59
- The main component for displaying stories with avatar list and viewer.
110
+ The all-in-one component with avatar list and story viewer.
111
+
112
+ ```tsx
113
+ import { Stories } from "react-instagram-stories";
114
+
115
+ <Stories users={myUsers} />;
116
+ ```
117
+
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) |
122
+
123
+ ### `<StoryViewer />` Component
124
+
125
+ The story viewer component. Supports two modes:
126
+
127
+ #### Query Param Mode (Default)
128
+
129
+ When `isOpen` is not provided, reads from URL query params:
130
+
131
+ ```tsx
132
+ <StoryViewer users={myUsers} />
133
+ ```
134
+
135
+ #### Controlled Mode
136
+
137
+ When `isOpen` is provided, you control the viewer state:
138
+
139
+ ```tsx
140
+ <StoryViewer
141
+ users={myUsers}
142
+ isOpen={true}
143
+ initialUserIndex={0}
144
+ initialStoryIndex={0}
145
+ onClose={() => console.log("closed")}
146
+ onStoryChange={(userIndex, storyIndex) => console.log(userIndex, storyIndex)}
147
+ />
148
+ ```
60
149
 
61
- #### Props
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) |
62
159
 
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 |
160
+ ### `<AvatarList />` Component
161
+
162
+ The horizontal scrollable avatar list.
163
+
164
+ ```tsx
165
+ <AvatarList
166
+ users={myUsers}
167
+ onAvatarClick={(userIndex) => console.log(userIndex)}
168
+ />
169
+ ```
67
170
 
68
- ### Story Types
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) |
69
176
 
70
- The component supports **4 core story types**:
177
+ ## Story Types
71
178
 
72
- #### 1. Image Story
179
+ ### 1. Image Story
73
180
 
74
181
  ```tsx
75
182
  {
@@ -81,7 +188,7 @@ The component supports **4 core story types**:
81
188
  }
82
189
  ```
83
190
 
84
- #### 2. Video Story
191
+ ### 2. Video Story
85
192
 
86
193
  ```tsx
87
194
  {
@@ -93,11 +200,12 @@ The component supports **4 core story types**:
93
200
  ```
94
201
 
95
202
  **Features:**
96
- - ✅ Audio enabled by default
97
- - ✅ Progress bar pauses during buffering
98
- - ✅ Auto-detects video duration
99
203
 
100
- #### 3. Text Story
204
+ - Audio enabled by default
205
+ - Progress bar pauses during buffering
206
+ - Auto-detects video duration
207
+
208
+ ### 3. Text Story
101
209
 
102
210
  ```tsx
103
211
  {
@@ -110,9 +218,9 @@ The component supports **4 core story types**:
110
218
  }
111
219
  ```
112
220
 
113
- #### 4. Custom Component Story
221
+ ### 4. Custom Component Story
114
222
 
115
- The most powerful feature - add ANY custom React component as a story!
223
+ Add ANY custom React component as a story!
116
224
 
117
225
  ```tsx
118
226
  const MyCustomStory: React.FC<StoryItemControls> = ({
@@ -140,23 +248,28 @@ const MyCustomStory: React.FC<StoryItemControls> = ({
140
248
  ```
141
249
 
142
250
  **Control Methods Available:**
251
+
143
252
  - `pause()` - Pause the story timer
144
253
  - `resume()` - Resume the story timer
145
254
  - `next()` - Go to next story
146
255
  - `prev()` - Go to previous story
147
256
  - `setDuration(ms: number)` - Update story duration dynamically
148
257
 
149
- ## 💡 Custom Component Examples
258
+ ## Custom Component Examples
150
259
 
151
260
  Build interactive experiences! Here are examples included in `demoUsers`:
152
261
 
153
- ### 📊 Poll Component
262
+ ### Poll Component
154
263
 
155
264
  ```tsx
156
- const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) => {
265
+ const PollComponent: React.FC<StoryItemControls> = ({
266
+ pause,
267
+ resume,
268
+ next,
269
+ }) => {
157
270
  const [selected, setSelected] = React.useState<number | null>(null);
158
271
  const [votes, setVotes] = React.useState([42, 28, 18, 12]);
159
- const options = ['React', 'Vue', 'Angular', 'Svelte'];
272
+ const options = ["React", "Vue", "Angular", "Svelte"];
160
273
 
161
274
  React.useEffect(() => {
162
275
  pause(); // Pause timer during interaction
@@ -178,14 +291,16 @@ const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) =>
178
291
  const total = votes.reduce((a, b) => a + b, 0);
179
292
 
180
293
  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' }}>
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" }}>
189
304
  What's your favorite framework?
190
305
  </h2>
191
306
  {options.map((option, index) => (
@@ -194,17 +309,18 @@ const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) =>
194
309
  onClick={() => handleVote(index)}
195
310
  disabled={selected !== null}
196
311
  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'
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",
205
321
  }}
206
322
  >
207
- <div style={{ display: 'flex', justifyContent: 'space-between' }}>
323
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
208
324
  <span>{option}</span>
209
325
  {selected !== null && (
210
326
  <span>{((votes[index] / total) * 100).toFixed(0)}%</span>
@@ -215,196 +331,35 @@ const PollComponent: React.FC<StoryItemControls> = ({ pause, resume, next }) =>
215
331
  </div>
216
332
  );
217
333
  };
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
334
  ```
227
335
 
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
- };
336
+ **Tip**: All these examples are included in `demoUsers`! Import and use them to see how they work.
248
337
 
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
- ```
338
+ ## Styling
276
339
 
277
- ### ⏱️ Countdown Component
340
+ Import the default styles:
278
341
 
279
342
  ```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
- };
343
+ import "react-instagram-stories/styles.css";
350
344
  ```
351
345
 
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]);
346
+ **With Tailwind CSS**: Import the library styles **before** your Tailwind CSS so that utility classes can override them:
362
347
 
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>
348
+ ```css
349
+ /* Your global CSS file */
350
+ @import "react-instagram-stories/styles.css";
377
351
 
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
- };
352
+ @tailwind base;
353
+ @tailwind components;
354
+ @tailwind utilities;
398
355
  ```
399
356
 
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:
357
+ Or in JS, import the library CSS before your app's CSS:
405
358
 
406
359
  ```tsx
407
- import 'react-instagram-stories/styles.css';
360
+ // main.tsx
361
+ import "react-instagram-stories/styles.css"; // first
362
+ import "./index.css"; // your Tailwind CSS — after
408
363
  ```
409
364
 
410
365
  Override with custom CSS:
@@ -425,24 +380,71 @@ Override with custom CSS:
425
380
  }
426
381
 
427
382
  /* Style avatars */
428
- .avatar-list {
383
+ .story-avatar-list {
429
384
  padding: 20px;
430
385
  gap: 16px;
431
386
  }
432
387
 
433
- .avatar {
388
+ .story-avatar-image-wrapper {
434
389
  width: 80px;
435
390
  height: 80px;
436
391
  }
437
392
  ```
438
393
 
439
- ## 💡 Advanced Usage
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
+
441
+ ## Advanced Usage
440
442
 
441
443
  ### Using Demo Data
442
444
 
443
445
  ```tsx
444
- import { Stories, demoUsers } from 'react-instagram-stories';
445
- import 'react-instagram-stories/styles.css';
446
+ import { Stories, demoUsers } from "react-instagram-stories";
447
+ import "react-instagram-stories/styles.css";
446
448
 
447
449
  function App() {
448
450
  return <Stories users={demoUsers} />;
@@ -454,7 +456,7 @@ The `demoUsers` includes examples of all story types including interactive polls
454
456
  ### Generate Demo Users
455
457
 
456
458
  ```tsx
457
- import { generateDemoUsers } from 'react-instagram-stories';
459
+ import { generateDemoUsers } from "react-instagram-stories";
458
460
 
459
461
  const users = generateDemoUsers(10); // 10 users with random stories
460
462
  ```
@@ -462,39 +464,39 @@ const users = generateDemoUsers(10); // 10 users with random stories
462
464
  ### Create Custom Stories
463
465
 
464
466
  ```tsx
465
- import type { User } from 'react-instagram-stories';
467
+ import type { User } from "react-instagram-stories";
466
468
 
467
469
  const myUsers: User[] = [
468
470
  {
469
- id: '1',
470
- username: 'johndoe',
471
- avatarUrl: 'https://example.com/avatar.jpg',
471
+ id: "1",
472
+ username: "johndoe",
473
+ avatarUrl: "https://example.com/avatar.jpg",
472
474
  hasUnreadStories: true, // Shows ring around avatar
473
475
  stories: [
474
476
  {
475
- id: 'story-1',
476
- type: 'image',
477
- src: 'https://example.com/photo.jpg',
478
- alt: 'Beach sunset',
477
+ id: "story-1",
478
+ type: "image",
479
+ src: "https://example.com/photo.jpg",
480
+ alt: "Beach sunset",
479
481
  duration: 5000,
480
482
  },
481
483
  {
482
- id: 'story-2',
483
- type: 'video',
484
- src: 'https://example.com/video.mp4',
484
+ id: "story-2",
485
+ type: "video",
486
+ src: "https://example.com/video.mp4",
485
487
  // duration auto-detected
486
488
  },
487
489
  {
488
- id: 'story-3',
489
- type: 'text',
490
- text: 'Hello from my story!',
491
- backgroundColor: '#FF6B6B',
492
- textColor: '#FFFFFF',
490
+ id: "story-3",
491
+ type: "text",
492
+ text: "Hello from my story!",
493
+ backgroundColor: "#FF6B6B",
494
+ textColor: "#FFFFFF",
493
495
  duration: 5000,
494
496
  },
495
497
  {
496
- id: 'story-4',
497
- type: 'custom_component',
498
+ id: "story-4",
499
+ type: "custom_component",
498
500
  component: MyPollComponent,
499
501
  duration: 10000,
500
502
  },
@@ -503,13 +505,13 @@ const myUsers: User[] = [
503
505
  ];
504
506
  ```
505
507
 
506
- ### Without React Router
508
+ ### Controlled Mode (Without URL)
507
509
 
508
- If you don't need URL navigation, use components directly:
510
+ If you don't want URL navigation, use controlled mode:
509
511
 
510
512
  ```tsx
511
- import { useState } from 'react';
512
- import { AvatarList, StoryViewer } from 'react-instagram-stories';
513
+ import { useState } from "react";
514
+ import { AvatarList, StoryViewer } from "react-instagram-stories";
513
515
 
514
516
  function App() {
515
517
  const [viewerState, setViewerState] = useState({
@@ -521,7 +523,9 @@ function App() {
521
523
  <>
522
524
  <AvatarList
523
525
  users={myUsers}
524
- onAvatarClick={(index) => setViewerState({ isOpen: true, userIndex: index })}
526
+ onAvatarClick={(index) =>
527
+ setViewerState({ isOpen: true, userIndex: index })
528
+ }
525
529
  />
526
530
  <StoryViewer
527
531
  users={myUsers}
@@ -534,20 +538,21 @@ function App() {
534
538
  }
535
539
  ```
536
540
 
537
- ## ⌨️ Keyboard Controls
541
+ ## Keyboard Controls
538
542
 
539
543
  - `←` `→` - Navigate stories
540
544
  - `Space` - Pause/Resume
541
545
  - `Esc` - Close viewer
542
546
 
543
- ## 🖱️ Mouse & Touch
547
+ ## Mouse & Touch
544
548
 
545
549
  - **Tap Left/Right** - Navigate stories
546
550
  - **Swipe Left/Right** - Change users
551
+ - **Drag Left/Right** - 3D cube transition between users (peek at next/previous user, snaps on release)
547
552
  - **Swipe Down** - Close
548
553
  - **Hold/Hover** - Pause
549
554
 
550
- ## 🎯 TypeScript Types
555
+ ## TypeScript Types
551
556
 
552
557
  ```tsx
553
558
  import type {
@@ -558,11 +563,18 @@ import type {
558
563
  ImageStoryItem,
559
564
  VideoStoryItem,
560
565
  TextStoryItem,
561
- CustomComponentStoryItem
562
- } 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";
563
575
 
564
576
  // Core Types
565
- type StoryItemType = 'image' | 'video' | 'text' | 'custom_component';
577
+ type StoryItemType = "image" | "video" | "text" | "custom_component";
566
578
 
567
579
  interface StoryItemControls {
568
580
  pause: () => void;
@@ -579,16 +591,61 @@ interface User {
579
591
  stories: StoryItem[];
580
592
  hasUnreadStories?: boolean;
581
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
+ }
582
633
  ```
583
634
 
584
- ## 📦 Package Exports
635
+ ## Package Exports
585
636
 
586
637
  ```tsx
587
- // Main component
588
- export { Stories } from 'react-instagram-stories';
638
+ // Components
639
+ import { Stories, StoryViewer, AvatarList } from "react-instagram-stories";
640
+
641
+ // Navigation helpers
642
+ import { navigateWithParams, clearQueryParams } from "react-instagram-stories";
643
+
644
+ // Demo data
645
+ import { demoUsers, generateDemoUsers } from "react-instagram-stories";
589
646
 
590
647
  // Types
591
- export type {
648
+ import type {
592
649
  User,
593
650
  StoryItem,
594
651
  StoryItemControls,
@@ -596,52 +653,51 @@ export type {
596
653
  ImageStoryItem,
597
654
  VideoStoryItem,
598
655
  TextStoryItem,
599
- CustomComponentStoryItem
600
- } from 'react-instagram-stories';
601
-
602
- // Utilities
603
- export { generateDemoUsers, demoUsers } 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";
604
665
 
605
666
  // Styles
606
- import 'react-instagram-stories/styles.css';
667
+ import "react-instagram-stories/styles.css";
607
668
  ```
608
669
 
609
- ## 🚀 Performance
670
+ ## Performance
610
671
 
611
- - **Bundle Size**: 74.8 KB (20 KB gzipped)
612
- - **Zero Runtime Dependencies**
672
+ - **Bundle Size**: ~30 KB (minified)
673
+ - **Gzipped**: ~10 KB
674
+ - **Zero Runtime Dependencies**: No production dependencies at all (framer-motion and tailwindcss-animate are dev-only)
613
675
  - **Smart Preloading**: Preloads adjacent stories
614
676
  - **Optimized Rendering**: Uses React.memo
615
677
  - **Video Buffering Detection**: Pauses progress during buffering
616
678
 
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
679
+ ## Tech Stack
625
680
 
626
681
  - React 18+
627
682
  - TypeScript
628
- - React Router DOM (peer dependency)
683
+ - CSS 3D Transforms (cube transition between users)
684
+ - Native Browser History API (no router needed)
629
685
  - tsup (bundler)
630
686
 
631
- ## 🤝 Contributing
687
+ ## Contributing
632
688
 
633
689
  Contributions welcome! Open an issue or PR.
634
690
 
635
- ## 📄 License
691
+ ## License
636
692
 
637
693
  MIT © [Ankit Jangir](https://github.com/ankit64jangir)
638
694
 
639
- ## 📞 Support
695
+ ## Support
640
696
 
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)
697
+ - [Issues](https://github.com/ankit64jangir/react-instagram-stories/issues)
698
+ - [Discussions](https://github.com/ankit64jangir/react-instagram-stories/discussions)
699
+ - [Star on GitHub](https://github.com/ankit64jangir/react-instagram-stories)
644
700
 
645
701
  ---
646
702
 
647
- Made with ❤️ by Ankit Jangir
703
+ Made with love by Ankit Jangir