├── .babelrc
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── demo
├── index.js
├── public
│ └── index.html
└── server.js
├── dist
├── components
│ ├── __tests__
│ │ ├── integration-test.js
│ │ ├── pulldown-close-test.js
│ │ ├── pulldown-goto-test.js
│ │ ├── pulldown-stage-test.js
│ │ └── pulldown-test.js
│ ├── pulldown-close.js
│ ├── pulldown-goto.js
│ ├── pulldown-stage.js
│ └── pulldown.js
└── index.js
├── docs
└── API.md
├── package.json
├── src
├── components
│ ├── __tests__
│ │ ├── integration-test.js
│ │ ├── pulldown-close-test.js
│ │ ├── pulldown-goto-test.js
│ │ ├── pulldown-stage-test.js
│ │ └── pulldown-test.js
│ ├── pulldown-close.js
│ ├── pulldown-goto.js
│ ├── pulldown-stage.js
│ └── pulldown.js
└── index.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react",
5 | "stage-2"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": [
4 | "react"
5 | ],
6 | "env": {
7 | "browser": true,
8 | "node": true
9 | },
10 | "ecmaFeatures": {
11 | "arrowFunctions": true,
12 | "blockBindings": true,
13 | "classes": true,
14 | "defaultParams": true,
15 | "destructuring": true,
16 | "forOf": true,
17 | "generators": false,
18 | "modules": true,
19 | "objectLiteralComputedProperties": true,
20 | "objectLiteralDuplicateProperties": false,
21 | "objectLiteralShorthandMethods": true,
22 | "objectLiteralShorthandProperties": true,
23 | "spread": true,
24 | "superInFunctions": true,
25 | "templateStrings": true,
26 | "jsx": true
27 | },
28 | "rules": {
29 | /**
30 | * Strict mode
31 | */
32 | // babel inserts "use strict"; for us
33 | // http://eslint.org/docs/rules/strict
34 | "strict": [2, "never"],
35 |
36 | /**
37 | * ES6
38 | */
39 | "no-var": 2, // const and let - no var
40 | "prefer-const": 1, // Warn when let is used but variable is never modified
41 |
42 | /**
43 | * General
44 | */
45 | "max-len": [2, 200, 4], // Max length of lines is 200 characters
46 |
47 | /**
48 | * Variables
49 | */
50 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow
51 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
52 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars
53 | "vars": "local",
54 | "args": "after-used"
55 | }],
56 | "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
57 |
58 | /**
59 | * Possible errors
60 | */
61 | "comma-dangle": [2, "always-multiline"], // http://eslint.org/docs/rules/comma-dangle
62 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign
63 | "no-console": 2, // http://eslint.org/docs/rules/no-console
64 | "no-debugger": 2, // http://eslint.org/docs/rules/no-debugger
65 | "no-alert": 2, // http://eslint.org/docs/rules/no-alert
66 | "no-constant-condition": 2, // http://eslint.org/docs/rules/no-constant-condition
67 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
68 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
69 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty
70 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
71 | "no-extra-boolean-cast": 2, // http://eslint.org/docs/rules/no-extra-boolean-cast
72 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
73 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
74 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
75 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
76 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
77 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
78 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
79 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
80 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
81 | "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
82 |
83 | /**
84 | * Best practices
85 | */
86 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
87 | "curly": 2, // http://eslint.org/docs/rules/curly
88 | "default-case": 2, // http://eslint.org/docs/rules/default-case
89 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation
90 | "allowKeywords": true
91 | }],
92 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq
93 | "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in
94 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller
95 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return
96 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
97 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval
98 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
99 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
100 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
101 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
102 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
103 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
104 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
105 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
106 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
107 | "no-new": 2, // http://eslint.org/docs/rules/no-new
108 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
109 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
110 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal
111 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
112 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign
113 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto
114 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
115 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
116 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url
117 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
118 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
119 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
120 | "no-with": 2, // http://eslint.org/docs/rules/no-with
121 | "radix": 2, // http://eslint.org/docs/rules/radix
122 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top
123 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife
124 | "yoda": 2, // http://eslint.org/docs/rules/yoda
125 |
126 | /**
127 | * Style
128 | */
129 | "indent": [2, 2, {"SwitchCase": 1}], // http://eslint.org/docs/rules/
130 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style
131 | "1tbs", {
132 | "allowSingleLine": false
133 | }],
134 | "quotes": [
135 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes
136 | ],
137 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase
138 | "properties": "never"
139 | }],
140 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing
141 | "before": false,
142 | "after": true
143 | }],
144 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style
145 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last
146 | "func-names": 2, // http://eslint.org/docs/rules/func-names
147 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing
148 | "beforeColon": false,
149 | "afterColon": true
150 | }],
151 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap
152 | "newIsCap": true,
153 | "capIsNewExceptions": ["Map", "Record"]
154 | }],
155 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines
156 | "max": 2
157 | }],
158 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary
159 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object
160 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func
161 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces
162 | "no-extra-parens": [2, "functions"], // No extra parentheses around functions
163 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle
164 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var
165 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks
166 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi
167 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing
168 | "before": false,
169 | "after": true
170 | }],
171 | "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords
172 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks
173 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren
174 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops
175 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case
176 | "spaced-comment": [2, "always"], // Require a space after the beginning of a comment before comment content begins
177 | "jsx-quotes": [2, "prefer-double"], // http://eslint.org/docs/rules/jsx-quotes
178 | /**
179 | * JSX style
180 | */
181 | "react/display-name": 0,
182 | "react/jsx-boolean-value": [2, "never"],
183 | "react/jsx-closing-bracket-location": 2,
184 | "react/jsx-curly-spacing": [2, "never"],
185 | "react/jsx-indent-props": [2, 2],
186 | "react/jsx-key": 2,
187 | "react/jsx-max-props-per-line": [2, {"maximum": 3}],
188 | "react/jsx-no-duplicate-props": 2,
189 | "react/jsx-no-undef": 2,
190 | "react/jsx-sort-props": 2,
191 | "react/jsx-sort-prop-types": 2,
192 | "react/jsx-uses-react": 2,
193 | "react/jsx-uses-vars": 2,
194 | "react/no-did-mount-set-state": [2, "allow-in-func"],
195 | "react/no-did-update-set-state": 2,
196 | "react/no-direct-mutation-state": 2,
197 | "react/no-multi-comp": 2,
198 | "react/no-unknown-property": 2,
199 | "react/prop-types": 2,
200 | "react/react-in-jsx-scope": 2,
201 | "react/self-closing-comp": 2,
202 | "react/wrap-multilines": 2,
203 | "react/sort-comp": [2, {
204 | "order": [
205 | "displayName",
206 | "baseClass",
207 | "mixins",
208 | "statics",
209 | "propTypes",
210 | "contextTypes",
211 | "childContextTypes",
212 | "getDefaultProps",
213 | "getInitialState",
214 | "componentWillMount",
215 | "componentDidMount",
216 | "componentWillReceiveProps",
217 | "shouldComponentUpdate",
218 | "componentWillUpdate",
219 | "componentWillUnmount",
220 | "/^component.+$/",
221 | "/^has.+$/",
222 | "/^reset.+$/",
223 | "/^get.+$/",
224 | "/^_handle.+$/",
225 | "/^_.+$/",
226 | "/^_render.+$/",
227 | "render"
228 | ]
229 | }]
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | demo/public/bundle.js
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - 4.2.3
5 | before_deploy:
6 | - npm run demo
7 | deploy:
8 | provider: heroku
9 | skip_cleanup: true
10 | api_key:
11 | secure: O1xa08rqlaa66l193GEblNSBCbjlYhn8pVIf0zHXXW6VlIt0yuhkudfG0deTB9f+qXx9u/wWxwvcgZ+mmwUq54MQujgHV4gKMzj8gVtXF6ZEeIK0JYrbe5EQzg5ZLj1+ioopmoIhvL9gJZCiselgfs5laaD3HE6PKpNLrAnIYbZ+Luc6T0bJLqAZqcog4UMuwvq6QrQg1L7l3AljG+9EkwQXiiSXWZFnOPF+0ok4lWmXZrPE7m6vB90JaIxZW/ffkfpYZOUFhE8UGG/Cl4N8FnOsr5NqdHaXyTMl/NWQ13zIei+vsBsunaIAQNvVg77XSzbvLSpdwwVPcQdyw+qKv938JjWCzWBLIGMXZdJRCPZJdqZt+J47Tvy6q4uKEXW7k4/xHO5oafhUVVpmg6yKjzZJPwH1vKWIYp39SAP1edXdV7bYhOu9lOxba5N22bnfkt96ynQUsLlhNoM6JBF5+ghrWKYHpUF9lOaMxcjE4r8ElyjPheUv+cN6WXSvF2KQeZbvv+PvwHos2Xz4XRhR1BvF380FVqUHlZ41fsL2oL0RbGpylHV3BxjPMNm/W5WXtIeXpkPLC4m8hOYRgqyis5EHZWHc5tmyotcdyI2I+vgKBL4ms0LcXkC0TeWR049gkdK21IQ4kwWkiOtUr1B0A5eTlcIbD7qiyWYhlI32rdY=
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 YPlan
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 | [](https://travis-ci.org/YPlan/react-pulldown)
2 |
3 | React Pulldown
4 | ==============
5 |
6 | A Pulldown component for [React.js](http://facebook.github.io/react/)
7 |
8 | Installation
9 | ------------
10 |
11 | ```sh
12 | $ npm install react-pulldown --save
13 | ```
14 |
15 | Demo
16 | ----
17 |
18 | [https://react-pulldown.herokuapp.com/](https://react-pulldown.herokuapp.com/)
19 |
20 | Usage
21 | -----
22 |
23 | See the [API](/docs/API.md) section for details.
24 |
25 | ```jsx
26 | import React from 'react';
27 | import ReactDOM from 'react-dom';
28 | import {PulldownClose, PulldownGoto, PulldownStage, Pulldown} from 'react-pulldown';
29 |
30 | const App = React.createClass({
31 |
32 | render() {
33 | return (
34 |
39 |
40 |
44 | First stage
45 |
48 | Next
49 |
50 |
51 |
52 |
57 | Second stage
58 |
59 | Close
60 |
61 |
62 |
63 |
64 | );
65 | },
66 |
67 | });
68 |
69 | ReactDOM.render(, document.getElementById('app'));
70 | ```
71 |
72 | Test
73 | ----
74 |
75 | ```sh
76 | $ npm test
77 | ```
78 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import {PulldownClose, PulldownGoto, PulldownStage, Pulldown} from '../dist';
4 |
5 | const App = React.createClass({
6 |
7 | getInitialState() {
8 | return {
9 | closed: false,
10 | countdown: 3,
11 | };
12 | },
13 |
14 | componentDidMount() {
15 | const interval = setInterval(() => {
16 | const {countdown} = this.state;
17 |
18 | if (countdown > 0) {
19 | this.setState({
20 | countdown: countdown - 1,
21 | });
22 | } else {
23 | clearTimeout(interval);
24 | }
25 | }, 1000);
26 | },
27 |
28 | _handleClose() {
29 | this.setState({
30 | closed: true,
31 | });
32 | },
33 |
34 | _renderCountdown() {
35 | const {countdown} = this.state;
36 |
37 | return (
38 |
39 | The pulldown will appear in
40 |
41 | {countdown}s
42 |
43 |
44 | );
45 | },
46 |
47 | _renderCredits() {
48 | return (
49 |
62 | );
63 | },
64 |
65 | render() {
66 | const {closed, countdown} = this.state;
67 |
68 | return (
69 |
70 | {countdown > 0 && this._renderCountdown()}
71 | {closed && this._renderCredits()}
72 |
73 |
80 |
81 |
86 |
87 | The
88 |
92 | next
93 |
94 | stage will come from the top
95 |
96 |
97 |
98 |
104 |
105 | The
106 |
110 | next
111 |
112 | one from the bottom
113 |
114 |
115 |
116 |
122 |
123 | The
124 |
128 | next
129 |
130 | one is gonna be bigger
131 |
132 |
133 |
134 |
140 |
141 | The
142 |
146 | last
147 |
148 | one is smaller
149 |
150 |
151 |
152 |
158 |
159 | See ya!
160 |
163 | Close
164 |
165 |
166 |
167 |
168 |
169 |
170 | );
171 | },
172 |
173 | });
174 |
175 | ReactDOM.render(, document.getElementById('app'));
176 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React Pulldown
5 |
6 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/demo/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 |
4 | app.use(express.static(__dirname + '/public'));
5 |
6 | var host = process.env.IP || '0.0.0.0',
7 | port = process.env.PORT || 3000;
8 |
9 | var server = app.listen(port, host, function() {
10 | console.log('Listening at http://%s:%s', host, port);
11 | });
12 |
--------------------------------------------------------------------------------
/dist/components/__tests__/integration-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _react = require('react');
4 |
5 | var _react2 = _interopRequireDefault(_react);
6 |
7 | var _reactAddonsTestUtils = require('react-addons-test-utils');
8 |
9 | var _reactAddonsTestUtils2 = _interopRequireDefault(_reactAddonsTestUtils);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | jest.dontMock('shallowequal');
14 | jest.dontMock('../pulldown');
15 | jest.dontMock('../pulldown-stage');
16 |
17 | var Pulldown = require('../pulldown').default;
18 | var PulldownStage = require('../pulldown-stage').default;
19 |
20 | describe('Pulldown', function () {
21 | it('updates its height when the current stage height changes', function () {
22 | var Parent = _react2.default.createClass({
23 | displayName: 'Parent',
24 | getInitialState: function getInitialState() {
25 | return {
26 | height: 100
27 | };
28 | },
29 | render: function render() {
30 | var height = this.state.height;
31 |
32 | return _react2.default.createElement(
33 | Pulldown,
34 | {
35 | className: 'pulldown',
36 | defaultStage: 'a'
37 | },
38 | _react2.default.createElement(PulldownStage, {
39 | height: height,
40 | name: 'a'
41 | })
42 | );
43 | }
44 | });
45 | var parent = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(Parent, null));
46 | var pulldown = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithClass(parent, 'pulldown');
47 |
48 | expect(pulldown.style.height).toBe('100px');
49 | parent.setState({
50 | height: 200
51 | });
52 | expect(pulldown.style.height).toBe('200px');
53 | });
54 | });
--------------------------------------------------------------------------------
/dist/components/__tests__/pulldown-close-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _react = require('react');
4 |
5 | var _react2 = _interopRequireDefault(_react);
6 |
7 | var _reactAddonsTestUtils = require('react-addons-test-utils');
8 |
9 | var _reactAddonsTestUtils2 = _interopRequireDefault(_reactAddonsTestUtils);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | jest.dontMock('../pulldown-close');
14 |
15 | var PulldownClose = require('../pulldown-close').default;
16 |
17 | describe('PulldownClose', function () {
18 | var className = 'className';
19 | var close = undefined;
20 | var pulldownClose = undefined;
21 |
22 | beforeEach(function () {
23 | close = jest.genMockFunction();
24 |
25 | var FakeContext = _react2.default.createClass({
26 | displayName: 'FakeContext',
27 |
28 | propTypes: {
29 | children: _react2.default.PropTypes.element
30 | },
31 | childContextTypes: {
32 | close: _react2.default.PropTypes.func
33 | },
34 | getChildContext: function getChildContext() {
35 | return {
36 | close: close
37 | };
38 | },
39 | render: function render() {
40 | return _react2.default.createElement(
41 | 'div',
42 | null,
43 | this.props.children
44 | );
45 | }
46 | });
47 |
48 | pulldownClose = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
49 | FakeContext,
50 | null,
51 | _react2.default.createElement(PulldownClose, { className: className })
52 | ));
53 | });
54 |
55 | it('applies the class name', function () {
56 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldownClose, 'button');
57 |
58 | expect(element.className).toBe(className);
59 | });
60 |
61 | it('calls the callback when clicked', function () {
62 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldownClose, 'button');
63 | _reactAddonsTestUtils2.default.Simulate.click(element);
64 |
65 | expect(close).toBeCalled();
66 | });
67 | });
--------------------------------------------------------------------------------
/dist/components/__tests__/pulldown-goto-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _react = require('react');
4 |
5 | var _react2 = _interopRequireDefault(_react);
6 |
7 | var _reactAddonsTestUtils = require('react-addons-test-utils');
8 |
9 | var _reactAddonsTestUtils2 = _interopRequireDefault(_reactAddonsTestUtils);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | jest.dontMock('../pulldown-goto');
14 |
15 | var PulldownGoto = require('../pulldown-goto').default;
16 |
17 | describe('PulldownGoto', function () {
18 | var className = 'className';
19 | var stage = 'stage';
20 | var goTo = undefined;
21 | var pulldownGoto = undefined;
22 |
23 | beforeEach(function () {
24 | goTo = jest.genMockFunction();
25 |
26 | var FakeContext = _react2.default.createClass({
27 | displayName: 'FakeContext',
28 |
29 | propTypes: {
30 | children: _react2.default.PropTypes.element
31 | },
32 | childContextTypes: {
33 | goTo: _react2.default.PropTypes.func
34 | },
35 | getChildContext: function getChildContext() {
36 | return {
37 | goTo: goTo
38 | };
39 | },
40 | render: function render() {
41 | return _react2.default.createElement(
42 | 'div',
43 | null,
44 | this.props.children
45 | );
46 | }
47 | });
48 |
49 | pulldownGoto = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
50 | FakeContext,
51 | null,
52 | _react2.default.createElement(PulldownGoto, { className: className, stage: stage })
53 | ));
54 | });
55 |
56 | it('applies the class name', function () {
57 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldownGoto, 'button');
58 |
59 | expect(element.className).toBe(className);
60 | });
61 |
62 | it('calls the callback when clicked', function () {
63 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldownGoto, 'button');
64 | _reactAddonsTestUtils2.default.Simulate.click(element);
65 |
66 | expect(goTo).toBeCalledWith(stage);
67 | });
68 | });
--------------------------------------------------------------------------------
/dist/components/__tests__/pulldown-stage-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _react = require('react');
4 |
5 | var _react2 = _interopRequireDefault(_react);
6 |
7 | var _reactAddonsTestUtils = require('react-addons-test-utils');
8 |
9 | var _reactAddonsTestUtils2 = _interopRequireDefault(_reactAddonsTestUtils);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | /* eslint react/no-multi-comp:0 */
14 |
15 | jest.dontMock('shallowequal');
16 | jest.dontMock('../pulldown-stage');
17 |
18 | var PulldownStage = require('../pulldown-stage').default;
19 |
20 | function createFakeContext(childContext) {
21 | return _react2.default.createClass({
22 | propTypes: {
23 | children: _react2.default.PropTypes.element
24 | },
25 | childContextTypes: {
26 | currentStage: _react2.default.PropTypes.object,
27 | previousStage: _react2.default.PropTypes.object,
28 | update: _react2.default.PropTypes.func
29 | },
30 | getChildContext: function getChildContext() {
31 | return childContext;
32 | },
33 | render: function render() {
34 | return _react2.default.createElement(
35 | 'div',
36 | null,
37 | this.props.children
38 | );
39 | }
40 | });
41 | }
42 |
43 | describe('PulldownStage', function () {
44 | var FakeContext = undefined;
45 | var pulldownStage = undefined;
46 |
47 | describe('current stage', function () {
48 | beforeEach(function () {
49 | FakeContext = createFakeContext({
50 | currentStage: {
51 | name: 'currentStage'
52 | }
53 | });
54 |
55 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
56 | FakeContext,
57 | null,
58 | _react2.default.createElement(PulldownStage, {
59 | className: 'className',
60 | height: 100,
61 | name: 'currentStage'
62 | })
63 | ));
64 | });
65 |
66 | it('applies the class modifier', function () {
67 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
68 |
69 | expect(element.className).toContain('className--active');
70 | });
71 |
72 | it('sets the top to zero', function () {
73 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
74 |
75 | expect(element.style.top).toBe('0px');
76 | });
77 | });
78 |
79 | describe('previous stage (top)', function () {
80 | beforeEach(function () {
81 | FakeContext = createFakeContext({
82 | currentStage: {
83 | direction: 'top',
84 | height: 100,
85 | name: 'currentStage'
86 | },
87 | previousStage: {
88 | name: 'previousStage'
89 | }
90 | });
91 |
92 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
93 | FakeContext,
94 | null,
95 | _react2.default.createElement(PulldownStage, {
96 | height: 100,
97 | name: 'previousStage'
98 | })
99 | ));
100 | });
101 |
102 | it('sets the top to height', function () {
103 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
104 |
105 | expect(element.style.top).toBe('100px');
106 | });
107 | });
108 |
109 | describe('previous stage (bottom)', function () {
110 | beforeEach(function () {
111 | FakeContext = createFakeContext({
112 | currentStage: {
113 | direction: 'bottom',
114 | height: 100,
115 | name: 'currentStage'
116 | },
117 | previousStage: {
118 | name: 'previousStage'
119 | }
120 | });
121 |
122 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
123 | FakeContext,
124 | null,
125 | _react2.default.createElement(PulldownStage, {
126 | height: 100,
127 | name: 'previousStage'
128 | })
129 | ));
130 | });
131 |
132 | it('sets the top to negative height', function () {
133 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
134 |
135 | expect(element.style.top).toBe('-100px');
136 | });
137 | });
138 |
139 | describe('stage', function () {
140 | beforeEach(function () {
141 | FakeContext = createFakeContext({
142 | currentStage: {}
143 | });
144 |
145 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
146 | FakeContext,
147 | null,
148 | _react2.default.createElement(PulldownStage, {
149 | className: 'className',
150 | height: 100,
151 | name: 'name'
152 | })
153 | ));
154 | });
155 |
156 | it('applies the class name', function () {
157 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
158 |
159 | expect(element.className).toContain('className');
160 | });
161 | });
162 |
163 | describe('stage (height > currentStage height)', function () {
164 | beforeEach(function () {
165 | FakeContext = createFakeContext({
166 | currentStage: {
167 | height: 100
168 | }
169 | });
170 |
171 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
172 | FakeContext,
173 | null,
174 | _react2.default.createElement(PulldownStage, {
175 | direction: 'top',
176 | height: 101,
177 | name: 'name'
178 | })
179 | ));
180 | });
181 |
182 | it('sets the top to the maximum height', function () {
183 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
184 |
185 | expect(element.style.top).toBe('-101px');
186 | });
187 | });
188 |
189 | describe('stage (height < currentStage height)', function () {
190 | beforeEach(function () {
191 | FakeContext = createFakeContext({
192 | currentStage: {
193 | height: 101
194 | }
195 | });
196 |
197 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
198 | FakeContext,
199 | null,
200 | _react2.default.createElement(PulldownStage, {
201 | direction: 'top',
202 | height: 100,
203 | name: 'name'
204 | })
205 | ));
206 | });
207 |
208 | it('sets the top to the maximum height', function () {
209 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
210 |
211 | expect(element.style.top).toBe('-101px');
212 | });
213 | });
214 |
215 | describe('stage (top)', function () {
216 | beforeEach(function () {
217 | FakeContext = createFakeContext({
218 | currentStage: {
219 | height: 100
220 | }
221 | });
222 |
223 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
224 | FakeContext,
225 | null,
226 | _react2.default.createElement(PulldownStage, {
227 | direction: 'top',
228 | height: 100,
229 | name: 'name'
230 | })
231 | ));
232 | });
233 |
234 | it('sets the top to the negative height', function () {
235 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
236 |
237 | expect(element.style.top).toBe('-100px');
238 | });
239 | });
240 |
241 | describe('stage (bottom)', function () {
242 | beforeEach(function () {
243 | FakeContext = createFakeContext({
244 | currentStage: {
245 | height: 100
246 | }
247 | });
248 |
249 | pulldownStage = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
250 | FakeContext,
251 | null,
252 | _react2.default.createElement(PulldownStage, {
253 | direction: 'bottom',
254 | height: 100,
255 | name: 'name'
256 | })
257 | ));
258 | });
259 |
260 | it('sets the top to the height', function () {
261 | var element = _reactAddonsTestUtils2.default.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
262 |
263 | expect(element.style.top).toBe('100px');
264 | });
265 | });
266 |
267 | describe('update', function () {
268 | function createParent(childContext) {
269 | FakeContext = createFakeContext(childContext);
270 | return _react2.default.createClass({
271 | getInitialState: function getInitialState() {
272 | return {
273 | height: 100
274 | };
275 | },
276 | render: function render() {
277 | var height = this.state.height;
278 |
279 | return _react2.default.createElement(
280 | FakeContext,
281 | null,
282 | _react2.default.createElement(PulldownStage, {
283 | height: height,
284 | name: 'a'
285 | })
286 | );
287 | }
288 | });
289 | }
290 |
291 | it('fires the callback if is current and props change', function () {
292 | var update = jest.genMockFunction();
293 | var Parent = createParent({
294 | currentStage: {
295 | name: 'a'
296 | },
297 | update: update
298 | });
299 | var parent = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(Parent, null));
300 | parent.setState({
301 | height: 200
302 | });
303 |
304 | expect(update).toBeCalled();
305 | });
306 |
307 | it('does not fire the callback if is current and props do not change', function () {
308 | var update = jest.genMockFunction();
309 | var Parent = createParent({
310 | currentStage: {
311 | name: 'a'
312 | },
313 | update: update
314 | });
315 | var parent = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(Parent, null));
316 | parent.setState({
317 | height: 100
318 | });
319 |
320 | expect(update).not.toBeCalled();
321 | });
322 |
323 | it('does not fire the callback if is not current', function () {
324 | var update = jest.genMockFunction();
325 | var Parent = createParent({
326 | currentStage: {
327 | name: 'b',
328 | height: 100
329 | },
330 | update: update
331 | });
332 | var parent = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(Parent, null));
333 | parent.setState({
334 | height: 200
335 | });
336 |
337 | expect(update).not.toBeCalled();
338 | });
339 | });
340 | });
--------------------------------------------------------------------------------
/dist/components/__tests__/pulldown-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _react = require('react');
4 |
5 | var _react2 = _interopRequireDefault(_react);
6 |
7 | var _reactAddonsTestUtils = require('react-addons-test-utils');
8 |
9 | var _reactAddonsTestUtils2 = _interopRequireDefault(_reactAddonsTestUtils);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | jest.dontMock('../pulldown');
14 |
15 | var Pulldown = require('../pulldown').default;
16 | var PulldownStage = require('../pulldown-stage').default;
17 |
18 | describe('Pulldown', function () {
19 | var className = 'className';
20 | var defaultStageName = 'a';
21 | var delay = 1000;
22 | var height = 100;
23 | var nextStageName = 'b';
24 | var onChange = jest.genMockFunction();
25 | var onClose = jest.genMockFunction();
26 | var onOpen = jest.genMockFunction();
27 | var pulldown = undefined;
28 |
29 | describe('default', function () {
30 | beforeEach(function () {
31 | pulldown = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
32 | Pulldown,
33 | {
34 | className: className,
35 | defaultStage: defaultStageName,
36 | delay: delay,
37 | onChange: onChange,
38 | onClose: onClose,
39 | onOpen: onOpen
40 | },
41 | _react2.default.createElement(PulldownStage, { height: height, name: defaultStageName }),
42 | _react2.default.createElement(PulldownStage, { name: nextStageName })
43 | ));
44 | });
45 |
46 | it('applies the class name', function () {
47 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
48 |
49 | expect(element.className).toBe(className);
50 | });
51 |
52 | it('sets the current stage object', function () {
53 | expect(pulldown.state.currentStage.name).toBe(defaultStageName);
54 | });
55 |
56 | it('sets its height according to the current stage height', function () {
57 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
58 |
59 | expect(element.style.height).toBe(height + 'px');
60 | });
61 |
62 | it('fires the onChange callback', function () {
63 | pulldown._goTo(nextStageName);
64 |
65 | expect(onChange).toBeCalledWith(nextStageName);
66 | });
67 |
68 | it('fires the onClose callback', function () {
69 | pulldown._close();
70 |
71 | expect(onClose).toBeCalled();
72 | });
73 |
74 | it('disappears when closed', function () {
75 | pulldown._close();
76 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
77 |
78 | expect(element.style.marginTop).toBe('-' + height + 'px');
79 | });
80 |
81 | it('does not apply the open modifier when closed', function () {
82 | pulldown._close();
83 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
84 |
85 | expect(element.className).not.toContain(className + '--open');
86 | });
87 |
88 | it('fires the onOpen callback', function () {
89 | jest.runAllTimers();
90 |
91 | expect(onOpen).toBeCalled();
92 | });
93 |
94 | it('appears when open', function () {
95 | jest.runAllTimers();
96 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
97 |
98 | expect(element.style.marginTop).toBe('0');
99 | });
100 |
101 | it('applies the open modifier when open', function () {
102 | jest.runAllTimers();
103 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
104 |
105 | expect(element.className).toContain(className + '--open');
106 | });
107 |
108 | it('waits for the delay to appear', function () {
109 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
110 | expect(element.style.marginTop).toBe('-' + height + 'px');
111 |
112 | jest.runAllTimers();
113 | expect(element.style.marginTop).toBe('0');
114 | });
115 | });
116 |
117 | describe('fixed', function () {
118 | beforeEach(function () {
119 | pulldown = _reactAddonsTestUtils2.default.renderIntoDocument(_react2.default.createElement(
120 | Pulldown,
121 | {
122 | defaultStage: defaultStageName,
123 | delay: delay,
124 | fixed: true
125 | },
126 | _react2.default.createElement(PulldownStage, { height: height, name: defaultStageName })
127 | ));
128 | });
129 |
130 | it('disappears when closed', function () {
131 | pulldown._close();
132 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
133 |
134 | expect(element.style.top).toBe('-100%');
135 | });
136 |
137 | it('appears when open', function () {
138 | jest.runAllTimers();
139 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
140 |
141 | expect(element.style.top).toBe('0px');
142 | });
143 |
144 | it('waits for the delay to appear', function () {
145 | var element = _reactAddonsTestUtils2.default.findRenderedDOMComponentWithTag(pulldown, 'div');
146 | expect(element.style.top).toBe('-100%');
147 |
148 | jest.runAllTimers();
149 | expect(element.style.top).toBe('0px');
150 | });
151 | });
152 | });
--------------------------------------------------------------------------------
/dist/components/pulldown-close.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _react = require('react');
8 |
9 | var _react2 = _interopRequireDefault(_react);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | var PulldownClose = _react2.default.createClass({
14 | displayName: 'PulldownClose',
15 |
16 | propTypes: {
17 | children: _react2.default.PropTypes.any,
18 | className: _react2.default.PropTypes.string
19 | },
20 |
21 | contextTypes: {
22 | close: _react2.default.PropTypes.func
23 | },
24 |
25 | _handleClick: function _handleClick() {
26 | var close = this.context.close;
27 |
28 | close();
29 | },
30 | render: function render() {
31 | var _props = this.props;
32 | var children = _props.children;
33 | var className = _props.className;
34 |
35 | return _react2.default.createElement(
36 | 'button',
37 | { className: className, onClick: this._handleClick },
38 | children
39 | );
40 | }
41 | });
42 |
43 | exports.default = PulldownClose;
--------------------------------------------------------------------------------
/dist/components/pulldown-goto.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _react = require('react');
8 |
9 | var _react2 = _interopRequireDefault(_react);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | var PulldownGoto = _react2.default.createClass({
14 | displayName: 'PulldownGoto',
15 |
16 | propTypes: {
17 | children: _react2.default.PropTypes.any,
18 | className: _react2.default.PropTypes.string,
19 | stage: _react2.default.PropTypes.string.isRequired
20 | },
21 |
22 | contextTypes: {
23 | goTo: _react2.default.PropTypes.func
24 | },
25 |
26 | _handleClick: function _handleClick() {
27 | var goTo = this.context.goTo;
28 | var stage = this.props.stage;
29 |
30 | goTo(stage);
31 | },
32 | render: function render() {
33 | var _props = this.props;
34 | var children = _props.children;
35 | var className = _props.className;
36 |
37 | return _react2.default.createElement(
38 | 'button',
39 | { className: className, onClick: this._handleClick },
40 | children
41 | );
42 | }
43 | });
44 |
45 | exports.default = PulldownGoto;
--------------------------------------------------------------------------------
/dist/components/pulldown-stage.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _react = require('react');
8 |
9 | var _react2 = _interopRequireDefault(_react);
10 |
11 | var _shallowequal = require('shallowequal');
12 |
13 | var _shallowequal2 = _interopRequireDefault(_shallowequal);
14 |
15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16 |
17 | var PulldownStage = _react2.default.createClass({
18 | displayName: 'PulldownStage',
19 |
20 | propTypes: {
21 | children: _react2.default.PropTypes.any,
22 | className: _react2.default.PropTypes.string,
23 | direction: _react2.default.PropTypes.string,
24 | height: _react2.default.PropTypes.number.isRequired,
25 | name: _react2.default.PropTypes.string.isRequired
26 | },
27 |
28 | contextTypes: {
29 | currentStage: _react2.default.PropTypes.object,
30 | previousStage: _react2.default.PropTypes.object,
31 | update: _react2.default.PropTypes.func
32 | },
33 |
34 | componentDidUpdate: function componentDidUpdate(prevProps) {
35 | var update = this.context.update;
36 |
37 | if (this._isCurrentStage() && !(0, _shallowequal2.default)(prevProps, this.props)) {
38 | update();
39 | }
40 | },
41 | _isCurrentStage: function _isCurrentStage() {
42 | var currentStage = this.context.currentStage;
43 | var name = this.props.name;
44 |
45 | return currentStage.name === name;
46 | },
47 | _getDirection: function _getDirection() {
48 | var _context = this.context;
49 | var currentStage = _context.currentStage;
50 | var previousStage = _context.previousStage;
51 | var _props = this.props;
52 | var direction = _props.direction;
53 | var name = _props.name;
54 |
55 | if (previousStage && previousStage.name === name) {
56 | this.direction = currentStage.direction === 'top' ? 'bottom' : 'top';
57 | }
58 |
59 | return this.direction || direction;
60 | },
61 | _getHeight: function _getHeight() {
62 | var currentStage = this.context.currentStage;
63 | var height = this.props.height;
64 |
65 | return this._isCurrentStage() ? 0 : Math.max(height, currentStage.height);
66 | },
67 | _getTop: function _getTop() {
68 | var height = this._getHeight();
69 |
70 | return this._getDirection() === 'top' ? -height : height;
71 | },
72 | _getStyle: function _getStyle() {
73 | var height = this.props.height;
74 |
75 | return {
76 | height: height,
77 | position: 'absolute',
78 | top: this._getTop(),
79 | transition: 'all 500ms',
80 | width: '100%'
81 | };
82 | },
83 | render: function render() {
84 | var children = this.props.children;
85 | var className = this.props.className;
86 |
87 | if (className && this._isCurrentStage()) {
88 | className += ' ' + className + '--active';
89 | }
90 |
91 | return _react2.default.createElement(
92 | 'div',
93 | { className: className, style: this._getStyle() },
94 | children
95 | );
96 | }
97 | });
98 |
99 | exports.default = PulldownStage;
--------------------------------------------------------------------------------
/dist/components/pulldown.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14 |
15 | var Pulldown = _react2.default.createClass({
16 | displayName: 'Pulldown',
17 |
18 | propTypes: {
19 | children: _react2.default.PropTypes.any,
20 | className: _react2.default.PropTypes.string,
21 | defaultStage: _react2.default.PropTypes.string.isRequired,
22 | delay: _react2.default.PropTypes.number,
23 | fixed: _react2.default.PropTypes.bool,
24 | onChange: _react2.default.PropTypes.func,
25 | onClose: _react2.default.PropTypes.func,
26 | onOpen: _react2.default.PropTypes.func
27 | },
28 |
29 | childContextTypes: {
30 | close: _react2.default.PropTypes.func,
31 | currentStage: _react2.default.PropTypes.object,
32 | goTo: _react2.default.PropTypes.func,
33 | previousStage: _react2.default.PropTypes.object,
34 | update: _react2.default.PropTypes.func
35 | },
36 |
37 | getInitialState: function getInitialState() {
38 | var _props = this.props;
39 | var defaultStage = _props.defaultStage;
40 | var delay = _props.delay;
41 |
42 | return {
43 | currentStage: this._getStage(defaultStage),
44 | open: !delay
45 | };
46 | },
47 | componentDidMount: function componentDidMount() {
48 | var delay = this.props.delay;
49 |
50 | this.timeout = setTimeout(this._open, delay);
51 | },
52 | componentWillUnmount: function componentWillUnmount() {
53 | if (this.timeout) {
54 | clearTimeout(this.timeout);
55 | }
56 | },
57 | getChildContext: function getChildContext() {
58 | var _state = this.state;
59 | var currentStage = _state.currentStage;
60 | var previousStage = _state.previousStage;
61 |
62 | return {
63 | close: this._close,
64 | currentStage: currentStage,
65 | goTo: this._goTo,
66 | previousStage: previousStage,
67 | update: this._update
68 | };
69 | },
70 | _getStage: function _getStage(stageName) {
71 | var children = this.props.children;
72 |
73 | var currentStage = undefined;
74 |
75 | _react2.default.Children.forEach(children, function (child) {
76 | if (child.props.name === stageName) {
77 | currentStage = child.props;
78 | }
79 | });
80 |
81 | return currentStage;
82 | },
83 | _goTo: function _goTo(stageName) {
84 | var onChange = this.props.onChange;
85 |
86 | this.setState({
87 | currentStage: this._getStage(stageName),
88 | previousStage: this.state.currentStage
89 | }, function () {
90 | if (onChange) {
91 | onChange(stageName);
92 | }
93 | });
94 | },
95 | _close: function _close() {
96 | var onClose = this.props.onClose;
97 |
98 | this.setState({
99 | open: false
100 | }, onClose);
101 | },
102 | _open: function _open() {
103 | var onOpen = this.props.onOpen;
104 |
105 | this.setState({
106 | open: true
107 | }, onOpen);
108 | },
109 | _update: function _update() {
110 | var currentStage = this.state.currentStage;
111 |
112 | this.setState({
113 | currentStage: this._getStage(currentStage.name)
114 | });
115 | },
116 | _getStyle: function _getStyle() {
117 | var _state2 = this.state;
118 | var currentStage = _state2.currentStage;
119 | var open = _state2.open;
120 | var fixed = this.props.fixed;
121 |
122 | var style = undefined;
123 |
124 | if (fixed) {
125 | style = {
126 | height: currentStage.height,
127 | position: 'fixed',
128 | top: open ? 0 : '-100%',
129 | zIndex: 999
130 | };
131 | } else {
132 | style = {
133 | height: currentStage.height,
134 | marginTop: open ? 0 : -currentStage.height + 'px',
135 | position: 'relative'
136 | };
137 | }
138 |
139 | return _extends({
140 | overflow: 'hidden',
141 | transition: 'all 500ms',
142 | width: '100%'
143 | }, style);
144 | },
145 | render: function render() {
146 | var children = this.props.children;
147 | var className = this.props.className;
148 | var open = this.state.open;
149 |
150 | if (className && open) {
151 | className += ' ' + className + '--open';
152 | }
153 |
154 | return _react2.default.createElement(
155 | 'div',
156 | { className: className, style: this._getStyle() },
157 | children
158 | );
159 | }
160 | });
161 |
162 | exports.default = Pulldown;
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.Pulldown = exports.PulldownStage = exports.PulldownGoto = exports.PulldownClose = undefined;
7 |
8 | var _pulldownClose = require('./components/pulldown-close');
9 |
10 | var _pulldownClose2 = _interopRequireDefault(_pulldownClose);
11 |
12 | var _pulldownGoto = require('./components/pulldown-goto');
13 |
14 | var _pulldownGoto2 = _interopRequireDefault(_pulldownGoto);
15 |
16 | var _pulldownStage = require('./components/pulldown-stage');
17 |
18 | var _pulldownStage2 = _interopRequireDefault(_pulldownStage);
19 |
20 | var _pulldown = require('./components/pulldown');
21 |
22 | var _pulldown2 = _interopRequireDefault(_pulldown);
23 |
24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25 |
26 | exports.PulldownClose = _pulldownClose2.default;
27 | exports.PulldownGoto = _pulldownGoto2.default;
28 | exports.PulldownStage = _pulldownStage2.default;
29 | exports.Pulldown = _pulldown2.default;
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # API Reference
2 |
3 | * [Pulldown](#pulldown)
4 | * [PulldownStage](#pulldownstage)
5 | * [PulldownGoto](#pulldowngoto)
6 | * [PulldownClose](#pulldownclose)
7 |
8 | ## Pulldown
9 |
10 | | Name | Type | Required | Description |
11 | | ---- | ---- | :------: | ----------- |
12 | | className | string | | The class applied to the root element. |
13 | | defaultStage | string | x | The name of the first stage. |
14 | | delay | number | | The number of milliseconds to pass before the component appears. |
15 | | fixed | bool | | The style of the root element. |
16 | | onChange | func | | The callback fired everytime the current stage changes. |
17 | | onClose | func | | The callback fired when the pulldown closes. |
18 | | onOpen | func | | The callback fired when the pulldown opens. |
19 |
20 | ## PulldownStage
21 |
22 | | Name | Type | Required | Description |
23 | | ---- | ---- | :------: | ----------- |
24 | | className | string | | The class applied to the root element. |
25 | | direction | string | x | The direction from where the stage will appear. |
26 | | height | number | x | The height in pixels. |
27 | | name | string | | The name of the stage. |
28 |
29 | ## PulldownGoto
30 |
31 | | Name | Type | Required | Description |
32 | | ---- | ---- | :------: | ----------- |
33 | | className | string | | The class applied to the root element. |
34 | | name | string | x | The destination stage. |
35 |
36 | ## PulldownClose
37 |
38 | | Name | Type | Required | Description |
39 | | ---- | ---- | :------: | ----------- |
40 | | className | string | | The class applied to the root element. |
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-pulldown",
3 | "version": "0.1.0",
4 | "description": "A Pulldown component for React.js",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "lint": "eslint ./src",
9 | "build": "babel ./src --out-dir ./dist",
10 | "demo": "webpack ./demo/index.js ./demo/public/bundle.js",
11 | "start": "node ./demo/server"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/YPlan/react-pulldown.git"
16 | },
17 | "keywords": [
18 | "react",
19 | "react-component",
20 | "pulldown"
21 | ],
22 | "authors": [
23 | "Alex Andrews",
24 | "Ben Davis",
25 | "Lucas Motta",
26 | "Michele Bertoli"
27 | ],
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/YPlan/react-pulldown/issues"
31 | },
32 | "homepage": "https://github.com/YPlan/react-pulldown#readme",
33 | "devDependencies": {
34 | "babel-cli": "^6.3.17",
35 | "babel-core": "^6.3.21",
36 | "babel-eslint": "^4.1.6",
37 | "babel-jest": "^6.0.1",
38 | "babel-loader": "^6.2.0",
39 | "babel-preset-es2015": "^6.3.13",
40 | "babel-preset-react": "^6.3.13",
41 | "babel-preset-stage-2": "^6.3.13",
42 | "eslint": "^1.10.3",
43 | "eslint-plugin-react": "^3.12.0",
44 | "express": "^4.13.3",
45 | "jest-cli": "^0.8.2",
46 | "react-addons-test-utils": "^0.14.3",
47 | "webpack": "^1.12.9"
48 | },
49 | "jest": {
50 | "testPathDirs": [
51 | "./src"
52 | ],
53 | "scriptPreprocessor": "/node_modules/babel-jest",
54 | "unmockedModulePathPatterns": [
55 | "/node_modules/react",
56 | "/node_modules/react-dom",
57 | "/node_modules/react-addons-test-utils",
58 | "/node_modules/fbjs"
59 | ]
60 | },
61 | "dependencies": {
62 | "react": "^0.14.3",
63 | "react-dom": "^0.14.3",
64 | "shallowequal": "^0.2.2"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/__tests__/integration-test.js:
--------------------------------------------------------------------------------
1 | jest.dontMock('shallowequal');
2 | jest.dontMock('../pulldown');
3 | jest.dontMock('../pulldown-stage');
4 |
5 | import React from 'react';
6 | import TestUtils from 'react-addons-test-utils';
7 |
8 | const Pulldown = require('../pulldown').default;
9 | const PulldownStage = require('../pulldown-stage').default;
10 |
11 | describe('Pulldown', () => {
12 | it('updates its height when the current stage height changes', () => {
13 | const Parent = React.createClass({
14 | getInitialState() {
15 | return {
16 | height: 100,
17 | };
18 | },
19 | render() {
20 | const {height} = this.state;
21 | return (
22 |
26 |
30 |
31 | );
32 | },
33 | });
34 | const parent = TestUtils.renderIntoDocument();
35 | const pulldown = TestUtils.findRenderedDOMComponentWithClass(parent, 'pulldown');
36 |
37 | expect(pulldown.style.height).toBe('100px');
38 | parent.setState({
39 | height: 200,
40 | });
41 | expect(pulldown.style.height).toBe('200px');
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/src/components/__tests__/pulldown-close-test.js:
--------------------------------------------------------------------------------
1 | jest.dontMock('../pulldown-close');
2 |
3 | import React from 'react';
4 | import TestUtils from 'react-addons-test-utils';
5 |
6 | const PulldownClose = require('../pulldown-close').default;
7 |
8 | describe('PulldownClose', () => {
9 | const className = 'className';
10 | let close;
11 | let pulldownClose;
12 |
13 | beforeEach(() => {
14 | close = jest.genMockFunction();
15 |
16 | const FakeContext = React.createClass({
17 | propTypes: {
18 | children: React.PropTypes.element,
19 | },
20 | childContextTypes: {
21 | close: React.PropTypes.func,
22 | },
23 | getChildContext() {
24 | return {
25 | close,
26 | };
27 | },
28 | render() {
29 | return {this.props.children}
;
30 | },
31 | });
32 |
33 | pulldownClose = TestUtils.renderIntoDocument(
34 |
35 |
36 |
37 | );
38 | });
39 |
40 | it('applies the class name', () => {
41 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldownClose, 'button');
42 |
43 | expect(element.className).toBe(className);
44 | });
45 |
46 | it('calls the callback when clicked', () => {
47 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldownClose, 'button');
48 | TestUtils.Simulate.click(element);
49 |
50 | expect(close).toBeCalled();
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/components/__tests__/pulldown-goto-test.js:
--------------------------------------------------------------------------------
1 | jest.dontMock('../pulldown-goto');
2 |
3 | import React from 'react';
4 | import TestUtils from 'react-addons-test-utils';
5 |
6 | const PulldownGoto = require('../pulldown-goto').default;
7 |
8 | describe('PulldownGoto', () => {
9 | const className = 'className';
10 | const stage = 'stage';
11 | let goTo;
12 | let pulldownGoto;
13 |
14 | beforeEach(() => {
15 | goTo = jest.genMockFunction();
16 |
17 | const FakeContext = React.createClass({
18 | propTypes: {
19 | children: React.PropTypes.element,
20 | },
21 | childContextTypes: {
22 | goTo: React.PropTypes.func,
23 | },
24 | getChildContext() {
25 | return {
26 | goTo,
27 | };
28 | },
29 | render() {
30 | return {this.props.children}
;
31 | },
32 | });
33 |
34 | pulldownGoto = TestUtils.renderIntoDocument(
35 |
36 |
37 |
38 | );
39 | });
40 |
41 | it('applies the class name', () => {
42 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldownGoto, 'button');
43 |
44 | expect(element.className).toBe(className);
45 | });
46 |
47 | it('calls the callback when clicked', () => {
48 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldownGoto, 'button');
49 | TestUtils.Simulate.click(element);
50 |
51 | expect(goTo).toBeCalledWith(stage);
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/src/components/__tests__/pulldown-stage-test.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0 */
2 |
3 | jest.dontMock('shallowequal');
4 | jest.dontMock('../pulldown-stage');
5 |
6 | import React from 'react';
7 | import TestUtils from 'react-addons-test-utils';
8 |
9 | const PulldownStage = require('../pulldown-stage').default;
10 |
11 | function createFakeContext(childContext) {
12 | return React.createClass({
13 | propTypes: {
14 | children: React.PropTypes.element,
15 | },
16 | childContextTypes: {
17 | currentStage: React.PropTypes.object,
18 | previousStage: React.PropTypes.object,
19 | update: React.PropTypes.func,
20 | },
21 | getChildContext() {
22 | return childContext;
23 | },
24 | render() {
25 | return {this.props.children}
;
26 | },
27 | });
28 | }
29 |
30 | describe('PulldownStage', () => {
31 | let FakeContext;
32 | let pulldownStage;
33 |
34 | describe('current stage', () => {
35 | beforeEach(() => {
36 | FakeContext = createFakeContext({
37 | currentStage: {
38 | name: 'currentStage',
39 | },
40 | });
41 |
42 | pulldownStage = TestUtils.renderIntoDocument(
43 |
44 |
49 |
50 | );
51 | });
52 |
53 | it('applies the class modifier', () => {
54 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
55 |
56 | expect(element.className).toContain('className--active');
57 | });
58 |
59 | it('sets the top to zero', () => {
60 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
61 |
62 | expect(element.style.top).toBe('0px');
63 | });
64 | });
65 |
66 | describe('previous stage (top)', () => {
67 | beforeEach(() => {
68 | FakeContext = createFakeContext({
69 | currentStage: {
70 | direction: 'top',
71 | height: 100,
72 | name: 'currentStage',
73 | },
74 | previousStage: {
75 | name: 'previousStage',
76 | },
77 | });
78 |
79 | pulldownStage = TestUtils.renderIntoDocument(
80 |
81 |
85 |
86 | );
87 | });
88 |
89 | it('sets the top to height', () => {
90 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
91 |
92 | expect(element.style.top).toBe('100px');
93 | });
94 | });
95 |
96 | describe('previous stage (bottom)', () => {
97 | beforeEach(() => {
98 | FakeContext = createFakeContext({
99 | currentStage: {
100 | direction: 'bottom',
101 | height: 100,
102 | name: 'currentStage',
103 | },
104 | previousStage: {
105 | name: 'previousStage',
106 | },
107 | });
108 |
109 | pulldownStage = TestUtils.renderIntoDocument(
110 |
111 |
115 |
116 | );
117 | });
118 |
119 | it('sets the top to negative height', () => {
120 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
121 |
122 | expect(element.style.top).toBe('-100px');
123 | });
124 | });
125 |
126 | describe('stage', () => {
127 | beforeEach(() => {
128 | FakeContext = createFakeContext({
129 | currentStage: {},
130 | });
131 |
132 | pulldownStage = TestUtils.renderIntoDocument(
133 |
134 |
139 |
140 | );
141 | });
142 |
143 | it('applies the class name', () => {
144 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
145 |
146 | expect(element.className).toContain('className');
147 | });
148 | });
149 |
150 | describe('stage (height > currentStage height)', () => {
151 | beforeEach(() => {
152 | FakeContext = createFakeContext({
153 | currentStage: {
154 | height: 100,
155 | },
156 | });
157 |
158 | pulldownStage = TestUtils.renderIntoDocument(
159 |
160 |
165 |
166 | );
167 | });
168 |
169 | it('sets the top to the maximum height', () => {
170 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
171 |
172 | expect(element.style.top).toBe('-101px');
173 | });
174 | });
175 |
176 | describe('stage (height < currentStage height)', () => {
177 | beforeEach(() => {
178 | FakeContext = createFakeContext({
179 | currentStage: {
180 | height: 101,
181 | },
182 | });
183 |
184 | pulldownStage = TestUtils.renderIntoDocument(
185 |
186 |
191 |
192 | );
193 | });
194 |
195 | it('sets the top to the maximum height', () => {
196 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
197 |
198 | expect(element.style.top).toBe('-101px');
199 | });
200 | });
201 |
202 | describe('stage (top)', () => {
203 | beforeEach(() => {
204 | FakeContext = createFakeContext({
205 | currentStage: {
206 | height: 100,
207 | },
208 | });
209 |
210 | pulldownStage = TestUtils.renderIntoDocument(
211 |
212 |
217 |
218 | );
219 | });
220 |
221 | it('sets the top to the negative height', () => {
222 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
223 |
224 | expect(element.style.top).toBe('-100px');
225 | });
226 | });
227 |
228 | describe('stage (bottom)', () => {
229 | beforeEach(() => {
230 | FakeContext = createFakeContext({
231 | currentStage: {
232 | height: 100,
233 | },
234 | });
235 |
236 | pulldownStage = TestUtils.renderIntoDocument(
237 |
238 |
243 |
244 | );
245 | });
246 |
247 | it('sets the top to the height', () => {
248 | const element = TestUtils.scryRenderedDOMComponentsWithTag(pulldownStage, 'div')[1];
249 |
250 | expect(element.style.top).toBe('100px');
251 | });
252 | });
253 |
254 | describe('update', () => {
255 | function createParent(childContext) {
256 | FakeContext = createFakeContext(childContext);
257 | return React.createClass({
258 | getInitialState() {
259 | return {
260 | height: 100,
261 | };
262 | },
263 | render() {
264 | const {height} = this.state;
265 | return (
266 |
267 |
271 |
272 | );
273 | },
274 | });
275 | }
276 |
277 | it('fires the callback if is current and props change', () => {
278 | const update = jest.genMockFunction();
279 | const Parent = createParent({
280 | currentStage: {
281 | name: 'a',
282 | },
283 | update,
284 | });
285 | const parent = TestUtils.renderIntoDocument();
286 | parent.setState({
287 | height: 200,
288 | });
289 |
290 | expect(update).toBeCalled();
291 | });
292 |
293 | it('does not fire the callback if is current and props do not change', () => {
294 | const update = jest.genMockFunction();
295 | const Parent = createParent({
296 | currentStage: {
297 | name: 'a',
298 | },
299 | update,
300 | });
301 | const parent = TestUtils.renderIntoDocument();
302 | parent.setState({
303 | height: 100,
304 | });
305 |
306 | expect(update).not.toBeCalled();
307 | });
308 |
309 | it('does not fire the callback if is not current', () => {
310 | const update = jest.genMockFunction();
311 | const Parent = createParent({
312 | currentStage: {
313 | name: 'b',
314 | height: 100,
315 | },
316 | update,
317 | });
318 | const parent = TestUtils.renderIntoDocument();
319 | parent.setState({
320 | height: 200,
321 | });
322 |
323 | expect(update).not.toBeCalled();
324 | });
325 | });
326 | });
327 |
--------------------------------------------------------------------------------
/src/components/__tests__/pulldown-test.js:
--------------------------------------------------------------------------------
1 | jest.dontMock('../pulldown');
2 |
3 | import React from 'react';
4 | import TestUtils from 'react-addons-test-utils';
5 |
6 | const Pulldown = require('../pulldown').default;
7 | const PulldownStage = require('../pulldown-stage').default;
8 |
9 | describe('Pulldown', () => {
10 | const className = 'className';
11 | const defaultStageName = 'a';
12 | const delay = 1000;
13 | const height = 100;
14 | const nextStageName = 'b';
15 | const onChange = jest.genMockFunction();
16 | const onClose = jest.genMockFunction();
17 | const onOpen = jest.genMockFunction();
18 | let pulldown;
19 |
20 | describe('default', () => {
21 | beforeEach(() => {
22 | pulldown = TestUtils.renderIntoDocument(
23 |
31 |
32 |
33 |
34 | );
35 | });
36 |
37 | it('applies the class name', () => {
38 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
39 |
40 | expect(element.className).toBe(className);
41 | });
42 |
43 | it('sets the current stage object', () => {
44 | expect(pulldown.state.currentStage.name).toBe(defaultStageName);
45 | });
46 |
47 | it('sets its height according to the current stage height', () => {
48 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
49 |
50 | expect(element.style.height).toBe(`${height}px`);
51 | });
52 |
53 | it('fires the onChange callback', () => {
54 | pulldown._goTo(nextStageName);
55 |
56 | expect(onChange).toBeCalledWith(nextStageName);
57 | });
58 |
59 | it('fires the onClose callback', () => {
60 | pulldown._close();
61 |
62 | expect(onClose).toBeCalled();
63 | });
64 |
65 | it('disappears when closed', () => {
66 | pulldown._close();
67 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
68 |
69 | expect(element.style.marginTop).toBe(`-${height}px`);
70 | });
71 |
72 | it('does not apply the open modifier when closed', () => {
73 | pulldown._close();
74 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
75 |
76 | expect(element.className).not.toContain(`${className}--open`);
77 | });
78 |
79 | it('fires the onOpen callback', () => {
80 | jest.runAllTimers();
81 |
82 | expect(onOpen).toBeCalled();
83 | });
84 |
85 | it('appears when open', () => {
86 | jest.runAllTimers();
87 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
88 |
89 | expect(element.style.marginTop).toBe('0');
90 | });
91 |
92 | it('applies the open modifier when open', () => {
93 | jest.runAllTimers();
94 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
95 |
96 | expect(element.className).toContain(`${className}--open`);
97 | });
98 |
99 | it('waits for the delay to appear', () => {
100 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
101 | expect(element.style.marginTop).toBe(`-${height}px`);
102 |
103 | jest.runAllTimers();
104 | expect(element.style.marginTop).toBe('0');
105 | });
106 | });
107 |
108 | describe('fixed', () => {
109 | beforeEach(() => {
110 | pulldown = TestUtils.renderIntoDocument(
111 |
116 |
117 |
118 | );
119 | });
120 |
121 | it('disappears when closed', () => {
122 | pulldown._close();
123 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
124 |
125 | expect(element.style.top).toBe('-100%');
126 | });
127 |
128 | it('appears when open', () => {
129 | jest.runAllTimers();
130 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
131 |
132 | expect(element.style.top).toBe('0px');
133 | });
134 |
135 | it('waits for the delay to appear', () => {
136 | const element = TestUtils.findRenderedDOMComponentWithTag(pulldown, 'div');
137 | expect(element.style.top).toBe('-100%');
138 |
139 | jest.runAllTimers();
140 | expect(element.style.top).toBe('0px');
141 | });
142 | });
143 | });
144 |
--------------------------------------------------------------------------------
/src/components/pulldown-close.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const PulldownClose = React.createClass({
4 |
5 | propTypes: {
6 | children: React.PropTypes.any,
7 | className: React.PropTypes.string,
8 | },
9 |
10 | contextTypes: {
11 | close: React.PropTypes.func,
12 | },
13 |
14 | _handleClick() {
15 | const {close} = this.context;
16 |
17 | close();
18 | },
19 |
20 | render() {
21 | const {children, className} = this.props;
22 |
23 | return (
24 |
27 | );
28 | },
29 |
30 | });
31 |
32 | export default PulldownClose;
33 |
--------------------------------------------------------------------------------
/src/components/pulldown-goto.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const PulldownGoto = React.createClass({
4 |
5 | propTypes: {
6 | children: React.PropTypes.any,
7 | className: React.PropTypes.string,
8 | stage: React.PropTypes.string.isRequired,
9 | },
10 |
11 | contextTypes: {
12 | goTo: React.PropTypes.func,
13 | },
14 |
15 | _handleClick() {
16 | const {goTo} = this.context;
17 | const {stage} = this.props;
18 |
19 | goTo(stage);
20 | },
21 |
22 | render() {
23 | const {children, className} = this.props;
24 |
25 | return (
26 |
29 | );
30 | },
31 |
32 | });
33 |
34 | export default PulldownGoto;
35 |
--------------------------------------------------------------------------------
/src/components/pulldown-stage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import shallowequal from 'shallowequal';
3 |
4 | const PulldownStage = React.createClass({
5 |
6 | propTypes: {
7 | children: React.PropTypes.any,
8 | className: React.PropTypes.string,
9 | direction: React.PropTypes.string,
10 | height: React.PropTypes.number.isRequired,
11 | name: React.PropTypes.string.isRequired,
12 | },
13 |
14 | contextTypes: {
15 | currentStage: React.PropTypes.object,
16 | previousStage: React.PropTypes.object,
17 | update: React.PropTypes.func,
18 | },
19 |
20 | componentDidUpdate(prevProps) {
21 | const {update} = this.context;
22 |
23 | if (this._isCurrentStage() && !shallowequal(prevProps, this.props)) {
24 | update();
25 | }
26 | },
27 |
28 | _isCurrentStage() {
29 | const {currentStage} = this.context;
30 | const {name} = this.props;
31 |
32 | return currentStage.name === name;
33 | },
34 |
35 | _getDirection() {
36 | const {currentStage, previousStage} = this.context;
37 | const {direction, name} = this.props;
38 |
39 | if (previousStage && previousStage.name === name) {
40 | this.direction = currentStage.direction === 'top' ? 'bottom' : 'top';
41 | }
42 |
43 | return this.direction || direction;
44 | },
45 |
46 | _getHeight() {
47 | const {currentStage} = this.context;
48 | const {height} = this.props;
49 |
50 | return this._isCurrentStage() ? 0 : Math.max(height, currentStage.height);
51 | },
52 |
53 | _getTop() {
54 | const height = this._getHeight();
55 |
56 | return this._getDirection() === 'top' ? -height : height;
57 | },
58 |
59 | _getStyle() {
60 | const {height} = this.props;
61 |
62 | return {
63 | height: height,
64 | position: 'absolute',
65 | top: this._getTop(),
66 | transition: 'all 500ms',
67 | width: '100%',
68 | };
69 | },
70 |
71 | render() {
72 | const {children} = this.props;
73 | let {className} = this.props;
74 |
75 | if (className && this._isCurrentStage()) {
76 | className += ` ${className}--active`;
77 | }
78 |
79 | return (
80 |
81 | {children}
82 |
83 | );
84 | },
85 |
86 | });
87 |
88 | export default PulldownStage;
89 |
--------------------------------------------------------------------------------
/src/components/pulldown.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Pulldown = React.createClass({
4 |
5 | propTypes: {
6 | children: React.PropTypes.any,
7 | className: React.PropTypes.string,
8 | defaultStage: React.PropTypes.string.isRequired,
9 | delay: React.PropTypes.number,
10 | fixed: React.PropTypes.bool,
11 | onChange: React.PropTypes.func,
12 | onClose: React.PropTypes.func,
13 | onOpen: React.PropTypes.func,
14 | },
15 |
16 | childContextTypes: {
17 | close: React.PropTypes.func,
18 | currentStage: React.PropTypes.object,
19 | goTo: React.PropTypes.func,
20 | previousStage: React.PropTypes.object,
21 | update: React.PropTypes.func,
22 | },
23 |
24 | getInitialState() {
25 | const {defaultStage, delay} = this.props;
26 |
27 | return {
28 | currentStage: this._getStage(defaultStage),
29 | open: !delay,
30 | };
31 | },
32 |
33 | componentDidMount() {
34 | const {delay} = this.props;
35 |
36 | this.timeout = setTimeout(this._open, delay);
37 | },
38 |
39 | componentWillUnmount() {
40 | if (this.timeout) {
41 | clearTimeout(this.timeout);
42 | }
43 | },
44 |
45 | getChildContext() {
46 | const {currentStage, previousStage} = this.state;
47 |
48 | return {
49 | close: this._close,
50 | currentStage: currentStage,
51 | goTo: this._goTo,
52 | previousStage: previousStage,
53 | update: this._update,
54 | };
55 | },
56 |
57 | _getStage(stageName) {
58 | const {children} = this.props;
59 | let currentStage;
60 |
61 | React.Children.forEach(children, child => {
62 | if (child.props.name === stageName) {
63 | currentStage = child.props;
64 | }
65 | });
66 |
67 | return currentStage;
68 | },
69 |
70 | _goTo(stageName) {
71 | const {onChange} = this.props;
72 |
73 | this.setState({
74 | currentStage: this._getStage(stageName),
75 | previousStage: this.state.currentStage,
76 | }, () => {
77 | if (onChange) {
78 | onChange(stageName);
79 | }
80 | });
81 | },
82 |
83 | _close() {
84 | const {onClose} = this.props;
85 |
86 | this.setState({
87 | open: false,
88 | }, onClose);
89 | },
90 |
91 | _open() {
92 | const {onOpen} = this.props;
93 |
94 | this.setState({
95 | open: true,
96 | }, onOpen);
97 | },
98 |
99 | _update() {
100 | const {currentStage} = this.state;
101 |
102 | this.setState({
103 | currentStage: this._getStage(currentStage.name),
104 | });
105 | },
106 |
107 | _getStyle() {
108 | const {currentStage, open} = this.state;
109 | const {fixed} = this.props;
110 | let style;
111 |
112 | if (fixed) {
113 | style = {
114 | height: currentStage.height,
115 | position: 'fixed',
116 | top: open ? 0 : '-100%',
117 | zIndex: 999,
118 | };
119 | } else {
120 | style = {
121 | height: currentStage.height,
122 | marginTop: open ? 0 : `${-currentStage.height}px`,
123 | position: 'relative',
124 | };
125 | }
126 |
127 | return {
128 | overflow: 'hidden',
129 | transition: 'all 500ms',
130 | width: '100%',
131 | ...style,
132 | };
133 | },
134 |
135 | render() {
136 | const {children} = this.props;
137 | let {className} = this.props;
138 | const {open} = this.state;
139 |
140 | if (className && open) {
141 | className += ` ${className}--open`;
142 | }
143 |
144 | return (
145 |
146 | {children}
147 |
148 | );
149 | },
150 |
151 | });
152 |
153 | export default Pulldown;
154 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import PulldownClose from './components/pulldown-close';
2 | import PulldownGoto from './components/pulldown-goto';
3 | import PulldownStage from './components/pulldown-stage';
4 | import Pulldown from './components/pulldown';
5 |
6 | export {PulldownClose, PulldownGoto, PulldownStage, Pulldown};
7 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | loaders: [{
4 | test: /\.jsx?$/,
5 | exclude: /(node_modules|bower_components)/,
6 | loader: 'babel'
7 | }]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------