├── .eslintignore ├── codecov.yml ├── .prettierrc.json ├── test ├── .eslintrc └── e2e.spec.js ├── .gitignore ├── examples ├── lottery │ ├── src │ │ ├── components │ │ │ ├── Header │ │ │ │ ├── index.js │ │ │ │ ├── Container.js │ │ │ │ └── Component.js │ │ │ └── RouletteNumbers │ │ │ │ ├── LotteryNumber.js │ │ │ │ ├── ScrollableSlider.js │ │ │ │ ├── Slider.js │ │ │ │ ├── index.js │ │ │ │ └── ScrollableLotteryNumber.js │ │ ├── App.js │ │ ├── index.js │ │ ├── store.js │ │ ├── reducer.js │ │ ├── rebass-config.js │ │ └── actions.js │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── main.css │ ├── .gitignore │ └── package.json ├── fiddler │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── main.css │ ├── src │ │ ├── actions.js │ │ ├── reducer.js │ │ ├── index.js │ │ ├── store.js │ │ ├── rebass-config.js │ │ ├── App.js │ │ └── components │ │ │ ├── ScrollArea.js │ │ │ └── ControlPanel.js │ ├── .gitignore │ └── package.json └── paragraphs │ ├── src │ ├── components │ │ ├── Content │ │ │ ├── index.js │ │ │ ├── Container.js │ │ │ └── Component.js │ │ └── Navbar │ │ │ ├── index.js │ │ │ ├── Container.js │ │ │ └── Component.js │ ├── App.js │ ├── actions.js │ ├── store.js │ ├── rebass-config.js │ └── index.js │ ├── public │ ├── favicon.ico │ ├── index.html │ └── main.css │ ├── .gitignore │ └── package.json ├── .travis.yml ├── src ├── index.js ├── scrollable-area-hoc.js ├── middleware.js ├── scroll-to-when-hoc.js └── scroll.js ├── .eslintrc ├── rollup.config.js ├── LICENSE.md ├── .babelrc ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist 5 | lib 6 | .nyc_output 7 | coverage 8 | es 9 | -------------------------------------------------------------------------------- /examples/lottery/src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import Container from './Container'; 2 | 3 | export default Container; 4 | -------------------------------------------------------------------------------- /examples/fiddler/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josepot/react-redux-scroll/HEAD/examples/fiddler/public/favicon.ico -------------------------------------------------------------------------------- /examples/lottery/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josepot/react-redux-scroll/HEAD/examples/lottery/public/favicon.ico -------------------------------------------------------------------------------- /examples/paragraphs/src/components/Content/index.js: -------------------------------------------------------------------------------- 1 | import Container from './Container'; 2 | 3 | export default Container; 4 | 5 | -------------------------------------------------------------------------------- /examples/paragraphs/src/components/Navbar/index.js: -------------------------------------------------------------------------------- 1 | import Container from './Container'; 2 | 3 | export default Container; 4 | 5 | -------------------------------------------------------------------------------- /examples/paragraphs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josepot/react-redux-scroll/HEAD/examples/paragraphs/public/favicon.ico -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | script: 5 | - npm run lint 6 | - npm test 7 | after_success: 8 | - npm run coverage 9 | -------------------------------------------------------------------------------- /examples/fiddler/src/actions.js: -------------------------------------------------------------------------------- 1 | // Action types 2 | export const SCROLL_STARTED = 'SCROLL_STARTED'; 3 | export const SCROLL_ENDED = 'SCROLL_ENDED'; 4 | 5 | // Action Creators 6 | export const onScrollStart = number => ({ 7 | type: SCROLL_STARTED, 8 | payload: number, 9 | }); 10 | -------------------------------------------------------------------------------- /examples/paragraphs/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import rebassConfig from './rebass-config'; 3 | import Navbar from './components/Navbar'; 4 | import Content from './components/Content'; 5 | 6 | export default rebassConfig(() => 7 |
8 | 9 | 10 |
11 | ); 12 | -------------------------------------------------------------------------------- /examples/fiddler/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | -------------------------------------------------------------------------------- /examples/lottery/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | -------------------------------------------------------------------------------- /examples/paragraphs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | -------------------------------------------------------------------------------- /examples/lottery/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import rebassConfig from './rebass-config'; 3 | import Header from './components/Header'; 4 | import RouletteNumbers from './components/RouletteNumbers'; 5 | 6 | export default rebassConfig(() => 7 |
8 |
9 | 10 |
11 | ); 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import createScrollMiddleware from './middleware'; 2 | import scrollToWhen from './scroll-to-when-hoc'; 3 | import scrollableArea from './scrollable-area-hoc'; 4 | import { ALIGNMENTS, TIMING_FUNCTIONS } from './scroll'; 5 | 6 | export { 7 | createScrollMiddleware, 8 | scrollableArea, 9 | scrollToWhen, 10 | ALIGNMENTS, 11 | TIMING_FUNCTIONS 12 | }; 13 | -------------------------------------------------------------------------------- /examples/paragraphs/src/actions.js: -------------------------------------------------------------------------------- 1 | // Action types 2 | export const SCROLL_TO_PARAGRAPH = 'SCROLL_TO_PARAGRAPH'; 3 | export const SCROLL_TO_HEADER = 'SCROLL_TO_HEADER'; 4 | 5 | // Action Creators 6 | export const scrollToParagraph = paragraphId => ({ 7 | type: SCROLL_TO_PARAGRAPH, 8 | paragraphId, 9 | }); 10 | export const scrollToHeader = () => ({ type: SCROLL_TO_HEADER }); 11 | -------------------------------------------------------------------------------- /examples/fiddler/src/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { SCROLL_STARTED, SCROLL_ENDED } from './actions'; 3 | 4 | const isScrollRunning = (state = false, { type }) => ( 5 | type === SCROLL_STARTED ? true : 6 | type === SCROLL_ENDED ? false : 7 | state 8 | ); 9 | 10 | export default combineReducers({ isScrollRunning }); 11 | -------------------------------------------------------------------------------- /examples/fiddler/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import App from './App'; 6 | import configureStore from './store'; 7 | import reducer from './reducer'; 8 | 9 | const store = configureStore(reducer); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | 18 | -------------------------------------------------------------------------------- /examples/lottery/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import App from './App'; 6 | import configureStore from './store'; 7 | import reducer from './reducer'; 8 | 9 | const store = configureStore(reducer); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | 18 | -------------------------------------------------------------------------------- /examples/fiddler/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React-Redux-Scroll: Lottery example 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/lottery/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React-Redux-Scroll: Lottery example 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/paragraphs/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React-Redux-Scroll: Lottery example 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/paragraphs/src/components/Navbar/Container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { compose, withState, withHandlers } from 'recompose'; 3 | import { scrollToParagraph, scrollToHeader } from '../../actions'; 4 | import Navbar from './Component'; 5 | 6 | export default compose( 7 | connect( 8 | paragraphs => ({ paragraphs }), 9 | { scrollToParagraph, scrollToHeader } 10 | ), 11 | withState('isDropdownOpen', 'setDropdownState', false), 12 | withHandlers({ toogleDropdown: 13 | ({ isDropdownOpen, setDropdownState }) => () => 14 | setDropdownState(!isDropdownOpen), 15 | }) 16 | )(Navbar); 17 | -------------------------------------------------------------------------------- /examples/lottery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lotery", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.9.5" 7 | }, 8 | "dependencies": { 9 | "react": "^15.4.2", 10 | "react-dom": "^15.4.2", 11 | "react-geomicons": "^2.1.0", 12 | "react-redux": "^5.0.3", 13 | "react-redux-scroll": "^0.0.7", 14 | "rebass": "^0.3.4", 15 | "recompose": "^0.22.0", 16 | "redux": "^3.6.0", 17 | "redux-logger": "^2.8.2" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test --env=jsdom", 23 | "eject": "react-scripts eject" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/paragraphs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paragraphs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.9.5" 7 | }, 8 | "dependencies": { 9 | "react": "^15.4.2", 10 | "react-dom": "^15.4.2", 11 | "react-geomicons": "^2.1.0", 12 | "react-redux": "^5.0.3", 13 | "react-redux-scroll": "^0.0.7", 14 | "rebass": "^0.3.4", 15 | "recompose": "^0.22.0", 16 | "redux": "^3.6.0", 17 | "redux-logger": "^2.8.2" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test --env=jsdom", 23 | "eject": "react-scripts eject" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:import/recommended", 6 | "plugin:react/recommended" 7 | ], 8 | "parserOptions": { 9 | "ecmaVersion": 6, 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "jsx": true, 13 | "experimentalObjectRestSpread": true 14 | } 15 | }, 16 | "env": { 17 | "es6": true, 18 | "browser": true, 19 | "mocha": true, 20 | "node": true 21 | }, 22 | "rules": { 23 | "valid-jsdoc": 2, 24 | "react/jsx-uses-react": 1, 25 | "react/jsx-no-undef": 2, 26 | "react/jsx-wrap-multilines": 2, 27 | "react/no-string-refs": 0 28 | }, 29 | "plugins": [ 30 | "import", 31 | "react" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/fiddler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lotery", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.9.5" 7 | }, 8 | "dependencies": { 9 | "ramda": "^0.23.0", 10 | "react": "^15.4.2", 11 | "react-dom": "^15.4.2", 12 | "react-geomicons": "^2.1.0", 13 | "react-redux": "^5.0.3", 14 | "react-redux-scroll": "0.0.12", 15 | "rebass": "^0.3.4", 16 | "recompose": "^0.22.0", 17 | "redux": "^3.6.0", 18 | "redux-logger": "^2.8.2", 19 | "reflexbox": "^2.2.3" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test --env=jsdom", 25 | "eject": "react-scripts eject" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/paragraphs/src/components/Content/Container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { compose, withProps } from 'recompose'; 3 | 4 | import { scrollToWhen } from 'react-redux-scroll'; 5 | import { SCROLL_TO_HEADER, SCROLL_TO_PARAGRAPH } from '../../actions'; 6 | import Content from './Component'; 7 | 8 | export default compose( 9 | connect(paragraphs => ({ paragraphs })), 10 | withProps({ 11 | scrollableHeader: scrollToWhen(SCROLL_TO_HEADER, null, { yMargin: -50 }), 12 | scrollableParagraph: scrollToWhen( 13 | (action, props) => ( 14 | action.type === SCROLL_TO_PARAGRAPH && props.id === action.paragraphId 15 | ), 16 | null, 17 | { yMargin: -60 }, 18 | ['id'] 19 | ), 20 | }) 21 | )(Content); 22 | -------------------------------------------------------------------------------- /examples/lottery/src/components/RouletteNumbers/LotteryNumber.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Badge, Block } from 'rebass'; 3 | 4 | const ScrollableNumber = ({ style, number }) => 5 | 6 | {number} 11 | ; 12 | 13 | ScrollableNumber.defaultProps = { number: null }; 14 | 15 | ScrollableNumber.propTypes = { 16 | style: PropTypes.shape({ 17 | width: PropTypes.string.isRequired, 18 | height: PropTypes.string.isRequired, 19 | }).isRequired, 20 | number: PropTypes.number, 21 | }; 22 | 23 | export default ScrollableNumber; 24 | 25 | -------------------------------------------------------------------------------- /examples/fiddler/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import createLogger from 'redux-logger'; 3 | import { createScrollMiddleware } from 'react-redux-scroll'; 4 | 5 | const devMode = process.env.NODE_ENV !== 'production'; 6 | 7 | const loggerMiddleware = createLogger(); 8 | 9 | const getMiddlewares = () => { 10 | const common = [createScrollMiddleware()]; 11 | const dev = [loggerMiddleware]; 12 | const prod = []; 13 | return [...common, ...(devMode ? dev : prod)]; 14 | }; 15 | 16 | const getEnhancers = () => ( 17 | devMode && window.devToolsExtension ? [window.devToolsExtension()] : [] 18 | ); 19 | 20 | export default reducer => compose( 21 | applyMiddleware(...getMiddlewares()), 22 | ...getEnhancers() 23 | )(createStore)(reducer); 24 | -------------------------------------------------------------------------------- /examples/lottery/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import createLogger from 'redux-logger'; 3 | import { createScrollMiddleware } from 'react-redux-scroll'; 4 | 5 | const devMode = process.env.NODE_ENV !== 'production'; 6 | 7 | const loggerMiddleware = createLogger(); 8 | 9 | const getMiddlewares = () => { 10 | const common = [createScrollMiddleware()]; 11 | const dev = [loggerMiddleware]; 12 | const prod = []; 13 | return [...common, ...(devMode ? dev : prod)]; 14 | }; 15 | 16 | const getEnhancers = () => ( 17 | devMode && window.devToolsExtension ? [window.devToolsExtension()] : [] 18 | ); 19 | 20 | export default reducer => compose( 21 | applyMiddleware(...getMiddlewares()), 22 | ...getEnhancers() 23 | )(createStore)(reducer); 24 | -------------------------------------------------------------------------------- /examples/paragraphs/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import createLogger from 'redux-logger'; 3 | import { createScrollMiddleware } from 'react-redux-scroll'; 4 | 5 | const devMode = process.env.NODE_ENV !== 'production'; 6 | 7 | const loggerMiddleware = createLogger(); 8 | 9 | const getMiddlewares = () => { 10 | const common = [createScrollMiddleware()]; 11 | const dev = [loggerMiddleware]; 12 | const prod = []; 13 | return [...common, ...(devMode ? dev : prod)]; 14 | }; 15 | 16 | const getEnhancers = () => ( 17 | devMode && window.devToolsExtension ? [window.devToolsExtension()] : [] 18 | ); 19 | 20 | export default reducer => compose( 21 | applyMiddleware(...getMiddlewares()), 22 | ...getEnhancers() 23 | )(createStore)(reducer); 24 | -------------------------------------------------------------------------------- /examples/lottery/src/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { 4 | DIGIT_DISPLAYED, 5 | MAX_RANGE_CHANGED, 6 | NUMBER_GENERATED, 7 | RESET, 8 | } from './actions'; 9 | 10 | const maxRange = (state = 9999, { type, payload }) => ( 11 | type === MAX_RANGE_CHANGED ? payload : state 12 | ); 13 | 14 | const digitsDisplayed = (state = 0, { type }) => ( 15 | type === DIGIT_DISPLAYED ? state + 1 : 16 | type === RESET ? 0 : 17 | state 18 | ); 19 | 20 | const winner = (state = null, { type, payload } = {}) => ( 21 | type === NUMBER_GENERATED ? payload : 22 | type === RESET ? null : 23 | state 24 | ); 25 | 26 | export default combineReducers({ maxRange, digitsDisplayed, winner }); 27 | -------------------------------------------------------------------------------- /examples/fiddler/src/rebass-config.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import { withContext } from 'recompose'; 3 | import { config } from 'rebass'; 4 | 5 | const rebass = { 6 | colors: { 7 | ...config.colors, 8 | gray2: '#666', 9 | darken: 'rgba(0, 0, 0, .9375)', 10 | d1: 'rgba(0, 0, 0, .125)', 11 | }, 12 | Divider: { 13 | borderColor: 'inherit', 14 | }, 15 | PageHeader: { 16 | borderColor: 'inherit', 17 | }, 18 | SectionHeader: { 19 | borderColor: 'inherit', 20 | }, 21 | }; 22 | 23 | export default withContext( 24 | { rebass: PropTypes.object, reflexbox: PropTypes.object }, 25 | () => ({ 26 | rebass, 27 | reflexbox: { 28 | breakpoints: { 29 | sm: '(min-width: 30em)', 30 | md: '(min-width: 48em)', 31 | lg: '(min-width: 57.75em)', 32 | }, 33 | }, 34 | }), 35 | ); 36 | 37 | -------------------------------------------------------------------------------- /examples/lottery/src/rebass-config.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import { withContext } from 'recompose'; 3 | import { config } from 'rebass'; 4 | 5 | const rebass = { 6 | colors: { 7 | ...config.colors, 8 | gray2: '#666', 9 | darken: 'rgba(0, 0, 0, .9375)', 10 | d1: 'rgba(0, 0, 0, .125)', 11 | }, 12 | Divider: { 13 | borderColor: 'inherit', 14 | }, 15 | PageHeader: { 16 | borderColor: 'inherit', 17 | }, 18 | SectionHeader: { 19 | borderColor: 'inherit', 20 | }, 21 | }; 22 | 23 | export default withContext( 24 | { rebass: PropTypes.object, reflexbox: PropTypes.object }, 25 | () => ({ 26 | rebass, 27 | reflexbox: { 28 | breakpoints: { 29 | sm: '(min-width: 30em)', 30 | md: '(min-width: 48em)', 31 | lg: '(min-width: 57.75em)', 32 | }, 33 | }, 34 | }), 35 | ); 36 | 37 | -------------------------------------------------------------------------------- /examples/paragraphs/src/rebass-config.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import { withContext } from 'recompose'; 3 | import { config } from 'rebass'; 4 | 5 | const rebass = { 6 | colors: { 7 | ...config.colors, 8 | gray2: '#666', 9 | darken: 'rgba(0, 0, 0, .9375)', 10 | d1: 'rgba(0, 0, 0, .125)', 11 | }, 12 | Divider: { 13 | borderColor: 'inherit', 14 | }, 15 | PageHeader: { 16 | borderColor: 'inherit', 17 | }, 18 | SectionHeader: { 19 | borderColor: 'inherit', 20 | }, 21 | }; 22 | 23 | export default withContext( 24 | { rebass: PropTypes.object, reflexbox: PropTypes.object }, 25 | () => ({ 26 | rebass, 27 | reflexbox: { 28 | breakpoints: { 29 | sm: '(min-width: 30em)', 30 | md: '(min-width: 48em)', 31 | lg: '(min-width: 57.75em)', 32 | }, 33 | }, 34 | }), 35 | ); 36 | 37 | -------------------------------------------------------------------------------- /examples/lottery/src/actions.js: -------------------------------------------------------------------------------- 1 | // Action types 2 | export const DIGIT_DISPLAYED = 'DIGIT_DISPLAYED'; 3 | export const MAX_RANGE_CHANGED = 'MAX_RANGE_CHANGED'; 4 | export const NUMBER_GENERATED = 'NUMBER_GENERATED'; 5 | export const RESET = 'RESET'; 6 | 7 | // Action Creators 8 | export const onDisplayDigit = () => ({ type: DIGIT_DISPLAYED }); 9 | 10 | export const onMaxRangeChange = newMaxRange => ({ 11 | type: MAX_RANGE_CHANGED, payload: newMaxRange, 12 | }); 13 | 14 | export const onGenerateNumber = (range) => { 15 | const numbers = Math.floor(Math.random() * (range + 1)) 16 | .toString(10) 17 | .split(''); 18 | 19 | const missingZeros = range.toString(10).length - numbers.length; 20 | for (let i = 0; i < missingZeros; i += 1) numbers.unshift('0'); 21 | 22 | return { type: NUMBER_GENERATED, payload: numbers.join('') }; 23 | }; 24 | 25 | export const onReset = () => ({ type: RESET }); 26 | -------------------------------------------------------------------------------- /examples/lottery/src/components/RouletteNumbers/ScrollableSlider.js: -------------------------------------------------------------------------------- 1 | import { scrollToWhen } from 'react-redux-scroll'; 2 | 3 | import { DIGIT_DISPLAYED, RESET, NUMBER_GENERATED } from '../../actions'; 4 | import Slider from './Slider'; 5 | 6 | const SCROLL_TO_SLIDER_ROW_DURATION = 500; 7 | const RESET_SCROLL_DURATION = 0; 8 | 9 | const isNumberGeneratedForRow = ({ type }, { positionNumber }, newState) => ( 10 | [NUMBER_GENERATED, DIGIT_DISPLAYED].includes(type) && 11 | positionNumber === newState.digitsDisplayed 12 | ); 13 | const isResetForFirstSlider = ({ type }, { positionNumber }) => ( 14 | type === RESET && positionNumber === 0 15 | ); 16 | 17 | const returnDurationWhen = ([conditionFn, duration]) => (...args) => ( 18 | conditionFn(...args) ? { scrollOptions: { duration } } : null 19 | ); 20 | 21 | export default scrollToWhen( 22 | [ 23 | [isNumberGeneratedForRow, SCROLL_TO_SLIDER_ROW_DURATION], 24 | [isResetForFirstSlider, RESET_SCROLL_DURATION], 25 | ].map(returnDurationWhen), 26 | null, 27 | { yMargin: -20 } 28 | )(Slider); 29 | -------------------------------------------------------------------------------- /examples/paragraphs/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import App from './App'; 6 | import configureStore from './store'; 7 | 8 | // eslint-disable-next-line max-len 9 | const content = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; 10 | 11 | const paragraphs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(id => ({ 12 | id, 13 | title: `This is paragraph ${id}`, 14 | content, 15 | })); 16 | 17 | const store = configureStore(() => paragraphs); 18 | 19 | ReactDOM.render( 20 | 21 | 22 | , 23 | document.getElementById('root') 24 | ); 25 | 26 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from 'rollup-plugin-node-resolve' 2 | import babel from 'rollup-plugin-babel' 3 | import replace from 'rollup-plugin-replace' 4 | import commonjs from 'rollup-plugin-commonjs' 5 | import uglify from 'rollup-plugin-uglify' 6 | 7 | const env = process.env.NODE_ENV 8 | 9 | const config = { 10 | input: 'src/index.js', 11 | external: ['react', 'redux'], 12 | output: { 13 | format: 'umd', 14 | name: 'ReactReduxScroll', 15 | globals: { 16 | react: 'React', 17 | redux: 'Redux' 18 | } 19 | }, 20 | plugins: [ 21 | nodeResolve(), 22 | babel({ 23 | exclude: '**/node_modules/**' 24 | }), 25 | replace({ 26 | 'process.env.NODE_ENV': JSON.stringify(env) 27 | }), 28 | commonjs() 29 | ] 30 | } 31 | 32 | if (env === 'production') { 33 | config.plugins.push( 34 | uglify({ 35 | compress: { 36 | pure_getters: true, 37 | unsafe: true, 38 | unsafe_comps: true, 39 | warnings: false 40 | } 41 | }) 42 | ) 43 | } 44 | 45 | export default config 46 | -------------------------------------------------------------------------------- /examples/paragraphs/src/components/Content/Component.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Block, Text } from 'rebass'; 3 | 4 | const Content = ({ 5 | paragraphs, 6 | scrollableHeader, 7 | scrollableParagraph, 8 | }) => { 9 | const ScrollableHeader = scrollableHeader('h1'); 10 | const ScrollableParagraph = scrollableParagraph(Block); 11 | 12 | return ( 13 | 14 | This is the main Header 15 | {paragraphs.map(p => 16 | 17 |

