diff --git a/README.md b/README.md index 009f112..aada7dd 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ For **React 0.13** you need to wrap `` child into a function. ``` #### Version without boundled css styles #### -If you prefer including scrollbar without css styles boundled inline to js file it's possible to import package without them. It's useful when you want to make custom css changes in scrollbars without using `!important` in each line. +If you prefer including scrollbar without css styles boundled inline to js file it's possible to import package without them. It's useful when you want to make custom css changes in scrollbars without using `!important` in each line. ```js var ScrollArea = require('react-scrollbar/no-css'); @@ -99,10 +99,14 @@ then open [http://localhost:8003](http://localhost:8003). contentClassName={String} contentStyle={Object} horizontal={Boolean} + horizontalContainerClassName={String} horizontalContainerStyle={Object} + horizontalScrollbarClassName={String} horizontalScrollbarStyle={Object} vertical={Boolean} + verticalContainerClassName={String} verticalContainerStyle={Object} + verticalScrollbarClassName={String} verticalScrollbarStyle={Object} onScroll={(value) => {}} contentWindow={Object} @@ -145,9 +149,15 @@ Inline styles applied to element with scroll area content. When set to false, horizontal scrollbar will not be available. **Default: true** +#### horizontalContainerClassName +CSS class names added to horizontal scrollbar's container. + #### horizontalContainerStyle Inline styles applied to horizontal scrollbar's container. +#### horizontalScrollbarClassName +CSS class names added to horizontal scrollbar. + #### horizontalScrollbarStyle Inline styles applied to horizontal scrollbar. @@ -155,9 +165,15 @@ Inline styles applied to horizontal scrollbar. When set to false, vertical scrollbar will not be available, regardless of the content height. **Default: true** +#### verticalContainerClassName +CSS class names added to vertical scrollbar's container. + #### verticalContainerStyle Inline styles applied to vertical scrollbar's container. +#### verticalScrollbarClassName +CSS class names added to vertical scrollbar. + #### verticalScrollbarStyle Inline styles applied to vertical scrollbar. @@ -170,7 +186,7 @@ You can override document to make scrollarea works inside iframe. **Default: document** #### smoothScrolling -When set to true, smooth scrolling for both scrollbars is enabled. +When set to true, smooth scrolling for both scrollbars is enabled. **Default: false** #### minScrollSize @@ -243,7 +259,7 @@ It allows to scroll to the right of `ScrollArea` component. It moves horizontal scrollbar. `leftPosition` is a distance between left edge of `scrollArea` container and left edge of `scrollArea` content. # Change log -Every release is documented on the Github [Releases](https://github.com/souhe/reactScrollbar/releases) page. +Every release is documented on the Github [Releases](https://github.com/souhe/reactScrollbar/releases) page. # License MIT diff --git a/package.json b/package.json index 6dd10e6..5872617 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "author": "benkeen", "license": "MIT", "dependencies": { + "classnames": "^2.2.5", "config": "^1.24.0", "line-height": "^0.1.1", "react": "^15.4.1", diff --git a/src/js/ScrollArea.jsx b/src/js/ScrollArea.jsx index e0936df..b179aad 100644 --- a/src/js/ScrollArea.jsx +++ b/src/js/ScrollArea.jsx @@ -4,6 +4,7 @@ import {findDOMNode, warnAboutFunctionChild, warnAboutElementChild, positiveOrZe import lineHeight from 'line-height'; import {Motion, spring} from 'react-motion'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; const eventTypes = { wheel: 'wheel', @@ -98,7 +99,9 @@ export default class ScrollArea extends React.Component { position={this.state.topPosition} onMove={this.handleScrollbarMove.bind(this)} onPositionChange={this.handleScrollbarYPositionChange.bind(this)} + containerClassName={this.props.verticalContainerClassName} containerStyle={this.props.verticalContainerStyle} + scrollbarClassName={this.props.verticalScrollbarClassName} scrollbarStyle={this.props.verticalScrollbarStyle} smoothScrolling={withMotion} minScrollSize={this.props.minScrollSize} @@ -114,7 +117,9 @@ export default class ScrollArea extends React.Component { position={this.state.leftPosition} onMove={this.handleScrollbarMove.bind(this)} onPositionChange={this.handleScrollbarXPositionChange.bind(this)} + containerClassName={this.props.horizontalContainerClassName} containerStyle={this.props.horizontalContainerStyle} + scrollbarClassName={this.props.horizontalScrollbarClassName} scrollbarStyle={this.props.horizontalScrollbarStyle} smoothScrolling={withMotion} minScrollSize={this.props.minScrollSize} @@ -129,8 +134,8 @@ export default class ScrollArea extends React.Component { warnAboutElementChild(); } - let classes = 'scrollarea ' + (className || ''); - let contentClasses = 'scrollarea-content ' + (contentClassName || ''); + let classes = classNames('scrollarea', className); + let contentClasses = classNames('scrollarea-content', contentClassName); let contentStyle = { marginTop: -this.state.topPosition, @@ -457,10 +462,14 @@ ScrollArea.propTypes = { contentClassName: PropTypes.string, contentStyle: PropTypes.object, vertical: PropTypes.bool, + verticalContainerClassName: PropTypes.string, verticalContainerStyle: PropTypes.object, + verticalScrollbarClassName: PropTypes.string, verticalScrollbarStyle: PropTypes.object, horizontal: PropTypes.bool, + horizontalContainerClassName: PropTypes.string, horizontalContainerStyle: PropTypes.object, + horizontalScrollbarClassName: PropTypes.string, horizontalScrollbarStyle: PropTypes.object, onScroll: PropTypes.func, contentWindow: PropTypes.any, diff --git a/src/js/Scrollbar.jsx b/src/js/Scrollbar.jsx index 1117689..a318745 100644 --- a/src/js/Scrollbar.jsx +++ b/src/js/Scrollbar.jsx @@ -2,6 +2,7 @@ import React from 'react'; import {Motion, spring} from 'react-motion'; import {modifyObjValues} from './utils'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; class ScrollBar extends React.Component { constructor(props){ @@ -60,25 +61,38 @@ class ScrollBar extends React.Component { } render(){ - let {smoothScrolling, isDragging, type, scrollbarStyle, containerStyle} = this.props; - let isVoriziontal = type === 'horizontal'; + let { + smoothScrolling, isDragging, type, + containerClassName, containerStyle, + scrollbarClassName, scrollbarStyle, + } = this.props; + let isHorizontal = type === 'horizontal'; let isVertical = type === 'vertical'; let scrollStyles = this.createScrollStyles(); let springifiedScrollStyles = smoothScrolling ? modifyObjValues(scrollStyles, x => spring(x)) : scrollStyles; - let scrollbarClasses = `scrollbar-container ${isDragging ? 'active' : ''} ${isVoriziontal ? 'horizontal' : ''} ${isVertical ? 'vertical' : ''}`; + const containerClassNames = classNames( + 'scrollbar-container', + { + active: isDragging, + horizontal: isHorizontal, + vertical: isVertical, + }, + containerClassName, + ); + const scrollbarClassNames = classNames('scrollbar', scrollbarClassName); return ( { style =>
this.scrollbarContainer = x } >
@@ -170,7 +184,9 @@ ScrollBar.propTypes = { realSize: PropTypes.number, containerSize: PropTypes.number, position: PropTypes.number, + containerClassName: PropTypes.string, containerStyle: PropTypes.object, + scrollbarClassName: PropTypes.string, scrollbarStyle: PropTypes.object, type: PropTypes.oneOf(['vertical', 'horizontal']), ownerDocument: PropTypes.any, diff --git a/test/scrollArea.spec.js b/test/scrollArea.spec.js index 91ed624..4e6e9a0 100644 --- a/test/scrollArea.spec.js +++ b/test/scrollArea.spec.js @@ -59,7 +59,7 @@ describe('ScrollArea component', () => { expect(content).toEqualJSX(
{}} style={{}} - className="scrollarea-content " + className="scrollarea-content" onTouchStart={() => {}} onTouchMove={() => {}} onTouchEnd={() => {}} @@ -78,7 +78,7 @@ describe('ScrollArea component', () => {
{}} style={{}} tabIndex={100} - className="scrollarea-content " + className="scrollarea-content" onTouchStart={() => {}} onTouchMove={() => {}} onTouchEnd={() => {}} @@ -118,6 +118,25 @@ describe('ScrollArea component', () => { expect(content.props.style).toEqual({ test: 'contentStyle' }); }); + it('Should have proper scrollbars classes', () => { + let {scrollbars} = setupComponentWithMockedSizes({ + vertical: true, + verticalContainerClassName: 'verticalContainerClassName', + verticalScrollbarClassName: 'verticalScrollbarClassName', + horizontal: true, + horizontalContainerClassName: 'horizontalContainerClassName', + horizontalScrollbarClassName: 'horizontalScrollbarClassName', + }); + + let verticalScrollbar = scrollbars.filter(component => component.props.type === 'vertical')[0]; + let horizontalScrollbar = scrollbars.filter(component => component.props.type === 'horizontal')[0]; + + expect(verticalScrollbar.props.containerClassName).toInclude('verticalContainerClassName'); + expect(verticalScrollbar.props.scrollbarClassName).toInclude('verticalScrollbarClassName'); + expect(horizontalScrollbar.props.containerClassName).toInclude('horizontalContainerClassName'); + expect(horizontalScrollbar.props.scrollbarClassName).toInclude('horizontalScrollbarClassName'); + }); + it('Should have proper scrollbars styles', () => { let {content, scrollbars} = setupComponentWithMockedSizes({ vertical: true, diff --git a/test/scrollBar.spec.js b/test/scrollBar.spec.js index f6fe2a4..71f9a8b 100644 --- a/test/scrollBar.spec.js +++ b/test/scrollBar.spec.js @@ -16,7 +16,7 @@ function setupScrollbar(props){ let wrapper = output.props.children(); let content = wrapper.props.children; - + return { wrapper, content, @@ -33,10 +33,10 @@ function getRendererComponentInstance(renderer){ describe('ScrollBar component', () => { it('Vertical should have proper class', () => { let {wrapper} = setupScrollbar({type: 'vertical'}); - + expect(wrapper.props.className).toInclude('vertical'); }); - + it('Vertical should have proper container styles', () => { let {wrapper} = setupScrollbar({ type: 'vertical', @@ -54,13 +54,13 @@ describe('ScrollBar component', () => { expect(content.props.style).toEqual({test: 'scrollbarStyle'}); }); - + it('Horizontal should have proper class', () => { let {wrapper} = setupScrollbar({type: 'horizontal'}); - + expect(wrapper.props.className).toInclude('horizontal'); }); - + it('Horizontal should have proper container styles', () => { let {wrapper} = setupScrollbar({ type: 'horizontal', @@ -78,30 +78,42 @@ describe('ScrollBar component', () => { expect(content.props.style).toEqual({test: 'scrollbarStyle'}); }); - + + it('Scrollbar should have proper container class', () => { + let {wrapper} = setupScrollbar({containerClassName: 'test-class'}); + + expect(wrapper.props.className).toInclude('test-class'); + }); + + it('Scrollbar should have proper element class', () => { + let {content} = setupScrollbar({scrollbarClassName: 'test-class'}); + + expect(content.props.className).toInclude('test-class'); + }); + it('ScrollBar should be in proper position', () => { let {instance} = setupScrollbar({ - realSize: 400, - containerSize: 100, + realSize: 400, + containerSize: 100, position: 20 }); - - expect(instance.state.position).toBe(5); + + expect(instance.state.position).toBe(5); }); - + it('ScrollBar should have proper size', () => { let { instance } = setupScrollbar({ - realSize: 400, + realSize: 400, containerSize: 100 }); - - expect(instance.state.scrollSize).toBe(25); + + expect(instance.state.scrollSize).toBe(25); }); - + it('Should propagate onMove event after move vertical scrollbar', () => { let handleMoveSpy = expect.createSpy(); let {instance} = setupScrollbar({ - realSize: 200, + realSize: 200, containerSize: 100, onMove: handleMoveSpy, onFocus: () => {} @@ -110,15 +122,15 @@ describe('ScrollBar component', () => { let moveEvent = {clientY: 25, preventDefault: () => {}}; instance.handleMouseDown(mouseDownEvent); instance.handleMouseMoveForVertical(moveEvent); - + expect(handleMoveSpy.calls.length).toEqual(1); expect(handleMoveSpy.calls[0].arguments).toEqual([-50 , 0]); }); - + it('Should propagate onMove event after move horizontal scrollbar', () => { let handleMoveSpy = expect.createSpy(); let {instance} = setupScrollbar({ - realSize: 200, + realSize: 200, containerSize: 100, onMove: handleMoveSpy, type: 'horizontal', @@ -128,15 +140,15 @@ describe('ScrollBar component', () => { let moveEvent = {clientX: 25, preventDefault: () => {}}; instance.handleMouseDown(mouseDownEvent); instance.handleMouseMoveForHorizontal(moveEvent); - + expect(handleMoveSpy.calls.length).toEqual(1); expect(handleMoveSpy.calls[0].arguments).toEqual([0, -50]); }); - + it('Should propagate onMove event multiple times', () => { let handleMoveSpy = expect.createSpy(); let {instance} = setupScrollbar({ - realSize: 400, + realSize: 400, containerSize: 100, onMove: handleMoveSpy, onFocus: () => {} @@ -151,65 +163,65 @@ describe('ScrollBar component', () => { instance.handleMouseMoveForVertical(moveEvent); moveEvent.clientY = 40; instance.handleMouseMoveForVertical(moveEvent); - + expect(handleMoveSpy.calls.length).toEqual(4); expect(handleMoveSpy.calls[3].arguments).toEqual([-40 , 0]); }); - + it('Should be possible to set min scrollbar size', () => { let minScrollBarSize = 10; let {instance} = setupScrollbar({ - realSize: 10000, + realSize: 10000, containerSize: 100, type: 'vertical', minScrollSize: minScrollBarSize, onFocus: () => {} - }); - - expect(instance.state.scrollSize).toBe(minScrollBarSize); + }); + + expect(instance.state.scrollSize).toBe(minScrollBarSize); }); - + it('Method calculateFractionalPosition should work properly for realSize: 300, containerSize: 100, position: 0', () => { let {instance} = setupScrollbar(); - + expect(instance.calculateFractionalPosition(300, 100, 0)).toEqual(0); }); - + it('Method calculateFractionalPosition should work properly for realSize: 300, containerSize: 100, position: 200', () => { let {instance} = setupScrollbar(); - + expect(instance.calculateFractionalPosition(300, 100, 200)).toEqual(1); }); - + it('Method calculateFractionalPosition should work properly for realSize: 300, containerSize: 100, position: 200', () => { let {instance} = setupScrollbar(); - + expect(instance.calculateFractionalPosition(300, 100, 100)).toEqual(0.5); }); - + it('Method calculateFractionalPosition should work properly for realSize: 160, containerSize: 80, position: 20', () => { let {instance} = setupScrollbar(); - + expect(instance.calculateFractionalPosition(160, 80, 20)).toEqual(0.25); }); - + it('Position of scrollbar should be proper when minScrollBarSize is set', () => { let {instance} = setupScrollbar({ - position: 9900, + position: 9900, realSize: 10000, containerSize: 100, type: 'vertical', minScrollSize: 10 - }); - + }); + expect(instance.state.position).toBe(90); }); - + it('vertical scrollbar container click should move scrollbar', () => { let handlePositionChangeSpy = expect.createSpy(); let {instance} = setupScrollbar({ position: 0, - realSize: 500, + realSize: 500, containerSize: 100, type: 'vertical', onPositionChange: handlePositionChangeSpy @@ -221,16 +233,16 @@ describe('ScrollBar component', () => { }) }; instance.handleScrollBarContainerClick(mouseDownEvent); - + expect(handlePositionChangeSpy.calls.length).toEqual(1); - expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); + expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); }); - + it('vertical scrollbar container click should move scrollbar when minScrollbar size is set', () => { let handlePositionChangeSpy = expect.createSpy(); let {instance} = setupScrollbar({ position: 0, - realSize: 1000, + realSize: 1000, containerSize: 100, type: 'vertical', onPositionChange: handlePositionChangeSpy, @@ -243,16 +255,16 @@ describe('ScrollBar component', () => { }) }; instance.handleScrollBarContainerClick(mouseDownEvent); - + expect(handlePositionChangeSpy.calls.length).toEqual(1); - expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); + expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); }); - + it('horizontal scrollbar container click should move scrollbar', () => { let handlePositionChangeSpy = expect.createSpy(); let {instance} = setupScrollbar({ position: 0, - realSize: 500, + realSize: 500, containerSize: 100, type: 'horizontal', onPositionChange: handlePositionChangeSpy @@ -264,16 +276,16 @@ describe('ScrollBar component', () => { }) }; instance.handleScrollBarContainerClick(mouseDownEvent); - + expect(handlePositionChangeSpy.calls.length).toEqual(1); - expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); + expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); }); - + it('horizontal scrollbar container click should move scrollbar when minScrollbar size is set', () => { let handlePositionChangeSpy = expect.createSpy(); let {instance} = setupScrollbar({ position: 0, - realSize: 1000, + realSize: 1000, containerSize: 100, type: 'horizontal', onPositionChange: handlePositionChangeSpy, @@ -286,8 +298,8 @@ describe('ScrollBar component', () => { }) }; instance.handleScrollBarContainerClick(mouseDownEvent); - + expect(handlePositionChangeSpy.calls.length).toEqual(1); - expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); + expect(handlePositionChangeSpy.calls[0].arguments).toEqual([200]); }); });