├── README.md
├── components
└── Carousel
│ ├── CarouselContainer.js
│ ├── CarouselSlot.js
│ ├── Indicator.js
│ ├── Wrapper.js
│ └── index.js
└── containers
└── CarouselPage
├── CarouselItem.js
└── index.js
/README.md:
--------------------------------------------------------------------------------
1 | # react-css-carousel
2 | A mobile-only carousel component built in React and CSS.
3 |
4 | A tutorial of how this carousel was built can be found on the [@incubation.ff](https://medium.com/@incubation.ff) Medium blog. Read [Part One](https://medium.com/@incubation.ff/build-your-own-css-carousel-in-react-part-one-86f71f6670ca) and [Part Two](https://medium.com/@incubation.ff/build-your-own-css-carousel-in-react-part-two-89ec247251ae) for more information.
5 |
6 | ## Features
7 |
8 | - A mobile-only carousel wrapper which accepts any number of child items
9 | - The carousel is cntrolled via swipe functionality
10 | - It also includes an indicator to show how many items are in the carousel, and which item is currently being viewed.
11 |
12 | ## Built using:
13 |
14 | - React
15 | - [Styled Components](https://www.styled-components.com/)
16 | - [React-Swipeable](https://github.com/dogfessional/react-swipeable)
17 | - [Lodash](https://lodash.com/)
18 | - Flexbox 😉
--------------------------------------------------------------------------------
/components/Carousel/CarouselContainer.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const CarouselContainer = styled.div`
4 | display: flex;
5 | margin: 0 0 20px 20px;
6 | transition: ${(props) => props.sliding ? 'none' : 'transform 1s ease'};
7 |
8 | transform: ${(props) => {
9 | if (props.numSlides === 1) return 'translateX(0%)'
10 |
11 | if (props.numSlides === 2) {
12 | if (!props.sliding && props.direction === 'next') return 'translateX(calc(-80% + 30px))'
13 | if (!props.sliding && props.direction === 'prev') return 'translateX(0%)'
14 | if (props.direction === 'prev') return 'translateX(calc(-80% + 30px))'
15 | return 'translateX(0%)'
16 | }
17 |
18 | if (!props.sliding) return 'translateX(calc(-80% - 20px))'
19 | if (props.direction === 'prev') return 'translateX(calc(2 * (-80% - 20px)))'
20 | return 'translateX(0%)'
21 | }};
22 | `
23 |
24 | export default CarouselContainer
25 |
--------------------------------------------------------------------------------
/components/Carousel/CarouselSlot.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const CarouselSlot = styled.div`
4 | flex: 1 0 100%;
5 | flex-basis: 80%;
6 | margin-right: 20px;
7 | order: ${(props) => props.order};
8 | opacity: ${(props) => {
9 | if (props.numSlides === 1) return 1
10 | if (props.numSlides === 2) return props.order === props.position ? 1 : 0.5
11 | return props.order === 1 ? 1 : 0.5
12 | }};
13 | transition: opacity 1s ease;
14 | `
15 |
16 | export default CarouselSlot;
17 |
--------------------------------------------------------------------------------
/components/Carousel/Indicator.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import styled from 'styled-components'
3 |
4 | const Container = styled.div`
5 | margin-bottom: 20px;
6 | margin-top: -20px;
7 | `
8 |
9 | const Pip = styled.span`
10 | background: ${(props) => (props.isCurrent) ? 'darkorange' : 'gainsboro'};
11 | width: 60px;
12 | height: 5px;
13 | margin-right: 5px;
14 | display: inline-block;
15 | transition: background 0.5s ease;
16 | cursor: pointer;
17 | `
18 |
19 | class Indicator extends Component {
20 | render() {
21 | const { length, position } = this.props
22 |
23 | return (
24 |
25 | {
26 | Array.from({ length }, (pip, i) =>
27 | ()
31 | )
32 | }
33 |
34 | )
35 | }
36 | }
37 |
38 | Indicator.propTypes = {
39 | length: PropTypes.number,
40 | position: PropTypes.number
41 | };
42 |
43 | export default Indicator;
44 |
--------------------------------------------------------------------------------
/components/Carousel/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Wrapper = styled.div`
4 | width: 100%;
5 | overflow: hidden;
6 | `
7 |
8 | export default Wrapper;
9 |
--------------------------------------------------------------------------------
/components/Carousel/index.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component, Children, cloneElement } from 'react';
2 | import styled from 'styled-components'
3 | import Swipeable from 'react-swipeable'
4 | import { throttle } from 'lodash'
5 |
6 | import CarouselContainer from './CarouselContainer'
7 | import Wrapper from './Wrapper'
8 | import CarouselSlot from './CarouselSlot'
9 | import Indicator from './Indicator'
10 |
11 | const TitleSection = styled.div`
12 | margin: 20px;
13 | `
14 |
15 | class Carousel extends Component {
16 | constructor(props) {
17 | super(props);
18 | this.state = {
19 | position: 0,
20 | direction: props.children.length === 2 ? 'prev' : 'next',
21 | sliding: false
22 | }
23 | }
24 |
25 | getOrder(itemIndex) {
26 | const { position } = this.state
27 | const { children } = this.props
28 | const numItems = children.length
29 |
30 | if (numItems === 2) return itemIndex
31 |
32 | if (itemIndex - position < 0) return numItems - Math.abs(itemIndex - position)
33 | return itemIndex - position
34 | }
35 |
36 | doSliding = (direction, position) => {
37 | this.setState({
38 | sliding: true,
39 | direction,
40 | position
41 | })
42 |
43 | setTimeout(() => {
44 | this.setState({
45 | sliding: false
46 | })
47 | }, 50)
48 | }
49 |
50 | nextSlide = () => {
51 | const { position } = this.state
52 | const { children } = this.props
53 | const numItems = children.length
54 |
55 | if (numItems === 2 && position === 1) return
56 |
57 | this.doSliding('next', position === numItems - 1 ? 0 : position + 1)
58 | }
59 |
60 | prevSlide = () => {
61 | const { position } = this.state
62 | const { children } = this.props
63 | const numItems = children.length
64 |
65 | if (numItems === 2 && position === 0) return
66 |
67 | this.doSliding('prev', position === 0 ? numItems - 1 : position - 1)
68 | }
69 |
70 | handleSwipe = throttle((isNext) => {
71 | const { children } = this.props
72 | const numItems = children.length || 1
73 |
74 | if (isNext && numItems > 1) {
75 | this.nextSlide()
76 | } else if (numItems > 1) {
77 | this.prevSlide()
78 | }
79 | }, 500, { trailing: false })
80 |
81 | render() {
82 | const { title, children } = this.props
83 | const { sliding, direction, position } = this.state
84 |
85 | const childrenWithProps = Children.map(children,
86 | (child) => cloneElement(child, {
87 | numSlides: children.length || 1
88 | })
89 | )
90 |
91 | return (
92 |
93 |
94 | { title }
95 | { childrenWithProps.length > 1 &&
96 | ()
100 | }
101 |
102 |
103 | this.handleSwipe(true) }
105 | onSwipingRight={ () => this.handleSwipe() }
106 | >
107 |
108 |
113 | { childrenWithProps.map((child, index) => (
114 |
120 | {child}
121 |
122 | )) }
123 |
124 |
125 |
126 |
127 | )
128 | }
129 | }
130 |
131 | Carousel.propTypes = {
132 | title: PropTypes.string,
133 | children: PropTypes.node
134 | };
135 |
136 | export default Carousel;
137 |
--------------------------------------------------------------------------------
/containers/CarouselPage/CarouselItem.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import styled from 'styled-components'
3 |
4 | const Item = styled.div`
5 | background: darkorange;
6 | text-align: center;
7 | padding: 50px;
8 | color: white;
9 | `
10 |
11 | function CarouselItem(props) {
12 | return (
13 | - Item {props.index} of {props.numSlides}
14 | )
15 | }
16 |
17 | CarouselItem.propTypes = {
18 | index: PropTypes.number,
19 | numSlides: PropTypes.number
20 | }
21 |
22 | export default CarouselItem
--------------------------------------------------------------------------------
/containers/CarouselPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Carousel from 'components/Carousel'
4 | import CarouselItem from './CarouselItem'
5 |
6 | export default class CarouselPage extends Component {
7 | render() {
8 | return (
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------