├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── docs ├── API.md ├── README.md ├── customization.md ├── upgrade-guide-v2-v3.md ├── usage.md └── v2-documentation.md ├── examples └── simple │ ├── components │ ├── ColoredScrollbars │ │ ├── App.js │ │ └── ColoredScrollbars.js │ ├── DefaultScrollbars │ │ └── App.js │ ├── ShadowScrollbars │ │ ├── App.js │ │ └── ShadowScrollbars.js │ └── SpringScrollbars │ │ ├── App.js │ │ └── SpringScrollbars.js │ ├── index.html │ ├── index.js │ ├── package.json │ ├── sass │ ├── app.scss │ ├── components.scss │ ├── config.scss │ └── type.scss │ ├── server.js │ ├── webpack.config.js │ └── yarn.lock ├── index.d.ts ├── karma.conf.js ├── package-lock.json ├── package.json ├── prepublish.js ├── src ├── Scrollbars │ ├── defaultRenderElements.js │ ├── index.js │ └── styles.js ├── index.js └── utils │ ├── getInnerHeight.js │ ├── getInnerWidth.js │ ├── getScrollbarWidth.js │ ├── isString.js │ └── returnFalse.js ├── test.js ├── test ├── .eslintrc ├── Scrollbars │ ├── autoHeight.js │ ├── autoHide.js │ ├── clickTrack.js │ ├── dragThumb.js │ ├── flexbox.js │ ├── gettersSetters.js │ ├── hideTracks.js │ ├── index.js │ ├── onUpdate.js │ ├── rendering.js │ ├── resizing.js │ ├── scrolling.js │ └── universal.js ├── browser.spec.js ├── mobile.spec.js └── utils.spec.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | **/node_modules 3 | **/webpack.config.js 4 | **/prepublish.js 5 | examples/**/server.js 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint-config-airbnb", 4 | "env": { 5 | "browser": true, 6 | "mocha": true, 7 | "node": true 8 | }, 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true 11 | }, 12 | "rules": { 13 | "react/jsx-uses-react": 2, 14 | "react/jsx-uses-vars": 2, 15 | "react/jsx-indent-props": 0, 16 | "react/jsx-no-bind": 0, 17 | "react/jsx-closing-bracket-location": 0, 18 | "react/jsx-space-before-closing": 0, 19 | "react/jsx-indent": 0, 20 | "react/react-in-jsx-scope": 2, 21 | "react/no-multi-comp": 0, 22 | "react/prefer-es6-class": 0, 23 | "react/prefer-stateless-function": 0, 24 | "import/no-unresolved": 0, 25 | "max-len": 0, 26 | "indent": [0, 4], 27 | "new-cap": 0, 28 | "comma-dangle": 0, 29 | "camelcase": 0, 30 | "id-length": 0, 31 | "no-nested-ternary": 0, 32 | "no-param-reassign": 2, 33 | "no-underscore-dangle": 0, 34 | "prefer-arrow-callback": 0 35 | }, 36 | "plugins": [ 37 | "react" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | dist 5 | lib 6 | coverage 7 | examples/simple/static 8 | .idea/ 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | src 4 | test 5 | examples 6 | coverage 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "iojs" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | [Have a look at the releases](https://github.com/RobPethick/react-custom-scrollbars-2/releases) 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 react-custom-scrollbars-2 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-custom-scrollbars-2 2 | ========================= 3 | 4 | [![npm](https://img.shields.io/badge/npm-react--custom--scrollbars--2-brightgreen.svg?style=flat-square)]() 5 | [![npm version](https://img.shields.io/npm/v/react-custom-scrollbars-2.svg?style=flat-square)](https://www.npmjs.com/package/react-custom-scrollbars-2) 6 | [![npm downloads](https://img.shields.io/npm/dm/react-custom-scrollbars-2.svg?style=flat-square)](https://www.npmjs.com/package/react-custom-scrollbars-2) 7 | 8 | * frictionless native browser scrolling 9 | * native scrollbars for mobile devices 10 | * [fully customizable](https://github.com/RobPethick/react-custom-scrollbars-2/blob/master/docs/customization.md) 11 | * [auto hide](https://github.com/RobPethick/react-custom-scrollbars-2/blob/master/docs/usage.md#auto-hide) 12 | * [auto height](https://github.com/RobPethick/react-custom-scrollbars-2/blob/master/docs/usage.md#auto-height) 13 | * [universal](https://github.com/RobPethick/react-custom-scrollbars-2/blob/master/docs/usage.md#universal-rendering) (runs on client & server) 14 | * `requestAnimationFrame` for 60fps 15 | * no extra stylesheets 16 | * well tested, 100% code coverage 17 | 18 | **[Demos](https://robpethick.github.io/react-custom-scrollbars-2/) · [Documentation](https://github.com/RobPethick/react-custom-scrollbars-2/tree/master/docs)** 19 | 20 | ## Quick note 21 | This repo is due to the original (fantastic) [`react-custom-scrollbars`](https://www.npmjs.com/package/react-custom-scrollbars) package going a little stale and we needed a handful of bug fixes which will be managed here. 22 | 23 | ## Installation 24 | ```bash 25 | npm install react-custom-scrollbars-2 --save 26 | ``` 27 | 28 | This assumes that you’re using [npm](http://npmjs.com/) package manager with a module bundler like [Webpack](http://webpack.github.io) or [Browserify](http://browserify.org/) to consume [CommonJS modules](http://webpack.github.io/docs/commonjs.html). 29 | 30 | If you don’t yet use [npm](http://npmjs.com/) or a modern module bundler, and would rather prefer a single-file [UMD](https://github.com/umdjs/umd) build that makes `ReactCustomScrollbars` available as a global object, you can grab a pre-built version from [unpkg](https://unpkg.com/react-custom-scrollbars-2@4.3.0/dist/react-custom-scrollbars.js). We *don’t* recommend this approach for any serious application, as most of the libraries complementary to `react-custom-scrollbars-2` are only available on [npm](http://npmjs.com/). 31 | 32 | ## Usage 33 | 34 | This is the minimal configuration. [Check out the Documentation for advanced usage](https://github.com/RobPethick/react-custom-scrollbars-2/tree/master/docs). 35 | 36 | ```javascript 37 | import { Scrollbars } from 'react-custom-scrollbars-2'; 38 | 39 | class App extends Component { 40 | render() { 41 | return ( 42 | 43 |

Some great content...

44 |
45 | ); 46 | } 47 | } 48 | ``` 49 | 50 | The `` component is completely customizable. Check out the following code: 51 | 52 | ```javascript 53 | import { Scrollbars } from 'react-custom-scrollbars-2'; 54 | 55 | class CustomScrollbars extends Component { 56 | render() { 57 | return ( 58 | 78 | ); 79 | } 80 | } 81 | ``` 82 | 83 | All properties are documented in the [API docs](https://github.com/RobPethick/react-custom-scrollbars-2/blob/master/docs/API.md) 84 | 85 | ## Examples 86 | 87 | Run the simple example: 88 | ```bash 89 | # Make sure that you've installed the dependencies 90 | npm install 91 | # Move to example directory 92 | cd react-custom-scrollbars-2/examples/simple 93 | npm install 94 | npm start 95 | ``` 96 | 97 | ## Tests 98 | ```bash 99 | # Make sure that you've installed the dependencies 100 | npm install 101 | # Run tests 102 | npm test 103 | ``` 104 | 105 | ### Code Coverage 106 | ```bash 107 | # Run code coverage. Results can be found in `./coverage` 108 | npm run test:cov 109 | ``` 110 | 111 | 112 | ## License 113 | 114 | MIT 115 | -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## `` 4 | 5 | ### Props 6 | 7 | * `onScroll`: (Function) Event handler 8 | * Signature: `onScroll(event)` 9 | * `onScrollFrame`: (Function) Runs inside the animation frame. 10 | * Signature: `onScroll(values)` 11 | * `values`: (Object) Values about the current position 12 | * `values.top`: (Number) scrollTop progess, from 0 to 1 13 | * `values.left`: (Number) scrollLeft progess, from 0 to 1 14 | * `values.clientWidth`: (Number) Width of the view 15 | * `values.clientHeight`: (Number) Height of the view 16 | * `values.scrollWidth`: (Number) Native scrollWidth 17 | * `values.scrollHeight`: (Number) Native scrollHeight 18 | * `values.scrollLeft`: (Number) Native scrollLeft 19 | * `values.scrollTop`: (Number) Native scrollTop 20 | * `onScrollStart` (Function) Called when scrolling starts 21 | * `onScrollStop` (Function) Called when scrolling stops 22 | * `onUpdate` (Function) Called when ever the component is updated. Runs inside the animation frame 23 | * Signature: `onUpdate(values)` 24 | * `renderView`: (Function) The element your content will be rendered in 25 | * `renderTrackHorizontal`: (Function) Horizontal track element 26 | * `renderTrackVertical`: (Function) Vertical track element 27 | * `renderThumbHorizontal`: (Function) Horizontal thumb element 28 | * `renderThumbVertical`: (Function) Vertical thumb element 29 | * `hideTracksWhenNotNeeded`: (Boolean) Hide tracks (`visibility: hidden`) when content does not overflow container. (default: false) 30 | * `thumbSize`: (Number) Set a fixed size for thumbs in px. 31 | * `thumbMinSize`: (Number) Minimal thumb size in px. (default: 30) 32 | * `autoHide`: (Boolean) Enable auto-hide mode (default: `false`) 33 | * When `true` tracks will hide automatically and are only visible while scrolling. 34 | * `autoHideTimeout`: (Number) Hide delay in ms. (default: 1000) 35 | * `autoHideDuration`: (Number) Duration for hide animation in ms. (default: 200) 36 | * `autoHeight`: (Boolean) Enable auto-height mode. (default: false) 37 | * When `true` container grows with content 38 | * `autoHeightMin`: (Number) Set a minimum height for auto-height mode (default: 0) 39 | * `autoHeightMax`: (Number) Set a maximum height for auto-height mode (default: 200) 40 | * `universal`: (Boolean) Enable universal rendering (default: `false`) 41 | * [Learn how to use universal rendering](usage.md#universal-rendering) 42 | 43 | ### Methods 44 | 45 | * `scrollTop(top = 0)`: scroll to the top value 46 | * `scrollLeft(left = 0)`: scroll to the left value 47 | * `scrollToTop()`: scroll to top 48 | * `scrollToBottom()`: scroll to bottom 49 | * `scrollToLeft()`: scroll to left 50 | * `scrollToRight()`: scroll to right 51 | * `getScrollLeft()`: get scrollLeft value 52 | * `getScrollTop()`: get scrollTop value 53 | * `getScrollWidth()`: get scrollWidth value 54 | * `getScrollHeight()`: get scrollHeight value 55 | * `getClientWidth()`: get view client width 56 | * `getClientHeight()`: get view client height 57 | * `getValues()`: get an object with values about the current position. 58 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | * [Usage](usage.md) 4 | * [Auto hide](usage.md#auto-hide) 5 | * [Auto height](usage.md#auto-height) 6 | * [Working with events](usage.md#events) 7 | * [Universal rendering](usage.md#universal-rendering) 8 | * [Customization](customization.md) 9 | * [API](API.md) 10 | 11 | ## Older versions 12 | * [Upgrade guide from v2.x to v3.x](upgrade-guide-v2-v3.md) 13 | * [v2.x documentation](v2-documentation.md) 14 | -------------------------------------------------------------------------------- /docs/customization.md: -------------------------------------------------------------------------------- 1 | # Customization 2 | 3 | The `` component consists of the following elements: 4 | 5 | * `view` The element your content is rendered in 6 | * `trackHorizontal` The horizontal scrollbars track 7 | * `trackVertical` The vertical scrollbars track 8 | * `thumbHorizontal` The horizontal thumb 9 | * `thumbVertical` The vertical thumb 10 | 11 | Each element can be **rendered individually** with a function that you pass to the component. Say, you want use your own `className` for each element: 12 | 13 | ```javascript 14 | import { Scrollbars } from 'react-custom-scrollbars-2'; 15 | 16 | class CustomScrollbars extends Component { 17 | render() { 18 | return ( 19 |
} 21 | renderTrackVertical={props =>
} 22 | renderThumbHorizontal={props =>
} 23 | renderThumbVertical={props =>
} 24 | renderView={props =>
}> 25 | {this.props.children} 26 | 27 | ); 28 | } 29 | } 30 | 31 | class App extends Component { 32 | render() { 33 | return ( 34 | 35 |

Some great content...

36 |
37 | ); 38 | } 39 | } 40 | ``` 41 | 42 | **Important**: **You will always need to pass through the given props** for the respective element like in the example above: `
`. 43 | This is because we need to pass some default `styles` down to the element in order to make the component work. 44 | 45 | If you are working with **inline styles**, you could do something like this: 46 | 47 | ```javascript 48 | import { Scrollbars } from 'react-custom-scrollbars-2'; 49 | 50 | class CustomScrollbars extends Component { 51 | render() { 52 | return ( 53 | 55 |
62 | }> 63 | {this.props.children} 64 | 65 | ); 66 | } 67 | } 68 | ``` 69 | 70 | ## Respond to scroll events 71 | 72 | If you want to change the appearance in respond to the scrolling position, you could do that like: 73 | 74 | ```javascript 75 | import { Scrollbars } from 'react-custom-scrollbars-2'; 76 | class CustomScrollbars extends Component { 77 | constructor(props, context) { 78 | super(props, context) 79 | this.state = { top: 0 }; 80 | this.handleScrollFrame = this.handleScrollFrame.bind(this); 81 | this.renderView = this.renderView.bind(this); 82 | } 83 | 84 | handleScrollFrame(values) { 85 | const { top } = values; 86 | this.setState({ top }); 87 | } 88 | 89 | renderView({ style, ...props }) { 90 | const { top } = this.state; 91 | const color = top * 255; 92 | const customStyle = { 93 | backgroundColor: `rgb(${color}, ${color}, ${color})` 94 | }; 95 | return ( 96 |
97 | ); 98 | } 99 | 100 | render() { 101 | return ( 102 | 106 | ); 107 | } 108 | } 109 | ``` 110 | 111 | Check out these examples for some inspiration: 112 | * [ColoredScrollbars](https://github.com/RobPethick/react-custom-scrollbars-2/tree/master/examples/simple/components/ColoredScrollbars) 113 | * [ShadowScrollbars](https://github.com/RobPethick/react-custom-scrollbars-2/tree/master/examples/simple/components/ShadowScrollbars) 114 | -------------------------------------------------------------------------------- /docs/upgrade-guide-v2-v3.md: -------------------------------------------------------------------------------- 1 | # Upgrade guide from 2.x to 3.x 2 | 3 | ## Render functions 4 | 5 | ```javascript 6 | // v2.x 7 |
} 9 | renderScrollbarVertical={props =>
}> 10 | {/* */} 11 | 12 | 13 | // v3.x 14 |
} 16 | renderTrackVertical={props =>
}> 17 | {/* */} 18 | 19 | ``` 20 | 21 | ## onScroll handler 22 | 23 | ```javascript 24 | // v2.x 25 | { 27 | // do something with event 28 | // do something with values, animate 29 | }}> 30 | {/* */} 31 | 32 | 33 | // v3.x 34 | { 36 | // do something with event 37 | }} 38 | onScrollFrame={values => { 39 | // do something with values, animate 40 | // runs inside animation frame 41 | }}> 42 | {/* */} 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Default Scrollbars 4 | 5 | The `` component works out of the box with some default styles. The only thing you need to care about is that the component has a `width` and `height`: 6 | 7 | ```javascript 8 | import { Scrollbars } from 'react-custom-scrollbars-2'; 9 | 10 | class App extends Component { 11 | render() { 12 | return ( 13 | 14 |

Some great content...

