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 +364 -308
- package/dist/index.cjs +9 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -5
- package/dist/index.d.ts +67 -5
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/styles.css +59 -388
- package/package.json +4 -7
package/README.md
CHANGED
|
@@ -5,71 +5,178 @@ A high-performance, fully customizable Instagram-style Stories component for Rea
|
|
|
5
5
|
[](https://www.npmjs.com/package/react-instagram-stories)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
29
|
+
npm install react-instagram-stories
|
|
26
30
|
# or
|
|
27
|
-
yarn add react-instagram-stories
|
|
31
|
+
yarn add react-instagram-stories
|
|
28
32
|
# or
|
|
29
|
-
pnpm add react-instagram-stories
|
|
33
|
+
pnpm add react-instagram-stories
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
**Note**:
|
|
36
|
+
**Note**: No additional dependencies required! Works without react-router-dom.
|
|
33
37
|
|
|
34
|
-
##
|
|
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 {
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
106
|
+
## API Reference
|
|
56
107
|
|
|
57
108
|
### `<Stories />` Component
|
|
58
109
|
|
|
59
|
-
The
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
+
## Story Types
|
|
71
178
|
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
221
|
+
### 4. Custom Component Story
|
|
114
222
|
|
|
115
|
-
|
|
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
|
-
##
|
|
258
|
+
## Custom Component Examples
|
|
150
259
|
|
|
151
260
|
Build interactive experiences! Here are examples included in `demoUsers`:
|
|
152
261
|
|
|
153
|
-
###
|
|
262
|
+
### Poll Component
|
|
154
263
|
|
|
155
264
|
```tsx
|
|
156
|
-
const PollComponent: React.FC<StoryItemControls> = ({
|
|
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 = [
|
|
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
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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:
|
|
198
|
-
padding:
|
|
199
|
-
background:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
340
|
+
Import the default styles:
|
|
278
341
|
|
|
279
342
|
```tsx
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
445
|
-
import
|
|
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
|
|
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
|
|
467
|
+
import type { User } from "react-instagram-stories";
|
|
466
468
|
|
|
467
469
|
const myUsers: User[] = [
|
|
468
470
|
{
|
|
469
|
-
id:
|
|
470
|
-
username:
|
|
471
|
-
avatarUrl:
|
|
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:
|
|
476
|
-
type:
|
|
477
|
-
src:
|
|
478
|
-
alt:
|
|
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:
|
|
483
|
-
type:
|
|
484
|
-
src:
|
|
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:
|
|
489
|
-
type:
|
|
490
|
-
text:
|
|
491
|
-
backgroundColor:
|
|
492
|
-
textColor:
|
|
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:
|
|
497
|
-
type:
|
|
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
|
|
508
|
+
### Controlled Mode (Without URL)
|
|
507
509
|
|
|
508
|
-
If you don't
|
|
510
|
+
If you don't want URL navigation, use controlled mode:
|
|
509
511
|
|
|
510
512
|
```tsx
|
|
511
|
-
import { useState } from
|
|
512
|
-
import { AvatarList, StoryViewer } from
|
|
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) =>
|
|
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
|
-
##
|
|
541
|
+
## Keyboard Controls
|
|
538
542
|
|
|
539
543
|
- `←` `→` - Navigate stories
|
|
540
544
|
- `Space` - Pause/Resume
|
|
541
545
|
- `Esc` - Close viewer
|
|
542
546
|
|
|
543
|
-
##
|
|
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
|
-
##
|
|
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
|
-
|
|
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 =
|
|
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
|
-
##
|
|
635
|
+
## Package Exports
|
|
585
636
|
|
|
586
637
|
```tsx
|
|
587
|
-
//
|
|
588
|
-
|
|
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
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
|
667
|
+
import "react-instagram-stories/styles.css";
|
|
607
668
|
```
|
|
608
669
|
|
|
609
|
-
##
|
|
670
|
+
## Performance
|
|
610
671
|
|
|
611
|
-
- **Bundle Size**:
|
|
612
|
-
- **
|
|
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
|
-
##
|
|
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
|
-
-
|
|
683
|
+
- CSS 3D Transforms (cube transition between users)
|
|
684
|
+
- Native Browser History API (no router needed)
|
|
629
685
|
- tsup (bundler)
|
|
630
686
|
|
|
631
|
-
##
|
|
687
|
+
## Contributing
|
|
632
688
|
|
|
633
689
|
Contributions welcome! Open an issue or PR.
|
|
634
690
|
|
|
635
|
-
##
|
|
691
|
+
## License
|
|
636
692
|
|
|
637
693
|
MIT © [Ankit Jangir](https://github.com/ankit64jangir)
|
|
638
694
|
|
|
639
|
-
##
|
|
695
|
+
## Support
|
|
640
696
|
|
|
641
|
-
-
|
|
642
|
-
-
|
|
643
|
-
-
|
|
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
|
|
703
|
+
Made with love by Ankit Jangir
|