├── .gitignore ├── doc ├── screenshot.png └── screencapture.gif ├── .babelrc ├── src ├── index.js ├── config.js ├── entry.js ├── timeline.js └── content.js ├── .vscode └── settings.json ├── .travis.yml ├── example ├── index.html └── index.js ├── dist ├── index.js ├── config.js ├── entry.js ├── timeline.js └── content.js ├── webpack.config.js ├── test └── timeline.test.js ├── .eslintrc.yml ├── webpack.prod.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /example/bundle.js 3 | -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ftes/react-dual-timeline/HEAD/doc/screenshot.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /doc/screencapture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ftes/react-dual-timeline/HEAD/doc/screencapture.gif -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Timeline from './timeline' 2 | export { default as Timeline } from './timeline.js' 3 | export default Timeline -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 2 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | cache: 5 | yarn: true 6 | directories: 7 | - node_modules 8 | before_script: 9 | - yarn install 10 | script: 11 | - yarn test -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Timeline = undefined; 7 | 8 | var _timeline = require('./timeline.js'); 9 | 10 | Object.defineProperty(exports, 'Timeline', { 11 | enumerable: true, 12 | get: function get() { 13 | return _interopRequireDefault(_timeline).default; 14 | } 15 | }); 16 | 17 | var _timeline2 = require('./timeline'); 18 | 19 | var _timeline3 = _interopRequireDefault(_timeline2); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | exports.default = _timeline3.default; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpack = require('webpack') 4 | 5 | module.exports = { 6 | 7 | entry: ['./example/index.js'], 8 | 9 | devtool: 'inline-source-map', 10 | 11 | output: { 12 | filename: './example/bundle.js', 13 | pathinfo: true 14 | }, 15 | 16 | module: { 17 | loaders: [{ 18 | loader: 'babel-loader', 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | query: { 22 | cacheDirectory: true, 23 | presets: ['react', 'es2015'] 24 | } 25 | }] 26 | }, 27 | 28 | plugins: [ 29 | new webpack.LoaderOptionsPlugin({ 30 | debug: false 31 | }) 32 | ] 33 | 34 | } -------------------------------------------------------------------------------- /test/timeline.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow, configure } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | 5 | configure({ adapter: new Adapter() }) 6 | 7 | import Timeline from '../src' 8 | 9 | describe('Timeline', () => { 10 | it('passes smoke test', () => { 11 | shallow(
) 12 | }) 13 | 14 | it('inserts children', () => { 15 | const tl = 16 | shallow(
1
2
3
) 17 | expect(tl.contains(
1
)).toEqual(true) 18 | expect(tl.contains(
2
)).toEqual(true) 19 | expect(tl.contains(
3
)).toEqual(true) 20 | }) 21 | }) -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | env: 3 | es6: true 4 | browser: true 5 | jasmine: true 6 | parserOptions: 7 | ecmaFeatures: 8 | experimentalObjectRestSpread: true 9 | jsx: true 10 | sourceType: module 11 | plugins: 12 | - react 13 | - jasmine 14 | extends: 15 | - eslint:recommended 16 | - plugin:react/recommended 17 | - plugin:jasmine/recommended 18 | rules: 19 | indent: 20 | - error 21 | - 2 22 | linebreak-style: 23 | - error 24 | - unix 25 | quotes: 26 | - error 27 | - single 28 | semi: 29 | - error 30 | - never 31 | no-console: off 32 | max-len: 33 | - error 34 | - 80 35 | object-curly-spacing: 36 | - error 37 | - always 38 | jsx-quotes: 39 | - error 40 | - prefer-single 41 | react/jsx-uses-react: 42 | - error 43 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | // global 3 | paddingTop: 50, 4 | mediaWidthMed: 900, 5 | mediaWidthSmall: 700, 6 | activeColor: '#F45B69', 7 | color: 'black', 8 | twoSidedOverlap: 80, // negative overlap between items if two-sided 9 | animations: true, 10 | addEvenPropToChildren: false, 11 | 12 | // line 13 | lineColor: '#FFF', 14 | circleWidth: 30, 15 | paddingToItem: 30, 16 | paddingToItemSmall: 20, 17 | lineWidth: 5, 18 | 19 | // triangle 20 | triangleWidth: 16, 21 | triangleHeight: 8, 22 | 23 | // list item content 24 | itemWidth: 350, 25 | itemWidthMed: 250, 26 | offsetHidden: 200, 27 | triangleOffset: 7, 28 | smallItemWidthPadding: 50, 29 | itemPadding: 16, 30 | evenItemOffset: 0, // important when using bootstrap.css 31 | } 32 | 33 | export default config -------------------------------------------------------------------------------- /dist/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var config = { 7 | // global 8 | paddingTop: 50, 9 | mediaWidthMed: 900, 10 | mediaWidthSmall: 700, 11 | activeColor: '#F45B69', 12 | color: 'black', 13 | twoSidedOverlap: 80, // negative overlap between items if two-sided 14 | animations: true, 15 | addEvenPropToChildren: false, 16 | 17 | // line 18 | lineColor: '#FFF', 19 | circleWidth: 30, 20 | paddingToItem: 30, 21 | paddingToItemSmall: 20, 22 | lineWidth: 5, 23 | 24 | // triangle 25 | triangleWidth: 16, 26 | triangleHeight: 8, 27 | 28 | // list item content 29 | itemWidth: 350, 30 | itemWidthMed: 250, 31 | offsetHidden: 200, 32 | triangleOffset: 7, 33 | smallItemWidthPadding: 50, 34 | itemPadding: 16, 35 | evenItemOffset: 0 // important when using bootstrap.css 36 | }; 37 | 38 | exports.default = config; -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpack = require('webpack') 4 | 5 | module.exports = { 6 | 7 | entry: ['./example/index.js'], 8 | 9 | output: { 10 | filename: './example/bundle.js', 11 | pathinfo: true 12 | }, 13 | 14 | module: { 15 | loaders: [{ 16 | loader: 'babel-loader', 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | query: { 20 | cacheDirectory: true, 21 | presets: ['react', 'es2015'] 22 | } 23 | }] 24 | }, 25 | 26 | plugins: [ 27 | new webpack.DefinePlugin({ 28 | 'process.env': { 29 | 'NODE_ENV': JSON.stringify('production') 30 | } 31 | }), 32 | new webpack.optimize.UglifyJsPlugin({ 33 | minimize: true, 34 | compress: { // hide warnings (unused var, always true etc.) 35 | warnings: false 36 | } 37 | }), 38 | new webpack.LoaderOptionsPlugin({ 39 | debug: false 40 | }) 41 | ] 42 | 43 | } 44 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { StyleRoot } from 'radium' 4 | 5 | import Timeline from '../src/timeline' 6 | 7 | 8 | render( 9 | 10 | 11 |