15 |
16 | ); 17 | } 18 | } 19 | ``` 20 | 21 | Also don't forget to set the `viewport` meta tag, if you want to **support mobile devices** 22 | 23 | ```html 24 | 27 | ``` 28 | 29 | ## Events 30 | 31 | There are several events you can listen to: 32 | 33 | ```javascript 34 | import { Scrollbars } from 'react-custom-scrollbars-2'; 35 | 36 | class App extends Component { 37 | render() { 38 | return ( 39 | 50 |

Some great content...

51 |
52 | ); 53 | } 54 | } 55 | ``` 56 | 57 | 58 | ## Auto-hide 59 | 60 | You can activate auto-hide by setting the `autoHide` property. 61 | 62 | ```javascript 63 | import { Scrollbars } from 'react-custom-scrollbars-2'; 64 | 65 | class App extends Component { 66 | render() { 67 | return ( 68 | 75 |

Some great content...

76 |
77 | ); 78 | } 79 | } 80 | ``` 81 | 82 | ## Auto-height 83 | 84 | You can activate auto-height by setting the `autoHeight` property. 85 | ```javascript 86 | import { Scrollbars } from 'react-custom-scrollbars-2'; 87 | 88 | class App extends Component { 89 | render() { 90 | return ( 91 | 96 |

Some great content...

97 |
98 | ); 99 | } 100 | } 101 | ``` 102 | 103 | ## Universal rendering 104 | 105 | If your app runs on both client and server, activate the `universal` mode. This will ensure that the initial markup on client and server are the same: 106 | 107 | ```javascript 108 | import { Scrollbars } from 'react-custom-scrollbars-2'; 109 | 110 | class App extends Component { 111 | render() { 112 | return ( 113 | // This will activate universal mode 114 | 115 |

Some great content...

116 |
117 | ); 118 | } 119 | } 120 | ``` 121 | -------------------------------------------------------------------------------- /docs/v2-documentation.md: -------------------------------------------------------------------------------- 1 | # v2.x Documentation 2 | ## Table of Contents 3 | 4 | - [Customization](#customization) 5 | - [API](#api) 6 | 7 | ## Customization 8 | ```javascript 9 | import { Scrollbars } from 'react-custom-scrollbars-2'; 10 | 11 | class CustomScrollbars extends Component { 12 | render() { 13 | return ( 14 |
} 17 | renderScrollbarVertical={props =>
} 18 | renderThumbHorizontal={props =>
} 19 | renderThumbVertical={props =>
} 20 | renderView={props =>
}> 21 | {this.props.children} 22 | 23 | ); 24 | } 25 | } 26 | 27 | class App extends Component { 28 | render() { 29 | return ( 30 | 31 |

Some great content...

32 |
33 | ); 34 | } 35 | } 36 | ``` 37 | 38 | **NOTE**: If you use `renderScrollbarHorizontal`, **make sure that you define a height value** with css or inline styles. If you use `renderScrollbarVertical`, **make sure that you define a width value with** css or inline styles. 39 | 40 | ## API 41 | 42 | ### `` 43 | 44 | #### Props 45 | 46 | * `renderScrollbarHorizontal`: (Function) Horizontal scrollbar element 47 | * `renderScrollbarVertical`: (Function) Vertical scrollbar element 48 | * `renderThumbHorizontal`: (Function) Horizontal thumb element 49 | * `renderThumbVertical`: (Function) Vertical thumb element 50 | * `renderView`: (Function) The element your content will be rendered in 51 | * `onScroll`: (Function) Event handler. Will be called with the native scroll event and some handy values about the current position. 52 | * **Signature**: `onScroll(event, values)` 53 | * `event`: (Event) Native onScroll event 54 | * `values`: (Object) Values about the current position 55 | * `values.top`: (Number) scrollTop progess, from 0 to 1 56 | * `values.left`: (Number) scrollLeft progess, from 0 to 1 57 | * `values.clientWidth`: (Number) width of the view 58 | * `values.clientHeight`: (Number) height of the view 59 | * `values.scrollWidth`: (Number) native scrollWidth 60 | * `values.scrollHeight`: (Number) native scrollHeight 61 | * `values.scrollLeft`: (Number) native scrollLeft 62 | * `values.scrollTop`: (Number) native scrollTop 63 | 64 | **Don't forget to pass the received props to your custom element. Example:** 65 | 66 | **NOTE**: If you use `renderScrollbarHorizontal`, **make sure that you define a height value** with css or inline styles. If you use `renderScrollbarVertical`, **make sure that you define a width value with** css or inline styles. 67 | 68 | ```javascript 69 | import { Scrollbars } from 'react-custom-scrollbars-2'; 70 | 71 | class CustomScrollbars extends Component { 72 | render() { 73 | return ( 74 |
} 77 | // Customize inline styles 78 | renderScrollbarVertical={({ style, ...props}) => { 79 | return
; 80 | }}> 81 | {this.props.children} 82 | 83 | ); 84 | } 85 | } 86 | ``` 87 | 88 | #### Methods 89 | 90 | * `scrollTop(top)`: scroll to the top value 91 | * `scrollLeft(left)`: scroll to the left value 92 | * `scrollToTop()`: scroll to top 93 | * `scrollToBottom()`: scroll to bottom 94 | * `scrollToLeft()`: scroll to left 95 | * `scrollToRight()`: scroll to right 96 | * `getScrollLeft`: get scrollLeft value 97 | * `getScrollTop`: get scrollTop value 98 | * `getScrollWidth`: get scrollWidth value 99 | * `getScrollHeight`: get scrollHeight value 100 | * `getWidth`: get view client width 101 | * `getHeight`: get view client height 102 | * `getValues`: get an object with values about the current position. 103 | * `left`, `top`, `scrollLeft`, `scrollTop`, `scrollWidth`, `scrollHeight`, `clientWidth`, `clientHeight` 104 | 105 | ```javascript 106 | import { Scrollbars } from 'react-custom-scrollbars-2'; 107 | 108 | class App extends Component { 109 | handleClick() { 110 | this.refs.scrollbars.scrollToTop() 111 | }, 112 | render() { 113 | return ( 114 |
115 | 118 | {/* your content */} 119 | 120 | 123 |
124 | ); 125 | } 126 | } 127 | ``` 128 | 129 | ### Receive values about the current position 130 | 131 | ```javascript 132 | class CustomScrollbars extends Component { 133 | handleScroll(event, values) { 134 | console.log(values); 135 | /* 136 | { 137 | left: 0, 138 | top: 0.21513353115727002 139 | clientWidth: 952 140 | clientHeight: 300 141 | scrollWidth: 952 142 | scrollHeight: 1648 143 | scrollLeft: 0 144 | scrollTop: 290 145 | } 146 | */ 147 | } 148 | render() { 149 | return ( 150 | 151 | {this.props.children} 152 | 153 | ); 154 | } 155 | } 156 | ``` 157 | -------------------------------------------------------------------------------- /examples/simple/components/ColoredScrollbars/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ColoredScrollbars from './ColoredScrollbars'; 3 | 4 | export default class App extends Component { 5 | render() { 6 | return ( 7 | 9 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

10 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

11 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

12 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

13 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

14 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

15 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

16 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

17 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

18 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

19 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

20 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

21 |
22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple/components/ColoredScrollbars/ColoredScrollbars.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Scrollbars } from 'react-custom-scrollbars-2'; 3 | 4 | export default class ColoredScrollbars extends Component { 5 | 6 | constructor(props, ...rest) { 7 | super(props, ...rest); 8 | this.state = { top: 0 }; 9 | this.handleUpdate = this.handleUpdate.bind(this); 10 | this.renderView = this.renderView.bind(this); 11 | this.renderThumb = this.renderThumb.bind(this); 12 | } 13 | 14 | handleUpdate(values) { 15 | const { top } = values; 16 | this.setState({ top }); 17 | } 18 | 19 | renderView({ style, ...props }) { 20 | const { top } = this.state; 21 | const viewStyle = { 22 | padding: 15, 23 | backgroundColor: `rgb(${Math.round(255 - (top * 255))}, ${Math.round(top * 255)}, ${Math.round(255)})`, 24 | color: `rgb(${Math.round(255 - (top * 255))}, ${Math.round(255 - (top * 255))}, ${Math.round(255 - (top * 255))})` 25 | }; 26 | return ( 27 |
31 | ); 32 | } 33 | 34 | renderThumb({ style, ...props }) { 35 | const { top } = this.state; 36 | const thumbStyle = { 37 | backgroundColor: `rgb(${Math.round(255 - (top * 255))}, ${Math.round(255 - (top * 255))}, ${Math.round(255 - (top * 255))})` 38 | }; 39 | return ( 40 |
43 | ); 44 | } 45 | 46 | render() { 47 | return ( 48 | 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/simple/components/DefaultScrollbars/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Scrollbars } from 'react-custom-scrollbars-2'; 3 | 4 | export default class App extends Component { 5 | render() { 6 | return ( 7 | 9 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

10 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

11 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

12 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

13 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

14 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

15 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

16 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

17 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

18 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

19 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

20 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

21 |
22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple/components/ShadowScrollbars/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ShadowScrollbars from './ShadowScrollbars'; 3 | 4 | export default class App extends Component { 5 | render() { 6 | return ( 7 | 9 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

10 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

11 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

12 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

13 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

14 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

15 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

16 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

17 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

18 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

19 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

20 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

21 |
22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple/components/ShadowScrollbars/ShadowScrollbars.js: -------------------------------------------------------------------------------- 1 | import css from 'dom-css'; 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Scrollbars } from 'react-custom-scrollbars-2'; 5 | 6 | class ShadowScrollbars extends Component { 7 | 8 | constructor(props, ...rest) { 9 | super(props, ...rest); 10 | this.state = { 11 | scrollTop: 0, 12 | scrollHeight: 0, 13 | clientHeight: 0 14 | }; 15 | this.handleUpdate = this.handleUpdate.bind(this); 16 | } 17 | 18 | handleUpdate(values) { 19 | const { shadowTop, shadowBottom } = this.refs; 20 | const { scrollTop, scrollHeight, clientHeight } = values; 21 | const shadowTopOpacity = 1 / 20 * Math.min(scrollTop, 20); 22 | const bottomScrollTop = scrollHeight - clientHeight; 23 | const shadowBottomOpacity = 1 / 20 * (bottomScrollTop - Math.max(scrollTop, bottomScrollTop - 20)); 24 | css(shadowTop, { opacity: shadowTopOpacity }); 25 | css(shadowBottom, { opacity: shadowBottomOpacity }); 26 | } 27 | 28 | render() { 29 | const { style, ...props } = this.props; 30 | const containerStyle = { 31 | ...style, 32 | position: 'relative' 33 | }; 34 | const shadowTopStyle = { 35 | position: 'absolute', 36 | top: 0, 37 | left: 0, 38 | right: 0, 39 | height: 10, 40 | background: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 100%)' 41 | }; 42 | const shadowBottomStyle = { 43 | position: 'absolute', 44 | bottom: 0, 45 | left: 0, 46 | right: 0, 47 | height: 10, 48 | background: 'linear-gradient(to top, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 100%)' 49 | }; 50 | return ( 51 |
52 | 56 |
59 |
62 |
63 | ); 64 | } 65 | } 66 | 67 | ShadowScrollbars.propTypes = { 68 | style: PropTypes.object 69 | }; 70 | 71 | export default ShadowScrollbars; 72 | -------------------------------------------------------------------------------- /examples/simple/components/SpringScrollbars/App.js: -------------------------------------------------------------------------------- 1 | import random from 'lodash/number/random'; 2 | import React, { Component } from 'react'; 3 | import SpringScrollbars from './SpringScrollbars'; 4 | 5 | export default class App extends Component { 6 | 7 | constructor(props, ...rest) { 8 | super(props, ...rest); 9 | this.handleClickRandomPosition = this.handleClickRandomPosition.bind(this); 10 | } 11 | 12 | handleClickRandomPosition() { 13 | const { scrollbars } = this.refs; 14 | const scrollHeight = scrollbars.getScrollHeight(); 15 | scrollbars.scrollTop(random(scrollHeight)); 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 | 24 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

25 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

26 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

27 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

28 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

29 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

30 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

31 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

32 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

33 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

34 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

35 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

36 |
37 | 42 |

43 | The Scrollbars are animated with Rebound. You can simply animate the Scrollbars with scrollbars.scrollTop(x). Don't forget to wrap your steps with requestAnimationFrame. 44 |

45 |
46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/simple/components/SpringScrollbars/SpringScrollbars.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Scrollbars } from 'react-custom-scrollbars-2'; 3 | import { SpringSystem, MathUtil } from 'rebound'; 4 | 5 | export default class SpringScrollbars extends Component { 6 | 7 | constructor(props, ...rest) { 8 | super(props, ...rest); 9 | this.handleSpringUpdate = this.handleSpringUpdate.bind(this); 10 | } 11 | 12 | componentDidMount() { 13 | this.springSystem = new SpringSystem(); 14 | this.spring = this.springSystem.createSpring(); 15 | this.spring.addListener({ onSpringUpdate: this.handleSpringUpdate }); 16 | } 17 | 18 | componentWillUnmount() { 19 | this.springSystem.deregisterSpring(this.spring); 20 | this.springSystem.removeAllListeners(); 21 | this.springSystem = undefined; 22 | this.spring.destroy(); 23 | this.spring = undefined; 24 | } 25 | 26 | getScrollTop() { 27 | return this.refs.scrollbars.getScrollTop(); 28 | } 29 | 30 | getScrollHeight() { 31 | return this.refs.scrollbars.getScrollHeight(); 32 | } 33 | 34 | getHeight() { 35 | return this.refs.scrollbars.getHeight(); 36 | } 37 | 38 | scrollTop(top) { 39 | const { scrollbars } = this.refs; 40 | const scrollTop = scrollbars.getScrollTop(); 41 | const scrollHeight = scrollbars.getScrollHeight(); 42 | const val = MathUtil.mapValueInRange(top, 0, scrollHeight, scrollHeight * 0.2, scrollHeight * 0.8); 43 | this.spring.setCurrentValue(scrollTop).setAtRest(); 44 | this.spring.setEndValue(val); 45 | } 46 | 47 | handleSpringUpdate(spring) { 48 | const { scrollbars } = this.refs; 49 | const val = spring.getCurrentValue(); 50 | scrollbars.scrollTop(val); 51 | } 52 | 53 | render() { 54 | return ( 55 | 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | react-custom-scrollbars-2 simple example 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 21 |
22 |

Features

23 |
    24 |
  • lightweight scrollbars made of 100% react goodness
  • 25 |
  • frictionless native browser scrolling
  • 26 |
  • native scrollbars for mobile devices
  • 27 |
  • fully customizable
  • 28 |
  • requestAnimationFrame for 60fps
  • 29 |
  • no extra stylesheets
  • 30 |
  • IE9+ support
  • 31 |
32 |
33 | 38 | 41 |
42 |
43 |
44 |
45 | 46 | 47 | View source code 48 | 49 |
50 |

Default style

51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | View source code 60 | 61 |
62 |

Custom style

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 | 73 | View source code 74 | 75 |
76 |

Spring scroll

77 |
78 |
79 |
80 |
81 |
82 |
83 | 84 | 85 | View source code 86 | 87 |
88 |

Shadow scrollbars

89 |
90 |
91 |
92 |
93 |
94 |
95 | 96 | 97 | react-custom-scrollbars-2 98 | 99 |
100 | Rob Pethick 101 |
102 |
103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /examples/simple/index.js: -------------------------------------------------------------------------------- 1 | import './sass/app.scss'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | 5 | import DefaultScrollbarsApp from './components/DefaultScrollbars/App'; 6 | import ColoredScrollbarsApp from './components/ColoredScrollbars/App'; 7 | import SpringScrollbarsApp from './components/SpringScrollbars/App'; 8 | import ShadowScrollbarsApp from './components/ShadowScrollbars/App'; 9 | 10 | render(, document.getElementById('default-scrollbars-root')); 11 | render(, document.getElementById('colored-scrollbars-root')); 12 | render(, document.getElementById('spring-scrollbars-root')); 13 | render(, document.getElementById('shadow-scrollbars-root')); 14 | -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-custom-scrollbars-2-example", 3 | "version": "0.1.0", 4 | "description": "Simple example", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=development node server.js", 8 | "build": "cross-env NODE_ENV=production node_modules/.bin/webpack", 9 | "build:pages": "npm run build && cp index.html ../../ && rm -rf ../../static && mv static ../../" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/RobPethick/react-custom-scrollbars-2.git" 14 | }, 15 | "keywords": [ 16 | "scroll", 17 | "scroller", 18 | "scrollbars", 19 | "react" 20 | ], 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/RobPethick/react-custom-scrollbars-2/issues" 24 | }, 25 | "homepage": "https://github.com/RobPethick/react-custom-scrollbars-2", 26 | "devDependencies": { 27 | "autoprefixer-loader": "^3.1.0", 28 | "babel-core": "^6.2.1", 29 | "babel-eslint": "^6.0.2", 30 | "babel-loader": "^6.2.0", 31 | "bootstrap-sass": "^3.3.6", 32 | "cross-env": "^3.1.3", 33 | "dom-css": "^2.0.0", 34 | "eslint": "^2.9.0", 35 | "eslint-config-airbnb": "^9.0.1", 36 | "eslint-plugin-import": "^1.10.2", 37 | "eslint-plugin-jsx-a11y": "^1.2.0", 38 | "eslint-plugin-react": "^5.2.2", 39 | "extract-text-webpack-plugin": "^1.0.1", 40 | "lodash": "^3.10.1", 41 | "node-libs-browser": "^0.5.2", 42 | "node-sass": "^3.4.2", 43 | "sass-loader": "^3.1.2", 44 | "style-loader": "^0.13.0", 45 | "webpack": "^1.9.11", 46 | "webpack-dev-server": "^1.9.0" 47 | }, 48 | "dependencies": { 49 | "css-loader": "^0.23.1", 50 | "prop-types": "^15.5.8", 51 | "rebound": "0.0.13" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/simple/sass/app.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,900); 2 | @import "config"; 3 | 4 | // Core variables and mixins 5 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/variables"; 6 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins"; 7 | 8 | // Reset and dependencies 9 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize"; 10 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/print"; 11 | // @import "~bootstrap-sass/assets/stylesheets/bootstrap/glyphicons"; 12 | 13 | // Core CSS 14 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding"; 15 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/type"; 16 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/code"; 17 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/grid"; 18 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/tables"; 19 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/forms"; 20 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/buttons"; 21 | 22 | // Components 23 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/component-animations"; 24 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns"; 25 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups"; 26 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/input-groups"; 27 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/navs"; 28 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/navbar"; 29 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs"; 30 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/pagination"; 31 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/pager"; 32 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/labels"; 33 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/badges"; 34 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/jumbotron"; 35 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/thumbnails"; 36 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/alerts"; 37 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/progress-bars"; 38 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/media"; 39 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/list-group"; 40 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/panels"; 41 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed"; 42 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/wells"; 43 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/close"; 44 | 45 | // Components w/ JavaScript 46 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/modals"; 47 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/tooltip"; 48 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/popovers"; 49 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/carousel"; 50 | 51 | // Utility classes 52 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/utilities"; 53 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities"; 54 | 55 | @import "type"; 56 | @import "components"; 57 | -------------------------------------------------------------------------------- /examples/simple/sass/components.scss: -------------------------------------------------------------------------------- 1 | .section { 2 | margin-bottom: 50px; 3 | } 4 | 5 | .section-paper { 6 | background: #fff; 7 | padding: 30px; 8 | color: $gray-light; 9 | } 10 | 11 | .separated { 12 | > * + * { 13 | margin-top: 20px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/simple/sass/type.scss: -------------------------------------------------------------------------------- 1 | h1, h2, h3, h4, h5, h6 { 2 | text-transform: uppercase; 3 | font-weight: 600; 4 | margin-top: 0; 5 | margin-bottom: 0.5em; 6 | color: $brand-primary; 7 | } 8 | -------------------------------------------------------------------------------- /examples/simple/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | watch: true, 8 | stats: { 9 | colors: true 10 | } 11 | }).listen(3000, 'localhost', function (err) { 12 | if (err) console.log(err); 13 | console.log('Listening at localhost:3000'); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/simple/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var extractTextPlugin = new ExtractTextPlugin('[name].css'); 5 | 6 | var entry = []; 7 | if(process.env.NODE_ENV === 'development') { 8 | entry.push( 9 | 'webpack-dev-server/client?http://localhost:3000' 10 | ); 11 | } 12 | 13 | var plugins = [ 14 | extractTextPlugin, 15 | new webpack.NoErrorsPlugin() 16 | ]; 17 | 18 | if(process.env.NODE_ENV === 'production') { 19 | plugins.push( 20 | new webpack.optimize.UglifyJsPlugin({ 21 | compress: { 22 | warnings: false 23 | } 24 | }) 25 | ); 26 | } 27 | 28 | var loaders = [{ 29 | test: /\.scss$/, 30 | loader: extractTextPlugin.extract([ 31 | 'css', 32 | 'autoprefixer?browsers=last 4 versions', 33 | 'sass?includePaths[]=' + path.resolve('./node_modules') 34 | ].join('!')) 35 | }]; 36 | if(process.env.NODE_ENV === 'development') { 37 | loaders.push({ 38 | test: /\.js$/, 39 | loaders: ['babel'], 40 | exclude: /node_modules/, 41 | include: __dirname 42 | }); 43 | } else { 44 | loaders.push({ 45 | test: /\.js$/, 46 | loaders: ['babel'], 47 | exclude: /node_modules/, 48 | include: __dirname 49 | }); 50 | } 51 | 52 | loaders.push({ 53 | test: /\.js$/, 54 | loaders: ['babel'], 55 | include: path.join(__dirname, '..', '..', 'src') 56 | }); 57 | 58 | module.exports = { 59 | devtool: 'eval', 60 | entry: entry.concat('./index'), 61 | output: { 62 | path: path.join(__dirname, 'static'), 63 | filename: 'bundle.js', 64 | publicPath: '/static/' 65 | }, 66 | plugins: plugins, 67 | resolve: { 68 | alias: { 69 | 'react-custom-scrollbars-2': path.join(__dirname, '..', '..', 'src') 70 | }, 71 | extensions: ['', '.js'] 72 | }, 73 | module: { 74 | loaders: loaders 75 | }, 76 | sassLoader: { 77 | includePaths: [path.resolve(__dirname, './node_modules')] 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for react-custom-scrollbars-2 4.0 2 | // Project: https://github.com/malte-wessel/react-custom-scrollbars-2 3 | // Definitions by: David-LeBlanc-git 4 | // kittimiyo 5 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 6 | // TypeScript Version: 2.8 7 | 8 | import * as React from "react"; 9 | 10 | export as namespace ReactCustomScrollbars; 11 | 12 | export interface positionValues { 13 | top: number; 14 | left: number; 15 | clientWidth: number; 16 | clientHeight: number; 17 | scrollWidth: number; 18 | scrollHeight: number; 19 | scrollLeft: number; 20 | scrollTop: number; 21 | } 22 | 23 | export interface ScrollbarProps extends React.HTMLProps { 24 | onScroll?: React.UIEventHandler; 25 | onScrollFrame?: (values: positionValues) => void; 26 | onScrollStart?: () => void; 27 | onScrollStop?: () => void; 28 | onUpdate?: (values: positionValues) => void; 29 | 30 | renderView?: React.ComponentType; 31 | renderTrackHorizontal?: React.ComponentType; 32 | renderTrackVertical?: React.ComponentType; 33 | renderThumbHorizontal?: React.ComponentType; 34 | renderThumbVertical?: React.ComponentType; 35 | 36 | tagName?: string; 37 | hideTracksWhenNotNeeded?: boolean; 38 | 39 | autoHide?: boolean; 40 | autoHideTimeout?: number; 41 | autoHideDuration?: number; 42 | 43 | thumbSize?: number; 44 | thumbMinSize?: number; 45 | universal?: boolean; 46 | 47 | autoHeight?: boolean; 48 | autoHeightMin?: number | string; 49 | autoHeightMax?: number | string; 50 | 51 | style?: React.CSSProperties; 52 | } 53 | 54 | export class Scrollbars extends React.Component { 55 | scrollTop(top: number): void; 56 | scrollLeft(left: number): void; 57 | scrollToTop(): void; 58 | scrollToBottom(): void; 59 | scrollToLeft(): void; 60 | scrollToRight(): void; 61 | getScrollLeft(): number; 62 | getScrollTop(): number; 63 | getScrollWidth(): number; 64 | getScrollHeight(): number; 65 | getClientWidth(): number; 66 | getClientHeight(): number; 67 | getValues(): positionValues; 68 | container: HTMLDivElement; 69 | } 70 | 71 | export default Scrollbars; 72 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* eslint no-var: 0, no-unused-vars: 0 */ 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | var runCoverage = process.env.COVERAGE === 'true'; 5 | 6 | var coverageLoaders = []; 7 | var coverageReporters = []; 8 | 9 | if (runCoverage) { 10 | coverageLoaders.push({ 11 | test: /\.js$/, 12 | include: path.resolve('src/'), 13 | loader: 'isparta' 14 | }); 15 | coverageReporters.push('coverage'); 16 | } 17 | 18 | module.exports = function karmaConfig(config) { 19 | config.set({ 20 | browsers: ['Chrome'], 21 | singleRun: true, 22 | frameworks: ['mocha'], 23 | files: ['./test.js'], 24 | preprocessors: { 25 | './test.js': ['webpack', 'sourcemap'] 26 | }, 27 | reporters: ['mocha'].concat(coverageReporters), 28 | webpack: { 29 | devtool: 'inline-source-map', 30 | resolve: { 31 | alias: { 32 | 'react-custom-scrollbars-2': path.resolve(__dirname, './src') 33 | } 34 | }, 35 | module: { 36 | loaders: [{ 37 | test: /\.js$/, 38 | loader: 'babel', 39 | exclude: /(node_modules)/ 40 | }].concat(coverageLoaders) 41 | } 42 | }, 43 | coverageReporter: { 44 | dir: 'coverage/', 45 | reporters: [ 46 | { type: 'html', subdir: 'report-html' }, 47 | { type: 'text', subdir: '.', file: 'text.txt' }, 48 | { type: 'text-summary', subdir: '.', file: 'text-summary.txt' }, 49 | ] 50 | } 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-custom-scrollbars-2", 3 | "version": "4.3.0", 4 | "description": "React scrollbars component", 5 | "main": "lib/index.js", 6 | "typings": "./index.d.ts", 7 | "scripts": { 8 | "clean": "rimraf lib dist", 9 | "build": "babel src --out-dir lib", 10 | "build:umd": "cross-env NODE_ENV=development webpack src/index.js dist/react-custom-scrollbars.js", 11 | "build:umd:min": "cross-env NODE_ENV=production webpack src/index.js dist/react-custom-scrollbars.min.js", 12 | "lint": "eslint src test examples", 13 | "test": "cross-env NODE_ENV=test karma start", 14 | "test:watch": "cross-env NODE_ENV=test karma start --auto-watch --no-single-run", 15 | "test:cov": "cross-env NODE_ENV=test COVERAGE=true karma start --single-run", 16 | "prepublishOnly": "npm run lint && npm run clean && npm run build && npm run build:umd && npm run build:umd:min && node ./prepublish" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com//RobPethick/react-custom-scrollbars-2.git" 21 | }, 22 | "keywords": [ 23 | "scroll", 24 | "scroller", 25 | "scrollbars", 26 | "react-component", 27 | "react", 28 | "custom" 29 | ], 30 | "author": "Rob Pethick", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com//RobPethick/react-custom-scrollbars-2/issues" 34 | }, 35 | "homepage": "https://github.com//RobPethick/react-custom-scrollbars-2", 36 | "devDependencies": { 37 | "babel-cli": "^6.2.0", 38 | "babel-core": "^6.2.1", 39 | "babel-eslint": "^6.1.2", 40 | "babel-loader": "^6.2.0", 41 | "babel-preset-es2015": "^6.1.18", 42 | "babel-preset-react": "^6.3.13", 43 | "babel-preset-stage-1": "^6.1.18", 44 | "babel-register": "^6.3.13", 45 | "babel-runtime": "^6.3.19", 46 | "cross-env": "^3.1.3", 47 | "es3ify": "^0.2.1", 48 | "eslint": "^2.9.0", 49 | "eslint-config-airbnb": "^9.0.1", 50 | "eslint-plugin-import": "^1.10.2", 51 | "eslint-plugin-jsx-a11y": "^1.2.0", 52 | "eslint-plugin-react": "^5.2.2", 53 | "expect": "^1.6.0", 54 | "glob": "^7.0.0", 55 | "isparta-loader": "^2.0.0", 56 | "karma": "^1.1.1", 57 | "karma-chrome-launcher": "^1.0.1", 58 | "karma-cli": "^1.0.1", 59 | "karma-coverage": "^1.1.0", 60 | "karma-mocha": "^0.2.0", 61 | "karma-mocha-reporter": "^2.0.4", 62 | "karma-sourcemap-loader": "^0.3.6", 63 | "karma-webpack": "^1.6.0", 64 | "mocha": "^2.2.5", 65 | "react": "^16.0.0", 66 | "react-dom": "^16.0.0", 67 | "rimraf": "^2.3.4", 68 | "simulant": "^0.2.2", 69 | "webpack": "^1.9.6", 70 | "webpack-dev-server": "^1.8.2" 71 | }, 72 | "peerDependencies": { 73 | "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", 74 | "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" 75 | }, 76 | "dependencies": { 77 | "dom-css": "^2.0.0", 78 | "prop-types": "^15.5.10", 79 | "raf": "^3.1.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /prepublish.js: -------------------------------------------------------------------------------- 1 | var glob = require('glob'); 2 | var fs = require('fs'); 3 | var es3ify = require('es3ify'); 4 | 5 | glob('./@(lib|dist)/**/*.js', function (err, files) { 6 | if (err) throw err; 7 | 8 | files.forEach(function(file) { 9 | fs.readFile(file, 'utf8', function (err, data) { 10 | if (err) throw err; 11 | fs.writeFile(file, es3ify.transform(data), function (err) { 12 | if (err) throw err 13 | console.log('es3ified ' + file); 14 | }) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/Scrollbars/defaultRenderElements.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | /* eslint-disable react/prop-types */ 3 | 4 | export function renderViewDefault(props) { 5 | return
; 6 | } 7 | 8 | export function renderTrackHorizontalDefault({ style, ...props }) { 9 | const finalStyle = { 10 | ...style, 11 | right: 2, 12 | bottom: 2, 13 | left: 2, 14 | borderRadius: 3 15 | }; 16 | return
; 17 | } 18 | 19 | export function renderTrackVerticalDefault({ style, ...props }) { 20 | const finalStyle = { 21 | ...style, 22 | right: 2, 23 | bottom: 2, 24 | top: 2, 25 | borderRadius: 3 26 | }; 27 | return
; 28 | } 29 | 30 | export function renderThumbHorizontalDefault({ style, ...props }) { 31 | const finalStyle = { 32 | ...style, 33 | cursor: 'pointer', 34 | borderRadius: 'inherit', 35 | backgroundColor: 'rgba(0,0,0,.2)' 36 | }; 37 | return
; 38 | } 39 | 40 | export function renderThumbVerticalDefault({ style, ...props }) { 41 | const finalStyle = { 42 | ...style, 43 | cursor: 'pointer', 44 | borderRadius: 'inherit', 45 | backgroundColor: 'rgba(0,0,0,.2)' 46 | }; 47 | return
; 48 | } 49 | -------------------------------------------------------------------------------- /src/Scrollbars/index.js: -------------------------------------------------------------------------------- 1 | import raf, { cancel as caf } from 'raf'; 2 | import css from 'dom-css'; 3 | import { Component, createElement, cloneElement } from 'react'; 4 | import PropTypes from 'prop-types'; 5 | 6 | import isString from '../utils/isString'; 7 | import getScrollbarWidth from '../utils/getScrollbarWidth'; 8 | import returnFalse from '../utils/returnFalse'; 9 | import getInnerWidth from '../utils/getInnerWidth'; 10 | import getInnerHeight from '../utils/getInnerHeight'; 11 | 12 | import { 13 | containerStyleDefault, 14 | containerStyleAutoHeight, 15 | viewStyleDefault, 16 | viewStyleAutoHeight, 17 | viewStyleUniversalInitial, 18 | trackHorizontalStyleDefault, 19 | trackVerticalStyleDefault, 20 | thumbHorizontalStyleDefault, 21 | thumbVerticalStyleDefault, 22 | disableSelectStyle, 23 | disableSelectStyleReset 24 | } from './styles'; 25 | 26 | import { 27 | renderViewDefault, 28 | renderTrackHorizontalDefault, 29 | renderTrackVerticalDefault, 30 | renderThumbHorizontalDefault, 31 | renderThumbVerticalDefault 32 | } from './defaultRenderElements'; 33 | 34 | export default class Scrollbars extends Component { 35 | 36 | constructor(props, ...rest) { 37 | super(props, ...rest); 38 | 39 | this.getScrollLeft = this.getScrollLeft.bind(this); 40 | this.getScrollTop = this.getScrollTop.bind(this); 41 | this.getScrollWidth = this.getScrollWidth.bind(this); 42 | this.getScrollHeight = this.getScrollHeight.bind(this); 43 | this.getClientWidth = this.getClientWidth.bind(this); 44 | this.getClientHeight = this.getClientHeight.bind(this); 45 | this.getValues = this.getValues.bind(this); 46 | this.getThumbHorizontalWidth = this.getThumbHorizontalWidth.bind(this); 47 | this.getThumbVerticalHeight = this.getThumbVerticalHeight.bind(this); 48 | this.getScrollLeftForOffset = this.getScrollLeftForOffset.bind(this); 49 | this.getScrollTopForOffset = this.getScrollTopForOffset.bind(this); 50 | 51 | this.scrollLeft = this.scrollLeft.bind(this); 52 | this.scrollTop = this.scrollTop.bind(this); 53 | this.scrollToLeft = this.scrollToLeft.bind(this); 54 | this.scrollToTop = this.scrollToTop.bind(this); 55 | this.scrollToRight = this.scrollToRight.bind(this); 56 | this.scrollToBottom = this.scrollToBottom.bind(this); 57 | 58 | this.handleTrackMouseEnter = this.handleTrackMouseEnter.bind(this); 59 | this.handleTrackMouseLeave = this.handleTrackMouseLeave.bind(this); 60 | this.handleHorizontalTrackMouseDown = this.handleHorizontalTrackMouseDown.bind(this); 61 | this.handleVerticalTrackMouseDown = this.handleVerticalTrackMouseDown.bind(this); 62 | this.handleHorizontalThumbMouseDown = this.handleHorizontalThumbMouseDown.bind(this); 63 | this.handleVerticalThumbMouseDown = this.handleVerticalThumbMouseDown.bind(this); 64 | this.handleWindowResize = this.handleWindowResize.bind(this); 65 | this.handleScroll = this.handleScroll.bind(this); 66 | this.handleDrag = this.handleDrag.bind(this); 67 | this.handleDragEnd = this.handleDragEnd.bind(this); 68 | 69 | this.state = { 70 | didMountUniversal: false 71 | }; 72 | } 73 | 74 | componentDidMount() { 75 | this.addListeners(); 76 | this.update(); 77 | this.componentDidMountUniversal(); 78 | } 79 | 80 | componentDidMountUniversal() { // eslint-disable-line react/sort-comp 81 | const { universal } = this.props; 82 | if (!universal) return; 83 | this.setState({ didMountUniversal: true }); 84 | } 85 | 86 | componentDidUpdate() { 87 | this.update(); 88 | } 89 | 90 | componentWillUnmount() { 91 | this.removeListeners(); 92 | caf(this.requestFrame); 93 | clearTimeout(this.hideTracksTimeout); 94 | clearInterval(this.detectScrollingInterval); 95 | } 96 | 97 | getScrollLeft() { 98 | if (!this.view) return 0; 99 | return this.view.scrollLeft; 100 | } 101 | 102 | getScrollTop() { 103 | if (!this.view) return 0; 104 | return this.view.scrollTop; 105 | } 106 | 107 | getScrollWidth() { 108 | if (!this.view) return 0; 109 | return this.view.scrollWidth; 110 | } 111 | 112 | getScrollHeight() { 113 | if (!this.view) return 0; 114 | return this.view.scrollHeight; 115 | } 116 | 117 | getClientWidth() { 118 | if (!this.view) return 0; 119 | return this.view.clientWidth; 120 | } 121 | 122 | getClientHeight() { 123 | if (!this.view) return 0; 124 | return this.view.clientHeight; 125 | } 126 | 127 | getValues() { 128 | const { 129 | scrollLeft = 0, 130 | scrollTop = 0, 131 | scrollWidth = 0, 132 | scrollHeight = 0, 133 | clientWidth = 0, 134 | clientHeight = 0 135 | } = this.view || {}; 136 | 137 | return { 138 | left: (scrollLeft / (scrollWidth - clientWidth)) || 0, 139 | top: (scrollTop / (scrollHeight - clientHeight)) || 0, 140 | scrollLeft, 141 | scrollTop, 142 | scrollWidth, 143 | scrollHeight, 144 | clientWidth, 145 | clientHeight 146 | }; 147 | } 148 | 149 | getThumbHorizontalWidth() { 150 | const { thumbSize, thumbMinSize } = this.props; 151 | const { scrollWidth, clientWidth } = this.view; 152 | const trackWidth = getInnerWidth(this.trackHorizontal); 153 | const width = Math.ceil(clientWidth / scrollWidth * trackWidth); 154 | if (trackWidth <= width) return 0; 155 | if (thumbSize) return thumbSize; 156 | return Math.max(width, thumbMinSize); 157 | } 158 | 159 | getThumbVerticalHeight() { 160 | const { thumbSize, thumbMinSize } = this.props; 161 | const { scrollHeight, clientHeight } = this.view; 162 | const trackHeight = getInnerHeight(this.trackVertical); 163 | const height = Math.ceil(clientHeight / scrollHeight * trackHeight); 164 | if (trackHeight <= height) return 0; 165 | if (thumbSize) return thumbSize; 166 | return Math.max(height, thumbMinSize); 167 | } 168 | 169 | getScrollLeftForOffset(offset) { 170 | const { scrollWidth, clientWidth } = this.view; 171 | const trackWidth = getInnerWidth(this.trackHorizontal); 172 | const thumbWidth = this.getThumbHorizontalWidth(); 173 | return offset / (trackWidth - thumbWidth) * (scrollWidth - clientWidth); 174 | } 175 | 176 | getScrollTopForOffset(offset) { 177 | const { scrollHeight, clientHeight } = this.view; 178 | const trackHeight = getInnerHeight(this.trackVertical); 179 | const thumbHeight = this.getThumbVerticalHeight(); 180 | return offset / (trackHeight - thumbHeight) * (scrollHeight - clientHeight); 181 | } 182 | 183 | scrollLeft(left = 0) { 184 | if (!this.view) return; 185 | this.view.scrollLeft = left; 186 | } 187 | 188 | scrollTop(top = 0) { 189 | if (!this.view) return; 190 | this.view.scrollTop = top; 191 | } 192 | 193 | scrollToLeft() { 194 | if (!this.view) return; 195 | this.view.scrollLeft = 0; 196 | } 197 | 198 | scrollToTop() { 199 | if (!this.view) return; 200 | this.view.scrollTop = 0; 201 | } 202 | 203 | scrollToRight() { 204 | if (!this.view) return; 205 | this.view.scrollLeft = this.view.scrollWidth; 206 | } 207 | 208 | scrollToBottom() { 209 | if (!this.view) return; 210 | this.view.scrollTop = this.view.scrollHeight; 211 | } 212 | 213 | addListeners() { 214 | /* istanbul ignore if */ 215 | if (typeof document === 'undefined' || !this.view) return; 216 | const { view, trackHorizontal, trackVertical, thumbHorizontal, thumbVertical } = this; 217 | view.addEventListener('scroll', this.handleScroll); 218 | if (!getScrollbarWidth()) return; 219 | trackHorizontal.addEventListener('mouseenter', this.handleTrackMouseEnter); 220 | trackHorizontal.addEventListener('mouseleave', this.handleTrackMouseLeave); 221 | trackHorizontal.addEventListener('mousedown', this.handleHorizontalTrackMouseDown); 222 | trackVertical.addEventListener('mouseenter', this.handleTrackMouseEnter); 223 | trackVertical.addEventListener('mouseleave', this.handleTrackMouseLeave); 224 | trackVertical.addEventListener('mousedown', this.handleVerticalTrackMouseDown); 225 | thumbHorizontal.addEventListener('mousedown', this.handleHorizontalThumbMouseDown); 226 | thumbVertical.addEventListener('mousedown', this.handleVerticalThumbMouseDown); 227 | window.addEventListener('resize', this.handleWindowResize); 228 | } 229 | 230 | removeListeners() { 231 | /* istanbul ignore if */ 232 | if (typeof document === 'undefined' || !this.view) return; 233 | const { view, trackHorizontal, trackVertical, thumbHorizontal, thumbVertical } = this; 234 | view.removeEventListener('scroll', this.handleScroll); 235 | if (!getScrollbarWidth()) return; 236 | trackHorizontal.removeEventListener('mouseenter', this.handleTrackMouseEnter); 237 | trackHorizontal.removeEventListener('mouseleave', this.handleTrackMouseLeave); 238 | trackHorizontal.removeEventListener('mousedown', this.handleHorizontalTrackMouseDown); 239 | trackVertical.removeEventListener('mouseenter', this.handleTrackMouseEnter); 240 | trackVertical.removeEventListener('mouseleave', this.handleTrackMouseLeave); 241 | trackVertical.removeEventListener('mousedown', this.handleVerticalTrackMouseDown); 242 | thumbHorizontal.removeEventListener('mousedown', this.handleHorizontalThumbMouseDown); 243 | thumbVertical.removeEventListener('mousedown', this.handleVerticalThumbMouseDown); 244 | window.removeEventListener('resize', this.handleWindowResize); 245 | // Possibly setup by `handleDragStart` 246 | this.teardownDragging(); 247 | } 248 | 249 | handleScroll(event) { 250 | const { onScroll, onScrollFrame } = this.props; 251 | if (onScroll) onScroll(event); 252 | this.update(values => { 253 | const { scrollLeft, scrollTop } = values; 254 | this.viewScrollLeft = scrollLeft; 255 | this.viewScrollTop = scrollTop; 256 | if (onScrollFrame) onScrollFrame(values); 257 | }); 258 | this.detectScrolling(); 259 | } 260 | 261 | handleScrollStart() { 262 | const { onScrollStart } = this.props; 263 | if (onScrollStart) onScrollStart(); 264 | this.handleScrollStartAutoHide(); 265 | } 266 | 267 | handleScrollStartAutoHide() { 268 | const { autoHide } = this.props; 269 | if (!autoHide) return; 270 | this.showTracks(); 271 | } 272 | 273 | handleScrollStop() { 274 | const { onScrollStop } = this.props; 275 | if (onScrollStop) onScrollStop(); 276 | this.handleScrollStopAutoHide(); 277 | } 278 | 279 | handleScrollStopAutoHide() { 280 | const { autoHide } = this.props; 281 | if (!autoHide) return; 282 | this.hideTracks(); 283 | } 284 | 285 | handleWindowResize() { 286 | getScrollbarWidth(false); 287 | this.forceUpdate(); 288 | } 289 | 290 | handleHorizontalTrackMouseDown(event) { 291 | event.preventDefault(); 292 | const { target, clientX } = event; 293 | const { left: targetLeft } = target.getBoundingClientRect(); 294 | const thumbWidth = this.getThumbHorizontalWidth(); 295 | const offset = Math.abs(targetLeft - clientX) - thumbWidth / 2; 296 | this.view.scrollLeft = this.getScrollLeftForOffset(offset); 297 | } 298 | 299 | handleVerticalTrackMouseDown(event) { 300 | event.preventDefault(); 301 | const { target, clientY } = event; 302 | const { top: targetTop } = target.getBoundingClientRect(); 303 | const thumbHeight = this.getThumbVerticalHeight(); 304 | const offset = Math.abs(targetTop - clientY) - thumbHeight / 2; 305 | this.view.scrollTop = this.getScrollTopForOffset(offset); 306 | } 307 | 308 | handleHorizontalThumbMouseDown(event) { 309 | event.preventDefault(); 310 | this.handleDragStart(event); 311 | const { target, clientX } = event; 312 | const { offsetWidth } = target; 313 | const { left } = target.getBoundingClientRect(); 314 | this.prevPageX = offsetWidth - (clientX - left); 315 | } 316 | 317 | handleVerticalThumbMouseDown(event) { 318 | event.preventDefault(); 319 | this.handleDragStart(event); 320 | const { target, clientY } = event; 321 | const { offsetHeight } = target; 322 | const { top } = target.getBoundingClientRect(); 323 | this.prevPageY = offsetHeight - (clientY - top); 324 | } 325 | 326 | setupDragging() { 327 | css(document.body, disableSelectStyle); 328 | document.addEventListener('mousemove', this.handleDrag); 329 | document.addEventListener('mouseup', this.handleDragEnd); 330 | document.onselectstart = returnFalse; 331 | } 332 | 333 | teardownDragging() { 334 | css(document.body, disableSelectStyleReset); 335 | document.removeEventListener('mousemove', this.handleDrag); 336 | document.removeEventListener('mouseup', this.handleDragEnd); 337 | document.onselectstart = undefined; 338 | } 339 | 340 | handleDragStart(event) { 341 | this.dragging = true; 342 | event.stopImmediatePropagation(); 343 | this.setupDragging(); 344 | } 345 | 346 | handleDrag(event) { 347 | if (this.prevPageX) { 348 | const { clientX } = event; 349 | const { left: trackLeft } = this.trackHorizontal.getBoundingClientRect(); 350 | const thumbWidth = this.getThumbHorizontalWidth(); 351 | const clickPosition = thumbWidth - this.prevPageX; 352 | const offset = -trackLeft + clientX - clickPosition; 353 | this.view.scrollLeft = this.getScrollLeftForOffset(offset); 354 | } 355 | if (this.prevPageY) { 356 | const { clientY } = event; 357 | const { top: trackTop } = this.trackVertical.getBoundingClientRect(); 358 | const thumbHeight = this.getThumbVerticalHeight(); 359 | const clickPosition = thumbHeight - this.prevPageY; 360 | const offset = -trackTop + clientY - clickPosition; 361 | this.view.scrollTop = this.getScrollTopForOffset(offset); 362 | } 363 | return false; 364 | } 365 | 366 | handleDragEnd() { 367 | this.dragging = false; 368 | this.prevPageX = this.prevPageY = 0; 369 | this.teardownDragging(); 370 | this.handleDragEndAutoHide(); 371 | } 372 | 373 | handleDragEndAutoHide() { 374 | const { autoHide } = this.props; 375 | if (!autoHide) return; 376 | this.hideTracks(); 377 | } 378 | 379 | handleTrackMouseEnter() { 380 | this.trackMouseOver = true; 381 | this.handleTrackMouseEnterAutoHide(); 382 | } 383 | 384 | handleTrackMouseEnterAutoHide() { 385 | const { autoHide } = this.props; 386 | if (!autoHide) return; 387 | this.showTracks(); 388 | } 389 | 390 | handleTrackMouseLeave() { 391 | this.trackMouseOver = false; 392 | this.handleTrackMouseLeaveAutoHide(); 393 | } 394 | 395 | handleTrackMouseLeaveAutoHide() { 396 | const { autoHide } = this.props; 397 | if (!autoHide) return; 398 | this.hideTracks(); 399 | } 400 | 401 | showTracks() { 402 | clearTimeout(this.hideTracksTimeout); 403 | css(this.trackHorizontal, { opacity: 1 }); 404 | css(this.trackVertical, { opacity: 1 }); 405 | } 406 | 407 | hideTracks() { 408 | if (this.dragging) return; 409 | if (this.scrolling) return; 410 | if (this.trackMouseOver) return; 411 | const { autoHideTimeout } = this.props; 412 | clearTimeout(this.hideTracksTimeout); 413 | this.hideTracksTimeout = setTimeout(() => { 414 | css(this.trackHorizontal, { opacity: 0 }); 415 | css(this.trackVertical, { opacity: 0 }); 416 | }, autoHideTimeout); 417 | } 418 | 419 | detectScrolling() { 420 | if (this.scrolling) return; 421 | this.scrolling = true; 422 | this.handleScrollStart(); 423 | this.detectScrollingInterval = setInterval(() => { 424 | if (this.lastViewScrollLeft === this.viewScrollLeft 425 | && this.lastViewScrollTop === this.viewScrollTop) { 426 | clearInterval(this.detectScrollingInterval); 427 | this.scrolling = false; 428 | this.handleScrollStop(); 429 | } 430 | this.lastViewScrollLeft = this.viewScrollLeft; 431 | this.lastViewScrollTop = this.viewScrollTop; 432 | }, 100); 433 | } 434 | 435 | raf(callback) { 436 | if (this.requestFrame) raf.cancel(this.requestFrame); 437 | this.requestFrame = raf(() => { 438 | this.requestFrame = undefined; 439 | callback(); 440 | }); 441 | } 442 | 443 | update(callback) { 444 | this.raf(() => this._update(callback)); 445 | } 446 | 447 | _update(callback) { 448 | const { onUpdate, hideTracksWhenNotNeeded } = this.props; 449 | const values = this.getValues(); 450 | if (getScrollbarWidth()) { 451 | const { scrollLeft, clientWidth, scrollWidth } = values; 452 | const trackHorizontalWidth = getInnerWidth(this.trackHorizontal); 453 | const thumbHorizontalWidth = this.getThumbHorizontalWidth(); 454 | const thumbHorizontalX = scrollLeft / (scrollWidth - clientWidth) * (trackHorizontalWidth - thumbHorizontalWidth); 455 | const thumbHorizontalStyle = { 456 | width: thumbHorizontalWidth, 457 | transform: `translateX(${thumbHorizontalX}px)` 458 | }; 459 | const { scrollTop, clientHeight, scrollHeight } = values; 460 | const trackVerticalHeight = getInnerHeight(this.trackVertical); 461 | const thumbVerticalHeight = this.getThumbVerticalHeight(); 462 | const thumbVerticalY = scrollTop / (scrollHeight - clientHeight) * (trackVerticalHeight - thumbVerticalHeight); 463 | const thumbVerticalStyle = { 464 | height: thumbVerticalHeight, 465 | transform: `translateY(${thumbVerticalY}px)` 466 | }; 467 | if (hideTracksWhenNotNeeded) { 468 | const trackHorizontalStyle = { 469 | visibility: scrollWidth > clientWidth ? 'visible' : 'hidden' 470 | }; 471 | const trackVerticalStyle = { 472 | visibility: scrollHeight > clientHeight ? 'visible' : 'hidden' 473 | }; 474 | css(this.trackHorizontal, trackHorizontalStyle); 475 | css(this.trackVertical, trackVerticalStyle); 476 | } 477 | css(this.thumbHorizontal, thumbHorizontalStyle); 478 | css(this.thumbVertical, thumbVerticalStyle); 479 | } 480 | if (onUpdate) onUpdate(values); 481 | if (typeof callback !== 'function') return; 482 | callback(values); 483 | } 484 | 485 | render() { 486 | const scrollbarWidth = getScrollbarWidth(); 487 | /* eslint-disable no-unused-vars */ 488 | const { 489 | onScroll, 490 | onScrollFrame, 491 | onScrollStart, 492 | onScrollStop, 493 | onUpdate, 494 | renderView, 495 | renderTrackHorizontal, 496 | renderTrackVertical, 497 | renderThumbHorizontal, 498 | renderThumbVertical, 499 | tagName, 500 | hideTracksWhenNotNeeded, 501 | autoHide, 502 | autoHideTimeout, 503 | autoHideDuration, 504 | thumbSize, 505 | thumbMinSize, 506 | universal, 507 | autoHeight, 508 | autoHeightMin, 509 | autoHeightMax, 510 | style, 511 | children, 512 | ...props 513 | } = this.props; 514 | /* eslint-enable no-unused-vars */ 515 | 516 | const { didMountUniversal } = this.state; 517 | 518 | const containerStyle = { 519 | ...containerStyleDefault, 520 | ...(autoHeight && { 521 | ...containerStyleAutoHeight, 522 | minHeight: autoHeightMin, 523 | maxHeight: autoHeightMax 524 | }), 525 | ...style 526 | }; 527 | 528 | const viewStyle = { 529 | ...viewStyleDefault, 530 | // Hide scrollbars by setting a negative margin 531 | marginRight: scrollbarWidth ? -scrollbarWidth : 0, 532 | marginBottom: scrollbarWidth ? -scrollbarWidth : 0, 533 | ...(autoHeight && { 534 | ...viewStyleAutoHeight, 535 | // Add scrollbarWidth to autoHeight in order to compensate negative margins 536 | minHeight: isString(autoHeightMin) 537 | ? `calc(${autoHeightMin} + ${scrollbarWidth}px)` 538 | : autoHeightMin + scrollbarWidth, 539 | maxHeight: isString(autoHeightMax) 540 | ? `calc(${autoHeightMax} + ${scrollbarWidth}px)` 541 | : autoHeightMax + scrollbarWidth 542 | }), 543 | // Override min/max height for initial universal rendering 544 | ...((autoHeight && universal && !didMountUniversal) && { 545 | minHeight: autoHeightMin, 546 | maxHeight: autoHeightMax 547 | }), 548 | // Override 549 | ...((universal && !didMountUniversal) && viewStyleUniversalInitial) 550 | }; 551 | 552 | const trackAutoHeightStyle = { 553 | transition: `opacity ${autoHideDuration}ms`, 554 | opacity: 0 555 | }; 556 | 557 | const trackHorizontalStyle = { 558 | ...trackHorizontalStyleDefault, 559 | ...(autoHide && trackAutoHeightStyle), 560 | ...((!scrollbarWidth || (universal && !didMountUniversal)) && { 561 | display: 'none' 562 | }) 563 | }; 564 | 565 | const trackVerticalStyle = { 566 | ...trackVerticalStyleDefault, 567 | ...(autoHide && trackAutoHeightStyle), 568 | ...((!scrollbarWidth || (universal && !didMountUniversal)) && { 569 | display: 'none' 570 | }) 571 | }; 572 | 573 | return createElement(tagName, { ...props, style: containerStyle, ref: (ref) => { this.container = ref; } }, [ 574 | cloneElement( 575 | renderView({ style: viewStyle }), 576 | { key: 'view', ref: (ref) => { this.view = ref; } }, 577 | children 578 | ), 579 | cloneElement( 580 | renderTrackHorizontal({ style: trackHorizontalStyle }), 581 | { key: 'trackHorizontal', ref: (ref) => { this.trackHorizontal = ref; } }, 582 | cloneElement( 583 | renderThumbHorizontal({ style: thumbHorizontalStyleDefault }), 584 | { ref: (ref) => { this.thumbHorizontal = ref; } } 585 | ) 586 | ), 587 | cloneElement( 588 | renderTrackVertical({ style: trackVerticalStyle }), 589 | { key: 'trackVertical', ref: (ref) => { this.trackVertical = ref; } }, 590 | cloneElement( 591 | renderThumbVertical({ style: thumbVerticalStyleDefault }), 592 | { ref: (ref) => { this.thumbVertical = ref; } } 593 | ) 594 | ) 595 | ]); 596 | } 597 | } 598 | 599 | Scrollbars.propTypes = { 600 | onScroll: PropTypes.func, 601 | onScrollFrame: PropTypes.func, 602 | onScrollStart: PropTypes.func, 603 | onScrollStop: PropTypes.func, 604 | onUpdate: PropTypes.func, 605 | renderView: PropTypes.func, 606 | renderTrackHorizontal: PropTypes.func, 607 | renderTrackVertical: PropTypes.func, 608 | renderThumbHorizontal: PropTypes.func, 609 | renderThumbVertical: PropTypes.func, 610 | tagName: PropTypes.string, 611 | thumbSize: PropTypes.number, 612 | thumbMinSize: PropTypes.number, 613 | hideTracksWhenNotNeeded: PropTypes.bool, 614 | autoHide: PropTypes.bool, 615 | autoHideTimeout: PropTypes.number, 616 | autoHideDuration: PropTypes.number, 617 | autoHeight: PropTypes.bool, 618 | autoHeightMin: PropTypes.oneOfType([ 619 | PropTypes.number, 620 | PropTypes.string 621 | ]), 622 | autoHeightMax: PropTypes.oneOfType([ 623 | PropTypes.number, 624 | PropTypes.string 625 | ]), 626 | universal: PropTypes.bool, 627 | style: PropTypes.object, 628 | children: PropTypes.node, 629 | }; 630 | 631 | Scrollbars.defaultProps = { 632 | renderView: renderViewDefault, 633 | renderTrackHorizontal: renderTrackHorizontalDefault, 634 | renderTrackVertical: renderTrackVerticalDefault, 635 | renderThumbHorizontal: renderThumbHorizontalDefault, 636 | renderThumbVertical: renderThumbVerticalDefault, 637 | tagName: 'div', 638 | thumbMinSize: 30, 639 | hideTracksWhenNotNeeded: false, 640 | autoHide: false, 641 | autoHideTimeout: 1000, 642 | autoHideDuration: 200, 643 | autoHeight: false, 644 | autoHeightMin: 0, 645 | autoHeightMax: 200, 646 | universal: false, 647 | }; 648 | -------------------------------------------------------------------------------- /src/Scrollbars/styles.js: -------------------------------------------------------------------------------- 1 | export const containerStyleDefault = { 2 | position: 'relative', 3 | overflow: 'hidden', 4 | width: '100%', 5 | height: '100%', 6 | }; 7 | 8 | // Overrides containerStyleDefault properties 9 | export const containerStyleAutoHeight = { 10 | height: 'auto' 11 | }; 12 | 13 | export const viewStyleDefault = { 14 | position: 'absolute', 15 | top: 0, 16 | left: 0, 17 | right: 0, 18 | bottom: 0, 19 | overflow: 'scroll', 20 | WebkitOverflowScrolling: 'touch' 21 | }; 22 | 23 | // Overrides viewStyleDefault properties 24 | export const viewStyleAutoHeight = { 25 | position: 'relative', 26 | top: undefined, 27 | left: undefined, 28 | right: undefined, 29 | bottom: undefined 30 | }; 31 | 32 | export const viewStyleUniversalInitial = { 33 | overflow: 'hidden', 34 | marginRight: 0, 35 | marginBottom: 0, 36 | }; 37 | 38 | export const trackHorizontalStyleDefault = { 39 | position: 'absolute', 40 | height: 6 41 | }; 42 | 43 | export const trackVerticalStyleDefault = { 44 | position: 'absolute', 45 | width: 6 46 | }; 47 | 48 | export const thumbHorizontalStyleDefault = { 49 | position: 'relative', 50 | display: 'block', 51 | height: '100%' 52 | }; 53 | 54 | export const thumbVerticalStyleDefault = { 55 | position: 'relative', 56 | display: 'block', 57 | width: '100%' 58 | }; 59 | 60 | export const disableSelectStyle = { 61 | userSelect: 'none' 62 | }; 63 | 64 | export const disableSelectStyleReset = { 65 | userSelect: '' 66 | }; 67 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Scrollbars from './Scrollbars'; 2 | export default Scrollbars; 3 | export { Scrollbars }; 4 | -------------------------------------------------------------------------------- /src/utils/getInnerHeight.js: -------------------------------------------------------------------------------- 1 | export default function getInnerHeight(el) { 2 | const { clientHeight } = el; 3 | const { paddingTop, paddingBottom } = getComputedStyle(el); 4 | return clientHeight - parseFloat(paddingTop) - parseFloat(paddingBottom); 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/getInnerWidth.js: -------------------------------------------------------------------------------- 1 | export default function getInnerWidth(el) { 2 | const { clientWidth } = el; 3 | const { paddingLeft, paddingRight } = getComputedStyle(el); 4 | return clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight); 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/getScrollbarWidth.js: -------------------------------------------------------------------------------- 1 | import css from 'dom-css'; 2 | let scrollbarWidth = false; 3 | 4 | export default function getScrollbarWidth(cacheEnabled = true) { 5 | if (cacheEnabled && scrollbarWidth !== false) return scrollbarWidth; 6 | /* istanbul ignore else */ 7 | if (typeof document !== 'undefined') { 8 | const div = document.createElement('div'); 9 | css(div, { 10 | width: 100, 11 | height: 100, 12 | position: 'absolute', 13 | top: -9999, 14 | overflow: 'scroll', 15 | MsOverflowStyle: 'scrollbar' 16 | }); 17 | document.body.appendChild(div); 18 | scrollbarWidth = (div.offsetWidth - div.clientWidth); 19 | document.body.removeChild(div); 20 | } else { 21 | scrollbarWidth = 0; 22 | } 23 | return scrollbarWidth || 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/isString.js: -------------------------------------------------------------------------------- 1 | export default function isString(maybe) { 2 | return typeof maybe === 'string'; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/returnFalse.js: -------------------------------------------------------------------------------- 1 | export default function returnFalse() { 2 | return false; 3 | } 4 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | window.expect = expect; 3 | window.createSpy = expect.createSpy; 4 | window.spyOn = expect.spyOn; 5 | window.isSpy = expect.isSpy; 6 | 7 | const context = require.context('./test', true, /\.spec\.js$/); 8 | context.keys().forEach(context); 9 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "describe": true, 4 | "it": true, 5 | "expect": true, 6 | "before": true, 7 | "beforeEach": true, 8 | "after": true, 9 | "afterEach": true, 10 | "createSpy": true, 11 | "spyOn": true, 12 | "isSpy": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/Scrollbars/autoHeight.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode, findDOMNode } from 'react-dom'; 3 | import React, { Component } from 'react'; 4 | 5 | export default function createTests(scrollbarWidth, envScrollbarWidth) { 6 | describe('autoHeight', () => { 7 | let node; 8 | beforeEach(() => { 9 | node = document.createElement('div'); 10 | document.body.appendChild(node); 11 | }); 12 | afterEach(() => { 13 | unmountComponentAtNode(node); 14 | document.body.removeChild(node); 15 | }); 16 | 17 | describe('when rendered', () => { 18 | it('should have min-height and max-height', done => { 19 | render(( 20 | 24 |
25 | 26 | ), node, function callback() { 27 | const scrollbars = findDOMNode(this); 28 | expect(scrollbars.style.position).toEqual('relative'); 29 | expect(scrollbars.style.minHeight).toEqual('0px'); 30 | expect(scrollbars.style.maxHeight).toEqual('100px'); 31 | expect(this.view.style.position).toEqual('relative'); 32 | expect(this.view.style.minHeight).toEqual(`${scrollbarWidth}px`); 33 | expect(this.view.style.maxHeight).toEqual(`${100 + scrollbarWidth}px`); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | 39 | describe('when native scrollbars have a width', () => { 40 | if (!scrollbarWidth) return; 41 | it('hides native scrollbars', done => { 42 | render(( 43 | 46 |
47 | 48 | ), node, function callback() { 49 | const width = `-${scrollbarWidth}px`; 50 | expect(this.view.style.marginRight).toEqual(width); 51 | expect(this.view.style.marginBottom).toEqual(width); 52 | done(); 53 | }); 54 | }); 55 | }); 56 | 57 | describe('when native scrollbars have no width', () => { 58 | if (scrollbarWidth) return; 59 | it('hides bars', done => { 60 | render(( 61 | 64 |
65 | 66 | ), node, function callback() { 67 | setTimeout(() => { 68 | expect(this.trackVertical.style.display).toEqual('none'); 69 | expect(this.trackHorizontal.style.display).toEqual('none'); 70 | done(); 71 | }, 100); 72 | }); 73 | }); 74 | }); 75 | 76 | describe('when content is smaller than maxHeight', () => { 77 | it('should have the content\'s height', done => { 78 | render(( 79 | 82 |
83 | 84 | ), node, function callback() { 85 | setTimeout(() => { 86 | const scrollbars = findDOMNode(this); 87 | expect(scrollbars.clientHeight).toEqual(50 + (envScrollbarWidth - scrollbarWidth)); 88 | expect(this.view.clientHeight).toEqual(50); 89 | expect(this.view.scrollHeight).toEqual(50); 90 | expect(this.thumbVertical.clientHeight).toEqual(0); 91 | done(); 92 | }, 100); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('when content is larger than maxHeight', () => { 98 | it('should show scrollbars', done => { 99 | render(( 100 | 103 |
104 | 105 | ), node, function callback() { 106 | setTimeout(() => { 107 | const scrollbars = findDOMNode(this); 108 | expect(scrollbars.clientHeight).toEqual(100); 109 | expect(this.view.clientHeight).toEqual(100 - (envScrollbarWidth - scrollbarWidth)); 110 | expect(this.view.scrollHeight).toEqual(200); 111 | if (scrollbarWidth) { 112 | // 100 / 200 * 96 = 48 113 | expect(this.thumbVertical.clientHeight).toEqual(48); 114 | } 115 | done(); 116 | }, 100); 117 | }); 118 | }); 119 | }); 120 | 121 | describe('when minHeight is greater than 0', () => { 122 | it('should have height greater than 0', done => { 123 | render(( 124 | 128 |
129 | 130 | ), node, function callback() { 131 | setTimeout(() => { 132 | const scrollbars = findDOMNode(this); 133 | expect(scrollbars.clientHeight).toEqual(100); 134 | expect(this.view.clientHeight).toEqual(100 - (envScrollbarWidth - scrollbarWidth)); 135 | expect(this.thumbVertical.clientHeight).toEqual(0); 136 | done(); 137 | }, 100); 138 | }); 139 | }); 140 | }); 141 | 142 | describe('when using perecentages', () => { 143 | it('should use calc', done => { 144 | class Root extends Component { 145 | render() { 146 | return ( 147 |
148 | { this.scrollbars = ref; }} 150 | autoHeight 151 | autoHeightMin="50%" 152 | autoHeightMax="100%"> 153 |
154 | 155 |
156 | ); 157 | } 158 | } 159 | render(, node, function callback() { 160 | setTimeout(() => { 161 | const $scrollbars = findDOMNode(this.scrollbars); 162 | const view = this.scrollbars.view; 163 | expect($scrollbars.clientWidth).toEqual(500); 164 | expect($scrollbars.clientHeight).toEqual(250); 165 | expect($scrollbars.style.position).toEqual('relative'); 166 | expect($scrollbars.style.minHeight).toEqual('50%'); 167 | expect($scrollbars.style.maxHeight).toEqual('100%'); 168 | expect(view.style.position).toEqual('relative'); 169 | expect(view.style.minHeight).toEqual(`calc(50% + ${scrollbarWidth}px)`); 170 | expect(view.style.maxHeight).toEqual(`calc(100% + ${scrollbarWidth}px)`); 171 | done(); 172 | }, 100); 173 | }); 174 | }); 175 | }); 176 | 177 | describe('when using other units', () => { 178 | it('should use calc', done => { 179 | render(( 180 | 184 |
185 | 186 | ), node, function callback() { 187 | const scrollbars = findDOMNode(this); 188 | expect(scrollbars.style.position).toEqual('relative'); 189 | expect(scrollbars.style.minHeight).toEqual('10em'); 190 | expect(scrollbars.style.maxHeight).toEqual('100em'); 191 | expect(this.view.style.position).toEqual('relative'); 192 | expect(this.view.style.minHeight).toEqual(`calc(10em + ${scrollbarWidth}px)`); 193 | expect(this.view.style.maxHeight).toEqual(`calc(100em + ${scrollbarWidth}px)`); 194 | done(); 195 | }); 196 | }); 197 | }); 198 | }); 199 | } 200 | -------------------------------------------------------------------------------- /test/Scrollbars/autoHide.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | import simulant from 'simulant'; 5 | 6 | export default function createTests(scrollbarWidth) { 7 | // Not for mobile environment 8 | if (!scrollbarWidth) return; 9 | 10 | let node; 11 | beforeEach(() => { 12 | node = document.createElement('div'); 13 | document.body.appendChild(node); 14 | }); 15 | afterEach(() => { 16 | unmountComponentAtNode(node); 17 | document.body.removeChild(node); 18 | }); 19 | 20 | describe('autoHide', () => { 21 | describe('when Scrollbars are rendered', () => { 22 | it('should hide tracks', done => { 23 | render(( 24 | 25 |
26 | 27 | ), node, function callback() { 28 | expect(this.trackHorizontal.style.opacity).toEqual('0'); 29 | expect(this.trackVertical.style.opacity).toEqual('0'); 30 | done(); 31 | }); 32 | }); 33 | }); 34 | describe('enter/leave track', () => { 35 | describe('when entering horizontal track', () => { 36 | it('should show tracks', done => { 37 | render(( 38 | 39 |
40 | 41 | ), node, function callback() { 42 | const { trackHorizontal: track } = this; 43 | simulant.fire(track, 'mouseenter'); 44 | expect(track.style.opacity).toEqual('1'); 45 | done(); 46 | }); 47 | }); 48 | it('should not hide tracks', done => { 49 | render(( 50 | 54 |
55 | 56 | ), node, function callback() { 57 | const { trackHorizontal: track } = this; 58 | simulant.fire(track, 'mouseenter'); 59 | setTimeout(() => this.hideTracks(), 10); 60 | setTimeout(() => { 61 | expect(track.style.opacity).toEqual('1'); 62 | }, 100); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | describe('when leaving horizontal track', () => { 68 | it('should hide tracks', done => { 69 | render(( 70 | 75 |
76 | 77 | ), node, function callback() { 78 | const { trackHorizontal: track } = this; 79 | simulant.fire(track, 'mouseenter'); 80 | simulant.fire(track, 'mouseleave'); 81 | setTimeout(() => { 82 | expect(track.style.opacity).toEqual('0'); 83 | done(); 84 | }, 100); 85 | }); 86 | }); 87 | }); 88 | describe('when entering vertical track', () => { 89 | it('should show tracks', done => { 90 | render(( 91 | 92 |
93 | 94 | ), node, function callback() { 95 | const { trackVertical: track } = this; 96 | simulant.fire(track, 'mouseenter'); 97 | expect(track.style.opacity).toEqual('1'); 98 | done(); 99 | }); 100 | }); 101 | it('should not hide tracks', done => { 102 | render(( 103 | 107 |
108 | 109 | ), node, function callback() { 110 | const { trackVertical: track } = this; 111 | simulant.fire(track, 'mouseenter'); 112 | setTimeout(() => this.hideTracks(), 10); 113 | setTimeout(() => { 114 | expect(track.style.opacity).toEqual('1'); 115 | }, 100); 116 | done(); 117 | }); 118 | }); 119 | }); 120 | describe('when leaving vertical track', () => { 121 | it('should hide tracks', done => { 122 | render(( 123 | 128 |
129 | 130 | ), node, function callback() { 131 | const { trackVertical: track } = this; 132 | simulant.fire(track, 'mouseenter'); 133 | simulant.fire(track, 'mouseleave'); 134 | setTimeout(() => { 135 | expect(track.style.opacity).toEqual('0'); 136 | done(); 137 | }, 100); 138 | }); 139 | }); 140 | }); 141 | }); 142 | describe('when scrolling', () => { 143 | it('should show tracks', done => { 144 | render(( 145 | 146 |
147 | 148 | ), node, function callback() { 149 | this.scrollTop(50); 150 | setTimeout(() => { 151 | const { trackHorizontal, trackVertical } = this; 152 | expect(trackHorizontal.style.opacity).toEqual('1'); 153 | expect(trackVertical.style.opacity).toEqual('1'); 154 | done(); 155 | }, 100); 156 | }); 157 | }); 158 | it('should hide tracks after scrolling', done => { 159 | render(( 160 | 165 |
166 | 167 | ), node, function callback() { 168 | this.scrollTop(50); 169 | setTimeout(() => { 170 | const { trackHorizontal, trackVertical } = this; 171 | expect(trackHorizontal.style.opacity).toEqual('0'); 172 | expect(trackVertical.style.opacity).toEqual('0'); 173 | done(); 174 | }, 300); 175 | }); 176 | }); 177 | it('should not hide tracks', done => { 178 | render(( 179 | 183 |
184 | 185 | ), node, function callback() { 186 | this.scrollTop(50); 187 | setTimeout(() => this.hideTracks()); 188 | setTimeout(() => { 189 | const { trackHorizontal, trackVertical } = this; 190 | expect(trackHorizontal.style.opacity).toEqual('1'); 191 | expect(trackVertical.style.opacity).toEqual('1'); 192 | done(); 193 | }, 50); 194 | }); 195 | }); 196 | }); 197 | describe('when dragging x-axis', () => { 198 | it('should show tracks', done => { 199 | render(( 200 | 205 |
206 | 207 | ), node, function callback() { 208 | const { thumbHorizontal: thumb, trackHorizontal: track } = this; 209 | const { left } = thumb.getBoundingClientRect(); 210 | simulant.fire(thumb, 'mousedown', { 211 | target: thumb, 212 | clientX: left + 1 213 | }); 214 | simulant.fire(document, 'mousemove', { 215 | clientX: left + 100 216 | }); 217 | setTimeout(() => { 218 | expect(track.style.opacity).toEqual('1'); 219 | done(); 220 | }, 100); 221 | }); 222 | }); 223 | 224 | it('should hide tracks on end', done => { 225 | render(( 226 | 231 |
232 | 233 | ), node, function callback() { 234 | const { thumbHorizontal: thumb, trackHorizontal: track } = this; 235 | const { left } = thumb.getBoundingClientRect(); 236 | simulant.fire(thumb, 'mousedown', { 237 | target: thumb, 238 | clientX: left + 1 239 | }); 240 | simulant.fire(document, 'mouseup'); 241 | setTimeout(() => { 242 | expect(track.style.opacity).toEqual('0'); 243 | done(); 244 | }, 100); 245 | }); 246 | }); 247 | 248 | describe('and leaving track', () => { 249 | it('should not hide tracks', done => { 250 | render(( 251 | 256 |
257 | 258 | ), node, function callback() { 259 | setTimeout(() => { 260 | const { thumbHorizontal: thumb, trackHorizontal: track } = this; 261 | const { left } = thumb.getBoundingClientRect(); 262 | simulant.fire(thumb, 'mousedown', { 263 | target: thumb, 264 | clientX: left + 1 265 | }); 266 | simulant.fire(document, 'mousemove', { 267 | clientX: left + 100 268 | }); 269 | simulant.fire(track, 'mouseleave'); 270 | setTimeout(() => { 271 | expect(track.style.opacity).toEqual('1'); 272 | done(); 273 | }, 200); 274 | }, 100); 275 | }); 276 | }); 277 | }); 278 | }); 279 | describe('when dragging y-axis', () => { 280 | it('should show tracks', done => { 281 | render(( 282 | 287 |
288 | 289 | ), node, function callback() { 290 | const { thumbVertical: thumb, trackVertical: track } = this; 291 | const { top } = thumb.getBoundingClientRect(); 292 | simulant.fire(thumb, 'mousedown', { 293 | target: thumb, 294 | clientY: top + 1 295 | }); 296 | simulant.fire(document, 'mousemove', { 297 | clientY: top + 100 298 | }); 299 | setTimeout(() => { 300 | expect(track.style.opacity).toEqual('1'); 301 | done(); 302 | }, 100); 303 | }); 304 | }); 305 | it('should hide tracks on end', done => { 306 | render(( 307 | 312 |
313 | 314 | ), node, function callback() { 315 | const { thumbVertical: thumb, trackVertical: track } = this; 316 | const { top } = thumb.getBoundingClientRect(); 317 | simulant.fire(thumb, 'mousedown', { 318 | target: thumb, 319 | clientY: top + 1 320 | }); 321 | simulant.fire(document, 'mouseup'); 322 | setTimeout(() => { 323 | expect(track.style.opacity).toEqual('0'); 324 | done(); 325 | }, 100); 326 | }); 327 | }); 328 | describe('and leaving track', () => { 329 | it('should not hide tracks', done => { 330 | render(( 331 | 336 |
337 | 338 | ), node, function callback() { 339 | setTimeout(() => { 340 | const { thumbVertical: thumb, trackVertical: track } = this; 341 | const { top } = thumb.getBoundingClientRect(); 342 | simulant.fire(thumb, 'mousedown', { 343 | target: thumb, 344 | clientY: top + 1 345 | }); 346 | simulant.fire(document, 'mousemove', { 347 | clientY: top + 100 348 | }); 349 | simulant.fire(track, 'mouseleave'); 350 | setTimeout(() => { 351 | expect(track.style.opacity).toEqual('1'); 352 | done(); 353 | }, 200); 354 | }, 100); 355 | }); 356 | }); 357 | }); 358 | }); 359 | }); 360 | 361 | describe('when autoHide is disabed', () => { 362 | describe('enter/leave track', () => { 363 | describe('when entering horizontal track', () => { 364 | it('should not call `showTracks`', done => { 365 | render(( 366 | 367 |
368 | 369 | ), node, function callback() { 370 | const spy = spyOn(this, 'showTracks'); 371 | const { trackHorizontal: track } = this; 372 | simulant.fire(track, 'mouseenter'); 373 | expect(spy.calls.length).toEqual(0); 374 | done(); 375 | }); 376 | }); 377 | }); 378 | describe('when leaving horizontal track', () => { 379 | it('should not call `hideTracks`', done => { 380 | render(( 381 | 382 |
383 | 384 | ), node, function callback() { 385 | const spy = spyOn(this, 'hideTracks'); 386 | const { trackHorizontal: track } = this; 387 | simulant.fire(track, 'mouseenter'); 388 | simulant.fire(track, 'mouseleave'); 389 | setTimeout(() => { 390 | expect(spy.calls.length).toEqual(0); 391 | done(); 392 | }, 100); 393 | }); 394 | }); 395 | }); 396 | describe('when entering vertical track', () => { 397 | it('should not call `showTracks`', done => { 398 | render(( 399 | 400 |
401 | 402 | ), node, function callback() { 403 | const spy = spyOn(this, 'showTracks'); 404 | const { trackVertical: track } = this; 405 | simulant.fire(track, 'mouseenter'); 406 | expect(spy.calls.length).toEqual(0); 407 | done(); 408 | }); 409 | }); 410 | }); 411 | describe('when leaving vertical track', () => { 412 | it('should not call `hideTracks`', done => { 413 | render(( 414 | 415 |
416 | 417 | ), node, function callback() { 418 | const spy = spyOn(this, 'hideTracks'); 419 | const { trackVertical: track } = this; 420 | simulant.fire(track, 'mouseenter'); 421 | simulant.fire(track, 'mouseleave'); 422 | setTimeout(() => { 423 | expect(spy.calls.length).toEqual(0); 424 | done(); 425 | }, 100); 426 | }); 427 | }); 428 | }); 429 | }); 430 | }); 431 | } 432 | -------------------------------------------------------------------------------- /test/Scrollbars/clickTrack.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | import simulant from 'simulant'; 5 | 6 | export default function createTests(scrollbarWidth) { 7 | // Not for mobile environment 8 | if (!scrollbarWidth) return; 9 | 10 | let node; 11 | beforeEach(() => { 12 | node = document.createElement('div'); 13 | document.body.appendChild(node); 14 | }); 15 | afterEach(() => { 16 | unmountComponentAtNode(node); 17 | document.body.removeChild(node); 18 | }); 19 | 20 | describe('when clicking on horizontal track', () => { 21 | it('should scroll to the respective position', done => { 22 | render(( 23 | 24 |
25 | 26 | ), node, function callback() { 27 | setTimeout(() => { 28 | const { view, trackHorizontal: bar } = this; 29 | const { left, width } = bar.getBoundingClientRect(); 30 | simulant.fire(bar, 'mousedown', { 31 | target: bar, 32 | clientX: left + (width / 2) 33 | }); 34 | expect(view.scrollLeft).toEqual(50); 35 | done(); 36 | }, 100); 37 | }); 38 | }); 39 | }); 40 | 41 | describe('when clicking on vertical track', () => { 42 | it('should scroll to the respective position', done => { 43 | render(( 44 | 45 |
46 | 47 | ), node, function callback() { 48 | setTimeout(() => { 49 | const { view, trackVertical: bar } = this; 50 | const { top, height } = bar.getBoundingClientRect(); 51 | simulant.fire(bar, 'mousedown', { 52 | target: bar, 53 | clientY: top + (height / 2) 54 | }); 55 | expect(view.scrollTop).toEqual(50); 56 | done(); 57 | }, 100); 58 | }); 59 | }); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /test/Scrollbars/dragThumb.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | import simulant from 'simulant'; 5 | 6 | export default function createTests(scrollbarWidth) { 7 | // Not for mobile environment 8 | if (!scrollbarWidth) return; 9 | 10 | let node; 11 | beforeEach(() => { 12 | node = document.createElement('div'); 13 | document.body.appendChild(node); 14 | }); 15 | afterEach(() => { 16 | unmountComponentAtNode(node); 17 | document.body.removeChild(node); 18 | }); 19 | describe('when dragging horizontal thumb', () => { 20 | it('should scroll to the respective position', done => { 21 | render(( 22 | 23 |
24 | 25 | ), node, function callback() { 26 | setTimeout(() => { 27 | const { view, thumbHorizontal: thumb } = this; 28 | const { left } = thumb.getBoundingClientRect(); 29 | simulant.fire(thumb, 'mousedown', { 30 | target: thumb, 31 | clientX: left + 1 32 | }); 33 | simulant.fire(document, 'mousemove', { 34 | clientX: left + 100 35 | }); 36 | simulant.fire(document, 'mouseup'); 37 | expect(view.scrollLeft).toEqual(100); 38 | done(); 39 | }, 100); 40 | }); 41 | }); 42 | 43 | it('should disable selection', done => { 44 | render(( 45 | 46 |
47 | 48 | ), node, function callback() { 49 | setTimeout(() => { 50 | const { thumbHorizontal: thumb } = this; 51 | const { left } = thumb.getBoundingClientRect(); 52 | simulant.fire(thumb, 'mousedown', { 53 | target: thumb, 54 | clientX: left + 1 55 | }); 56 | expect(document.body.style.webkitUserSelect).toEqual('none'); 57 | simulant.fire(document, 'mouseup'); 58 | expect(document.body.style.webkitUserSelect).toEqual(''); 59 | done(); 60 | }, 100); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('when dragging vertical thumb', () => { 66 | it('should scroll to the respective position', done => { 67 | render(( 68 | 69 |
70 | 71 | ), node, function callback() { 72 | setTimeout(() => { 73 | const { view, thumbVertical: thumb } = this; 74 | const { top } = thumb.getBoundingClientRect(); 75 | simulant.fire(thumb, 'mousedown', { 76 | target: thumb, 77 | clientY: top + 1 78 | }); 79 | simulant.fire(document, 'mousemove', { 80 | clientY: top + 100 81 | }); 82 | simulant.fire(document, 'mouseup'); 83 | expect(view.scrollTop).toEqual(100); 84 | done(); 85 | }, 100); 86 | }); 87 | }); 88 | 89 | it('should disable selection', done => { 90 | render(( 91 | 92 |
93 | 94 | ), node, function callback() { 95 | setTimeout(() => { 96 | const { thumbVertical: thumb } = this; 97 | const { top } = thumb.getBoundingClientRect(); 98 | simulant.fire(thumb, 'mousedown', { 99 | target: thumb, 100 | clientY: top + 1 101 | }); 102 | expect(document.body.style.webkitUserSelect).toEqual('none'); 103 | simulant.fire(document, 'mouseup'); 104 | expect(document.body.style.webkitUserSelect).toEqual(''); 105 | done(); 106 | }, 100); 107 | }); 108 | }); 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /test/Scrollbars/flexbox.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode, findDOMNode } from 'react-dom'; 3 | import React, { Component } from 'react'; 4 | 5 | export default function createTests() { 6 | let node; 7 | beforeEach(() => { 8 | node = document.createElement('div'); 9 | document.body.appendChild(node); 10 | }); 11 | afterEach(() => { 12 | unmountComponentAtNode(node); 13 | document.body.removeChild(node); 14 | }); 15 | describe('when scrollbars are in flexbox environment', () => { 16 | it('should still work', done => { 17 | class Root extends Component { 18 | render() { 19 | return ( 20 |
21 | { this.scrollbars = ref; }}> 22 |
23 | 24 |
25 | ); 26 | } 27 | } 28 | render(, node, function callback() { 29 | setTimeout(() => { 30 | const { scrollbars } = this; 31 | const $scrollbars = findDOMNode(scrollbars); 32 | const $view = scrollbars.view; 33 | expect($scrollbars.clientHeight).toBeGreaterThan(0); 34 | expect($view.clientHeight).toBeGreaterThan(0); 35 | done(); 36 | }, 100); 37 | }); 38 | }); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/Scrollbars/gettersSetters.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | 5 | export default function createTests(scrollbarWidth, envScrollbarWidth) { 6 | let node; 7 | beforeEach(() => { 8 | node = document.createElement('div'); 9 | document.body.appendChild(node); 10 | }); 11 | afterEach(() => { 12 | unmountComponentAtNode(node); 13 | document.body.removeChild(node); 14 | }); 15 | 16 | describe('getters', () => { 17 | function renderScrollbars(callback) { 18 | render(( 19 | 20 |
21 | 22 | ), node, callback); 23 | } 24 | describe('getScrollLeft', () => { 25 | it('should return scrollLeft', done => { 26 | renderScrollbars(function callback() { 27 | this.scrollLeft(50); 28 | expect(this.getScrollLeft()).toEqual(50); 29 | done(); 30 | }); 31 | }); 32 | }); 33 | describe('getScrollTop', () => { 34 | it('should return scrollTop', done => { 35 | renderScrollbars(function callback() { 36 | this.scrollTop(50); 37 | expect(this.getScrollTop()).toEqual(50); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | describe('getScrollWidth', () => { 43 | it('should return scrollWidth', done => { 44 | renderScrollbars(function callback() { 45 | expect(this.getScrollWidth()).toEqual(200); 46 | done(); 47 | }); 48 | }); 49 | }); 50 | describe('getScrollHeight', () => { 51 | it('should return scrollHeight', done => { 52 | renderScrollbars(function callback() { 53 | expect(this.getScrollHeight()).toEqual(200); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | describe('getClientWidth', () => { 59 | it('should return scrollWidth', done => { 60 | renderScrollbars(function callback() { 61 | expect(this.getClientWidth()).toEqual(100 + (scrollbarWidth - envScrollbarWidth)); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | describe('getClientHeight', () => { 67 | it('should return scrollHeight', done => { 68 | renderScrollbars(function callback() { 69 | expect(this.getClientHeight()).toEqual(100 + (scrollbarWidth - envScrollbarWidth)); 70 | done(); 71 | }); 72 | }); 73 | }); 74 | }); 75 | 76 | describe('setters', () => { 77 | function renderScrollbars(callback) { 78 | render(( 79 | 80 |
81 | 82 | ), node, callback); 83 | } 84 | describe('scrollLeft/scrollToLeft', () => { 85 | it('should scroll to given left value', done => { 86 | renderScrollbars(function callback() { 87 | this.scrollLeft(50); 88 | expect(this.getScrollLeft()).toEqual(50); 89 | this.scrollToLeft(); 90 | expect(this.getScrollLeft()).toEqual(0); 91 | this.scrollLeft(50); 92 | this.scrollLeft(); 93 | expect(this.getScrollLeft()).toEqual(0); 94 | done(); 95 | }); 96 | }); 97 | }); 98 | describe('scrollTop/scrollToTop', () => { 99 | it('should scroll to given top value', done => { 100 | renderScrollbars(function callback() { 101 | this.scrollTop(50); 102 | expect(this.getScrollTop()).toEqual(50); 103 | this.scrollToTop(); 104 | expect(this.getScrollTop()).toEqual(0); 105 | this.scrollTop(50); 106 | this.scrollTop(); 107 | expect(this.getScrollTop()).toEqual(0); 108 | done(); 109 | }); 110 | }); 111 | }); 112 | describe('scrollToRight', () => { 113 | it('should scroll to right', done => { 114 | renderScrollbars(function callback() { 115 | this.scrollToRight(); 116 | expect(this.getScrollLeft()).toEqual(100 + (envScrollbarWidth - scrollbarWidth)); 117 | done(); 118 | }); 119 | }); 120 | }); 121 | describe('scrollToBottom', () => { 122 | it('should scroll to bottom', done => { 123 | renderScrollbars(function callback() { 124 | this.scrollToBottom(); 125 | expect(this.getScrollTop()).toEqual(100 + (envScrollbarWidth - scrollbarWidth)); 126 | done(); 127 | }); 128 | }); 129 | }); 130 | }); 131 | } 132 | -------------------------------------------------------------------------------- /test/Scrollbars/hideTracks.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | 5 | export default function createTests(scrollbarWidth) { 6 | describe('hide tracks', () => { 7 | let node; 8 | beforeEach(() => { 9 | node = document.createElement('div'); 10 | document.body.appendChild(node); 11 | }); 12 | afterEach(() => { 13 | unmountComponentAtNode(node); 14 | document.body.removeChild(node); 15 | }); 16 | 17 | describe('when native scrollbars have a width', () => { 18 | if (!scrollbarWidth) return; 19 | describe('when content is greater than wrapper', () => { 20 | it('should show tracks', done => { 21 | render(( 22 | 25 |
26 | 27 | ), node, function callback() { 28 | setTimeout(() => { 29 | const { trackHorizontal, trackVertical } = this; 30 | expect(trackHorizontal.style.visibility).toEqual('visible'); 31 | expect(trackVertical.style.visibility).toEqual('visible'); 32 | done(); 33 | }, 100); 34 | }); 35 | }); 36 | }); 37 | describe('when content is smaller than wrapper', () => { 38 | it('should hide tracks', done => { 39 | render(( 40 | 43 |
44 | 45 | ), node, function callback() { 46 | setTimeout(() => { 47 | const { trackHorizontal, trackVertical } = this; 48 | expect(trackHorizontal.style.visibility).toEqual('hidden'); 49 | expect(trackVertical.style.visibility).toEqual('hidden'); 50 | done(); 51 | }, 100); 52 | }); 53 | }); 54 | }); 55 | }); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /test/Scrollbars/index.js: -------------------------------------------------------------------------------- 1 | import rendering from './rendering'; 2 | import gettersSetters from './gettersSetters'; 3 | import scrolling from './scrolling'; 4 | import resizing from './resizing'; 5 | import clickTrack from './clickTrack'; 6 | import dragThumb from './dragThumb'; 7 | import flexbox from './flexbox'; 8 | import autoHide from './autoHide'; 9 | import autoHeight from './autoHeight'; 10 | import hideTracks from './hideTracks'; 11 | import universal from './universal'; 12 | import onUpdate from './onUpdate'; 13 | 14 | export default function createTests(scrollbarWidth, envScrollbarWidth) { 15 | rendering(scrollbarWidth, envScrollbarWidth); 16 | gettersSetters(scrollbarWidth, envScrollbarWidth); 17 | scrolling(scrollbarWidth, envScrollbarWidth); 18 | resizing(scrollbarWidth, envScrollbarWidth); 19 | clickTrack(scrollbarWidth, envScrollbarWidth); 20 | dragThumb(scrollbarWidth, envScrollbarWidth); 21 | flexbox(scrollbarWidth, envScrollbarWidth); 22 | autoHide(scrollbarWidth, envScrollbarWidth); 23 | autoHeight(scrollbarWidth, envScrollbarWidth); 24 | hideTracks(scrollbarWidth, envScrollbarWidth); 25 | universal(scrollbarWidth, envScrollbarWidth); 26 | onUpdate(scrollbarWidth, envScrollbarWidth); 27 | } 28 | -------------------------------------------------------------------------------- /test/Scrollbars/onUpdate.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | 5 | export default function createTests() { 6 | let node; 7 | beforeEach(() => { 8 | node = document.createElement('div'); 9 | document.body.appendChild(node); 10 | }); 11 | afterEach(() => { 12 | unmountComponentAtNode(node); 13 | document.body.removeChild(node); 14 | }); 15 | 16 | describe('onUpdate', () => { 17 | describe('when scrolling x-axis', () => { 18 | it('should call `onUpdate`', done => { 19 | const spy = createSpy(); 20 | render(( 21 | 22 |
23 | 24 | ), node, function callback() { 25 | this.scrollLeft(50); 26 | setTimeout(() => { 27 | expect(spy.calls.length).toEqual(1); 28 | done(); 29 | }, 100); 30 | }); 31 | }); 32 | }); 33 | describe('when scrolling y-axis', () => { 34 | it('should call `onUpdate`', done => { 35 | const spy = createSpy(); 36 | render(( 37 | 38 |
39 | 40 | ), node, function callback() { 41 | this.scrollTop(50); 42 | setTimeout(() => { 43 | expect(spy.calls.length).toEqual(1); 44 | done(); 45 | }, 100); 46 | }); 47 | }); 48 | }); 49 | 50 | describe('when resizing window', () => { 51 | it('should call onUpdate', done => { 52 | const spy = createSpy(); 53 | render(( 54 | 55 |
56 | 57 | ), node, function callback() { 58 | setTimeout(() => { 59 | expect(spy.calls.length).toEqual(1); 60 | done(); 61 | }, 100); 62 | }); 63 | }); 64 | }); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /test/Scrollbars/rendering.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode, findDOMNode } from 'react-dom'; 3 | import React from 'react'; 4 | 5 | export default function createTests(scrollbarWidth) { 6 | describe('rendering', () => { 7 | let node; 8 | beforeEach(() => { 9 | node = document.createElement('div'); 10 | document.body.appendChild(node); 11 | }); 12 | afterEach(() => { 13 | unmountComponentAtNode(node); 14 | document.body.removeChild(node); 15 | }); 16 | 17 | describe('when Scrollbars are rendered', () => { 18 | it('takes className', done => { 19 | render(( 20 | 21 |
22 | 23 | ), node, function callback() { 24 | expect(findDOMNode(this).className).toEqual('foo'); 25 | done(); 26 | }); 27 | }); 28 | 29 | it('takes styles', done => { 30 | render(( 31 | 32 |
33 | 34 | ), node, function callback() { 35 | expect(findDOMNode(this).style.width).toEqual('100px'); 36 | expect(findDOMNode(this).style.height).toEqual('100px'); 37 | expect(findDOMNode(this).style.overflow).toEqual('hidden'); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('renders view', done => { 43 | render(( 44 | 45 |
46 | 47 | ), node, function callback() { 48 | expect(this.view).toBeA(Node); 49 | done(); 50 | }); 51 | }); 52 | 53 | describe('when using custom tagName', () => { 54 | it('should use the defined tagName', done => { 55 | render(( 56 | 59 |
60 | 61 | ), node, function callback() { 62 | const el = findDOMNode(this); 63 | expect(el.tagName.toLowerCase()).toEqual('nav'); 64 | done(); 65 | }); 66 | }); 67 | }); 68 | 69 | describe('when custom `renderView` is passed', () => { 70 | it('should render custom element', done => { 71 | render(( 72 |
}> 75 |
76 | 77 | ), node, function callback() { 78 | expect(this.view.tagName).toEqual('SECTION'); 79 | expect(this.view.style.color).toEqual('red'); 80 | expect(this.view.style.position).toEqual('absolute'); 81 | done(); 82 | }); 83 | }); 84 | }); 85 | 86 | describe('when native scrollbars have a width', () => { 87 | if (!scrollbarWidth) return; 88 | 89 | it('hides native scrollbars', done => { 90 | render(( 91 | 92 |
93 | 94 | ), node, function callback() { 95 | setTimeout(() => { 96 | const width = `-${scrollbarWidth}px`; 97 | expect(this.view.style.marginRight).toEqual(width); 98 | expect(this.view.style.marginBottom).toEqual(width); 99 | done(); 100 | }, 100); 101 | }); 102 | }); 103 | 104 | it('renders bars', done => { 105 | render(( 106 | 107 |
108 | 109 | ), node, function callback() { 110 | expect(this.trackHorizontal).toBeA(Node); 111 | expect(this.trackVertical).toBeA(Node); 112 | done(); 113 | }); 114 | }); 115 | 116 | it('renders thumbs', done => { 117 | render(( 118 | 119 |
120 | 121 | ), node, function callback() { 122 | expect(this.thumbHorizontal).toBeA(Node); 123 | expect(this.thumbVertical).toBeA(Node); 124 | done(); 125 | }); 126 | }); 127 | 128 | it('renders thumbs with correct size', done => { 129 | render(( 130 | 131 |
132 | 133 | ), node, function callback() { 134 | setTimeout(() => { 135 | // 100 / 200 * 96 = 48 136 | expect(this.thumbVertical.style.height).toEqual('48px'); 137 | expect(this.thumbHorizontal.style.width).toEqual('48px'); 138 | done(); 139 | }, 100); 140 | }); 141 | }); 142 | 143 | it('the thumbs size should not be less than the given `thumbMinSize`', done => { 144 | render(( 145 | 146 |
147 | 148 | ), node, function callback() { 149 | setTimeout(() => { 150 | // 100 / 200 * 96 = 48 151 | expect(this.thumbVertical.style.height).toEqual('30px'); 152 | expect(this.thumbHorizontal.style.width).toEqual('30px'); 153 | done(); 154 | }, 100); 155 | }); 156 | }); 157 | 158 | describe('when thumbs have a fixed size', () => { 159 | it('thumbs should have the given fixed size', done => { 160 | render(( 161 | 162 |
163 | 164 | ), node, function callback() { 165 | setTimeout(() => { 166 | // 100 / 200 * 96 = 48 167 | expect(this.thumbVertical.style.height).toEqual('50px'); 168 | expect(this.thumbHorizontal.style.width).toEqual('50px'); 169 | done(); 170 | }, 100); 171 | }); 172 | }); 173 | }); 174 | 175 | describe('when custom `renderTrackHorizontal` is passed', () => { 176 | it('should render custom element', done => { 177 | render(( 178 |
}> 181 |
182 | 183 | ), node, function callback() { 184 | expect(this.trackHorizontal.tagName).toEqual('SECTION'); 185 | expect(this.trackHorizontal.style.position).toEqual('absolute'); 186 | expect(this.trackHorizontal.style.color).toEqual('red'); 187 | done(); 188 | }); 189 | }); 190 | }); 191 | 192 | describe('when custom `renderTrackVertical` is passed', () => { 193 | it('should render custom element', done => { 194 | render(( 195 |
}> 198 |
199 | 200 | ), node, function callback() { 201 | expect(this.trackVertical.tagName).toEqual('SECTION'); 202 | expect(this.trackVertical.style.position).toEqual('absolute'); 203 | expect(this.trackVertical.style.color).toEqual('red'); 204 | done(); 205 | }); 206 | }); 207 | }); 208 | 209 | describe('when custom `renderThumbHorizontal` is passed', () => { 210 | it('should render custom element', done => { 211 | render(( 212 |
}> 215 |
216 | 217 | ), node, function callback() { 218 | expect(this.thumbHorizontal.tagName).toEqual('SECTION'); 219 | expect(this.thumbHorizontal.style.position).toEqual('relative'); 220 | expect(this.thumbHorizontal.style.color).toEqual('red'); 221 | done(); 222 | }); 223 | }); 224 | }); 225 | 226 | describe('when custom `renderThumbVertical` is passed', () => { 227 | it('should render custom element', done => { 228 | render(( 229 |
}> 232 |
233 | 234 | ), node, function callback() { 235 | expect(this.thumbVertical.tagName).toEqual('SECTION'); 236 | expect(this.thumbVertical.style.position).toEqual('relative'); 237 | expect(this.thumbVertical.style.color).toEqual('red'); 238 | done(); 239 | }); 240 | }); 241 | }); 242 | 243 | it('positions view absolute', done => { 244 | render(( 245 | 246 |
247 | 248 | ), node, function callback() { 249 | expect(this.view.style.position).toEqual('absolute'); 250 | expect(this.view.style.top).toEqual('0px'); 251 | expect(this.view.style.left).toEqual('0px'); 252 | done(); 253 | }); 254 | }); 255 | 256 | it('should not override the scrollbars width/height values', done => { 257 | render(( 258 | 261 |
} 262 | renderTrackVertical={({ style, ...props }) => 263 |
}> 264 |
265 | 266 | ), node, function callback() { 267 | setTimeout(() => { 268 | expect(this.trackHorizontal.style.height).toEqual('10px'); 269 | expect(this.trackVertical.style.width).toEqual('10px'); 270 | done(); 271 | }, 100); 272 | }); 273 | }); 274 | 275 | describe('when view does not overflow container', () => { 276 | it('should hide scrollbars', done => { 277 | render(( 278 | 281 |
} 282 | renderTrackVertical={({ style, ...props }) => 283 |
}> 284 |
285 | 286 | ), node, function callback() { 287 | setTimeout(() => { 288 | expect(this.thumbHorizontal.style.width).toEqual('0px'); 289 | expect(this.thumbVertical.style.height).toEqual('0px'); 290 | done(); 291 | }, 100); 292 | }); 293 | }); 294 | }); 295 | }); 296 | 297 | describe('when native scrollbars have no width', () => { 298 | if (scrollbarWidth) return; 299 | 300 | it('hides bars', done => { 301 | render(( 302 | 303 |
304 | 305 | ), node, function callback() { 306 | setTimeout(() => { 307 | expect(this.trackVertical.style.display).toEqual('none'); 308 | expect(this.trackHorizontal.style.display).toEqual('none'); 309 | done(); 310 | }, 100); 311 | }); 312 | }); 313 | }); 314 | }); 315 | 316 | describe('when rerendering Scrollbars', () => { 317 | function renderScrollbars(callback) { 318 | render(( 319 | 320 |
321 | 322 | ), node, callback); 323 | } 324 | it('should update scrollbars', done => { 325 | renderScrollbars(function callback() { 326 | const spy = spyOn(this, 'update').andCallThrough(); 327 | renderScrollbars(function rerenderCallback() { 328 | expect(spy.calls.length).toEqual(1); 329 | spy.restore(); 330 | done(); 331 | }); 332 | }); 333 | }); 334 | }); 335 | }); 336 | } 337 | -------------------------------------------------------------------------------- /test/Scrollbars/resizing.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | import simulant from 'simulant'; 5 | 6 | export default function createTests(scrollbarWidth) { 7 | // Not for mobile environment 8 | if (!scrollbarWidth) return; 9 | 10 | let node; 11 | beforeEach(() => { 12 | node = document.createElement('div'); 13 | document.body.appendChild(node); 14 | }); 15 | afterEach(() => { 16 | unmountComponentAtNode(node); 17 | document.body.removeChild(node); 18 | }); 19 | 20 | describe('when resizing window', () => { 21 | it('should update scrollbars', done => { 22 | render(( 23 | 24 |
25 | 26 | ), node, function callback() { 27 | setTimeout(() => { 28 | const spy = spyOn(this, 'update'); 29 | simulant.fire(window, 'resize'); 30 | expect(spy.calls.length).toEqual(1); 31 | done(); 32 | }, 100); 33 | }); 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/Scrollbars/scrolling.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | 5 | export default function createTests(scrollbarWidth, envScrollbarWidth) { 6 | let node; 7 | beforeEach(() => { 8 | node = document.createElement('div'); 9 | document.body.appendChild(node); 10 | }); 11 | afterEach(() => { 12 | unmountComponentAtNode(node); 13 | document.body.removeChild(node); 14 | }); 15 | 16 | describe('when scrolling', () => { 17 | describe('when native scrollbars have a width', () => { 18 | if (!scrollbarWidth) return; 19 | it('should update thumbs position', done => { 20 | render(( 21 | 22 |
23 | 24 | ), node, function callback() { 25 | this.scrollTop(50); 26 | this.scrollLeft(50); 27 | setTimeout(() => { 28 | if (scrollbarWidth) { 29 | // 50 / (200 - 100) * (96 - 48) = 24 30 | expect(this.thumbVertical.style.transform).toEqual('translateY(24px)'); 31 | expect(this.thumbHorizontal.style.transform).toEqual('translateX(24px)'); 32 | } else { 33 | expect(this.thumbVertical.style.transform).toEqual(''); 34 | expect(this.thumbHorizontal.style.transform).toEqual(''); 35 | } 36 | done(); 37 | }, 100); 38 | }); 39 | }); 40 | }); 41 | 42 | it('should not trigger a rerender', () => { 43 | render(( 44 | 45 |
46 | 47 | ), node, function callback() { 48 | const spy = spyOn(this, 'render').andCallThrough(); 49 | this.scrollTop(50); 50 | expect(spy.calls.length).toEqual(0); 51 | spy.restore(); 52 | }); 53 | }); 54 | 55 | describe('when scrolling x-axis', () => { 56 | it('should call `onScroll`', done => { 57 | const spy = createSpy(); 58 | render(( 59 | 60 |
61 | 62 | ), node, function callback() { 63 | this.scrollLeft(50); 64 | setTimeout(() => { 65 | expect(spy.calls.length).toEqual(1); 66 | const args = spy.calls[0].arguments; 67 | const event = args[0]; 68 | expect(event).toBeA(Event); 69 | done(); 70 | }, 100); 71 | }); 72 | }); 73 | it('should call `onScrollFrame`', done => { 74 | const spy = createSpy(); 75 | render(( 76 | 77 |
78 | 79 | ), node, function callback() { 80 | this.scrollLeft(50); 81 | setTimeout(() => { 82 | expect(spy.calls.length).toEqual(1); 83 | const args = spy.calls[0].arguments; 84 | const values = args[0]; 85 | expect(values).toBeA(Object); 86 | 87 | if (scrollbarWidth) { 88 | expect(values).toEqual({ 89 | left: 0.5, 90 | top: 0, 91 | scrollLeft: 50, 92 | scrollTop: 0, 93 | scrollWidth: 200, 94 | scrollHeight: 200, 95 | clientWidth: 100, 96 | clientHeight: 100 97 | }); 98 | } else { 99 | expect(values).toEqual({ 100 | left: values.scrollLeft / (values.scrollWidth - (values.clientWidth)), 101 | top: 0, 102 | scrollLeft: 50, 103 | scrollTop: 0, 104 | scrollWidth: 200, 105 | scrollHeight: 200, 106 | clientWidth: 100 - envScrollbarWidth, 107 | clientHeight: 100 - envScrollbarWidth 108 | }); 109 | } 110 | done(); 111 | }, 100); 112 | }); 113 | }); 114 | it('should call `onScrollStart` once', done => { 115 | const spy = createSpy(); 116 | render(( 117 | 118 |
119 | 120 | ), node, function callback() { 121 | let left = 0; 122 | const interval = setInterval(() => { 123 | this.scrollLeft(++left); 124 | if (left >= 50) { 125 | clearInterval(interval); 126 | expect(spy.calls.length).toEqual(1); 127 | done(); 128 | } 129 | }, 10); 130 | }); 131 | }); 132 | it('should call `onScrollStop` once when scrolling stops', done => { 133 | const spy = createSpy(); 134 | render(( 135 | 136 |
137 | 138 | ), node, function callback() { 139 | let left = 0; 140 | const interval = setInterval(() => { 141 | this.scrollLeft(++left); 142 | if (left >= 50) { 143 | clearInterval(interval); 144 | setTimeout(() => { 145 | expect(spy.calls.length).toEqual(1); 146 | done(); 147 | }, 300); 148 | } 149 | }, 10); 150 | }); 151 | }); 152 | }); 153 | 154 | describe('when scrolling y-axis', () => { 155 | it('should call `onScroll`', done => { 156 | const spy = createSpy(); 157 | render(( 158 | 159 |
160 | 161 | ), node, function callback() { 162 | this.scrollTop(50); 163 | setTimeout(() => { 164 | expect(spy.calls.length).toEqual(1); 165 | const args = spy.calls[0].arguments; 166 | const event = args[0]; 167 | expect(event).toBeA(Event); 168 | done(); 169 | }, 100); 170 | }); 171 | }); 172 | it('should call `onScrollFrame`', done => { 173 | const spy = createSpy(); 174 | render(( 175 | 176 |
177 | 178 | ), node, function callback() { 179 | this.scrollTop(50); 180 | setTimeout(() => { 181 | expect(spy.calls.length).toEqual(1); 182 | const args = spy.calls[0].arguments; 183 | const values = args[0]; 184 | expect(values).toBeA(Object); 185 | 186 | if (scrollbarWidth) { 187 | expect(values).toEqual({ 188 | left: 0, 189 | top: 0.5, 190 | scrollLeft: 0, 191 | scrollTop: 50, 192 | scrollWidth: 200, 193 | scrollHeight: 200, 194 | clientWidth: 100, 195 | clientHeight: 100, 196 | }); 197 | } else { 198 | expect(values).toEqual({ 199 | left: 0, 200 | top: values.scrollTop / (values.scrollHeight - (values.clientHeight)), 201 | scrollLeft: 0, 202 | scrollTop: 50, 203 | scrollWidth: 200, 204 | scrollHeight: 200, 205 | clientWidth: 100 - envScrollbarWidth, 206 | clientHeight: 100 - envScrollbarWidth, 207 | }); 208 | } 209 | done(); 210 | }, 100); 211 | }); 212 | }); 213 | it('should call `onScrollStart` once', done => { 214 | const spy = createSpy(); 215 | render(( 216 | 217 |
218 | 219 | ), node, function callback() { 220 | let top = 0; 221 | const interval = setInterval(() => { 222 | this.scrollTop(++top); 223 | if (top >= 50) { 224 | clearInterval(interval); 225 | expect(spy.calls.length).toEqual(1); 226 | done(); 227 | } 228 | }, 10); 229 | }); 230 | }); 231 | it('should call `onScrollStop` once when scrolling stops', done => { 232 | const spy = createSpy(); 233 | render(( 234 | 235 |
236 | 237 | ), node, function callback() { 238 | let top = 0; 239 | const interval = setInterval(() => { 240 | this.scrollTop(++top); 241 | if (top >= 50) { 242 | clearInterval(interval); 243 | setTimeout(() => { 244 | expect(spy.calls.length).toEqual(1); 245 | done(); 246 | }, 300); 247 | } 248 | }, 10); 249 | }); 250 | }); 251 | }); 252 | }); 253 | } 254 | -------------------------------------------------------------------------------- /test/Scrollbars/universal.js: -------------------------------------------------------------------------------- 1 | import { Scrollbars } from 'react-custom-scrollbars-2'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import React from 'react'; 4 | 5 | export default function createTests(scrollbarWidth) { 6 | let node; 7 | beforeEach(() => { 8 | node = document.createElement('div'); 9 | document.body.appendChild(node); 10 | }); 11 | afterEach(() => { 12 | unmountComponentAtNode(node); 13 | document.body.removeChild(node); 14 | }); 15 | 16 | describe('universal', () => { 17 | describe('default', () => { 18 | describe('when rendered', () => { 19 | it('should hide overflow', done => { 20 | class ScrollbarsTest extends Scrollbars { 21 | // Override componentDidMount, so we can check, how the markup 22 | // looks like on the first rendering 23 | componentDidMount() {} 24 | } 25 | render(( 26 | 27 |
28 | 29 | ), node, function callback() { 30 | const { view, trackHorizontal, trackVertical } = this; 31 | expect(view.style.position).toEqual('absolute'); 32 | expect(view.style.overflow).toEqual('hidden'); 33 | expect(view.style.top).toEqual('0px'); 34 | expect(view.style.bottom).toEqual('0px'); 35 | expect(view.style.left).toEqual('0px'); 36 | expect(view.style.right).toEqual('0px'); 37 | expect(view.style.marginBottom).toEqual('0px'); 38 | expect(view.style.marginRight).toEqual('0px'); 39 | expect(trackHorizontal.style.display).toEqual('none'); 40 | expect(trackVertical.style.display).toEqual('none'); 41 | done(); 42 | }); 43 | }); 44 | }); 45 | describe('when componentDidMount', () => { 46 | it('should rerender', done => { 47 | render(( 48 | 49 |
50 | 51 | ), node, function callback() { 52 | setTimeout(() => { 53 | const { view } = this; 54 | expect(view.style.overflow).toEqual('scroll'); 55 | expect(view.style.marginBottom).toEqual(`${-scrollbarWidth}px`); 56 | expect(view.style.marginRight).toEqual(`${-scrollbarWidth}px`); 57 | done(); 58 | }, 100); 59 | }); 60 | }); 61 | }); 62 | }); 63 | describe('when using autoHeight', () => { 64 | describe('when rendered', () => { 65 | it('should hide overflow', done => { 66 | class ScrollbarsTest extends Scrollbars { 67 | // Override componentDidMount, so we can check, how the markup 68 | // looks like on the first rendering 69 | componentDidMount() {} 70 | } 71 | render(( 72 | 73 |
74 | 75 | ), node, function callback() { 76 | const { view, trackHorizontal, trackVertical } = this; 77 | expect(view.style.position).toEqual('relative'); 78 | expect(view.style.overflow).toEqual('hidden'); 79 | expect(view.style.marginBottom).toEqual('0px'); 80 | expect(view.style.marginRight).toEqual('0px'); 81 | expect(view.style.minHeight).toEqual('0px'); 82 | expect(view.style.maxHeight).toEqual('100px'); 83 | expect(trackHorizontal.style.display).toEqual('none'); 84 | expect(trackVertical.style.display).toEqual('none'); 85 | done(); 86 | }); 87 | }); 88 | }); 89 | describe('when componentDidMount', () => { 90 | it('should rerender', done => { 91 | render(( 92 | 93 |
94 | 95 | ), node, function callback() { 96 | setTimeout(() => { 97 | const { view } = this; 98 | expect(view.style.overflow).toEqual('scroll'); 99 | expect(view.style.marginBottom).toEqual(`${-scrollbarWidth}px`); 100 | expect(view.style.marginRight).toEqual(`${-scrollbarWidth}px`); 101 | expect(view.style.minHeight).toEqual(`${scrollbarWidth}px`); 102 | expect(view.style.maxHeight).toEqual(`${100 + scrollbarWidth}px`); 103 | done(); 104 | }); 105 | }); 106 | }); 107 | }); 108 | }); 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /test/browser.spec.js: -------------------------------------------------------------------------------- 1 | import getScrollbarWidth from '../src/utils/getScrollbarWidth'; 2 | import createTests from './Scrollbars'; 3 | 4 | describe('Scrollbars (browser)', () => { 5 | createTests(getScrollbarWidth(), getScrollbarWidth()); 6 | }); 7 | -------------------------------------------------------------------------------- /test/mobile.spec.js: -------------------------------------------------------------------------------- 1 | const getScrollbarWidthModule = require('../src/utils/getScrollbarWidth'); 2 | const envScrollbarWidth = getScrollbarWidthModule.default(); 3 | import createTests from './Scrollbars'; 4 | 5 | describe('Scrollbars (mobile)', () => { 6 | const mobileScrollbarsWidth = 0; 7 | let getScrollbarWidthSpy; 8 | 9 | before(() => { 10 | getScrollbarWidthSpy = spyOn(getScrollbarWidthModule, 'default'); 11 | getScrollbarWidthSpy.andReturn(mobileScrollbarsWidth); 12 | }); 13 | 14 | after(() => { 15 | getScrollbarWidthSpy.restore(); 16 | }); 17 | 18 | createTests(mobileScrollbarsWidth, envScrollbarWidth); 19 | }); 20 | -------------------------------------------------------------------------------- /test/utils.spec.js: -------------------------------------------------------------------------------- 1 | import returnFalse from '../src/utils/returnFalse'; 2 | describe('utils', () => { 3 | describe('returnFalse', () => { 4 | it('should return false', done => { 5 | expect(returnFalse()).toEqual(false); 6 | done(); 7 | }); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | 5 | var plugins = [ 6 | new webpack.optimize.OccurenceOrderPlugin(), 7 | new webpack.DefinePlugin({ 8 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 9 | }) 10 | ]; 11 | 12 | if (process.env.NODE_ENV === 'production') { 13 | plugins.push( 14 | new webpack.optimize.UglifyJsPlugin({ 15 | compressor: { 16 | screw_ie8: true, 17 | warnings: false 18 | } 19 | }) 20 | ); 21 | } 22 | 23 | module.exports = { 24 | externals: { 25 | react: { 26 | root: 'React', 27 | commonjs2: 'react', 28 | commonjs: 'react', 29 | amd: 'react' 30 | } 31 | }, 32 | module: { 33 | loaders: [{ 34 | test: /\.js$/, 35 | loaders: ['babel-loader'], 36 | exclude: /node_modules/ 37 | }] 38 | }, 39 | output: { 40 | library: 'ReactCustomScrollbars', 41 | libraryTarget: 'umd' 42 | }, 43 | plugins: plugins, 44 | resolve: { 45 | extensions: ['', '.js'] 46 | } 47 | }; 48 | --------------------------------------------------------------------------------