From ddb781ecf9c36e7f275c63d20b240d4e4d41597b Mon Sep 17 00:00:00 2001 From: J Smith Date: 2018年7月20日 23:17:03 -0700 Subject: [PATCH 01/53] #36 use new context API --- .gitignore | 1 + .prettierignore | 2 + .prettierrc | 9 ++++ __tests__/Parallax.test.js | 46 +++++++++------------ __tests__/ParallaxBanner.test.js | 1 - __tests__/ParallaxController.test.js | 2 +- __tests__/ParallaxProvider.test.js | 32 ++++++-------- package.json | 8 ++-- src/components/Parallax.js | 18 ++++---- src/components/ParallaxProvider.js | 40 +++++++++--------- src/components/withController.js | 26 ++++++++++++ src/index.js | 2 +- src/modules/ParallaxContext.js | 5 +++ src/{libs => modules}/ParallaxController.js | 0 yarn.lock | 23 +++++++---- 15 files changed, 123 insertions(+), 92 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 src/components/withController.js create mode 100644 src/modules/ParallaxContext.js rename src/{libs => modules}/ParallaxController.js (100%) diff --git a/.gitignore b/.gitignore index 03eadfe48..c2ea261ec 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ coverage dist build npm-debug.log +lib \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..993e2f8ce --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +package.json +node_modules \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..75eee592e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "useTabs": false, + "printWidth": 80, + "tabWidth": 4, + "singleQuote": true, + "trailingComma": "es5", + "jsxBracketSameLine": false, + "parser": "babylon" +} diff --git a/__tests__/Parallax.test.js b/__tests__/Parallax.test.js index 47815d62d..022aca738 100644 --- a/__tests__/Parallax.test.js +++ b/__tests__/Parallax.test.js @@ -1,29 +1,29 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; + import ReactDOM from 'react-dom'; import renderer from 'react-test-renderer'; import Parallax from 'components/Parallax'; import ParallaxProvider from 'components/ParallaxProvider'; -import ParallaxController from 'libs/ParallaxController'; +import ParallaxController from 'modules/ParallaxController'; +import ParallaxContext from 'modules/ParallaxContext'; -// provides a controller to the provider for mocking tests class MockProvider extends Component { - static childContextTypes = { - parallaxController: PropTypes.object, - }; - - getChildContext() { - const { controllerMock } = this.props; - return { parallaxController: controllerMock }; + componentWillMount() { + this.controller = this.props.controllerMock; } componentWillUnmount() { - this.props.controllerMock.destroy(); + this.controller = this.controller.destroy(); } render() { const { children } = this.props; - return children; + + return ( + + {children} + + ); } } @@ -153,19 +153,17 @@ describe('Expect the component', () => { const controller = ParallaxController.init(); controller.removeElement = jest.fn(); - let instance; ReactDOM.render( - (instance = ref)}> +
, node ); - const element = instance.element; ReactDOM.unmountComponentAtNode(node); - expect(controller.removeElement).toBeCalledWith(element); + expect(controller.removeElement).toBeCalled(); }); it('to update an element in the controller when receiving relevant new props', () => { @@ -174,13 +172,10 @@ describe('Expect the component', () => { const controller = ParallaxController.init(); controller.updateElement = jest.fn(); - let instance; class StateChanger extends React.Component { state = { disabled: false }; render() { - return ( - (instance = ref)} /> - ); + return ; } } @@ -204,7 +199,7 @@ describe('Expect the component', () => { // trigger an update stateInstance.setState(testProps); - expect(controller.updateElement).toBeCalledWith(instance.element, { + expect(controller.updateElement).toBeCalledWith(expect.any(Object), { props: { ...testProps, }, @@ -220,19 +215,16 @@ describe('Expect the component', () => { expect(controller.updateElement).toHaveBeenCalledTimes(1); }); - it('to reset styles on an elment if the disabled prop is true', () => { + it('to reset styles on an element if the disabled prop is true', () => { const node = document.createElement('div'); const controller = ParallaxController.init(); controller.resetElementStyles = jest.fn(); - let instance; class StateChanger extends React.Component { state = { disabled: false }; render() { - return ( - (instance = ref)} /> - ); + return ; } } @@ -246,6 +238,6 @@ describe('Expect the component', () => { stateInstance.setState({ disabled: true }); - expect(controller.resetElementStyles).toBeCalledWith(instance.element); + expect(controller.resetElementStyles).toBeCalled(); }); }); diff --git a/__tests__/ParallaxBanner.test.js b/__tests__/ParallaxBanner.test.js index 189a3f726..ba72f3470 100644 --- a/__tests__/ParallaxBanner.test.js +++ b/__tests__/ParallaxBanner.test.js @@ -4,7 +4,6 @@ import ReactDOM from 'react-dom'; import renderer from 'react-test-renderer'; import ParallaxBanner from 'components/ParallaxBanner'; import ParallaxProvider from 'components/ParallaxProvider'; -import ParallaxController from 'libs/ParallaxController'; // Workaround for refs // See https://github.com/facebook/react/issues/7740 diff --git a/__tests__/ParallaxController.test.js b/__tests__/ParallaxController.test.js index 7f8af52cb..97ecacf27 100644 --- a/__tests__/ParallaxController.test.js +++ b/__tests__/ParallaxController.test.js @@ -1,4 +1,4 @@ -import ParallaxController from 'libs/ParallaxController'; +import ParallaxController from 'modules/ParallaxController'; const addEventListener = window.addEventListener; const removeEventListener = window.removeEventListener; diff --git a/__tests__/ParallaxProvider.test.js b/__tests__/ParallaxProvider.test.js index 32664a9de..360e3fa53 100644 --- a/__tests__/ParallaxProvider.test.js +++ b/__tests__/ParallaxProvider.test.js @@ -6,7 +6,8 @@ import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import ReactDOMServer from 'react-dom/server'; import ParallaxProvider from 'components/ParallaxProvider'; -import ParallaxController from 'libs/ParallaxController'; +import ParallaxController from 'modules/ParallaxController'; +import withController from 'components/withController'; describe('A ', () => { // afterEach(() => ) @@ -35,20 +36,11 @@ describe('A ', () => { it('to pass the controller context', () => { const node = document.createElement('div'); - let rootCtx; - const ContextChecker = (props, context) => { - rootCtx = context; + let parallaxController; + const ContextChecker = withController(props => { + parallaxController = props.parallaxController; return null; - }; - - const testController = ParallaxController.init(); - - ContextChecker.contextTypes = { - parallaxController: PropTypes.shape({ - destroy: PropTypes.func.isRequired, - update: PropTypes.func.isRequired, - }), - }; + }); ReactDOM.render( @@ -58,7 +50,7 @@ describe('A ', () => { ); // Expected methods and state - expect(rootCtx.parallaxController).toBeInstanceOf(ParallaxController); + expect(parallaxController).toBeInstanceOf(ParallaxController); }); it('to destroy the controller when unmounting', () => { @@ -72,8 +64,8 @@ describe('A ', () => { node ); - instance.parallaxController.destroy = jest.fn(); - const spy = instance.parallaxController.destroy; + instance.controller.destroy = jest.fn(); + const spy = instance.controller.destroy; ReactDOM.unmountComponentAtNode(node); @@ -126,12 +118,12 @@ describe('A ', () => { // first instance mounted const instance1 = render(node1); expect(window.ParallaxController).toBeInstanceOf(ParallaxController); - expect(instance1.parallaxController).toBeInstanceOf(ParallaxController); + expect(instance1.controller).toBeInstanceOf(ParallaxController); // second instance mounted const instance2 = render(node2); expect(window.ParallaxController).toBeInstanceOf(ParallaxController); - expect(instance2.parallaxController).toBeInstanceOf(ParallaxController); + expect(instance2.controller).toBeInstanceOf(ParallaxController); // unmount first instance ReactDOM.unmountComponentAtNode(node1); @@ -139,7 +131,7 @@ describe('A ', () => { expect(window.ParallaxController).toBeNull(); // this must still be defined - expect(instance2.parallaxController).toBeInstanceOf(ParallaxController); + expect(instance2.controller).toBeInstanceOf(ParallaxController); }); it('to not init the controller on the server'); diff --git a/package.json b/package.json index 841162966..27f4f4c0f 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "prop-types": "^15.5.10" }, "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" + "react": "^16.3.0-0" }, "devDependencies": { "@storybook/addon-actions": "^3.3.9", @@ -63,9 +63,9 @@ "jest": "22.0.6", "node-sass": "^4.5.0", "postcss-loader": "^1.3.3", - "react": "^16.2.0", - "react-dom": "^16.2.0", - "react-test-renderer": "^16.2.0", + "react": "^16.4.1", + "react-dom": "^16.4.1", + "react-test-renderer": "^16.4.1", "regenerator-runtime": "^0.10.5", "sass-loader": "^6.0.3", "style-loader": "^0.16.1", diff --git a/src/components/Parallax.js b/src/components/Parallax.js index 21a7179d5..c4a321f57 100644 --- a/src/components/Parallax.js +++ b/src/components/Parallax.js @@ -1,16 +1,17 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { offsetMin, offsetMax } from '../utils/propValidation'; -import ParallaxController from '../libs/ParallaxController'; +import ParallaxController from '../modules/ParallaxController'; +import withController from './withController'; -export default class Parallax extends Component { +class Parallax extends Component { static defaultProps = { disabled: false, offsetYMax: 0, offsetYMin: 0, offsetXMax: 0, offsetXMin: 0, - slowerScrollRate: false, // determines whether scroll rate is faster or slower than standard scroll + slowerScrollRate: false, tag: 'div', }; @@ -26,10 +27,7 @@ export default class Parallax extends Component { styleOuter: PropTypes.object, styleInner: PropTypes.object, tag: PropTypes.string.isRequired, - }; - - static contextTypes = { - parallaxController: PropTypes.object, // not required because this could be rendered on the server. + parallaxController: PropTypes.object, }; componentDidMount() { @@ -45,7 +43,7 @@ export default class Parallax extends Component { // Deprecation warning for <=1.0.0 // If no context is available but the window global is then warn - if (!this.context.parallaxController && window.ParallaxController) { + if (!this.props.parallaxController && window.ParallaxController) { console.log( 'Calling ParallaxController.init() has been deprecated in favor of using the component. For usage details see: https://github.com/jscottsmith/react-scroll-parallax/tree/v1.1.0#usage' ); @@ -99,7 +97,7 @@ export default class Parallax extends Component { get controller() { // Legacy versions may use the global, not context - return this.context.parallaxController || window.ParallaxController; + return this.props.parallaxController || window.ParallaxController; } // refs @@ -139,3 +137,5 @@ export default class Parallax extends Component { ); } } + +export default withController(Parallax); diff --git a/src/components/ParallaxProvider.js b/src/components/ParallaxProvider.js index 78bcafbc6..51a3fef3e 100644 --- a/src/components/ParallaxProvider.js +++ b/src/components/ParallaxProvider.js @@ -1,39 +1,39 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import ParallaxController from '../libs/ParallaxController'; +import ParallaxContext from '../modules/ParallaxContext'; +import ParallaxController from '../modules/ParallaxController'; + +const createController = () => { + // Don't initialize on the server + const isServer = typeof window === 'undefined'; + + if (!isServer) { + // Must not be the server so kick it off... + return ParallaxController.init(); + } + return null; +}; export default class ParallaxProvider extends Component { static propTypes = { children: PropTypes.node.isRequired, }; - static childContextTypes = { - parallaxController: PropTypes.object, - }; - - getChildContext() { - // Passes down the reference to the controller - const { parallaxController } = this; - return { parallaxController }; - } - componentWillMount() { - // Don't initialize on the server - const isServer = typeof window === 'undefined'; - - if (!isServer) { - // Must not be the server so kick it off... - this.parallaxController = ParallaxController.init(); - } + this.controller = createController(); } componentWillUnmount() { - this.parallaxController = this.parallaxController.destroy(); + this.controller = this.controller.destroy(); } render() { const { children } = this.props; - return children; + return ( + + {children} + + ); } } diff --git a/src/components/withController.js b/src/components/withController.js new file mode 100644 index 000000000..3af6074f5 --- /dev/null +++ b/src/components/withController.js @@ -0,0 +1,26 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ParallaxContext from '../modules/ParallaxContext'; + +export default WrappedComponent => { + class WithController extends Component { + static propTypes = { + parallaxController: PropTypes.object, + }; + + render() { + return ( + + {controller => ( + + )} + + ); + } + } + + return WithController; +}; diff --git a/src/index.js b/src/index.js index 8556e587a..ff33bf29c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ export Parallax from './components/Parallax'; export ParallaxProvider from './components/ParallaxProvider'; export ParallaxBanner from './components/ParallaxBanner'; -export ParallaxController from './libs/ParallaxController'; +export ParallaxController from './modules/ParallaxController'; diff --git a/src/modules/ParallaxContext.js b/src/modules/ParallaxContext.js new file mode 100644 index 000000000..8dfb6e76c --- /dev/null +++ b/src/modules/ParallaxContext.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const ParallaxContext = React.createContext(null); + +export default ParallaxContext; diff --git a/src/libs/ParallaxController.js b/src/modules/ParallaxController.js similarity index 100% rename from src/libs/ParallaxController.js rename to src/modules/ParallaxController.js diff --git a/yarn.lock b/yarn.lock index 8fad20009..f005aa107 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5664,9 +5664,9 @@ react-docgen@^2.15.0: node-dir "^0.1.10" recast "^0.12.6" -react-dom@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" +react-dom@^16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.1.tgz#7f8b0223b3a5fbe205116c56deb85de32685dad6" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -5705,6 +5705,10 @@ react-inspector@^2.2.2: babel-runtime "^6.26.0" is-dom "^1.0.9" +react-is@^16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e" + react-modal@^3.1.10: version "3.1.10" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.1.10.tgz#8898b5cc4ebba78adbb8dea4c55a69818aa682cc" @@ -5733,13 +5737,14 @@ react-style-proptype@^3.0.0: dependencies: prop-types "^15.5.4" -react-test-renderer@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211" +react-test-renderer@^16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70" dependencies: fbjs "^0.8.16" object-assign "^4.1.1" prop-types "^15.6.0" + react-is "^16.4.1" react-textarea-autosize@^5.2.1: version "5.2.1" @@ -5768,9 +5773,9 @@ react-treebeard@^2.1.0: shallowequal "^0.2.2" velocity-react "^1.3.1" -react@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" +react@^16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" From c48f82508e40c0033851536da2c6618cf364438f Mon Sep 17 00:00:00 2001 From: J Smith Date: 2018年7月21日 13:01:49 -0700 Subject: [PATCH 02/53] #36 test and readme updates --- README.md | 38 +++++++++---------- __tests__/withController.test.js | 65 ++++++++++++++++++++++++++++++++ src/index.js | 2 + 3 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 __tests__/withController.test.js diff --git a/README.md b/README.md index 9bca83bb5..53ed2e507 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ const ParallaxImage = () => ( ``` **Warnings:** 1. This lib was designed to be used on `relative` or `absolute` positioned elements that scroll naturally with the page. If you use `fixed` positioning on either the element itself or the parent you will encounter issues. More on that in [troubleshooting](#troubleshooting). -2. Scroll state and positions of elements on the page are cached for performance reasons. This means that if the page height changes (most likely from [images loading](#example-usage-of-context)) after `` components are mounted the controller won't properly determine when the elements are in view. To correct this you can call the `parallaxController.update()` method from any child component of the `` via `context`. More details on how here: [Parallax Controller Context](#parallax-controller-context). +2. Scroll state and positions of elements on the page are cached for performance reasons. This means that if the page height changes (most likely from [images loading](#example-usage-of-context)) after `` components are mounted the controller won't properly determine when the elements are in view. To correct this you can call the `parallaxController.update()` method from any child component of the `` via context and the `withController()` HOC. More details on how here: [Parallax Controller Context](#parallax-controller-context). ## \ @@ -176,30 +176,26 @@ const AppContainer = () => ( ### Parallax Controller Context -Access the Parallax Controller via [React context](https://facebook.github.io/react/docs/context.html) in any components rendered within a `` by defining the `contextTypes` like so: +Access the controller via [React context](https://facebook.github.io/react/docs/context.html) in any components rendered within a `` by using the `withController()` HOC: ```jsx -class Foo extends Component { - static contextTypes = { - parallaxController: PropTypes.object.isRequired, + +import { withController } from 'react-scroll-parallax'; + +class MyComponent extends Component { + static propTypes = { + parallaxController: PropTypes.object, }; doSomething() { - // do stuff with this.context.parallaxController + const { parallaxController } = this.props; + // do stuff with `parallaxController` } } -``` -or for stateless functional components like: - -```jsx -const Bar = (props, context) => ( - // do stuff with context.parallaxController -); +// Compose your component with the Higher Order Component +export withController(MyComponent); -Bar.contextTypes = { - parallaxController: PropTypes.object.isRequired, -}; ``` ### Available Methods @@ -219,20 +215,20 @@ Removes window scroll and resize listeners then resets all styles applied to par The most common use case that would require access to the controller is dealing with images. Since the controller caches attributes for performance they will need to be updated with the correct values once the image loads. Here's an example of how you could do that with an `` component: ```jsx -class Image extends Component { - static contextTypes = { - parallaxController: PropTypes.object.isRequired, - }; +import { withController } from 'react-scroll-parallax'; +class Image extends Component { handleLoad = () => { // updates cached values after image dimensions have loaded - this.context.parallaxController.update(); + this.props.parallaxController.update(); }; render() { return ; } } + +export withController(Image); ``` ## Troubleshooting diff --git a/__tests__/withController.test.js b/__tests__/withController.test.js new file mode 100644 index 000000000..570deaf6c --- /dev/null +++ b/__tests__/withController.test.js @@ -0,0 +1,65 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import withController from 'components/withController'; +import ParallaxProvider from 'components/ParallaxProvider'; +import ParallaxController from 'modules/ParallaxController'; + +describe('Expect the withController HoC', () => { + it('to provide the controller to a component as a prop', () => { + const node = document.createElement('div'); + + let controllerContext = null; + + const MyComponent = ({ parallaxController }) => { + controllerContext = parallaxController; + return null; + }; + + const WithControllerContext = withController(MyComponent); + + const render = () => { + ReactDOM.render( + + + , + node + ); + }; + + render(); + + expect(controllerContext).toBeInstanceOf(ParallaxController); + expect(controllerContext).toEqual( + expect.objectContaining({ + getElements: expect.any(Function), + createElement: expect.any(Function), + removeElement: expect.any(Function), + updateElement: expect.any(Function), + resetElementStyles: expect.any(Function), + update: expect.any(Function), + destroy: expect.any(Function), + }) + ); + }); + + it('to fail and not provide the controller if not wrapped in a ParallaxProvider', () => { + const node = document.createElement('div'); + + let controllerContext = null; + + const MyComponent = ({ parallaxController }) => { + controllerContext = parallaxController; + return null; + }; + + const WithControllerContext = withController(MyComponent); + + const render = () => { + ReactDOM.render(, node); + }; + + render(); + + expect(controllerContext).toEqual(null); + }); +}); diff --git a/src/index.js b/src/index.js index ff33bf29c..cad786424 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,6 @@ +export withController from './components/withController'; export Parallax from './components/Parallax'; export ParallaxProvider from './components/ParallaxProvider'; export ParallaxBanner from './components/ParallaxBanner'; +export withController from './components/withController'; export ParallaxController from './modules/ParallaxController'; From ea4b5a13b0ef8872c58e95352e0fedcfa4b27895 Mon Sep 17 00:00:00 2001 From: J Smith Date: 2019年1月27日 13:53:45 -0800 Subject: [PATCH 03/53] fix export --- src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.js b/src/index.js index cad786424..dbf93bd59 100644 --- a/src/index.js +++ b/src/index.js @@ -2,5 +2,4 @@ export withController from './components/withController'; export Parallax from './components/Parallax'; export ParallaxProvider from './components/ParallaxProvider'; export ParallaxBanner from './components/ParallaxBanner'; -export withController from './components/withController'; export ParallaxController from './modules/ParallaxController'; From b1d867d1823d54f4a03ffaa8f9c44af5bf5c79f2 Mon Sep 17 00:00:00 2001 From: J Smith Date: 2019年1月27日 13:53:52 -0800 Subject: [PATCH 04/53] ignore cache --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c2ea261ec..9874e0b36 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ coverage dist build npm-debug.log -lib \ No newline at end of file +lib +.cache \ No newline at end of file From 7de737f3a1397606d6cd1c31c650d9b1b2e3db4a Mon Sep 17 00:00:00 2001 From: J Smith Date: Fri, 8 Feb 2019 23:09:22 -0800 Subject: [PATCH 05/53] =?UTF-8?q?next=20=F0=9F=91=8C=F0=9F=8F=BB?= =?UTF-8?q?=F0=9F=86=97=F0=9F=94=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc | 438 +++++----- .prettierrc | 13 +- .storybook/addons.js | 3 +- .travis.yml | 30 +- README.md | 82 +- __tests__/Parallax.test.js | 158 ++-- __tests__/ParallaxBanner.test.js | 49 +- __tests__/ParallaxController.test.js | 83 +- __tests__/ParallaxProvider.test.js | 32 +- __tests__/addAttributes.test.js | 62 ++ __tests__/addOffsets.test.js | 43 + __tests__/getParallaxOffsets.test.js | 45 +- __tests__/propValidation.test.js | 109 --- __tests__/testForPassiveScroll.test.js | 2 +- __tests__/testUtils/MockProvider.js | 28 + __tests__/testUtils/createNodeMock.js | 10 + __tests__/testUtils/expectRenderError.js | 42 + package.json | 15 +- src/components/Parallax.js | 88 +- src/components/ParallaxBanner.js | 28 +- src/modules/ParallaxController.js | 264 ++---- src/utils/addAttributes.js | 72 ++ src/utils/addOffsets.js | 36 + src/utils/elementStyles.js | 38 + src/utils/getParallaxOffsets.js | 26 +- src/utils/index.js | 3 + src/utils/propValidation.js | 45 -- stories/Parallax/Parallax.scss | 5 + stories/Parallax/Parallax.stories.js | 139 ++-- .../ParallaxBanner/ParallaxBanner.stories.js | 16 +- stories/styles.scss | 5 + yarn.lock | 762 +++++++++++++++++- 32 files changed, 1636 insertions(+), 1135 deletions(-) create mode 100644 __tests__/addAttributes.test.js create mode 100644 __tests__/addOffsets.test.js delete mode 100644 __tests__/propValidation.test.js create mode 100644 __tests__/testUtils/MockProvider.js create mode 100644 __tests__/testUtils/createNodeMock.js create mode 100644 __tests__/testUtils/expectRenderError.js create mode 100644 src/utils/addAttributes.js create mode 100644 src/utils/addOffsets.js create mode 100644 src/utils/elementStyles.js delete mode 100644 src/utils/propValidation.js diff --git a/.eslintrc b/.eslintrc index 29f5b22f3..7156246da 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,245 +1,215 @@ { - "globals" : { - "ParallaxController": true, - "test": true, - "expect": true - }, - "ecmaFeatures": { - "arrowFunctions": true, - "binaryLiterals": false, - "blockBindings": true, - "classes": true, - "defaultParams": true, - "destructuring": true, - "forOf": true, - "generators": true, - "modules": false, - "objectLiteralComputedProperties": true, - "objectLiteralDuplicateProperties": false, - "objectLiteralShorthandMethods": true, - "objectLiteralShorthandProperties": true, - "octalLiterals": false, - "regexUFlag": false, - "regexYFlag": false, - "restParams": true, - "spread": true, - "superInFunctions": true, - "templateStrings": true, - "unicodePointEscapes": true, - "globalReturn": false, - "jsx": true - }, - "env": { - "es6": true, - "browser": true, - "node": true - }, - "parser": "babel-eslint", - "plugins": [ - "babel", - "react" - ], - "rules": { + "globals": { + "ParallaxController": true + }, + "env": { + "es6": true, + "browser": true, + "node": true, + "jest/globals": true + }, + "parser": "babel-eslint", + "plugins": ["babel", "react", "jest"], + "rules": { + /* Possible Errors */ + "comma-dangle": [1, "always-multiline"], + "no-cond-assign": [1, "except-parens"], + "no-console": 0, + "no-constant-condition": 1, + "no-control-regex": 1, + "no-debugger": 1, + "no-dupe-args": 1, + "no-dupe-keys": 1, + "no-duplicate-case": 0, + "no-empty-character-class": 1, + "no-empty": 0, + "no-ex-assign": 1, + "no-extra-boolean-cast": 1, + "no-extra-parens": 0, + "no-extra-semi": 1, + "no-func-assign": 1, + "no-inner-declarations": [1, "functions"], + "no-invalid-regexp": 1, + "no-irregular-whitespace": 1, + "no-negated-in-lhs": 1, + "no-obj-calls": 1, + "no-regex-spaces": 1, + "no-reserved-keys": 0, + "no-sparse-arrays": 1, + "no-unexpected-multiline": 1, + "no-unreachable": 1, + "use-isnan": 1, + "valid-jsdoc": 0, + "valid-typeof": 1, - /* Possible Errors */ - "comma-dangle": [1, "always-multiline"], - "no-cond-assign": [1, "except-parens"], - "no-console": 0, - "no-constant-condition": 1, - "no-control-regex": 1, - "no-debugger": 1, - "no-dupe-args": 1, - "no-dupe-keys": 1, - "no-duplicate-case": 0, - "no-empty-character-class": 1, - "no-empty": 1, - "no-ex-assign": 1, - "no-extra-boolean-cast": 1, - "no-extra-parens": 0, - "no-extra-semi": 1, - "no-func-assign": 1, - "no-inner-declarations": [1, "functions"], - "no-invalid-regexp": 1, - "no-irregular-whitespace": 1, - "no-negated-in-lhs": 1, - "no-obj-calls": 1, - "no-regex-spaces": 1, - "no-reserved-keys": 0, - "no-sparse-arrays": 1, - "no-unexpected-multiline": 1, - "no-unreachable": 1, - "use-isnan": 1, - "valid-jsdoc": 0, - "valid-typeof": 1, + /* Best Practices */ + "accessor-pairs": 0, + "block-scoped-var": 0, // see Babel section + "complexity": 0, + "consistent-return": 1, + "curly": 0, + "default-case": 0, + "dot-notation": [1, { "allowKeywords": true, "allowPattern": "" }], + "dot-location": [1, "property"], + "eqeqeq": 1, + "guard-for-in": 0, + "no-alert": 1, + "no-caller": 1, + "no-div-regex": 1, + "no-else-return": 1, + "no-eq-null": 0, + "no-eval": 1, + "no-extend-native": 0, + "no-extra-bind": 1, + "no-fallthrough": 0, + "no-floating-decimal": 1, + "no-implied-eval": 1, + "no-iterator": 1, + "no-labels": 1, + "no-lone-blocks": 1, + "no-loop-func": 1, + "no-multi-spaces": 0, + "no-multi-str": 1, + "no-native-reassign": 1, + "no-new-func": 1, + "no-new-wrappers": 1, + "no-new": 1, + "no-octal-escape": 1, + "no-octal": 1, + "no-param-reassign": 0, + "no-process-env": 0, + "no-proto": 0, + "no-redeclare": 1, + "no-return-assign": 1, + "no-script-url": 1, + "no-self-compare": 1, + "no-sequences": 1, + "no-throw-literal": 1, + "no-unused-expressions": 0, + "no-void": 0, + "no-warning-comments": 0, + "no-with": 1, + "radix": 1, + "vars-on-top": 1, + "wrap-iife": [1, "inside"], + "yoda": [1, "never"], - /* Best Practices */ - "accessor-pairs": 0, - "block-scoped-var": 0, // see Babel section - "complexity": 0, - "consistent-return": 1, - "curly": [1, "all"], - "default-case": 0, - "dot-notation": [1, { "allowKeywords": true, "allowPattern": "" }], - "dot-location": [1, "property"], - "eqeqeq": 1, - "guard-for-in": 0, - "no-alert": 1, - "no-caller": 1, - "no-div-regex": 1, - "no-else-return": 1, - "no-eq-null": 0, - "no-eval": 1, - "no-extend-native": 1, - "no-extra-bind": 1, - "no-fallthrough": 0, - "no-floating-decimal": 1, - "no-implied-eval": 1, - "no-iterator": 1, - "no-labels": 1, - "no-lone-blocks": 1, - "no-loop-func": 1, - "no-multi-spaces": 0, - "no-multi-str": 1, - "no-native-reassign": 1, - "no-new-func": 1, - "no-new-wrappers": 1, - "no-new": 1, - "no-octal-escape": 1, - "no-octal": 1, - "no-param-reassign": 0, - "no-process-env": 0, - "no-proto": 1, - "no-redeclare": 1, - "no-return-assign": 1, - "no-script-url": 1, - "no-self-compare": 1, - "no-sequences": 1, - "no-throw-literal": 1, - "no-unused-expressions": 0, - "no-void": 0, - "no-warning-comments": [1, { "terms": ["todo", "tofix"], "location": "start" }], - "no-with": 1, - "radix": 1, - "vars-on-top": 1, - "wrap-iife": [1, "inside"], - "yoda": [1, "never"], + /* Strict Mode */ + "strict": [1, "never"], - /* Strict Mode */ - "strict": [1, "never"], + /* Variables */ + "no-catch-shadow": 0, + "no-delete-var": 1, + "no-label-var": 1, + "no-shadow-restricted-names": 1, + "no-shadow": 1, + "no-undef-init": 1, + "no-undef": 2, + "no-undefined": 0, + "no-unused-vars": 2, + "no-use-before-define": 0, - /* Variables */ - "no-catch-shadow": 0, - "no-delete-var": 1, - "no-label-var": 1, - "no-shadow-restricted-names": 1, - "no-shadow": 1, - "no-undef-init": 1, - "no-undef": 2, - "no-undefined": 0, - "no-unused-vars": [1, { "vars": "local", "args": "after-used" }], - "no-use-before-define": 0, + /* Node.js */ + "handle-callback-err": 1, + "no-mixed-requires": 1, + "no-new-require": 1, + "no-path-concat": 1, + "no-process-exit": 1, + "no-restricted-modules": [1, ""], // add any unwanted Node.js core modules + "no-sync": 1, - /* Node.js */ - "handle-callback-err": 1, - "no-mixed-requires": 1, - "no-new-require": 1, - "no-path-concat": 1, - "no-process-exit": 1, - "no-restricted-modules": [1, ""], // add any unwanted Node.js core modules - "no-sync": 1, + /* Stylistic Issues */ + "array-bracket-spacing": [1, "never"], + "brace-style": [2, "1tbs"], + "camelcase": [1, { "properties": "always" }], + "comma-spacing": [1, { "before": false, "after": true }], + "comma-style": [1, "last"], + "computed-property-spacing": 0, + "consistent-this": 0, + "eol-last": 1, + "func-names": 0, + "func-style": 0, + "indent": 0, + "key-spacing": [1, { "beforeColon": false, "afterColon": true }], + "linebreak-style": 0, + "max-nested-callbacks": [0, 3], + "new-cap": 0, // see Babel section + "new-parens": 1, + "newline-after-var": 0, + "no-array-constructor": 1, + "no-continue": 1, + "no-inline-comments": 0, + "no-lonely-if": 1, + "no-mixed-spaces-and-tabs": 1, + "no-multiple-empty-lines": [1, { "max": 1 }], + "no-nested-ternary": 0, + "no-new-object": 1, + "no-spaced-func": 1, + "no-ternary": 0, + "no-trailing-spaces": 1, + "no-underscore-dangle": 0, + "no-unneeded-ternary": 1, + "object-curly-spacing": 0, // see Babel section + "one-var": [1, "never"], + "operator-assignment": [1, "never"], + "padded-blocks": [0, "never"], + "quote-props": [0, "as-needed"], + "quotes": 0, + "semi-spacing": [1, { "before": false, "after": true }], + "semi": [1, "always"], + "sort-vars": 0, + "space-after-keywords": 0, + "space-before-blocks": [1, "always"], + "space-before-function-paren": [1, "never"], + "space-in-parens": [1, "never"], + "space-infix-ops": 1, + "space-unary-ops": 0, + "spaced-comment": [1, "always"], + "wrap-regex": 1, - /* Stylistic Issues */ - "array-bracket-spacing": [1, "never"], - "brace-style": [2, "1tbs"], - "camelcase": [1, { "properties": "always" }], - "comma-spacing": [1, { "before": false, "after": true }], - "comma-style": [1, "last"], - "computed-property-spacing": 0, - "consistent-this": 0, - "eol-last": 1, - "func-names": 0, - "func-style": 0, - "indent": [1, 4], - "key-spacing": [1, { "beforeColon": false, "afterColon": true }], - "linebreak-style": 0, - "max-nested-callbacks": [0, 3], - "new-cap": 0, // see Babel section - "new-parens": 1, - "newline-after-var": 0, - "no-array-constructor": 1, - "no-continue": 1, - "no-inline-comments": 0, - "no-lonely-if": 1, - "no-mixed-spaces-and-tabs": 1, - "no-multiple-empty-lines": [1, { "max": 1 }], - "no-nested-ternary": 0, - "no-new-object": 1, - "no-spaced-func": 1, - "no-ternary": 0, - "no-trailing-spaces": 1, - "no-underscore-dangle": 0, - "no-unneeded-ternary": 1, - "object-curly-spacing": 0, // see Babel section - "one-var": [1, "never"], - "operator-assignment": [1, "never"], - "padded-blocks": [0, "never"], - "quote-props": [0, "as-needed"], - "quotes": [1, "single"], - "semi-spacing": [1, { "before": false, "after": true }], - "semi": [1, "always"], - "sort-vars": 0, - "space-after-keywords": 0, - "space-before-blocks": [1, "always"], - "space-before-function-paren": [1, "never"], - "space-in-parens": [1, "never"], - "space-infix-ops": 1, - "space-unary-ops": 0, - "spaced-comment": [1, "always"], - "wrap-regex": 1, + /* ECMAScript 6 */ + "constructor-super": 1, + "generator-star-spacing": 0, // see Babel section + "no-this-before-super": 1, + "no-var": 1, + "object-shorthand": 0, // see Babel section + "prefer-const": 1, - /* ECMAScript 6 */ - "constructor-super": 1, - "generator-star-spacing": 0, // see Babel section - "no-this-before-super": 1, - "no-var": 1, - "object-shorthand": 0, // see Babel section - "prefer-const": 1, + /* Legacy */ + "max-depth": [0, 3], + "max-len": [0, 121, 2], + "max-params": 0, + "max-statements": 0, + "no-bitwise": 1, + "no-plusplus": 0, - /* Legacy */ - "max-depth": [0, 3], - "max-len": [0, 121, 2], - "max-params": 0, - "max-statements": 0, - "no-bitwise": 1, - "no-plusplus": 1, + /* Babel */ + "babel/object-shorthand": [1, "always"], + "babel/generator-star-spacing": [1, "after"], + "babel/new-cap": 1, + "babel/object-curly-spacing": [1, "always"], - /* Babel */ - "babel/object-shorthand": [1, "always"], - "babel/generator-star-spacing": [1, "after"], - "babel/new-cap": 1, - "babel/object-curly-spacing": [1, "always"], - - /* React */ - "jsx-quotes": [1, "prefer-double"], - "react/display-name": 1, - "react/jsx-boolean-value": 1, - "react/jsx-indent": [1, 4], - "react/jsx-indent-props": [1, 4], - "react/jsx-no-duplicate-props": 1, - "react/jsx-no-undef": 1, - "react/jsx-sort-props": 0, - "react/jsx-uses-react": 1, - "react/jsx-uses-vars": 1, - "react/no-danger": 1, - "react/no-did-mount-set-state": 1, - "react/no-did-update-set-state": 1, - "react/no-multi-comp": 1, - "react/no-unknown-property": 1, - "react/prop-types": 1, - "react/react-in-jsx-scope": 1, - "react/self-closing-comp": 1, - "react/sort-comp": 1, - "react/sort-prop-types": 1, - "react/wrap-multilines": 0, - } + /* React */ + "jsx-quotes": [1, "prefer-double"], + "react/display-name": 1, + "react/jsx-boolean-value": 1, + "react/jsx-indent": [1, 4], + "react/jsx-indent-props": [1, 4], + "react/jsx-no-duplicate-props": 1, + "react/jsx-no-undef": 1, + "react/jsx-sort-props": 0, + "react/jsx-uses-react": 1, + "react/jsx-uses-vars": 1, + "react/no-danger": 1, + "react/no-did-mount-set-state": 1, + "react/no-did-update-set-state": 1, + "react/no-multi-comp": 0, + "react/no-unknown-property": 1, + "react/prop-types": 1, + "react/react-in-jsx-scope": 1, + "react/self-closing-comp": 1, + "react/sort-comp": 1, + "react/sort-prop-types": 1, + "react/wrap-multilines": 0 + } } diff --git a/.prettierrc b/.prettierrc index 75eee592e..b00e266c2 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,9 +1,8 @@ { - "useTabs": false, - "printWidth": 80, - "tabWidth": 4, - "singleQuote": true, - "trailingComma": "es5", - "jsxBracketSameLine": false, - "parser": "babylon" + "useTabs": false, + "printWidth": 80, + "tabWidth": 4, + "singleQuote": true, + "trailingComma": "es5", + "jsxBracketSameLine": false } diff --git a/.storybook/addons.js b/.storybook/addons.js index 8523bb5b2..22c78e490 100644 --- a/.storybook/addons.js +++ b/.storybook/addons.js @@ -1,3 +1,2 @@ // Addons go here - -// import '@storybook/addon-knobs/register'; +import '@storybook/addon-knobs/register'; diff --git a/.travis.yml b/.travis.yml index 73dc33810..928ba67d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,13 @@ language: node_js node_js: - - "7" + - '7' cache: - directories: - - "node_modules" + directories: + - 'node_modules' script: yarn test && codecov && yarn storybook:export deploy: - skip-cleanup: true - provider: surge - project: build - domain: react-scroll-parallax-v1.surge.sh - on: dev -# TODO: Fix this gh-pages deploy. -# https://docs.travis-ci.com/user/deployment/pages/ -# https://github.com/travis-ci/dpl/pull/501/files - # provider: pages - # skip-cleanup: true - # name: "jscottsmith" - # email: "jscsmith@gmail.com" - # github-token: GITHUB_TOKEN - # committer-from-gh: true - # verbose: true - # keep-history: true - # local-dir: build - # on: - # branch: dev \ No newline at end of file + skip-cleanup: true + provider: surge + project: build + domain: react-scroll-parallax-next.surge.sh + on: next diff --git a/README.md b/README.md index 53ed2e507..3d7ad8fa3 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ React components to create parallax scroll effects for banners, images or any ot Some links demonstrating possible effects created with this lib: -* [Example Site](https://jscottsmith.github.io/react-scroll-parallax-examples/examples/parallax-example/) -* [Storybook](http://react-scroll-parallax-v1.surge.sh/) -* [Parallax Testing](https://jscottsmith.github.io/react-scroll-parallax-examples/examples/parallax-test/) -* [CodePen Parallax](https://codepen.io/jscottsmith/pen/eREbwz) -* [CodePen Parallax Banner](https://codepen.io/jscottsmith/pen/aVBvGj) +- [Example Site](https://jscottsmith.github.io/react-scroll-parallax-examples/examples/parallax-example/) +- [Storybook](http://react-scroll-parallax-v1.surge.sh/) +- [Parallax Testing](https://jscottsmith.github.io/react-scroll-parallax-examples/examples/parallax-test/) +- [CodePen Parallax](https://codepen.io/jscottsmith/pen/eREbwz) +- [CodePen Parallax Banner](https://codepen.io/jscottsmith/pen/aVBvGj) You can also view [the source code for these examples](https://github.com/jscottsmith/react-scroll-parallax-examples) on Github. @@ -32,19 +32,19 @@ yarn add react-scroll-parallax ## Overview -* [Usage](#usage) -* [``](#parallax) - * [Parallax Props](#parallax-props) -* [``](#parallaxbanner) - * [Banner Usage](#banner-usage) - * [Banner Props](#banner-props) - * [Banner Layers Prop](#banner-layers-prop) -* [``](#parallaxprovider) - * [Parallax Controller Context](#parallax-controller-context) - * [Available Methods](#available-methods) -* [Browser Support](#browser-support) -* [Optimizations to Reduce Jank](#optimizations-to-reduce-jank) - * [PSA](#psa) +- [Usage](#usage) +- [``](#parallax) + - [Parallax Props](#parallax-props) +- [``](#parallaxbanner) + - [Banner Usage](#banner-usage) + - [Banner Props](#banner-props) + - [Banner Layers Prop](#banner-layers-prop) +- [``](#parallaxprovider) + - [Parallax Controller Context](#parallax-controller-context) + - [Available Methods](#available-methods) +- [Browser Support](#browser-support) +- [Optimizations to Reduce Jank](#optimizations-to-reduce-jank) + - [PSA](#psa) ## Usage @@ -70,18 +70,14 @@ Import the `Parallax` component and use it anywhere within the provider like so: import { Parallax } from 'react-scroll-parallax'; const ParallaxImage = () => ( - + ); ``` + **Warnings:** + 1. This lib was designed to be used on `relative` or `absolute` positioned elements that scroll naturally with the page. If you use `fixed` positioning on either the element itself or the parent you will encounter issues. More on that in [troubleshooting](#troubleshooting). 2. Scroll state and positions of elements on the page are cached for performance reasons. This means that if the page height changes (most likely from [images loading](#example-usage-of-context)) after `` components are mounted the controller won't properly determine when the elements are in view. To correct this you can call the `parallaxController.update()` method from any child component of the `` via context and the `withController()` HOC. More details on how here: [Parallax Controller Context](#parallax-controller-context). @@ -93,18 +89,16 @@ The main component for manipulating a DOM element's position based on it's posit The following are all props that can be passed to the `` component: -| Name | Type | Default | Description | -| -------------------- | :------------------: | :------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **className** | `String` | | Optionally pass additional class names to be added to the outermost parallax element. | -| **disabled** | `Boolean` | `false` | Determines if the component will have parallax offsets applied. If `true` parallax styles are completely removed from the element and it is no longer updated. | -| **offsetXMax** | `Number` or `String` | `0` | Maximum **x** offset in `%` or `px`. If no unit is passed percent is assumed. Percent is based on the elements width. | -| **offsetXMin** | `Number` or `String` | `0` | Minimum **x** offset in `%` or `px`. If no unit is passed percent is assumed. Percent is based on the elements width. | -| **offsetYMax** | `Number` or `String` | `0` | Maximum **y** offset in `%` or `px`. If no unit is passed percent is assumed. Percent is based on the elements height. | -| **offsetYMin** | `Number` or `String` | `0` | Minimum **y** offset in `%` or `px`. If no unit is passed percent is assumed. Percent is based on the elements height. | -| **slowerScrollRate** | `Boolean` | `false` | Internally swaps the min/max offset y values of the parallax component to give the appearance of moving faster or slower than the default rate of scroll. | -| **styleInner** | `Object` | | Optionally pass a style object to be added to the innermost parallax element. | -| **styleOuter** | `Object` | | Optionally pass a style object to be added to the outermost parallax element. | -| **tag** | `String` | `div` | Optionally pass an element tag name to be applied to the outermost parallax element. | +| Name | Type | Default | Description | +| -------------- | :-----------------------------------: | :------- | ------------------------------------------------------------------------------------------------------------------ | +| **x** | `Array` of types `String` or `Number` | `[0, 0]` | Offsets on x-axis in `%` or `px`. If no unit is passed percent is assumed. Percent is based on the elements width. | +| **y** | `Array` of types `String` or `Number` | `[0, 0]` | Offsets on y-axis in `%` or `px`. If no unit is passed percent is assumed. Percent is based on the elements width. | +| **className** | `String` | | Optionally pass additional class names to be added to the outermost parallax element. | +| **disabled** | `Boolean` | `false` | Disables parallax effects on individual elements when `true`. | +| **styleInner** | `Object` | | Optionally pass a style object to be added to the innermost parallax element. | +| **styleOuter** | `Object` | | Optionally pass a style object to be added to the outermost parallax element. | +| **tagInner** | `String` | `div` | Optionally pass an element tag name to be applied to the innermost parallax element. | +| **tagOuter** | `String` | `div` | Optionally pass an element tag name to be applied to the outermost parallax element. | ## \ @@ -152,13 +146,13 @@ The following are all props that can be passed to the `` compone The `layers` prop takes an array of objects that will represent each image (or custom children) of the parallax banner. The following properties describe a layer object: -| Name | Type | Default | Description | -| -------------------- | :-------: | :------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| **amount** | `Number` | | A value from `0 – 1` that represents the vertical offset to be applied to the current layer, `0.1` would equal a `10%` offset on the top and bottom. | -| **children** | `Element` | | Custom layer children provided as a React element, for example `