├── .editorconfig
├── .gitignore
├── .travis.yml
├── HISTORY.md
├── README.md
├── assets
└── index.less
├── examples
├── simple.html
├── simple.js
└── simple.less
├── index.js
├── package.json
├── src
├── Drawer.jsx
└── index.js
└── tests
├── Drawer.spec.js
└── __snapshots__
└── Drawer.spec.js.snap
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | # Unix-style newlines with a newline ending every file
5 | [*.{js,css}]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.log
3 | .idea/
4 | .ipr
5 | .iws
6 | *~
7 | ~*
8 | *.diff
9 | *.patch
10 | *.bak
11 | .DS_Store
12 | Thumbs.db
13 | .project
14 | .*proj
15 | .svn/
16 | *.swp
17 | *.swo
18 | *.pyc
19 | *.pyo
20 | .build
21 | node_modules
22 | .cache
23 | dist
24 | *.css
25 | build
26 | lib
27 | /coverage
28 | yarn.lock
29 | package-lock.json
30 | es/
31 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | sudo: false
4 |
5 | notifications:
6 | email:
7 | -
8 |
9 | node_js:
10 | - 4.0.0
11 |
12 | before_install:
13 | - |
14 | if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/'
15 | then
16 | echo "Only docs were updated, stopping build process."
17 | exit
18 | fi
19 | npm install npm@3.x -g
20 | phantomjs --version
21 | script:
22 | - |
23 | if [ "$TEST_TYPE" = test ]; then
24 | npm test
25 | else
26 | npm run $TEST_TYPE
27 | fi
28 | env:
29 | matrix:
30 | - TEST_TYPE=lint
31 | - TEST_TYPE=test
32 | - TEST_TYPE=coverage
33 | - TEST_TYPE=saucelabs
34 |
35 |
36 | matrix:
37 | allow_failures:
38 | - env: "TEST_TYPE=saucelabs"
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/m-drawer/c968110b6a79190c1e04ded36dff0bec178dfdfd/HISTORY.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rmc-drawer
2 | ---
3 |
4 | React Drawer Component
5 |
6 |
7 | [![NPM version][npm-image]][npm-url]
8 | [![build status][travis-image]][travis-url]
9 | [![Test coverage][coveralls-image]][coveralls-url]
10 | [![gemnasium deps][gemnasium-image]][gemnasium-url]
11 | [![npm download][download-image]][download-url]
12 |
13 | [npm-image]: http://img.shields.io/npm/v/rmc-drawer.svg?style=flat-square
14 | [npm-url]: http://npmjs.org/package/rmc-drawer
15 | [travis-image]: https://img.shields.io/travis/react-component/m-drawer.svg?style=flat-square
16 | [travis-url]: https://travis-ci.org/react-component/m-drawer
17 | [coveralls-image]: https://img.shields.io/coveralls/react-component/m-drawer.svg?style=flat-square
18 | [coveralls-url]: https://coveralls.io/r/react-component/m-drawer?branch=master
19 | [gemnasium-image]: http://img.shields.io/gemnasium/react-component/m-drawer.svg?style=flat-square
20 | [gemnasium-url]: https://gemnasium.com/react-component/m-drawer
21 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square
22 | [node-url]: http://nodejs.org/download/
23 | [download-image]: https://img.shields.io/npm/dm/rmc-drawer.svg?style=flat-square
24 | [download-url]: https://npmjs.org/package/rmc-drawer
25 |
26 |
27 | ## Screenshots
28 |
29 |
30 |
31 |
32 | ## Development
33 |
34 | ```
35 | npm install
36 | npm start
37 | ```
38 |
39 | ## Example
40 |
41 | http://localhost:8099/examples/
42 |
43 |
44 | online example: http://react-component.github.io/m-drawer/
45 |
46 |
47 | ## install
48 |
49 |
50 | [](https://npmjs.org/package/rmc-drawer)
51 |
52 |
53 | ## Usage
54 |
55 | ```js
56 | var Drawer = require('rmc-drawer');
57 | var React = require('react');
58 | React.render(, container);
59 | ```
60 |
61 | ## API
62 |
63 | ### props
64 |
65 | | Property name | Description | Type | Default |
66 | |---------------|-------------|------|---------|
67 | | className | additional css class of root dom node | String | '' |
68 | | prefixCls | prefix class | String | 'rmc-drawer' |
69 | | children | The main content | any | n/a |
70 | | style | container styles. | Object | |
71 | | sidebarStyle | Inline styles. | Object | {} |
72 | | contentStyle | Inline styles. | Object | {} |
73 | | overlayStyle | Inline styles. | Object | {} |
74 | | dragHandleStyle | Inline styles. | Object | {} |
75 | | sidebar | The sidebar content | any | n/a |
76 | | onOpenChange | Callback called when the sidebar wants to change the open prop. Happens after sliding the sidebar and when the overlay is clicked when the sidebar is open. | Function | n/a |
77 | | open | If the sidebar should be open | Boolean | false |
78 | | position | where to place the sidebar | String | 'left', enum{'left', 'right', 'top', 'bottom'} |
79 | | docked | If the sidebar should be docked in document | Boolean | false |
80 | | transitions | If transitions should be enabled | Boolean | true |
81 | | touch | If touch gestures should be enabled | Boolean | true |
82 | | enableDragHandle | If dragHandle should be enabled | Boolean | true |
83 | | dragToggleDistance | Distance the sidebar has to be dragged before it will open/close after it is released. | Number | 30 |
84 |
85 | > change from [https://github.com/balloob/react-sidebar](https://github.com/balloob/react-sidebar)
86 |
87 |
88 | ## Test Case
89 |
90 | ```
91 | npm test
92 | npm run chrome-test
93 | ```
94 |
95 | ## Coverage
96 |
97 | ```
98 | npm run coverage
99 | ```
100 |
101 | open coverage/ dir
102 |
103 | ## License
104 |
105 | rmc-drawer is released under the MIT license.
106 |
--------------------------------------------------------------------------------
/assets/index.less:
--------------------------------------------------------------------------------
1 | @drawerPrefixCls: rmc-drawer;
2 | .@{drawerPrefixCls} {
3 | position: absolute;
4 | top: 0;
5 | left: 0;
6 | right: 0;
7 | bottom: 0;
8 | overflow: hidden;
9 |
10 | &-sidebar {
11 | z-index: 2;
12 | position: absolute;
13 | transition: transform .3s ease-out;
14 | will-change: transform;
15 | overflow-y: auto;
16 | }
17 | &-draghandle {
18 | z-index: 1;
19 | position: absolute;
20 | background-color: rgba(50, 50, 50, 0.1);
21 | }
22 | &-overlay {
23 | z-index: 1;
24 | position: absolute;
25 | top: 0;
26 | left: 0;
27 | right: 0;
28 | bottom: 0;
29 | opacity: 0;
30 | visibility: hidden;
31 | transition: opacity 0.3s ease-out;
32 | background-color: rgba(0, 0, 0, 0.3);
33 | }
34 | &-content {
35 | position: absolute;
36 | top: 0;
37 | left: 0;
38 | right: 0;
39 | bottom: 0;
40 | overflow: auto;
41 | transition: left .3s ease-out, right .3s ease-out, top .3s ease-out, bottom .3s ease-out;
42 | }
43 |
44 | &&-left,
45 | &&-right {
46 | .@{drawerPrefixCls}-sidebar,
47 | .@{drawerPrefixCls}-draghandle {
48 | top: 0;
49 | bottom: 0;
50 | }
51 | .@{drawerPrefixCls}-draghandle {
52 | width: 20px;
53 | height: 100%;
54 | }
55 | }
56 | &&-top,
57 | &&-bottom {
58 | .@{drawerPrefixCls}-sidebar,
59 | .@{drawerPrefixCls}-draghandle {
60 | left: 0;
61 | right: 0;
62 | }
63 | .@{drawerPrefixCls}-draghandle {
64 | width: 100%;
65 | height: 20px;
66 | }
67 | }
68 | &&-left {
69 | .@{drawerPrefixCls}-sidebar {
70 | left: 0;
71 | transform: translateX(-100%);
72 | .@{drawerPrefixCls}-open& {
73 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15);
74 | }
75 | }
76 | .@{drawerPrefixCls}-draghandle {
77 | left: 0;
78 | }
79 | }
80 |
81 | &&-right {
82 | .@{drawerPrefixCls}-sidebar {
83 | right: 0;
84 | transform: translateX(100%);
85 | .@{drawerPrefixCls}-open& {
86 | box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.15);
87 | }
88 | }
89 | .@{drawerPrefixCls}-draghandle {
90 | right: 0;
91 | }
92 | }
93 |
94 | &&-top {
95 | .@{drawerPrefixCls}-sidebar {
96 | top: 0;
97 | transform: translateY(-100%);
98 | .@{drawerPrefixCls}-open& {
99 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15);
100 | }
101 | }
102 | .@{drawerPrefixCls}-draghandle {
103 | top: 0;
104 | }
105 | }
106 |
107 | &&-bottom {
108 | .@{drawerPrefixCls}-sidebar {
109 | bottom: 0;
110 | transform: translateY(100%);
111 | .@{drawerPrefixCls}-open& {
112 | box-shadow: 2px -2px 4px rgba(0, 0, 0, 0.15);
113 | }
114 | }
115 | .@{drawerPrefixCls}-draghandle {
116 | bottom: 0;
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/simple.js:
--------------------------------------------------------------------------------
1 | // use jsx to render html, do not modify simple.html
2 |
3 | import 'rmc-drawer/assets/index.less';
4 | import './simple.less';
5 | import Drawer from 'rmc-drawer';
6 | import React from 'react';
7 | import ReactDOM from 'react-dom';
8 |
9 | class App extends React.Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | docked: false,
15 | open: false,
16 | transitions: true,
17 | touch: true,
18 | enableDragHandle: true,
19 | position: 'left',
20 | dragToggleDistance: 30,
21 | };
22 | }
23 | onOpenChange = (open) => {
24 | this.setState({ open });
25 | }
26 | onDock = () => {
27 | const docked = !this.state.docked;
28 | this.setState({
29 | docked,
30 | });
31 | if (!docked) {
32 | this.onOpenChange(false);
33 | }
34 | }
35 | render() {
36 | const sidebar = (
37 |
38 | sidebar
39 |
42 |
43 |
this is content!
44 |
);
45 |
46 | const drawerProps = {
47 | docked: this.state.docked,
48 | open: this.state.open,
49 | touch: this.state.touch,
50 | enableDragHandle: this.state.enableDragHandle,
51 | position: this.state.position,
52 | dragToggleDistance: this.state.dragToggleDistance,
53 | transitions: this.state.transitions,
54 | onOpenChange: this.onOpenChange,
55 | };
56 | return (
57 |
58 |
59 |
React component
60 |
63 |
64 | {['left', 'right', 'top', 'bottom'].map((i, index) => (
67 | { this.setState({ position: e.target.value }); }}
70 | />
71 | ))}
72 |
73 |
right content
74 |
bottom content
75 |
76 |
77 |
);
78 | }
79 | }
80 |
81 | ReactDOM.render(, document.getElementById('__react-content'));
82 |
--------------------------------------------------------------------------------
/examples/simple.less:
--------------------------------------------------------------------------------
1 |
2 | .drawer-container {
3 | position: relative;
4 | height: 400px;
5 | border: 1px solid #ccc;
6 | .main {
7 | position: relative;
8 | padding: 10px;
9 | box-sizing: border-box;
10 | height: 100%;
11 | }
12 | }
13 | .rmc-drawer {
14 | &-sidebar {
15 | padding: 16px;
16 | background-color: #e4f6fe;
17 | }
18 | &-left &-sidebar,
19 | &-right &-sidebar, {
20 | height: 100%;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // export this package's api
2 | import Drawer from './src/';
3 | export default Drawer;
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rmc-drawer",
3 | "version": "0.4.11",
4 | "description": "drawer ui component for react",
5 | "keywords": [
6 | "react",
7 | "react-component",
8 | "react-drawer",
9 | "drawer"
10 | ],
11 | "engines": {
12 | "node": ">=4.0.0"
13 | },
14 | "homepage": "https://github.com/react-component/m-drawer",
15 | "author": "",
16 | "repository": "react-component/m-drawer",
17 | "bugs": "https://github.com/react-component/m-drawer/issues",
18 | "files": [
19 | "es",
20 | "lib",
21 | "assets/*.css"
22 | ],
23 | "license": "MIT",
24 | "main": "./lib/index",
25 | "module": "./es/index",
26 | "config": {
27 | "port": 8099
28 | },
29 | "scripts": {
30 | "build": "rc-tools run build",
31 | "dist": "rc-tools run dist",
32 | "compile": "rc-tools run compile --babel-runtime",
33 | "gh-pages": "rc-tools run gh-pages",
34 | "start": "rc-tools run server",
35 | "prepublish": "rc-tools run guard",
36 | "prepare": "rc-tools run guard",
37 | "prepublishOnly": "rc-tools run guard",
38 | "pub": "rc-tools run pub --babel-runtime",
39 | "lint": "rc-tools run lint",
40 | "test": "jest",
41 | "coverage": "jest --coverage && cat ./coverage/lcov.info | coveralls"
42 | },
43 | "jest": {
44 | "collectCoverageFrom": [
45 | "src/**/*"
46 | ],
47 | "transform": {
48 | "\\.jsx?$": "./node_modules/rc-tools/scripts/jestPreprocessor.js"
49 | }
50 | },
51 | "devDependencies": {
52 | "coveralls": "^2.13.1",
53 | "enzyme": "^2.6.0",
54 | "enzyme-to-json": "^1.4.5",
55 | "jest": "^18.0.0",
56 | "pre-commit": "1.x",
57 | "rc-tools": "6.x",
58 | "react": "15.x",
59 | "react-dom": "15.x",
60 | "react-test-renderer": "^15.6.1"
61 | },
62 | "pre-commit": [
63 | "lint"
64 | ],
65 | "dependencies": {
66 | "babel-runtime": "6.x",
67 | "classnames": "^2.2.4",
68 | "prop-types": "^15.5.10"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Drawer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ReactDOM from 'react-dom';
4 | import classNames from 'classnames';
5 |
6 | function getOffset(ele) {
7 | let el = ele;
8 | let _x = 0;
9 | let _y = 0;
10 | while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
11 | _x += el.offsetLeft - el.scrollLeft;
12 | _y += el.offsetTop - el.scrollTop;
13 | el = el.offsetParent;
14 | }
15 | return { top: _y, left: _x };
16 | }
17 |
18 | const CANCEL_DISTANCE_ON_SCROLL = 20;
19 |
20 | export default class Drawer extends React.Component {
21 | static propTypes = {
22 | prefixCls: PropTypes.string,
23 | className: PropTypes.string,
24 | // main content to render
25 | children: PropTypes.node.isRequired,
26 |
27 | // styles
28 | // styles: PropTypes.shape({
29 | // dragHandle: PropTypes.object,
30 | // }),
31 | style: PropTypes.object,
32 | sidebarStyle: PropTypes.object,
33 | contentStyle: PropTypes.object,
34 | overlayStyle: PropTypes.object,
35 | dragHandleStyle: PropTypes.object,
36 |
37 | // sidebar content to render
38 | sidebar: PropTypes.node.isRequired,
39 |
40 | // boolean if sidebar should be docked
41 | docked: PropTypes.bool,
42 |
43 | // boolean if sidebar should slide open
44 | open: PropTypes.bool,
45 |
46 | // boolean if transitions should be disabled
47 | transitions: PropTypes.bool,
48 |
49 | // boolean if touch gestures are enabled
50 | touch: PropTypes.bool,
51 | enableDragHandle: PropTypes.bool,
52 |
53 | // where to place the sidebar
54 | position: PropTypes.oneOf(['left', 'right', 'top', 'bottom']),
55 |
56 | // distance we have to drag the sidebar to toggle open state
57 | dragToggleDistance: PropTypes.number,
58 |
59 | // callback called when the overlay is clicked
60 | onOpenChange: PropTypes.func,
61 | }
62 |
63 | static defaultProps = {
64 | prefixCls: 'rmc-drawer',
65 | sidebarStyle: {},
66 | contentStyle: {},
67 | overlayStyle: {},
68 | dragHandleStyle: {},
69 | docked: false,
70 | open: false,
71 | transitions: true,
72 | touch: true,
73 | enableDragHandle: true,
74 | position: 'left',
75 | dragToggleDistance: 30,
76 | onOpenChange: () => {},
77 | }
78 |
79 | constructor(props) {
80 | super(props);
81 |
82 | this.state = {
83 | // the detected width of the sidebar in pixels
84 | sidebarWidth: 0,
85 | sidebarHeight: 0,
86 | sidebarTop: 0,
87 | dragHandleTop: 0,
88 |
89 | // keep track of touching params
90 | touchIdentifier: null,
91 | touchStartX: null,
92 | touchStartY: null,
93 | touchCurrentX: null,
94 | touchCurrentY: null,
95 |
96 | // if touch is supported by the browser
97 | touchSupported: typeof window === 'object' && 'ontouchstart' in window,
98 | };
99 | }
100 |
101 | componentDidMount() {
102 | this.saveSidebarSize();
103 | }
104 |
105 | componentDidUpdate() {
106 | // filter out the updates when we're touching
107 | if (!this.isTouching()) {
108 | this.saveSidebarSize();
109 | }
110 | }
111 |
112 | onOverlayClicked = () => {
113 | if (this.props.open) {
114 | // see https://github.com/react-component/drawer/issues/9
115 | setTimeout(() => {
116 | this.props.onOpenChange(false, { overlayClicked: true });
117 | }, 0);
118 | }
119 | }
120 |
121 | onTouchStart = (ev) => {
122 | // filter out if a user starts swiping with a second finger
123 | if (!this.isTouching()) {
124 | const touch = ev.targetTouches[0];
125 | this.setState({
126 | touchIdentifier: !this.notTouch ? touch.identifier : null,
127 | touchStartX: touch.clientX,
128 | touchStartY: touch.clientY,
129 | touchCurrentX: touch.clientX,
130 | touchCurrentY: touch.clientY,
131 | });
132 | }
133 | }
134 |
135 | onTouchMove = (ev) => {
136 | // ev.preventDefault(); // cannot touchmove with FastClick
137 | if (this.isTouching()) {
138 | for (let ind = 0; ind < ev.targetTouches.length; ind++) {
139 | // we only care about the finger that we are tracking
140 | if (ev.targetTouches[ind].identifier === this.state.touchIdentifier) {
141 | this.setState({
142 | touchCurrentX: ev.targetTouches[ind].clientX,
143 | touchCurrentY: ev.targetTouches[ind].clientY,
144 | });
145 | break;
146 | }
147 | }
148 | }
149 | }
150 |
151 | onTouchEnd = () => {
152 | this.notTouch = false;
153 | if (this.isTouching()) {
154 | // trigger a change to open if sidebar has been dragged beyond dragToggleDistance
155 | const touchWidth = this.touchSidebarWidth();
156 |
157 | if (this.props.open && touchWidth < this.state.sidebarWidth - this.props.dragToggleDistance ||
158 | !this.props.open && touchWidth > this.props.dragToggleDistance) {
159 | this.props.onOpenChange(!this.props.open);
160 | }
161 |
162 | const touchHeight = this.touchSidebarHeight();
163 |
164 | if (this.props.open &&
165 | touchHeight < this.state.sidebarHeight - this.props.dragToggleDistance ||
166 | !this.props.open && touchHeight > this.props.dragToggleDistance) {
167 | this.props.onOpenChange(!this.props.open);
168 | }
169 |
170 | this.setState({
171 | touchIdentifier: null,
172 | touchStartX: null,
173 | touchStartY: null,
174 | touchCurrentX: null,
175 | touchCurrentY: null,
176 | });
177 | }
178 | }
179 |
180 | // This logic helps us prevents the user from sliding the sidebar horizontally
181 | // while scrolling the sidebar vertically. When a scroll event comes in, we're
182 | // cancelling the ongoing gesture if it did not move horizontally much.
183 | onScroll = () => {
184 | if (this.isTouching() && this.inCancelDistanceOnScroll()) {
185 | this.setState({
186 | touchIdentifier: null,
187 | touchStartX: null,
188 | touchStartY: null,
189 | touchCurrentX: null,
190 | touchCurrentY: null,
191 | });
192 | }
193 | }
194 |
195 | // True if the on going gesture X distance is less than the cancel distance
196 | inCancelDistanceOnScroll = () => {
197 | let cancelDistanceOnScroll;
198 | switch (this.props.position) {
199 | case 'right':
200 | cancelDistanceOnScroll = Math.abs(this.state.touchCurrentX - this.state.touchStartX) <
201 | CANCEL_DISTANCE_ON_SCROLL;
202 | break;
203 | case 'bottom':
204 | cancelDistanceOnScroll = Math.abs(this.state.touchCurrentY - this.state.touchStartY) <
205 | CANCEL_DISTANCE_ON_SCROLL;
206 | break;
207 | case 'top':
208 | cancelDistanceOnScroll = Math.abs(this.state.touchStartY - this.state.touchCurrentY) <
209 | CANCEL_DISTANCE_ON_SCROLL;
210 | break;
211 | case 'left':
212 | default:
213 | cancelDistanceOnScroll = Math.abs(this.state.touchStartX - this.state.touchCurrentX) <
214 | CANCEL_DISTANCE_ON_SCROLL;
215 | }
216 | return cancelDistanceOnScroll;
217 | }
218 |
219 | isTouching = () => {
220 | return this.state.touchIdentifier !== null;
221 | }
222 |
223 | saveSidebarSize = () => {
224 | const sidebar = ReactDOM.findDOMNode(this.refs.sidebar);
225 | const width = sidebar.offsetWidth;
226 | const height = sidebar.offsetHeight;
227 | const sidebarTop = getOffset(ReactDOM.findDOMNode(this.refs.sidebar)).top;
228 | const dragHandleTop = getOffset(ReactDOM.findDOMNode(this.refs.dragHandle)).top;
229 |
230 | if (width !== this.state.sidebarWidth) {
231 | this.setState({ sidebarWidth: width });
232 | }
233 | if (height !== this.state.sidebarHeight) {
234 | this.setState({ sidebarHeight: height });
235 | }
236 | if (sidebarTop !== this.state.sidebarTop) {
237 | this.setState({ sidebarTop });
238 | }
239 | if (dragHandleTop !== this.state.dragHandleTop) {
240 | this.setState({ dragHandleTop });
241 | }
242 | }
243 |
244 | // calculate the sidebarWidth based on current touch info
245 | touchSidebarWidth = () => {
246 | // if the sidebar is open and start point of drag is inside the sidebar
247 | // we will only drag the distance they moved their finger
248 | // otherwise we will move the sidebar to be below the finger.
249 | if (this.props.position === 'right') {
250 | if (this.props.open && window.innerWidth - this.state.touchStartX < this.state.sidebarWidth) {
251 | if (this.state.touchCurrentX > this.state.touchStartX) {
252 | return this.state.sidebarWidth + this.state.touchStartX - this.state.touchCurrentX;
253 | }
254 | return this.state.sidebarWidth;
255 | }
256 | return Math.min(window.innerWidth - this.state.touchCurrentX, this.state.sidebarWidth);
257 | }
258 |
259 | if (this.props.position === 'left') {
260 | if (this.props.open && this.state.touchStartX < this.state.sidebarWidth) {
261 | if (this.state.touchCurrentX > this.state.touchStartX) {
262 | return this.state.sidebarWidth;
263 | }
264 | return this.state.sidebarWidth - this.state.touchStartX + this.state.touchCurrentX;
265 | }
266 | return Math.min(this.state.touchCurrentX, this.state.sidebarWidth);
267 | }
268 | }
269 | // calculate the sidebarHeight based on current touch info
270 | touchSidebarHeight = () => {
271 | // if the sidebar is open and start point of drag is inside the sidebar
272 | // we will only drag the distance they moved their finger
273 | // otherwise we will move the sidebar to be below the finger.
274 | if (this.props.position === 'bottom') {
275 | if (this.props.open &&
276 | window.innerHeight - this.state.touchStartY < this.state.sidebarHeight) {
277 | if (this.state.touchCurrentY > this.state.touchStartY) {
278 | return this.state.sidebarHeight + this.state.touchStartY - this.state.touchCurrentY;
279 | }
280 | return this.state.sidebarHeight;
281 | }
282 | return Math.min(window.innerHeight - this.state.touchCurrentY, this.state.sidebarHeight);
283 | }
284 |
285 | if (this.props.position === 'top') {
286 | const touchStartOffsetY = this.state.touchStartY - this.state.sidebarTop;
287 | if (this.props.open && touchStartOffsetY < this.state.sidebarHeight) {
288 | if (this.state.touchCurrentY > this.state.touchStartY) {
289 | return this.state.sidebarHeight;
290 | }
291 | return this.state.sidebarHeight - this.state.touchStartY + this.state.touchCurrentY;
292 | }
293 | return Math.min(this.state.touchCurrentY - this.state.dragHandleTop,
294 | this.state.sidebarHeight);
295 | }
296 | }
297 |
298 | renderStyle = ({ sidebarStyle, isTouching, overlayStyle, contentStyle }) => {
299 | if (this.props.position === 'right' || this.props.position === 'left') {
300 | sidebarStyle.transform = `translateX(0%)`;
301 | sidebarStyle.WebkitTransform = `translateX(0%)`;
302 | if (isTouching) {
303 | const percentage = this.touchSidebarWidth() / this.state.sidebarWidth;
304 | // slide open to what we dragged
305 | if (this.props.position === 'right') {
306 | sidebarStyle.transform = `translateX(${(1 - percentage) * 100}%)`;
307 | sidebarStyle.WebkitTransform = `translateX(${(1 - percentage) * 100}%)`;
308 | }
309 | if (this.props.position === 'left') {
310 | sidebarStyle.transform = `translateX(-${(1 - percentage) * 100}%)`;
311 | sidebarStyle.WebkitTransform = `translateX(-${(1 - percentage) * 100}%)`;
312 | }
313 | // fade overlay to match distance of drag
314 | overlayStyle.opacity = percentage;
315 | overlayStyle.visibility = 'visible';
316 | }
317 | if (contentStyle) {
318 | contentStyle[this.props.position] = `${this.state.sidebarWidth}px`;
319 | }
320 | }
321 | if (this.props.position === 'top' || this.props.position === 'bottom') {
322 | sidebarStyle.transform = `translateY(0%)`;
323 | sidebarStyle.WebkitTransform = `translateY(0%)`;
324 | if (isTouching) {
325 | const percentage = this.touchSidebarHeight() / this.state.sidebarHeight;
326 | // slide open to what we dragged
327 | if (this.props.position === 'bottom') {
328 | sidebarStyle.transform = `translateY(${(1 - percentage) * 100}%)`;
329 | sidebarStyle.WebkitTransform = `translateY(${(1 - percentage) * 100}%)`;
330 | }
331 | if (this.props.position === 'top') {
332 | sidebarStyle.transform = `translateY(-${(1 - percentage) * 100}%)`;
333 | sidebarStyle.WebkitTransform = `translateY(-${(1 - percentage) * 100}%)`;
334 | }
335 | // fade overlay to match distance of drag
336 | overlayStyle.opacity = percentage;
337 | overlayStyle.visibility = 'visible';
338 | }
339 | if (contentStyle) {
340 | contentStyle[this.props.position] = `${this.state.sidebarHeight}px`;
341 | }
342 | }
343 | }
344 |
345 | render() {
346 | const { className, style, prefixCls, position, transitions,
347 | touch, enableDragHandle, sidebar, children, docked, open } = this.props;
348 |
349 | const sidebarStyle = { ...this.props.sidebarStyle };
350 | const contentStyle = { ...this.props.contentStyle };
351 | const overlayStyle = { ...this.props.overlayStyle };
352 |
353 | const rootCls = {
354 | [className]: !!className,
355 | [prefixCls]: true,
356 | [`${prefixCls}-${position}`]: true,
357 | };
358 |
359 | const rootProps = { style };
360 | const isTouching = this.isTouching();
361 |
362 | if (isTouching) {
363 | this.renderStyle({ sidebarStyle, isTouching: true, overlayStyle });
364 | } else if (docked) {
365 | if (this.state.sidebarWidth !== 0) {
366 | rootCls[`${prefixCls}-docked`] = true;
367 | this.renderStyle({ sidebarStyle, contentStyle });
368 | }
369 | } else if (open) {
370 | rootCls[`${prefixCls}-open`] = true;
371 | this.renderStyle({ sidebarStyle });
372 | overlayStyle.opacity = 1;
373 | overlayStyle.visibility = 'visible';
374 | }
375 |
376 | if (isTouching || !transitions) {
377 | sidebarStyle.transition = 'none';
378 | sidebarStyle.WebkitTransition = 'none';
379 | contentStyle.transition = 'none';
380 | overlayStyle.transition = 'none';
381 | }
382 |
383 | let dragHandle = null;
384 |
385 | if (this.state.touchSupported && touch) {
386 | if (open) {
387 | rootProps.onTouchStart = (ev) => {
388 | this.notTouch = true;
389 | this.onTouchStart(ev);
390 | };
391 | rootProps.onTouchMove = this.onTouchMove;
392 | rootProps.onTouchEnd = this.onTouchEnd;
393 | rootProps.onTouchCancel = this.onTouchEnd;
394 | rootProps.onScroll = this.onScroll;
395 | } else if (enableDragHandle) {
396 | dragHandle = (
397 | );
402 | }
403 | }
404 |
405 | // const evt = {};
406 | // // FastClick use touchstart instead of click
407 | // if (this.state.touchSupported) {
408 | // evt.onTouchStart = () => {
409 | // this.notTouch = true;
410 | // this.onOverlayClicked();
411 | // };
412 | // evt.onTouchEnd = () => {
413 | // this.notTouch = false;
414 | // this.setState({
415 | // touchIdentifier: null,
416 | // });
417 | // };
418 | // } else {
419 | // evt.onClick = this.onOverlayClicked;
420 | // }
421 |
422 | return (
423 |
424 |
427 | {sidebar}
428 |
429 | {/*
430 |
*/}
435 |
441 |
444 | {dragHandle}
445 | {children}
446 |
447 |
448 | );
449 | }
450 | }
451 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // export this package's api
2 | import Drawer from './Drawer';
3 | export default Drawer;
4 |
--------------------------------------------------------------------------------
/tests/Drawer.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React from 'react';
3 | import { render, mount } from 'enzyme';
4 | import { renderToJson } from 'enzyme-to-json';
5 | import Drawer from '..';
6 |
7 | describe('Drawer', () => {
8 | it('renders correctly', () => {
9 | const wrapper = render(
10 | sidebar}>
11 | React Sidebar is a sidebar component for React.
12 |
13 | );
14 | expect(renderToJson(wrapper)).toMatchSnapshot();
15 | expect(wrapper.find('.forTest').length === 1).toBe(true);
16 | });
17 |
18 | it('should close the sidebar', () => {
19 | const cb = jest.fn();
20 | const wrapper = mount(
21 | sidebar} onOpenChange={cb} open>
22 | React Sidebar is a sidebar component for React.
23 |
24 | );
25 | // console.log(wrapper.html())
26 | wrapper.find('.rmc-drawer-overlay').simulate('click');
27 | expect(cb).toBeCalledWith(false, { overlayClicked: true });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/__snapshots__/Drawer.spec.js.snap:
--------------------------------------------------------------------------------
1 | exports[`Drawer renders correctly 1`] = `
2 |
4 |
10 |
13 |
15 |
16 | React Sidebar is a sidebar component for React.
17 |
18 |
19 |
20 | `;
21 |
--------------------------------------------------------------------------------