├── .DS_Store
├── .github
└── FUNDING.yml
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
└── src
├── .DS_Store
├── assets
├── .DS_Store
└── avatars
│ └── no_avatar.png
├── components
├── .DS_Store
├── avatar
│ └── view
│ │ ├── avatarStyles.js
│ │ └── avatarView.js
├── button
│ └── view
│ │ └── buttonView.js
├── index.js
├── stories
│ └── view
│ │ ├── storiesStyles.js
│ │ └── storiesView.js
├── story
│ └── view
│ │ ├── storyStyles.js
│ │ └── storyView.js
├── storyItem
│ └── view
│ │ ├── storyItemStyles.js
│ │ └── storyItemView.js
├── storyList
│ └── view
│ │ ├── storyListStyles.js
│ │ └── storyListView.js
└── storyListItem
│ └── view
│ ├── storyListItemStyles.js
│ └── storyListItemView.js
└── index.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ue/react-native-story/adcb8bb2aa60ce38d875c838b048a07938c81080/.DS_Store
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ue]
4 | patreon: ugurerdal
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 uğur erdal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-story
2 |
3 | A react native component instagram stories
4 |
5 | [](https://raw.githubusercontent.com/ue/react-native-story/master/LICENSE)
6 | [ ](https://www.npmjs.com/package/react-native-story)
7 |
8 | ## Installation
9 |
10 | - 1.Run `npm i react-native-story --save` or `yarn add react-native-story`
11 | - 2.`import Story from 'react-native-story'`
12 |
13 | 
14 |
15 | ## Getting started
16 |
17 | Add `react-native-story` to your js file.
18 |
19 | `import Story from 'react-native-story'`
20 |
21 | Inside your component's render method, use Story:
22 |
23 | ```javascript
24 | const stories = [
25 | {
26 | id: "4",
27 | source: require("../../../assets/stories/4.jpg"),
28 | user: "Ugur Erdal",
29 | avatar: require("../../../assets/avatars/ugurerdal.png")
30 | },
31 | {
32 | id: "2",
33 | source: require("../../../assets/stories/2.jpg"),
34 | user: "Mustafa",
35 | avatar: require("../../../assets/avatars/mustafa.png")
36 | },
37 | {
38 | id: "5",
39 | source: require("../../../assets/stories/5.jpg"),
40 | user: "Emre Yilmaz",
41 | avatar: require("../../../assets/avatars/emre.png")
42 | },
43 | {
44 | id: "3",
45 | source: require("../../../assets/stories/3.jpg"),
46 | user: "Cenk Gun",
47 | avatar: require("../../../assets/avatars/cenk.png")
48 | },
49 | ];
50 |
51 | render() {
52 | return (
53 |
62 | }
63 | />
64 | }
65 | ```
66 |
67 | ## API
68 |
69 | | Props | Type | Optional | Default | Description |
70 | | -------------------- | ------ | -------- | --------- | -------------------------------------- |
71 | | id | string | required | - | Json story data must have this |
72 | | stories | object | required | - | As above example |
73 | | unPressedBorderColor | string | true | "#e95950" | Unpressed Border color |
74 | | pressedBorderColor | string | true | "#ebebeb" | Pressed border color |
75 | | footerComponent | jsx | true | - | Bottom of the stories footer component |
76 |
77 |
78 | Thanx for the help
79 | - [@wcandillon](https://github.com/wcandillon)
80 | https://snack.expo.io/@wcandillon/instagram-stories
81 |
82 | **MIT Licensed UE**
83 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-story",
3 | "description": "Instagram story for react-native",
4 | "main": "src/index.js",
5 | "scripts": {
6 | "test": "mocha --require react-native-mock/mock.js --compilers js:babel-core/register --recursive tests/**/*.test.js",
7 | "test:watch": "npm test -- --watch",
8 | "test:cover": "istanbul cover -x *.test.js _mocha -- -R spec --require react-native-mock/mock.js --compilers js:babel-core/register 'tests/**/*.test.js'",
9 | "test:report": "cat ./coverage/lcov.info | codecov && rm -rf ./coverage",
10 | "lint": "eslint src/**/*.js tests/**/*.js",
11 | "lintfix": "eslint --fix src/**/*.js tests/**/*.js",
12 | "format": "prettier-eslint --write src/**/*.js tests/**/*.js",
13 | "precommit": "lint-staged"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/ue/react-native-story"
18 | },
19 | "keywords": [
20 | "react-native",
21 | "instagram",
22 | "story",
23 | "snapchat"
24 | ],
25 | "author": "Ugur ERDAL",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.comue/react-native-story/issues"
29 | },
30 | "homepage": "https://github.com/ue/react-native-story",
31 | "config": {
32 | "commitizen": {
33 | "path": "node_modules/cz-conventional-changelog"
34 | }
35 | },
36 | "peerDependencies": {
37 | "react": ">=15.0.0",
38 | "react-native": ">=0.40.0"
39 | },
40 | "devDependencies": {
41 | "babel": "^6.23.0",
42 | "babel-eslint": "^10.0.1",
43 | "babel-jest": "^20.0.3",
44 | "babel-preset-react-native-stage-0": "^1.0.1",
45 | "chai": "^4.0.2",
46 | "codecov.io": "^0.1.6",
47 | "cz-conventional-changelog": "^2.0.0",
48 | "enzyme": "^2.8.2",
49 | "eslint": "^4.0.0",
50 | "eslint-config-standard": "^10.2.1",
51 | "eslint-config-standard-react": "^5.0.0",
52 | "eslint-plugin-import": "^2.3.0",
53 | "eslint-plugin-node": "^5.0.0",
54 | "eslint-plugin-promise": "^3.5.0",
55 | "eslint-plugin-react": "^7.1.0",
56 | "eslint-plugin-standard": "^3.0.1",
57 | "husky": "^0.13.4",
58 | "istanbul": "^1.1.0-alpha.1",
59 | "lint-staged": "^4.0.0",
60 | "mocha": "^5.2.0",
61 | "prettier-eslint-cli": "^4.1.1",
62 | "prop-types": "^15.5.10",
63 | "react-dom": "^15.5.4",
64 | "react-native-mock": "^0.3.1",
65 | "semantic-release": "^15.13.3"
66 | },
67 | "lint-staged": {
68 | "src/**/*.js": [
69 | "prettier-eslint --write",
70 | "git add"
71 | ],
72 | "tests/**/*.js": [
73 | "prettier-eslint --write",
74 | "git add"
75 | ]
76 | },
77 | "version": "0.1.2",
78 | "directories": {
79 | "test": "tests"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ue/react-native-story/adcb8bb2aa60ce38d875c838b048a07938c81080/src/.DS_Store
--------------------------------------------------------------------------------
/src/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ue/react-native-story/adcb8bb2aa60ce38d875c838b048a07938c81080/src/assets/.DS_Store
--------------------------------------------------------------------------------
/src/assets/avatars/no_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ue/react-native-story/adcb8bb2aa60ce38d875c838b048a07938c81080/src/assets/avatars/no_avatar.png
--------------------------------------------------------------------------------
/src/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ue/react-native-story/adcb8bb2aa60ce38d875c838b048a07938c81080/src/components/.DS_Store
--------------------------------------------------------------------------------
/src/components/avatar/view/avatarStyles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | flexDirection: "row",
6 | padding: 16,
7 | alignItems: "center"
8 | },
9 | avatar: {
10 | width: 36,
11 | height: 36,
12 | borderRadius: 36 / 2,
13 | marginRight: 16,
14 | },
15 | username: {
16 | color: "white",
17 | fontSize: 16
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/src/components/avatar/view/avatarView.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from "react";
3 | import { View, Image, Text, SafeAreaView } from "react-native";
4 | import styles from "./avatarStyles";
5 | import type { ImageSourcePropType } from "react-native/Libraries/Image/ImageSourcePropType";
6 | import DEFAULT_AVATAR from "../../../assets/avatars/no_avatar.png";
7 |
8 | export default class Avatar extends React.PureComponent {
9 | render() {
10 | const { user, avatar: source } = this.props;
11 | return (
12 |
13 |
14 |
19 | {user}
20 |
21 |
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/button/view/buttonView.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Text, TouchableOpacity } from "react-native";
3 |
4 | // Constants
5 |
6 | // Components
7 |
8 | class ButtonView extends Component {
9 | /* Props
10 | * ------------------------------------------------
11 | * @prop { type } name - Description....
12 | */
13 |
14 | constructor(props) {
15 | super(props);
16 | this.state = {};
17 | }
18 |
19 | // Component Life Cycles
20 |
21 | // Component Functions
22 |
23 | render() {
24 | const { text, onPress } = this.props;
25 |
26 | return (
27 | onPress()} style={{ backgroundColor: "tomato"}}>
28 | {text}
29 |
30 | );
31 | }
32 | }
33 |
34 | export default ButtonView;
35 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Story } from "./story/view/storyView";
2 | export { default as StoryItem } from "./storyItem/view/storyItemView";
3 | export { default as Stories } from "./stories/view/storiesView";
4 | export { default as StoryList } from "./storyList/view/storyListView";
5 | export {
6 | default as StoryListItem
7 | } from "./storyListItem/view/storyListItemView";
8 |
--------------------------------------------------------------------------------
/src/components/stories/view/storiesStyles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | flex: 1,
6 | backgroundColor: "white"
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/stories/view/storiesView.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import {
3 | StyleSheet,
4 | Animated,
5 | Dimensions,
6 | Platform,
7 | View,
8 | ActivityIndicator
9 | } from "react-native";
10 | import styles from "./storiesStyles";
11 | import { StoryItem } from "../../";
12 |
13 | const { width } = Dimensions.get("window");
14 | const perspective = width;
15 | const angle = Math.atan(perspective / (width / 2));
16 | const ratio = Platform.OS === "ios" ? 2 : 1.2;
17 |
18 | export default class Stories extends PureComponent {
19 | stories = [];
20 |
21 | state = {
22 | x: new Animated.Value(0),
23 | ready: false
24 | };
25 |
26 | constructor(props) {
27 | super(props);
28 | this.stories = props.stories.map(() => React.createRef());
29 | }
30 |
31 | async componentDidMount() {
32 | const { x } = this.state;
33 | await x.addListener(() =>
34 | this.stories.forEach((story, index) => {
35 | const offset = index * width;
36 | const inputRange = [offset - width, offset + width];
37 | const translateX = x
38 | .interpolate({
39 | inputRange,
40 | outputRange: [width / ratio, -width / ratio],
41 | extrapolate: "clamp"
42 | })
43 | .__getValue();
44 |
45 | const rotateY = x
46 | .interpolate({
47 | inputRange,
48 | outputRange: [`${angle}rad`, `-${angle}rad`],
49 | extrapolate: "clamp"
50 | })
51 | .__getValue();
52 |
53 | const parsed = parseFloat(
54 | rotateY.substr(0, rotateY.indexOf("rad")),
55 | 10
56 | );
57 | const alpha = Math.abs(parsed);
58 | const gamma = angle - alpha;
59 | const beta = Math.PI - alpha - gamma;
60 | const w = width / 2 - ((width / 2) * Math.sin(gamma)) / Math.sin(beta);
61 | const translateX2 = parsed > 0 ? w : -w;
62 |
63 | const style = {
64 | transform: [
65 | { perspective },
66 | { translateX },
67 | { rotateY },
68 | { translateX: translateX2 }
69 | ]
70 | };
71 | story.current.setNativeProps({ style });
72 | })
73 | );
74 | }
75 |
76 | _handleSelectedStoryOnLoaded = () => {
77 | this.setState({ ready: true });
78 | };
79 |
80 | _handleSwipeLeftRight = () => {
81 | alert("swipe");
82 | };
83 |
84 | render() {
85 | const { x, ready } = this.state;
86 | const { stories, selectedStory, footerComponent } = this.props;
87 |
88 | return (
89 |
90 | {!ready && (
91 |
100 |
101 |
102 | )}
103 | {stories
104 | .map((story, i) => (
105 |
110 |
116 |
117 | ))
118 | .reverse()}
119 |
139 |
140 | );
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/components/story/view/storyStyles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Platform } from "react-native";
2 | import { Dimensions } from "react-native";
3 |
4 | export default StyleSheet.create({
5 | storyListContainer: {
6 | marginTop: 50
7 | },
8 | modal: {
9 | height: Dimensions.get("window").height,
10 | width: Dimensions.get("window").width,
11 | flex: 1
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/src/components/story/view/storyView.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from "react";
2 | import { View } from "react-native";
3 | import Modal from "react-native-modalbox";
4 |
5 | // Components
6 | import { StoryList, Stories } from "../../../components";
7 |
8 | import styles from "./storyStyles";
9 |
10 | class StoryListView extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | isModalOpen: false,
15 | orderedStories: null,
16 | selectedStory: null
17 | };
18 | }
19 |
20 | // Component Life Cycles
21 |
22 | // Component Functions
23 | _handleStoryItemPress = (item, index) => {
24 | const { stories } = this.props;
25 |
26 | this.setState({ selectedStory: item });
27 |
28 | const _stories = Array.from(stories);
29 |
30 | const rest = _stories.splice(index);
31 | const first = _stories;
32 |
33 | const orderedStories = rest.concat(first);
34 |
35 | this.setState({ orderedStories });
36 | this.setState({ isModalOpen: true });
37 | };
38 |
39 | render() {
40 | const {
41 | stories,
42 | footerComponent,
43 | unPressedBorderColor,
44 | pressedBorderColor
45 | } = this.props;
46 | const { isModalOpen, orderedStories, selectedStory } = this.state;
47 |
48 | return (
49 |
50 |
51 |
57 |
58 | this.setState({ isModalOpen: false })}
62 | position="center"
63 | swipeToClose
64 | swipeArea={250}
65 | backButtonClose
66 | coverScreen={true}
67 | >
68 |
73 |
74 |
75 | );
76 | }
77 | }
78 |
79 | export default StoryListView;
80 |
--------------------------------------------------------------------------------
/src/components/storyItem/view/storyItemStyles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Platform } from "react-native";
2 | import { Dimensions } from "react-native";
3 |
4 | export default StyleSheet.create({
5 | container: {
6 | flex: 1,
7 | },
8 | image: {
9 | ...StyleSheet.absoluteFillObject,
10 | width: null,
11 | height: Dimensions.get("window").height,
12 | },
13 | footer: {
14 | flexDirection: 'row',
15 | alignItems: 'center',
16 | justifyContent: 'space-between',
17 | padding: 16,
18 | },
19 | });
--------------------------------------------------------------------------------
/src/components/storyItem/view/storyItemView.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Fragment, PureComponent } from "react";
3 | import { View, Image } from "react-native";
4 |
5 | import styles from "./storyItemStyles";
6 |
7 | import Avatar from "../../avatar/view/avatarView";
8 |
9 | export default class extends PureComponent {
10 | render() {
11 | const {
12 | story: { source, user, avatar, id },
13 | selectedStory,
14 | handleSelectedStoryOnLoaded,
15 | footerComponent
16 | } = this.props;
17 | return (
18 |
19 |
20 |
22 | selectedStory &&
23 | selectedStory.id === id &&
24 | handleSelectedStoryOnLoaded()
25 | }
26 | style={styles.image}
27 | {...{ source }}
28 | />
29 |
30 |
31 | {footerComponent && (
32 | {footerComponent}
33 | )}
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/storyList/view/storyListStyles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | //flex: 1
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/src/components/storyList/view/storyListView.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { View, FlatList } from "react-native";
3 | // Components
4 | import { StoryListItem } from "../../../components";
5 | import styles from "./storyListStyles";
6 |
7 | class StoryListView extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {};
11 | }
12 |
13 | // Component Life Cycles
14 |
15 | // Component Functions
16 |
17 | render() {
18 | const {
19 | stories,
20 | handleStoryItemPress,
21 | unPressedBorderColor,
22 | pressedBorderColor
23 | } = this.props;
24 |
25 | return (
26 |
27 | index.toString()}
29 | data={stories}
30 | horizontal
31 | renderItem={({ item, index }) => (
32 |
34 | handleStoryItemPress && handleStoryItemPress(item, index)
35 | }
36 | unPressedBorderColor={unPressedBorderColor}
37 | pressedBorderColor={pressedBorderColor}
38 | item={item}
39 | />
40 | )}
41 | />
42 |
43 | );
44 | }
45 | }
46 |
47 | export default StoryListView;
48 |
--------------------------------------------------------------------------------
/src/components/storyListItem/view/storyListItemStyles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | marginVertical: 5
6 | },
7 | unPressedAvatar: {
8 | borderColor: "#e95950"
9 | },
10 | pressedAvatar: {
11 | borderColor: "#ebebeb"
12 | },
13 | avatarWrapper: {
14 | borderWidth: 2,
15 | justifyContent: "center",
16 | alignItems: "center",
17 | borderColor: "#e95950",
18 | margin: 8,
19 | borderRadius: 57 / 2,
20 | height: 57,
21 | width: 57
22 | },
23 | avatar: {
24 | height: 50,
25 | width: 50,
26 | borderRadius: 50 / 2,
27 | borderColor: "white",
28 | borderWidth: 1
29 | },
30 | itemText: {
31 | textAlign: "center",
32 | fontSize: 9
33 | }
34 | });
35 |
--------------------------------------------------------------------------------
/src/components/storyListItem/view/storyListItemView.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { View, Image, TouchableOpacity, Text } from "react-native";
3 |
4 | // Constants
5 | import DEFAULT_AVATAR from "../../../assets/avatars/no_avatar.png";
6 |
7 | // Components
8 | import styles from "./storyListItemStyles";
9 |
10 | class StoryListItemView extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | isPressed: false
15 | };
16 | }
17 |
18 | // Component Life Cycles
19 |
20 | // Component Functions
21 | _handleItemPress = item => {
22 | const { handleStoryItemPress } = this.props;
23 |
24 | if (handleStoryItemPress) handleStoryItemPress(item);
25 |
26 | this.setState({ isPressed: true });
27 | };
28 |
29 | render() {
30 | const { item, unPressedBorderColor, pressedBorderColor } = this.props;
31 | const { isPressed } = this.state;
32 |
33 | return (
34 |
35 | this._handleItemPress(item)}
37 | style={[
38 | styles.avatarWrapper,
39 | !isPressed
40 | ? {
41 | borderColor: unPressedBorderColor
42 | ? unPressedBorderColor
43 | : "#e95950"
44 | }
45 | : {
46 | borderColor: pressedBorderColor
47 | ? pressedBorderColor
48 | : "#ebebeb"
49 | }
50 | ]}
51 | >
52 |
57 |
58 | {item.user}
59 |
60 | );
61 | }
62 | }
63 |
64 | export default StoryListItemView;
65 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { Story } from "./components";
2 |
3 | export default Story;
4 |
--------------------------------------------------------------------------------