{p.title}

18 | {p.content} 19 |
20 | )} 21 |
22 | ); 23 | }; 24 | 25 | Content.propTypes = { 26 | paragraphs: PropTypes.arrayOf(PropTypes.shape({ 27 | id: PropTypes.number.isRequired, 28 | title: PropTypes.string.isRequired, 29 | content: PropTypes.string.isRequired, 30 | })).isRequired, 31 | scrollableHeader: PropTypes.func.isRequired, 32 | scrollableParagraph: PropTypes.func.isRequired, 33 | }; 34 | 35 | export default Content; 36 | -------------------------------------------------------------------------------- /examples/lottery/src/components/Header/Container.js: -------------------------------------------------------------------------------- 1 | import { compose, mapProps, withHandlers } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { onGenerateNumber, onMaxRangeChange, onReset } from '../../actions'; 5 | import Header from './Component'; 6 | 7 | export default compose( 8 | connect( 9 | ({ digitsDisplayed, maxRange, winner }) => ({ 10 | numbers: (winner === null ? maxRange.toString(10) : winner) 11 | .split('') 12 | .map((digit, idx) => (digitsDisplayed > idx ? digit : '')), 13 | maxRange, 14 | isReady: winner === null, 15 | }), 16 | { onGenerateNumber, onMaxRangeChange, onReset } 17 | ), 18 | mapProps(({ 19 | isReady, 20 | maxRange, 21 | onGenerateNumber: generate, 22 | onReset: reset, 23 | ...props 24 | }) => ({ 25 | isReady, 26 | maxRange, 27 | onClick: isReady ? generate.bind(null, maxRange) : reset, 28 | ...props, 29 | })), 30 | withHandlers({ onChange: 31 | ({ onMaxRangeChange: rangeChange }) => e => 32 | rangeChange(parseInt(e.target.value, 10)), 33 | }) 34 | )(Header); 35 | 36 | -------------------------------------------------------------------------------- /src/scrollable-area-hoc.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import hoistStatics from 'hoist-non-react-statics'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | export default Component => { 7 | if (process.env.IS_SSR) return Component; 8 | 9 | class ScrollableArea extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.getScrollContext = this.getScrollContext.bind(this); 13 | this._domNode = null; 14 | } 15 | 16 | getChildContext() { 17 | return { getScrollContext: this.getScrollContext }; 18 | } 19 | 20 | componentDidMount() { 21 | // eslint-disable-next-line react/no-find-dom-node 22 | this._domNode = this._domNode || ReactDOM.findDOMNode(this); 23 | } 24 | 25 | getScrollContext() { 26 | return this._domNode; 27 | } 28 | 29 | render() { 30 | return (this._domNode = x)} {...this.props} />; 31 | } 32 | } 33 | 34 | ScrollableArea.childContextTypes = { 35 | getScrollContext: PropTypes.func 36 | }; 37 | 38 | return hoistStatics(ScrollableArea, Component); 39 | }; 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present Josep M Sobrepere Profitos 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. 22 | -------------------------------------------------------------------------------- /examples/lottery/src/components/RouletteNumbers/Slider.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Block } from 'rebass'; 3 | import { scrollableArea } from 'react-redux-scroll'; 4 | import ScrollableLotteryNumber from './ScrollableLotteryNumber'; 5 | 6 | const ScrollableArea = scrollableArea(Block); 7 | const SLIDE_SIZE = '200px'; 8 | const dimensions = { width: SLIDE_SIZE, height: SLIDE_SIZE }; 9 | 10 | const Slider = ({ positionNumber }) => 11 | 22 | 23 | {[null, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(number => 24 | 30 | )} 31 | 32 | ; 33 | 34 | Slider.propTypes = { 35 | positionNumber: PropTypes.number.isRequired, 36 | }; 37 | 38 | export default Slider; 39 | 40 | -------------------------------------------------------------------------------- /examples/lottery/src/components/RouletteNumbers/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Block } from 'rebass'; 3 | import { connect } from 'react-redux'; 4 | import { compose } from 'recompose'; 5 | import { scrollableArea } from 'react-redux-scroll'; 6 | import ScrollableSlider from './ScrollableSlider'; 7 | 8 | const getRange = (start, end) => { 9 | const result = []; 10 | for (let i = start; i < end; i += 1) result.push(i); 11 | return result; 12 | }; 13 | 14 | const RouletteNumbers = ({ nDigits }) => 15 | 28 | {getRange(0, nDigits).map(positionNumber => 29 | 30 | )} 31 | ; 32 | 33 | RouletteNumbers.propTypes = { 34 | nDigits: PropTypes.number.isRequired, 35 | }; 36 | 37 | export default compose( 38 | connect(({ maxRange }) => ({ nDigits: maxRange.toString(10).length })), 39 | scrollableArea 40 | )(RouletteNumbers); 41 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-decorators-legacy", 4 | ["transform-es2015-template-literals", { "loose": true }], 5 | "transform-es2015-literals", 6 | "transform-es2015-function-name", 7 | "transform-es2015-arrow-functions", 8 | "transform-es2015-block-scoped-functions", 9 | ["transform-es2015-classes", { "loose": true }], 10 | "transform-es2015-object-super", 11 | "transform-es2015-shorthand-properties", 12 | ["transform-es2015-computed-properties", { "loose": true }], 13 | ["transform-es2015-for-of", { "loose": true }], 14 | "transform-es2015-sticky-regex", 15 | "transform-es2015-unicode-regex", 16 | "check-es2015-constants", 17 | ["transform-es2015-spread", { "loose": true }], 18 | "transform-es2015-parameters", 19 | ["transform-es2015-destructuring", { "loose": true }], 20 | "transform-es2015-block-scoping", 21 | "transform-object-rest-spread", 22 | "transform-react-jsx", 23 | "syntax-jsx" 24 | ], 25 | "env": { 26 | "test": { 27 | "plugins": [ 28 | "istanbul", 29 | ["transform-es2015-modules-commonjs", { "loose": true }] 30 | ] 31 | }, 32 | "commonjs": { 33 | "plugins": [ 34 | ["transform-es2015-modules-commonjs", { "loose": true }] 35 | ] 36 | }, 37 | "rollup": { 38 | "plugins": [ 39 | "external-helpers" 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/lottery/src/components/RouletteNumbers/ScrollableLotteryNumber.js: -------------------------------------------------------------------------------- 1 | import { scrollToWhen } from 'react-redux-scroll'; 2 | 3 | import LoteryNumber from './LotteryNumber'; 4 | import { 5 | onDisplayDigit, 6 | DIGIT_DISPLAYED, 7 | NUMBER_GENERATED, 8 | RESET, 9 | } from '../../actions'; 10 | 11 | const SCROLL_TO_NUMBER_DURATION = 2000; 12 | const RESET_DURATION = 0; 13 | 14 | const isNumberSelected = ( 15 | { type, payload }, 16 | { positionNumber, number }, 17 | newState 18 | ) => ( 19 | number !== null && 20 | [NUMBER_GENERATED, DIGIT_DISPLAYED].includes(type) && 21 | positionNumber === newState.digitsDisplayed && 22 | number.toString(10) === newState.winner.substr(positionNumber, 1) 23 | ); 24 | 25 | const isReset = ({ type }, { number }) => (type === RESET && number === null); 26 | 27 | const onEndScrollingToNumber = (dispatch, canceled) => { 28 | if (!canceled) dispatch(onDisplayDigit()); 29 | }; 30 | 31 | const returnOptionsWhen = ([conditionFn, options]) => (...args) => ( 32 | conditionFn(...args) ? options : null 33 | ); 34 | 35 | export default scrollToWhen( 36 | [ 37 | [isNumberSelected, { 38 | onEnd: onEndScrollingToNumber, 39 | scrollOptions: { duration: SCROLL_TO_NUMBER_DURATION }, 40 | }], 41 | [isReset, { 42 | scrollOptions: { duration: RESET_DURATION }, 43 | }], 44 | ].map(returnOptionsWhen), 45 | null, 46 | { xAlignment: 'LEFT', yAlignment: null } 47 | )(LoteryNumber); 48 | -------------------------------------------------------------------------------- /examples/fiddler/src/App.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import R from 'ramda'; 3 | import { compose, withState, withHandlers, withContext } from 'recompose'; 4 | import { connect } from 'react-redux'; 5 | 6 | import rebassConfig from './rebass-config'; 7 | import ControlPanel from './components/ControlPanel'; 8 | import { onScrollStart } from './actions'; 9 | 10 | const withEventState = ([name, updater, initialValue]) => compose( 11 | withState(name, updater, initialValue), 12 | withHandlers({ [updater]: props => e => props[updater]( 13 | typeof initialValue === 'number' ? 14 | parseInt(e.target.value, 10) : 15 | e.target.value 16 | ) }) 17 | ); 18 | 19 | export default compose( 20 | rebassConfig, 21 | ...[ 22 | ['numberToScroll', 'onNumberToScrollChange', 6], 23 | ['duration', 'onDurationChange', 500], 24 | ['transitionTimingFunction', 'onTimingFnChange', 'EASE_IN_QUAD'], 25 | ['yAlignment', 'onYAlignmentChange', 'TOP'], 26 | ['xAlignment', 'onXAlignmentChange', 'LEFT'], 27 | ['yMargin', 'onYMarginChange', 0], 28 | ['xMargin', 'onXMarginChange', 0], 29 | ].map(withEventState), 30 | connect(R.identity, { onScrollStart }), 31 | withContext( 32 | { scrollOptions: PropTypes.object }, 33 | props => ({ 34 | scrollOptions: R.pick([ 35 | 'duration', 'transitionTimingFunction', 36 | 'yAlignment', 'xAlignment', 'yMargin', 'xMargin', 37 | ], props), 38 | }) 39 | ) 40 | )(ControlPanel); 41 | -------------------------------------------------------------------------------- /examples/fiddler/public/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: 7 | -apple-system, 8 | BlinkMacSystemFont, 9 | 'Segoe UI', 10 | 'Roboto', 11 | 'Helvetica Neue', 12 | Helvetica, 13 | sans-serif; 14 | line-height: 1.5; 15 | margin: 0; 16 | color: #111; 17 | background-color: #fff; 18 | } 19 | 20 | img { 21 | max-width: 100%; 22 | height: auto; 23 | } 24 | 25 | svg { 26 | max-height: 100%; 27 | } 28 | 29 | a { 30 | color: #07c; 31 | } 32 | 33 | h1, h2, h3, 34 | h4, h5, h6 { 35 | font-weight: 600; 36 | line-height: 1.25; 37 | margin-top: 1em; 38 | margin-bottom: .5em; 39 | } 40 | 41 | h1 { font-size: 2rem } 42 | h2 { font-size: 1.5rem } 43 | h3 { font-size: 1.25rem } 44 | h4 { font-size: 1rem } 45 | h5 { font-size: .875rem } 46 | h6 { font-size: .75rem } 47 | 48 | p, dl, ol, ul, pre, blockquote { 49 | margin-top: 1em; 50 | margin-bottom: 1em; 51 | } 52 | 53 | code, 54 | pre, 55 | samp { 56 | font-family: 57 | 'Roboto Mono', 58 | 'Source Code Pro', 59 | Menlo, 60 | Consolas, 61 | 'Liberation Mono', 62 | monospace; 63 | } 64 | 65 | code, samp { 66 | font-size: 87.5%; 67 | padding: .125em; 68 | } 69 | 70 | pre { 71 | font-size: 87.5%; 72 | overflow: scroll; 73 | } 74 | 75 | blockquote { 76 | font-size: 1.25rem; 77 | font-style: italic; 78 | margin-left: 0; 79 | } 80 | 81 | hr { 82 | margin-top: 1.5em; 83 | margin-bottom: 1.5em; 84 | border: 0; 85 | border-bottom-width: 1px; 86 | border-bottom-style: solid; 87 | border-bottom-color: #ccc; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /examples/lottery/public/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: 7 | -apple-system, 8 | BlinkMacSystemFont, 9 | 'Segoe UI', 10 | 'Roboto', 11 | 'Helvetica Neue', 12 | Helvetica, 13 | sans-serif; 14 | line-height: 1.5; 15 | margin: 0; 16 | color: #111; 17 | background-color: #fff; 18 | } 19 | 20 | img { 21 | max-width: 100%; 22 | height: auto; 23 | } 24 | 25 | svg { 26 | max-height: 100%; 27 | } 28 | 29 | a { 30 | color: #07c; 31 | } 32 | 33 | h1, h2, h3, 34 | h4, h5, h6 { 35 | font-weight: 600; 36 | line-height: 1.25; 37 | margin-top: 1em; 38 | margin-bottom: .5em; 39 | } 40 | 41 | h1 { font-size: 2rem } 42 | h2 { font-size: 1.5rem } 43 | h3 { font-size: 1.25rem } 44 | h4 { font-size: 1rem } 45 | h5 { font-size: .875rem } 46 | h6 { font-size: .75rem } 47 | 48 | p, dl, ol, ul, pre, blockquote { 49 | margin-top: 1em; 50 | margin-bottom: 1em; 51 | } 52 | 53 | code, 54 | pre, 55 | samp { 56 | font-family: 57 | 'Roboto Mono', 58 | 'Source Code Pro', 59 | Menlo, 60 | Consolas, 61 | 'Liberation Mono', 62 | monospace; 63 | } 64 | 65 | code, samp { 66 | font-size: 87.5%; 67 | padding: .125em; 68 | } 69 | 70 | pre { 71 | font-size: 87.5%; 72 | overflow: scroll; 73 | } 74 | 75 | blockquote { 76 | font-size: 1.25rem; 77 | font-style: italic; 78 | margin-left: 0; 79 | } 80 | 81 | hr { 82 | margin-top: 1.5em; 83 | margin-bottom: 1.5em; 84 | border: 0; 85 | border-bottom-width: 1px; 86 | border-bottom-style: solid; 87 | border-bottom-color: #ccc; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /examples/paragraphs/public/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: 7 | -apple-system, 8 | BlinkMacSystemFont, 9 | 'Segoe UI', 10 | 'Roboto', 11 | 'Helvetica Neue', 12 | Helvetica, 13 | sans-serif; 14 | line-height: 1.5; 15 | margin: 0; 16 | color: #111; 17 | background-color: #fff; 18 | } 19 | 20 | img { 21 | max-width: 100%; 22 | height: auto; 23 | } 24 | 25 | svg { 26 | max-height: 100%; 27 | } 28 | 29 | a { 30 | color: #07c; 31 | } 32 | 33 | h1, h2, h3, 34 | h4, h5, h6 { 35 | font-weight: 600; 36 | line-height: 1.25; 37 | margin-top: 1em; 38 | margin-bottom: .5em; 39 | } 40 | 41 | h1 { font-size: 2rem } 42 | h2 { font-size: 1.5rem } 43 | h3 { font-size: 1.25rem } 44 | h4 { font-size: 1rem } 45 | h5 { font-size: .875rem } 46 | h6 { font-size: .75rem } 47 | 48 | p, dl, ol, ul, pre, blockquote { 49 | margin-top: 1em; 50 | margin-bottom: 1em; 51 | } 52 | 53 | code, 54 | pre, 55 | samp { 56 | font-family: 57 | 'Roboto Mono', 58 | 'Source Code Pro', 59 | Menlo, 60 | Consolas, 61 | 'Liberation Mono', 62 | monospace; 63 | } 64 | 65 | code, samp { 66 | font-size: 87.5%; 67 | padding: .125em; 68 | } 69 | 70 | pre { 71 | font-size: 87.5%; 72 | overflow: scroll; 73 | } 74 | 75 | blockquote { 76 | font-size: 1.25rem; 77 | font-style: italic; 78 | margin-left: 0; 79 | } 80 | 81 | hr { 82 | margin-top: 1.5em; 83 | margin-bottom: 1.5em; 84 | border: 0; 85 | border-bottom-width: 1px; 86 | border-bottom-style: solid; 87 | border-bottom-color: #ccc; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /examples/paragraphs/src/components/Navbar/Component.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { 3 | Arrow, 4 | Dropdown, 5 | DropdownMenu, 6 | Fixed, 7 | NavItem, 8 | Space, 9 | Toolbar, 10 | } from 'rebass'; 11 | 12 | const Navbar = ({ 13 | toogleDropdown, 14 | isDropdownOpen, 15 | paragraphs, 16 | scrollToParagraph, 17 | scrollToHeader, 18 | }) => ( 19 | 20 | 21 | 22 | React Redux Scroll 23 | 24 | 25 | 26 | 27 | Scroll to Paragraph 28 | 29 | 30 | 35 | {paragraphs.map(({ id, title }) => 36 | scrollToParagraph(id)}> 37 | {title} 38 | 39 | )} 40 | 41 | 42 | 43 | 44 | Scroll to Header 45 | 46 | 47 | 48 | ); 49 | 50 | Navbar.propTypes = { 51 | toogleDropdown: PropTypes.func.isRequired, 52 | isDropdownOpen: PropTypes.bool.isRequired, 53 | paragraphs: PropTypes.arrayOf(PropTypes.shape({ 54 | id: PropTypes.number.isRequired, 55 | title: PropTypes.string.isRequired, 56 | })).isRequired, 57 | scrollToParagraph: PropTypes.func.isRequired, 58 | scrollToHeader: PropTypes.func.isRequired, 59 | }; 60 | 61 | export default Navbar; 62 | -------------------------------------------------------------------------------- /examples/fiddler/src/components/ScrollArea.js: -------------------------------------------------------------------------------- 1 | import R from 'ramda'; 2 | import React, { PropTypes } from 'react'; 3 | import { Block } from 'rebass'; 4 | import { compose, getContext } from 'recompose'; 5 | 6 | import { scrollableArea, scrollToWhen } from 'react-redux-scroll'; 7 | import { SCROLL_STARTED, SCROLL_ENDED } from '../actions'; 8 | 9 | const SIZE = 300; 10 | 11 | const BlockNumber = ({ number }) => 12 |
{number}
; 23 | BlockNumber.propTypes = { number: PropTypes.number.isRequired }; 24 | 25 | const ScrollableNumber = compose( 26 | getContext({ scrollOptions: PropTypes.object }), 27 | scrollToWhen( 28 | ({ type, payload }, { number, scrollOptions }) => ( 29 | type === SCROLL_STARTED && payload === number ? { scrollOptions } : null 30 | ), 31 | (dispatch, canceled) => dispatch({ type: SCROLL_ENDED, canceled }) 32 | ) 33 | )(BlockNumber); 34 | 35 | const ScrollArea = () => 36 | 48 | 49 | {R.range(1, 17).map(number => 50 | 51 | )} 52 | 53 | ; 54 | 55 | export default scrollableArea(ScrollArea); 56 | -------------------------------------------------------------------------------- /examples/lottery/src/components/Header/Component.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { withProps } from 'recompose'; 3 | import { 4 | Badge, 5 | Block, 6 | ButtonCircle, 7 | Slider, 8 | Text, 9 | } from 'rebass'; 10 | import Icon from 'react-geomicons'; 11 | 12 | const CenteredBlock = withProps({ 13 | style: { 14 | margin: '0 auto', 15 | textAlign: 'center', 16 | }, 17 | })(Block); 18 | 19 | const Button = ({ isReady, onClick }) => 20 | 25 | 26 | ; 27 | 28 | const Result = ({ numbers }) => 29 | 30 | {numbers.map((n, idx) => 31 | {n})} 39 | ; 40 | 41 | Result.propTypes = { 42 | numbers: PropTypes.arrayOf(PropTypes.string).isRequired, 43 | }; 44 | 45 | Button.propTypes = { 46 | isReady: PropTypes.bool.isRequired, 47 | onClick: PropTypes.func.isRequired, 48 | }; 49 | 50 | const Header = ({ 51 | isReady, 52 | onClick, 53 | onChange, 54 | maxRange, 55 | numbers, 56 | }) => ( 57 | 58 | 59 | {`Generate Random number between 0 and ${maxRange}`} 63 | 64 | {isReady ? 65 | : null} 76 | 77 | 78 | 79 | 52 | 53 | 64 | 94 |