Entry 1

12 |
13 |

Entry 2

14 |
    15 |
  • arbitrary content in entries
  • 16 |
17 |
18 | {[0,1,2,3,4,5,6,7,8,9,10].map(i => 19 |
20 |

{i}

21 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy 22 | eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam 23 | voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet 24 | clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit 25 | amet. 26 |
27 | )} 28 |
29 |
, 30 | document.getElementById('root') 31 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-dual-timeline", 3 | "version": "0.3.0", 4 | "description": "Timeline Component for React", 5 | "keywords": [ 6 | "react", 7 | "timeline", 8 | "component", 9 | "dual", 10 | "two-sided" 11 | ], 12 | "repository": "https://github.com/ftes/react-dual-timeline.git", 13 | "author": "Fredrik Teschke", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/ftes/react-dual-timeline/issues" 17 | }, 18 | "homepage": "https://github.com/ftes/react-dual-timeline#readme", 19 | "main": "dist", 20 | "scripts": { 21 | "build": "babel src --out-dir dist", 22 | "patch": "npm run build && git add dist && git commit -m 'build new dist' && npm version patch && npm publish && git push", 23 | "dev": "babel src --out-dir dist --watch", 24 | "build-example": "webpack -p --config=webpack.prod.config.js", 25 | "dev-example": "webpack-dev-server --host 0.0.0.0 --hot --inline", 26 | "test": "jest", 27 | "start": "npm run dev-example" 28 | }, 29 | "devDependencies": { 30 | "babel-cli": "^6.26.0", 31 | "babel-jest": "^22.1.0", 32 | "babel-loader": "^6.2.4", 33 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 34 | "babel-preset-es2015": "^6.24.1", 35 | "babel-preset-react": "^6.24.1", 36 | "enzyme": "^3.3.0", 37 | "enzyme-adapter-react-16": "^1.1.1", 38 | "eslint": "^4.17.0", 39 | "eslint-plugin-jasmine": "^2.9.1", 40 | "eslint-plugin-react": "^7.6.1", 41 | "jest": "^22.1.4", 42 | "jest-cli": "^22.1.4", 43 | "radium": "^0.21.2", 44 | "react": "^16.2.0", 45 | "react-addons-test-utils": "^15.6.2", 46 | "react-dom": "^16.2.0", 47 | "webpack": "^3.10.0", 48 | "webpack-dev-server": "^2.11.1" 49 | }, 50 | "dependencies": { 51 | "prop-types": "^15.6.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Deprecated. Consider switching to [react-vertical-timeline-component](https://www.npmjs.com/package/react-vertical-timeline-component) or using [vanilla CSS](https://codepen.io/savalazic/pen/QKwERN/).** 2 | 3 | [![Build Status](https://travis-ci.org/ftes/react-dual-timeline.svg?branch=master)](https://travis-ci.org/ftes/react-dual-timeline) 4 | [![npm version](https://badge.fury.io/js/react-dual-timeline.svg)](https://www.npmjs.com/package/react-dual-timeline) 5 | 6 | # React Timeline 7 | A react component for animated timelines. 8 | 9 | ![screen capture](./doc/screencapture.gif) 10 | 11 | Based on the [CSS and Javascript template](https://webdesign.tutsplus.com/tutorials/building-a-vertical-timeline-with-css-and-a-touch-of-javascript--cms-26528) 12 | by George Martsoukos ([CodePen](http://codepen.io/tutsplus/full/QNeJgR/)). 13 | 14 | ## Usage 15 | For a full example see [example/index.js](./example/index.js). 16 | 17 | Requirements: `radium` 18 | 19 | ``` 20 | import Timeline from 'timeline' 21 | 22 | render( 23 | 24 | 25 |
Arbitrary entry
26 |
Arbitrary entry
27 |
28 |
29 | ) 30 | ``` 31 | 32 | A custom icon can (optionally) be provided for each entry. 33 | 34 | ## Configuration 35 | [src/config.js](./src/config.js) holds the default configuration. 36 | 37 | Alternative values can be passed to the `` component, 38 | e.g. ` 71 |
72 | 75 | {children} 76 | 77 |
78 | 82 | {icon} 83 | 84 | 85 | ) 86 | } 87 | } 88 | 89 | Entry.propTypes = { 90 | children: PropTypes.node.isRequired, 91 | even: PropTypes.bool.isRequired, 92 | config: PropTypes.object.isRequired, 93 | icon: PropTypes.node, 94 | } 95 | 96 | export default Radium(Entry) -------------------------------------------------------------------------------- /src/timeline.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Radium from 'radium' 4 | 5 | import Entry from './entry' 6 | import defaultConfig from './config' 7 | 8 | export class Timeline extends React.Component { 9 | constructor(props) { 10 | super(props) 11 | this.state = { twoSided: true } 12 | this.onTwoSidedChange = this.onTwoSidedChange.bind(this) 13 | this.componentWillReceiveProps(props) 14 | } 15 | 16 | /** 17 | * Merge config with default only once (optimize) 18 | */ 19 | componentWillReceiveProps(newProps) { 20 | //eslint-disable-next-line no-unused-vars 21 | const { children, ...config } = newProps // children are not config 22 | this.mergedConfig = { 23 | ...defaultConfig, 24 | ...config, 25 | } 26 | } 27 | 28 | componentWillMount() { 29 | const { mediaWidthSmall } = this.mergedConfig 30 | if (window && window.matchMedia) { 31 | this.mqTwoSided = window.matchMedia(`(min-width: ${mediaWidthSmall}px)`) 32 | this.mqTwoSided.addListener(this.onTwoSidedChange) 33 | this.onTwoSidedChange(this.mqTwoSided) 34 | } 35 | } 36 | 37 | componentWillUnmount() { 38 | if (this.mqTwoSided) { 39 | this.mqTwoSided.removeListener(this.onTwoSidedChange) 40 | } 41 | } 42 | 43 | onTwoSidedChange(mq) { 44 | this.setState({ twoSided: mq.matches }) 45 | } 46 | 47 | render() { 48 | const { children } = this.props 49 | const { color, twoSidedOverlap } = this.mergedConfig 50 | const twoSided = this.state.twoSided 51 | let i = 0 52 | 53 | const styles = { 54 | base: { 55 | textAlign: 'center', 56 | paddingBottom: twoSided && twoSidedOverlap + 'px', 57 | color: color, 58 | overflow: 'hidden', 59 | [this.mqTwoSidedString]: { 60 | marginBottom: twoSidedOverlap + 'px', 61 | } 62 | } 63 | } 64 | 65 | return ( 66 |
67 | {React.Children.map(children, c => 68 | 70 | {c} 71 | 72 | )} 73 |
74 | ) 75 | } 76 | } 77 | 78 | Timeline.propTypes = { 79 | children: PropTypes.node.isRequired, 80 | 81 | // global 82 | paddingTop: PropTypes.number, 83 | mediaWidthMed: PropTypes.number, 84 | mediaWidthSmall: PropTypes.number, 85 | activeColor: PropTypes.string, 86 | color: PropTypes.string, 87 | twoSidedOverlap: PropTypes.number, 88 | animations: PropTypes.bool, 89 | addEvenPropToChildren: PropTypes.bool, 90 | 91 | // line 92 | lineColor: PropTypes.string, 93 | circleWidth: PropTypes.number, 94 | paddingToItem: PropTypes.number, 95 | paddingToItemSmall: PropTypes.number, 96 | lineWidth: PropTypes.number, 97 | 98 | // triangle 99 | triangleWidth: PropTypes.number, 100 | triangleHeight: PropTypes.number, 101 | 102 | // list item content 103 | itemWidth: PropTypes.number, 104 | itemWidthMed: PropTypes.number, 105 | offsetHidden: PropTypes.number, 106 | triangleOffset: PropTypes.number, 107 | smallItemWidthPadding: PropTypes.number, 108 | itemPadding: PropTypes.number, 109 | evenItemOffset: PropTypes.number, 110 | } 111 | 112 | export default Radium(Timeline) 113 | -------------------------------------------------------------------------------- /src/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Radium from 'radium' 4 | 5 | export class Content extends React.Component { 6 | constructor(props) { 7 | super(props) 8 | this.onScroll = this.onScroll.bind(this) 9 | } 10 | 11 | areChildrenInView() { 12 | const rect = this.children.getBoundingClientRect() 13 | const vwHeight = window.innerHeight || document.documentElement.clientHeight 14 | return ( 15 | ( rect.bottom >= 0 && rect.bottom <= vwHeight ) || 16 | ( rect.top >= 0 && rect.top <= vwHeight ) 17 | ) 18 | } 19 | 20 | componentDidMount() { 21 | if (this.props.config.animations) { 22 | window.addEventListener('scroll', this.onScroll) 23 | window.addEventListener('resize', this.onScroll) 24 | this.onScroll() 25 | } 26 | } 27 | 28 | componentWillUnmount() { 29 | window.removeEventListener('scroll', this.onScroll) 30 | window.removeEventListener('resize', this.onScroll) 31 | } 32 | 33 | onScroll() { 34 | const inView = this.areChildrenInView() 35 | this.props.onInView(inView) 36 | } 37 | 38 | render() { 39 | const { children, even, inView, config } = this.props 40 | 41 | const { circleWidth, mediaWidthMed, paddingToItem, paddingToItemSmall, 42 | itemWidth, itemWidthMed, animations, lineWidth, evenItemOffset, 43 | offsetHidden, triangleWidth, triangleOffset, triangleHeight, 44 | activeColor, mediaWidthSmall, smallItemWidthPadding, itemPadding } 45 | = config 46 | 47 | const offsetEven = circleWidth + paddingToItem + triangleWidth - lineWidth 48 | + evenItemOffset 49 | const offsetEvenNml = offsetEven + itemWidth 50 | const offsetEvenMed = offsetEven + itemWidthMed 51 | 52 | const triangleLeft = { 53 | right: `-${triangleWidth - 1}px`, 54 | borderWidth: 55 | `${triangleHeight}px 0 ${triangleHeight}px ${triangleWidth}px`, 56 | borderColor: `transparent transparent transparent ${activeColor}`, 57 | } 58 | const triangleRight = { 59 | left: `-${triangleWidth - 1}px`, 60 | borderWidth: 61 | `${triangleHeight}px ${triangleWidth}px ${triangleHeight}px 0`, 62 | borderColor: `transparent ${activeColor} transparent transparent`, 63 | } 64 | 65 | const mediaMed = `@media screen and (min-width: ${mediaWidthSmall}px) 66 | and (max-width: ${mediaWidthMed}px)` 67 | const mediaSmall = `@media screen and (max-width: ${mediaWidthSmall}px)` 68 | const mediaPrint = '@media print' 69 | 70 | const styles = { 71 | base: { 72 | position: 'relative', 73 | bottom: '0', 74 | width: itemWidth + 'px', 75 | padding: itemPadding + 'px', 76 | background: activeColor, 77 | visibility: animations ? 'hidden' : null, 78 | opacity: animations ? 0 : 1, 79 | transition: animations ? 'all .5s ease-in-out' : null, 80 | [mediaMed]: { 81 | width: itemWidthMed + 'px', 82 | }, 83 | [mediaSmall]: { 84 | width: `calc(100vw - ${triangleWidth + circleWidth + 85 | smallItemWidthPadding}px)`, 86 | }, 87 | [mediaPrint]: { 88 | width: '100%', 89 | left: 0, 90 | transform: null, 91 | } 92 | }, 93 | inView: { 94 | transform: 'none', 95 | visibility: 'visible', 96 | opacity: '1', 97 | }, 98 | even: { 99 | left: `-${offsetEvenNml}px`, 100 | transform: animations ? `translate3d(-${offsetHidden}px,0,0)` : null, 101 | [mediaMed]: { 102 | left: `-${offsetEvenMed}px`, 103 | }, 104 | [mediaSmall]: { 105 | left: paddingToItemSmall + triangleWidth + 'px', 106 | }, 107 | }, 108 | odd: { 109 | left: paddingToItem + triangleWidth + 'px', 110 | transform: animations ? `translate3d(${offsetHidden}px,0,0)` : null, 111 | [mediaSmall]: { 112 | left: paddingToItemSmall + triangleWidth + 'px', 113 | }, 114 | }, 115 | triangle: { 116 | base: { 117 | position: 'absolute', 118 | bottom: triangleOffset + 'px', 119 | width: '0', 120 | height: '0', 121 | borderStyle: 'solid', 122 | }, 123 | even: { 124 | ...triangleLeft, 125 | [mediaSmall]: triangleRight 126 | }, 127 | odd: triangleRight, 128 | } 129 | } 130 | 131 | let propsToAdd = {} 132 | if (config.addEvenPropToChildren) { 133 | propsToAdd = { 134 | ...propsToAdd, 135 | even, 136 | } 137 | } 138 | 139 | return ( 140 |
145 | 150 |
this.children = c}> 151 | {React.cloneElement(children, propsToAdd)} 152 |
153 |
154 | ) 155 | } 156 | } 157 | 158 | Content.propTypes = { 159 | children: PropTypes.node.isRequired, 160 | even: PropTypes.bool.isRequired, 161 | inView: PropTypes.bool.isRequired, 162 | onInView: PropTypes.func, 163 | config: PropTypes.object.isRequired, 164 | } 165 | 166 | export default Radium(Content) 167 | -------------------------------------------------------------------------------- /dist/entry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Entry = undefined; 7 | 8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 9 | 10 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 11 | 12 | var _react = require('react'); 13 | 14 | var _react2 = _interopRequireDefault(_react); 15 | 16 | var _propTypes = require('prop-types'); 17 | 18 | var _propTypes2 = _interopRequireDefault(_propTypes); 19 | 20 | var _radium = require('radium'); 21 | 22 | var _radium2 = _interopRequireDefault(_radium); 23 | 24 | var _content = require('./content'); 25 | 26 | var _content2 = _interopRequireDefault(_content); 27 | 28 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 29 | 30 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 31 | 32 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 33 | 34 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 35 | 36 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 37 | 38 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 39 | 40 | var Entry = exports.Entry = function (_React$Component) { 41 | _inherits(Entry, _React$Component); 42 | 43 | function Entry(props) { 44 | _classCallCheck(this, Entry); 45 | 46 | var _this = _possibleConstructorReturn(this, (Entry.__proto__ || Object.getPrototypeOf(Entry)).call(this, props)); 47 | 48 | _this.onInView = _this.onInView.bind(_this); 49 | _this.state = { inView: false }; 50 | return _this; 51 | } 52 | 53 | _createClass(Entry, [{ 54 | key: 'onInView', 55 | value: function onInView(inView) { 56 | this.setState({ inView: inView }); 57 | } 58 | }, { 59 | key: 'render', 60 | value: function render() { 61 | var _base; 62 | 63 | var _props = this.props, 64 | children = _props.children, 65 | icon = _props.icon, 66 | props = _objectWithoutProperties(_props, ['children', 'icon']); 67 | 68 | var inView = this.state.inView; 69 | var _props$config = this.props.config, 70 | lineWidth = _props$config.lineWidth, 71 | circleWidth = _props$config.circleWidth, 72 | paddingTop = _props$config.paddingTop, 73 | lineColor = _props$config.lineColor, 74 | activeColor = _props$config.activeColor, 75 | mediaWidthSmall = _props$config.mediaWidthSmall, 76 | twoSidedOverlap = _props$config.twoSidedOverlap, 77 | animations = _props$config.animations; 78 | 79 | 80 | var styles = { 81 | base: (_base = { 82 | listStyleType: 'none', 83 | position: 'relative', // base for map position 84 | width: lineWidth + 'px', 85 | margin: '0 auto -' + twoSidedOverlap + 'px auto', 86 | paddingTop: paddingTop + 'px', 87 | background: lineColor 88 | }, _defineProperty(_base, '@media screen and (max-width: ' + mediaWidthSmall + 'px)', { 89 | margin: '0 auto 0 20px' 90 | }), _defineProperty(_base, '@media print', { 91 | margin: 0, 92 | width: '100%', 93 | paddingTop: 0 94 | }), _base), 95 | circle: { 96 | base: { 97 | position: 'absolute', 98 | bottom: '0', 99 | transform: 'translateX(-50%)', 100 | width: circleWidth + 'px', 101 | height: circleWidth + 'px', 102 | borderRadius: '50%', 103 | background: lineColor, 104 | transition: animations ? 'background .5s ease-in-out' : null, 105 | zIndex: 1 106 | }, 107 | inner: { 108 | base: { 109 | display: 'flex', 110 | justifyContent: 'center', 111 | alignItems: 'center', 112 | width: '100%', 113 | height: '100%' 114 | } 115 | }, 116 | inView: { 117 | background: activeColor 118 | } 119 | } 120 | }; 121 | 122 | return _react2.default.createElement( 123 | 'div', 124 | { style: [styles.base] }, 125 | _react2.default.createElement( 126 | 'div', 127 | null, 128 | _react2.default.createElement( 129 | _content2.default, 130 | _extends({}, props, { 131 | inView: inView, onInView: this.onInView 132 | }), 133 | children 134 | ) 135 | ), 136 | _react2.default.createElement( 137 | 'span', 138 | { className: 'no-print', style: [styles.circle.base, inView && styles.circle.inView] }, 139 | _react2.default.createElement( 140 | 'span', 141 | { style: [styles.circle.inner.base] }, 142 | icon 143 | ) 144 | ) 145 | ); 146 | } 147 | }]); 148 | 149 | return Entry; 150 | }(_react2.default.Component); 151 | 152 | Entry.propTypes = { 153 | children: _propTypes2.default.node.isRequired, 154 | even: _propTypes2.default.bool.isRequired, 155 | config: _propTypes2.default.object.isRequired, 156 | icon: _propTypes2.default.node 157 | }; 158 | 159 | exports.default = (0, _radium2.default)(Entry); -------------------------------------------------------------------------------- /dist/timeline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Timeline = undefined; 7 | 8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 9 | 10 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 11 | 12 | var _react = require('react'); 13 | 14 | var _react2 = _interopRequireDefault(_react); 15 | 16 | var _propTypes = require('prop-types'); 17 | 18 | var _propTypes2 = _interopRequireDefault(_propTypes); 19 | 20 | var _radium = require('radium'); 21 | 22 | var _radium2 = _interopRequireDefault(_radium); 23 | 24 | var _entry = require('./entry'); 25 | 26 | var _entry2 = _interopRequireDefault(_entry); 27 | 28 | var _config = require('./config'); 29 | 30 | var _config2 = _interopRequireDefault(_config); 31 | 32 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 33 | 34 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 35 | 36 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 37 | 38 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 39 | 40 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 41 | 42 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 43 | 44 | var Timeline = exports.Timeline = function (_React$Component) { 45 | _inherits(Timeline, _React$Component); 46 | 47 | function Timeline(props) { 48 | _classCallCheck(this, Timeline); 49 | 50 | var _this = _possibleConstructorReturn(this, (Timeline.__proto__ || Object.getPrototypeOf(Timeline)).call(this, props)); 51 | 52 | _this.state = { twoSided: true }; 53 | _this.onTwoSidedChange = _this.onTwoSidedChange.bind(_this); 54 | _this.componentWillReceiveProps(props); 55 | return _this; 56 | } 57 | 58 | /** 59 | * Merge config with default only once (optimize) 60 | */ 61 | 62 | 63 | _createClass(Timeline, [{ 64 | key: 'componentWillReceiveProps', 65 | value: function componentWillReceiveProps(newProps) { 66 | //eslint-disable-next-line no-unused-vars 67 | var children = newProps.children, 68 | config = _objectWithoutProperties(newProps, ['children']); // children are not config 69 | 70 | 71 | this.mergedConfig = _extends({}, _config2.default, config); 72 | } 73 | }, { 74 | key: 'componentWillMount', 75 | value: function componentWillMount() { 76 | var mediaWidthSmall = this.mergedConfig.mediaWidthSmall; 77 | 78 | if (window && window.matchMedia) { 79 | this.mqTwoSided = window.matchMedia('(min-width: ' + mediaWidthSmall + 'px)'); 80 | this.mqTwoSided.addListener(this.onTwoSidedChange); 81 | this.onTwoSidedChange(this.mqTwoSided); 82 | } 83 | } 84 | }, { 85 | key: 'componentWillUnmount', 86 | value: function componentWillUnmount() { 87 | if (this.mqTwoSided) { 88 | this.mqTwoSided.removeListener(this.onTwoSidedChange); 89 | } 90 | } 91 | }, { 92 | key: 'onTwoSidedChange', 93 | value: function onTwoSidedChange(mq) { 94 | this.setState({ twoSided: mq.matches }); 95 | } 96 | }, { 97 | key: 'render', 98 | value: function render() { 99 | var _this2 = this; 100 | 101 | var children = this.props.children; 102 | var _mergedConfig = this.mergedConfig, 103 | color = _mergedConfig.color, 104 | twoSidedOverlap = _mergedConfig.twoSidedOverlap; 105 | 106 | var twoSided = this.state.twoSided; 107 | var i = 0; 108 | 109 | var styles = { 110 | base: _defineProperty({ 111 | textAlign: 'center', 112 | paddingBottom: twoSided && twoSidedOverlap + 'px', 113 | color: color, 114 | overflow: 'hidden' 115 | }, this.mqTwoSidedString, { 116 | marginBottom: twoSidedOverlap + 'px' 117 | }) 118 | }; 119 | 120 | return _react2.default.createElement( 121 | 'div', 122 | { style: [styles.base] }, 123 | _react2.default.Children.map(children, function (c) { 124 | return _react2.default.createElement( 125 | _entry2.default, 126 | { even: i++ % 2 === 0 && twoSided, config: _this2.mergedConfig, 127 | icon: c.props.icon }, 128 | c 129 | ); 130 | }) 131 | ); 132 | } 133 | }]); 134 | 135 | return Timeline; 136 | }(_react2.default.Component); 137 | 138 | Timeline.propTypes = { 139 | children: _propTypes2.default.node.isRequired, 140 | 141 | // global 142 | paddingTop: _propTypes2.default.number, 143 | mediaWidthMed: _propTypes2.default.number, 144 | mediaWidthSmall: _propTypes2.default.number, 145 | activeColor: _propTypes2.default.string, 146 | color: _propTypes2.default.string, 147 | twoSidedOverlap: _propTypes2.default.number, 148 | animations: _propTypes2.default.bool, 149 | addEvenPropToChildren: _propTypes2.default.bool, 150 | 151 | // line 152 | lineColor: _propTypes2.default.string, 153 | circleWidth: _propTypes2.default.number, 154 | paddingToItem: _propTypes2.default.number, 155 | paddingToItemSmall: _propTypes2.default.number, 156 | lineWidth: _propTypes2.default.number, 157 | 158 | // triangle 159 | triangleWidth: _propTypes2.default.number, 160 | triangleHeight: _propTypes2.default.number, 161 | 162 | // list item content 163 | itemWidth: _propTypes2.default.number, 164 | itemWidthMed: _propTypes2.default.number, 165 | offsetHidden: _propTypes2.default.number, 166 | triangleOffset: _propTypes2.default.number, 167 | smallItemWidthPadding: _propTypes2.default.number, 168 | itemPadding: _propTypes2.default.number, 169 | evenItemOffset: _propTypes2.default.number 170 | }; 171 | 172 | exports.default = (0, _radium2.default)(Timeline); -------------------------------------------------------------------------------- /dist/content.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Content = undefined; 7 | 8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 9 | 10 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 11 | 12 | var _react = require('react'); 13 | 14 | var _react2 = _interopRequireDefault(_react); 15 | 16 | var _propTypes = require('prop-types'); 17 | 18 | var _propTypes2 = _interopRequireDefault(_propTypes); 19 | 20 | var _radium = require('radium'); 21 | 22 | var _radium2 = _interopRequireDefault(_radium); 23 | 24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 25 | 26 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 27 | 28 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 29 | 30 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 31 | 32 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 33 | 34 | var Content = exports.Content = function (_React$Component) { 35 | _inherits(Content, _React$Component); 36 | 37 | function Content(props) { 38 | _classCallCheck(this, Content); 39 | 40 | var _this = _possibleConstructorReturn(this, (Content.__proto__ || Object.getPrototypeOf(Content)).call(this, props)); 41 | 42 | _this.onScroll = _this.onScroll.bind(_this); 43 | return _this; 44 | } 45 | 46 | _createClass(Content, [{ 47 | key: 'areChildrenInView', 48 | value: function areChildrenInView() { 49 | var rect = this.children.getBoundingClientRect(); 50 | var vwHeight = window.innerHeight || document.documentElement.clientHeight; 51 | return rect.bottom >= 0 && rect.bottom <= vwHeight || rect.top >= 0 && rect.top <= vwHeight; 52 | } 53 | }, { 54 | key: 'componentDidMount', 55 | value: function componentDidMount() { 56 | if (this.props.config.animations) { 57 | window.addEventListener('scroll', this.onScroll); 58 | window.addEventListener('resize', this.onScroll); 59 | this.onScroll(); 60 | } 61 | } 62 | }, { 63 | key: 'componentWillUnmount', 64 | value: function componentWillUnmount() { 65 | window.removeEventListener('scroll', this.onScroll); 66 | window.removeEventListener('resize', this.onScroll); 67 | } 68 | }, { 69 | key: 'onScroll', 70 | value: function onScroll() { 71 | var inView = this.areChildrenInView(); 72 | this.props.onInView(inView); 73 | } 74 | }, { 75 | key: 'render', 76 | value: function render() { 77 | var _base, 78 | _even, 79 | _this2 = this; 80 | 81 | var _props = this.props, 82 | children = _props.children, 83 | even = _props.even, 84 | inView = _props.inView, 85 | config = _props.config; 86 | var circleWidth = config.circleWidth, 87 | mediaWidthMed = config.mediaWidthMed, 88 | paddingToItem = config.paddingToItem, 89 | paddingToItemSmall = config.paddingToItemSmall, 90 | itemWidth = config.itemWidth, 91 | itemWidthMed = config.itemWidthMed, 92 | animations = config.animations, 93 | lineWidth = config.lineWidth, 94 | evenItemOffset = config.evenItemOffset, 95 | offsetHidden = config.offsetHidden, 96 | triangleWidth = config.triangleWidth, 97 | triangleOffset = config.triangleOffset, 98 | triangleHeight = config.triangleHeight, 99 | activeColor = config.activeColor, 100 | mediaWidthSmall = config.mediaWidthSmall, 101 | smallItemWidthPadding = config.smallItemWidthPadding, 102 | itemPadding = config.itemPadding; 103 | 104 | 105 | var offsetEven = circleWidth + paddingToItem + triangleWidth - lineWidth + evenItemOffset; 106 | var offsetEvenNml = offsetEven + itemWidth; 107 | var offsetEvenMed = offsetEven + itemWidthMed; 108 | 109 | var triangleLeft = { 110 | right: '-' + (triangleWidth - 1) + 'px', 111 | borderWidth: triangleHeight + 'px 0 ' + triangleHeight + 'px ' + triangleWidth + 'px', 112 | borderColor: 'transparent transparent transparent ' + activeColor 113 | }; 114 | var triangleRight = { 115 | left: '-' + (triangleWidth - 1) + 'px', 116 | borderWidth: triangleHeight + 'px ' + triangleWidth + 'px ' + triangleHeight + 'px 0', 117 | borderColor: 'transparent ' + activeColor + ' transparent transparent' 118 | }; 119 | 120 | var mediaMed = '@media screen and (min-width: ' + mediaWidthSmall + 'px)\n and (max-width: ' + mediaWidthMed + 'px)'; 121 | var mediaSmall = '@media screen and (max-width: ' + mediaWidthSmall + 'px)'; 122 | var mediaPrint = '@media print'; 123 | 124 | var styles = { 125 | base: (_base = { 126 | position: 'relative', 127 | bottom: '0', 128 | width: itemWidth + 'px', 129 | padding: itemPadding + 'px', 130 | background: activeColor, 131 | visibility: animations ? 'hidden' : null, 132 | opacity: animations ? 0 : 1, 133 | transition: animations ? 'all .5s ease-in-out' : null 134 | }, _defineProperty(_base, mediaMed, { 135 | width: itemWidthMed + 'px' 136 | }), _defineProperty(_base, mediaSmall, { 137 | width: 'calc(100vw - ' + (triangleWidth + circleWidth + smallItemWidthPadding) + 'px)' 138 | }), _defineProperty(_base, mediaPrint, { 139 | width: '100%', 140 | left: 0, 141 | transform: null 142 | }), _base), 143 | inView: { 144 | transform: 'none', 145 | visibility: 'visible', 146 | opacity: '1' 147 | }, 148 | even: (_even = { 149 | left: '-' + offsetEvenNml + 'px', 150 | transform: animations ? 'translate3d(-' + offsetHidden + 'px,0,0)' : null 151 | }, _defineProperty(_even, mediaMed, { 152 | left: '-' + offsetEvenMed + 'px' 153 | }), _defineProperty(_even, mediaSmall, { 154 | left: paddingToItemSmall + triangleWidth + 'px' 155 | }), _even), 156 | odd: _defineProperty({ 157 | left: paddingToItem + triangleWidth + 'px', 158 | transform: animations ? 'translate3d(' + offsetHidden + 'px,0,0)' : null 159 | }, mediaSmall, { 160 | left: paddingToItemSmall + triangleWidth + 'px' 161 | }), 162 | triangle: { 163 | base: { 164 | position: 'absolute', 165 | bottom: triangleOffset + 'px', 166 | width: '0', 167 | height: '0', 168 | borderStyle: 'solid' 169 | }, 170 | even: _extends({}, triangleLeft, _defineProperty({}, mediaSmall, triangleRight)), 171 | odd: triangleRight 172 | } 173 | }; 174 | 175 | var propsToAdd = {}; 176 | if (config.addEvenPropToChildren) { 177 | propsToAdd = _extends({}, propsToAdd, { 178 | even: even 179 | }); 180 | } 181 | 182 | return _react2.default.createElement( 183 | 'div', 184 | { style: [styles.base, even ? styles.even : styles.odd, inView && styles.inView] }, 185 | _react2.default.createElement('span', { style: [styles.triangle.base, even ? styles.triangle.even : styles.triangle.odd, inView && styles.triangle.inView] }), 186 | _react2.default.createElement( 187 | 'div', 188 | { ref: function ref(c) { 189 | return _this2.children = c; 190 | } }, 191 | _react2.default.cloneElement(children, propsToAdd) 192 | ) 193 | ); 194 | } 195 | }]); 196 | 197 | return Content; 198 | }(_react2.default.Component); 199 | 200 | Content.propTypes = { 201 | children: _propTypes2.default.node.isRequired, 202 | even: _propTypes2.default.bool.isRequired, 203 | inView: _propTypes2.default.bool.isRequired, 204 | onInView: _propTypes2.default.func, 205 | config: _propTypes2.default.object.isRequired 206 | }; 207 | 208 | exports.default = (0, _radium2.default)(Content); --------------------------------------------------------------------------------