├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── README.md ├── package.json ├── public │ └── index.html └── src │ ├── App.jsx │ ├── demo.css │ └── index.js ├── package.json ├── src ├── LaddaButton.jsx ├── constants.js └── index.js └── test ├── .eslintrc ├── .setup.js └── LaddaButton-test.jsx /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": [ 4 | "transform-regenerator", 5 | "transform-flow-strip-types" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "rules": { 5 | "semi": [2, "never"], 6 | "import/prefer-default-export": [0, "never"], 7 | "import/no-extraneous-dependencies": [ 8 | "error", { "devDependencies": true } 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | dist 40 | 41 | # Webstorm 42 | .idea 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.1" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [6.0.0] - 2018-02-10 6 | ### Changed 7 | - Support `react@16.x.x` 8 | 9 | ## [5.0.7] - 2017-05-12 10 | ### Changed 11 | - Use external prop-types 12 | 13 | ## [5.0.6] - 2017-02-13 14 | ### Changed 15 | - Fixed a bug where setting `loading` to `false` would remove the `disabled` property from the button. 16 | 17 | ## [5.0.5] - 2016-12-31 18 | ### Changed 19 | - Disable button when loading 20 | 21 | ## [5.0.4] - 2016-11-09 22 | ### Changed 23 | - Use `ladda` as a dependency instead of a peerDependency 24 | 25 | ## [5.0.1] - 2016-09-22 26 | ### Changed 27 | - Support `react@15.x.x` 28 | 29 | ## [5.0.0] - 2016-09-17 30 | ### Changed 31 | - Refactor 32 | - Use Babel/ES7 for compilation 33 | - Fixed React warning described in #36 34 | - Changed prop names to the actual [data attributes that Ladda uses](https://github.com/hakimel/Ladda#html) 35 | 36 | ## [4.0.0] - ? 37 | 38 | ## [3.0.0] - 2015-07-11 39 | ### Changed 40 | - New props for `LaddaButton` that do not collide with existing element props (`buttonStyle` instead of `style`) 41 | - Gave `LaddaButton` ownership of the `button` component. Any props applied to the `LaddaButton` are applied to the wrapped `button`. The children of the `LaddaButton` are rendered inside `ladda-label`. 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2014, ∞) Jason Sommer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-ladda 2 | =========== 3 | 4 | [![Build Status](https://img.shields.io/travis/jsdir/react-ladda.svg?style=flat)](https://travis-ci.org/jsdir/react-ladda) 5 | [![Dependency Status](https://img.shields.io/david/jsdir/react-ladda.svg?style=flat)](https://david-dm.org/jsdir/react-ladda) 6 | [![NPM version](https://img.shields.io/npm/v/react-ladda.svg?style=flat)](https://www.npmjs.org/package/react-ladda) 7 | [![Code Climate](https://img.shields.io/codeclimate/github/jsdir/react-ladda.svg?style=flat)](https://codeclimate.com/github/jsdir/react-ladda) 8 | 9 | A React wrapper for [Ladda buttons](https://github.com/hakimel/Ladda). [Example](https://github.com/jsdir/react-ladda/blob/master/example/README.md) 10 | 11 | ## Installation 12 | 13 | `react-ladda` can be installed directly through npm: 14 | 15 | ```sh 16 | $ npm install --save react-ladda 17 | ``` 18 | 19 | ## Usage 20 | 21 | `LaddaButton` is a React component that renders a [Ladda button](https://github.com/hakimel/Ladda). You can change the button's loading state and progress using the `loading` and `progress` props. 22 | 23 | ```jsx 24 | import React, { Component } from 'react'; 25 | 26 | import LaddaButton, { XL, SLIDE_UP } from 'react-ladda'; 27 | 28 | class App extends Component { 29 | 30 | state = { loading: false }; 31 | 32 | toggle() { 33 | this.setState({ 34 | loading: !this.state.loading, 35 | progress: 0.5, 36 | }); 37 | } 38 | 39 | render() { 40 | return ( 41 | 51 | Click Here! 52 | 53 | ); 54 | } 55 | }; 56 | 57 | ReactDOM.render(, document.body); 58 | ``` 59 | 60 | Although this package doesn't include the styles for the Ladda buttons, there are many different ways to include them. The easiest way is to add the following tag to your document: 61 | 62 | ```html 63 | 64 | ``` 65 | 66 | ## Props 67 | 68 | All of the native [Ladda button options](https://github.com/hakimel/Ladda#html) are supported through props: 69 | 70 | Prop | Type | Description 71 | -------------------- | --------- | ----------- 72 | `loading` | `boolean` | Displays the button's loading indicator 73 | `progress` | `number` | Number from 0.0 to 1.0 74 | `data-color` | `string` | Color applied to the button (`{green,red,blue,purple,mint}`) 75 | `data-size` | `string` | A [button size](#sizes) 76 | `data-style` | `string` | A [button style](#styles) 77 | `data-spinner-size` | `number` | Number representing the size of the spinner in pixels 78 | `data-spinner-color` | `string` | Color applied to the spinner (eg. `#eee`) 79 | `data-spinner-lines` | `number` | Number of spokes in the spinner 80 | 81 | ## Sizes and Styles 82 | 83 | Ladda comes with a variety of different [sizes and styles](http://lab.hakim.se/ladda/) that you can use. Button sizes and styles can be directly imported from `react-ladda`: 84 | 85 | ```js 86 | import LaddaButton, { XS, EXPAND_LEFT } from 'react-ladda' 87 | ``` 88 | 89 | ### Sizes 90 | 91 | - `XS` 92 | - `S` 93 | - `L` 94 | - `XL` 95 | 96 | ### Styles 97 | 98 | - `CONTRACT` 99 | - `CONTRACT_OVERLAY` 100 | - `EXPAND_LEFT` 101 | - `EXPAND_RIGHT` 102 | - `EXPAND_UP` 103 | - `EXPAND_DOWN` 104 | - `SLIDE_LEFT` 105 | - `SLIDE_RIGHT` 106 | - `SLIDE_UP` 107 | - `SLIDE_DOWN` 108 | - `ZOOM_IN` 109 | - `ZOOM_OUT` 110 | 111 | ## Development 112 | 113 | After cloning and running `npm install`, you can use the following `npm` commands for easier development: 114 | 115 | Command | Description 116 | --------------- | ----------- 117 | `npm test` | Runs the test suite 118 | `npm run watch` | Runs the test suite and reruns when any source or test file changes 119 | `npm run lint` | Lints both the source and test files 120 | `npm run build` | Compiles the source into ES5 and outputs the results into `dist` 121 | 122 | _Contributions are more than welcome!_ 123 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | React Ladda Button Example 2 | ============================== 3 | 4 | This is a runnable demo showing how to use `react-ladda` 5 | 6 | ## Running Example 7 | 8 | **In the example directory, run:** 9 | ``` 10 | $ npm install 11 | $ npm start 12 | ``` 13 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.6.1" 7 | }, 8 | "dependencies": { 9 | "react": "^15.3.2", 10 | "react-dom": "^15.3.2", 11 | "react-ladda": "^5.0.3" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ladda 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import LaddaButton, { 3 | EXPAND_LEFT, 4 | EXPAND_RIGHT, 5 | EXPAND_UP, 6 | EXPAND_DOWN, 7 | CONTRACT, 8 | CONTRACT_OVERLAY, 9 | SLIDE_LEFT, 10 | SLIDE_RIGHT, 11 | SLIDE_UP, 12 | SLIDE_DOWN, 13 | ZOOM_IN, 14 | ZOOM_OUT 15 | } from 'react-ladda' 16 | 17 | import './demo.css' 18 | 19 | class App extends Component { 20 | state = { 21 | expLeft: false, 22 | expRight: false, 23 | expUp: false, 24 | expDown: false, 25 | expContract: false, 26 | expOverlay: false, 27 | expSlideLeft: false, 28 | expSlideRight: false, 29 | expSlideUp: false, 30 | expSlideDown: false, 31 | expZoomIn: false, 32 | expZoomOut: false, 33 | } 34 | 35 | toggle(name) { 36 | this.setState({ 37 | [name]: !this.state[name], 38 | progress: 0.5, 39 | }) 40 | } 41 | 42 | render() { 43 | return ( 44 |
45 |
46 |

Ladda

47 |

48 | A UI concept which merges loading indicators into the action that invoked them. 49 | Primarily intended for use with forms where 50 | it gives users immediate feedback upon submit rather than leaving them wondering 51 | while the browser does its thing. For a 52 | real-world example, check out any of the forms on slides.com. 53 |

54 |
55 |

expand-left

56 | this.toggle('expLeft')} 59 | data-color="green" 60 | data-style={EXPAND_LEFT} 61 | > 62 | Submit! 63 | 64 |
65 |
66 |

expand-right

67 | this.toggle('expRight')} 70 | data-color="green" 71 | data-style={EXPAND_RIGHT} 72 | > 73 | Submit! 74 | 75 |
76 | 77 |
78 |

expand-up

79 | this.toggle('expUp')} 82 | data-color="green" 83 | data-style={EXPAND_UP} 84 | > 85 | Submit! 86 | 87 |
88 | 89 |
90 |

expand-down

91 | this.toggle('expDown')} 94 | data-color="green" 95 | data-style={EXPAND_DOWN} 96 | > 97 | Submit! 98 | 99 |
100 | 101 | {/* Set 2 */} 102 |
103 |

contract

104 | this.toggle('expContract')} 107 | data-color="red" 108 | data-style={CONTRACT} 109 | > 110 | Submit! 111 | 112 |
113 | 114 |
115 |

contract-overlay

116 | this.toggle('expOverlay')} 119 | data-color="red" 120 | data-style={CONTRACT_OVERLAY} 121 | > 122 | Submit! 123 | 124 |
125 | 126 |
127 |

zoom-in

128 | this.toggle('expZoomIn')} 131 | data-color="red" 132 | data-style={ZOOM_IN} 133 | > 134 | Submit! 135 | 136 |
137 | 138 |
139 |

zoom-out

140 | this.toggle('expZoomOut')} 143 | data-color="red" 144 | data-style={ZOOM_OUT} 145 | > 146 | Submit! 147 | 148 |
149 | 150 | {/* Set 3 */} 151 | 152 |
153 |

slide-left

154 | this.toggle('expSlideLeft')} 157 | data-color="blue" 158 | data-style={SLIDE_LEFT} 159 | > 160 | Submit! 161 | 162 |
163 | 164 |
165 |

slide-right

166 | this.toggle('expSlideRight')} 169 | data-color="blue" 170 | data-style={SLIDE_RIGHT} 171 | > 172 | Submit! 173 | 174 |
175 | 176 |
177 |

slide-up

178 | this.toggle('expSlideUp')} 181 | data-color="blue" 182 | data-style={SLIDE_UP} 183 | > 184 | Submit! 185 | 186 |
187 | 188 |
189 |

slide-down

190 | this.toggle('expSlideDown')} 193 | data-color="blue" 194 | data-style={SLIDE_DOWN} 195 | > 196 | Submit! 197 | 198 |
199 |
200 |
201 | ) 202 | } 203 | } 204 | 205 | export default App 206 | -------------------------------------------------------------------------------- /example/src/demo.css: -------------------------------------------------------------------------------- 1 | /* Styles used specifically for the demo page */ 2 | 3 | body { 4 | background: #f5f5f5; 5 | font-family: monospace; 6 | font-size: 14px; 7 | color: #333333; 8 | } 9 | 10 | button { 11 | outline: 0; 12 | } 13 | 14 | .examples { 15 | max-width: 670px; 16 | margin: 2em auto; 17 | padding: 4em; 18 | background: #fff; 19 | text-align: center 20 | } 21 | .examples .intro { 22 | margin-bottom: 3em; 23 | line-height: 1.4em; 24 | font-size: 16px; 25 | text-align: left; 26 | } 27 | .examples .intro h1 { 28 | margin-top: 0; 29 | font-size: 18px; 30 | } 31 | 32 | .examples .outro { 33 | display: block; 34 | text-align: right; 35 | margin-top: 3em; 36 | } 37 | .examples section { 38 | display: inline-block; 39 | width: 24%; 40 | min-width: 160px; 41 | margin-bottom: 2em; 42 | text-align: center; 43 | vertical-align: top; 44 | } 45 | .examples section h3 { 46 | color: #bbb; 47 | font-weight: normal; 48 | font-size: 15px; 49 | } 50 | 51 | .sharing { 52 | float: left; 53 | } 54 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | ReactDOM.render( 5 | , 6 | document.getElementById('root') 7 | ); 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ladda", 3 | "version": "6.0.0", 4 | "description": "React wrapper for Ladda buttons", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "watch": "./node_modules/.bin/mocha -w --watch-extensions=jsx test/.setup.js test/**/*-test.jsx", 8 | "test": "./node_modules/.bin/mocha test/.setup.js test/**/*-test.jsx", 9 | "lint": "./node_modules/.bin/eslint src/**/* test/**/*", 10 | "build": "npm run lint && ./node_modules/.bin/babel src --out-dir dist", 11 | "prepublish": "npm run build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/jsdir/react-ladda.git" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "component", 20 | "boilerplate" 21 | ], 22 | "author": "Jason Sommer", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/jsdir/react-ladda/issues" 26 | }, 27 | "homepage": "https://github.com/jsdir/react-ladda#readme", 28 | "dependencies": { 29 | "ladda": "^1.0.0", 30 | "prop-types": "^15.5.8" 31 | }, 32 | "devDependencies": { 33 | "babel-cli": "^6.14.0", 34 | "babel-eslint": "^6.1.2", 35 | "babel-plugin-transform-flow-strip-types": "^6.8.0", 36 | "babel-plugin-transform-regenerator": "^6.4.4", 37 | "babel-preset-es2015": "^6.3.13", 38 | "babel-preset-react": "^6.3.13", 39 | "babel-preset-stage-0": "^6.3.13", 40 | "babel-register": "^6.4.3", 41 | "chai": "^3.5.0", 42 | "chai-enzyme": "^1.0.0-beta.0", 43 | "cheerio": "^1.0.0-rc.2", 44 | "enzyme": "^3.0.0", 45 | "enzyme-adapter-react-16": "^1.1.1", 46 | "eslint": "^3.7.0", 47 | "eslint-config-airbnb": "^12.0.0", 48 | "eslint-plugin-import": "^1.16.0", 49 | "eslint-plugin-jsx-a11y": "^2.2.2", 50 | "eslint-plugin-react": "^6.3.0", 51 | "jsdom": "^8.0.1", 52 | "mocha": "^2.4.5", 53 | "react": "^16.0.0", 54 | "react-dom": "^16.0.0", 55 | "react-test-renderer": "^16.2.0", 56 | "sinon": "^1.17.5", 57 | "sinon-chai": "^2.8.0" 58 | }, 59 | "peerDependencies": { 60 | "react": "^16.0.0", 61 | "react-dom": "^16.0.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/LaddaButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import Ladda from 'ladda' 4 | 5 | import { SIZES, STYLES } from './constants' 6 | 7 | const isUndefined = value => typeof value === 'undefined' 8 | 9 | const OMITTED_PROPS = [ 10 | 'loading', 11 | 'progress', 12 | ] 13 | 14 | const omit = (data, keys) => { 15 | const result = {} 16 | Object.keys(data).forEach((key) => { 17 | if (keys.indexOf(key) === -1) { 18 | result[key] = data[key] 19 | } 20 | }) 21 | 22 | return result 23 | } 24 | 25 | export default 26 | class LaddaButton extends Component { 27 | 28 | static propTypes = { 29 | children: PropTypes.node, 30 | className: PropTypes.string, 31 | progress: PropTypes.number, 32 | loading: PropTypes.bool, 33 | disabled: PropTypes.bool, 34 | 35 | // Ladda props 36 | // eslint-disable-next-line react/no-unused-prop-types 37 | 'data-color': PropTypes.oneOf(['green', 'red', 'blue', 'purple', 'mint']), 38 | // eslint-disable-next-line react/no-unused-prop-types 39 | 'data-size': PropTypes.oneOf(SIZES), 40 | // eslint-disable-next-line react/no-unused-prop-types 41 | 'data-style': PropTypes.oneOf(STYLES), 42 | // eslint-disable-next-line react/no-unused-prop-types 43 | 'data-spinner-size': PropTypes.number, 44 | // eslint-disable-next-line react/no-unused-prop-types 45 | 'data-spinner-color': PropTypes.string, 46 | // eslint-disable-next-line react/no-unused-prop-types 47 | 'data-spinner-lines': PropTypes.number, 48 | }; 49 | 50 | componentDidMount() { 51 | this.laddaInstance = Ladda.create(this.node) 52 | 53 | if (this.props.loading) { 54 | this.laddaInstance.start() 55 | } 56 | 57 | if (!isUndefined(this.props.progress)) { 58 | this.laddaInstance.setProgress(this.props.progress) 59 | } 60 | } 61 | 62 | componentWillReceiveProps(nextProps) { 63 | this.updateLaddaInstance(nextProps) 64 | } 65 | 66 | componentWillUnmount() { 67 | this.laddaInstance.remove() 68 | } 69 | 70 | setNode = (node) => { 71 | this.node = node 72 | } 73 | 74 | updateLaddaInstance = (props) => { 75 | if (props.loading !== this.props.loading) { 76 | if (props.loading) { 77 | this.laddaInstance.start() 78 | } else if (props.disabled) { 79 | // .stop removes the attribute "disabled" 80 | // .disable calls .stop then adds the attribute "disabled" 81 | // see https://github.com/hakimel/Ladda/blob/master/js/ladda.js 82 | this.laddaInstance.disable() 83 | } else { 84 | this.laddaInstance.stop() 85 | } 86 | } 87 | 88 | if (props.progress !== this.props.progress) { 89 | this.laddaInstance.setProgress(props.progress) 90 | } 91 | } 92 | 93 | render() { 94 | return ( 95 | 105 | ) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const XS = 'xs' 2 | export const S = 's' 3 | export const L = 'l' 4 | export const XL = 'xl' 5 | 6 | export const SIZES = [ 7 | XS, 8 | S, 9 | L, 10 | XL, 11 | ] 12 | 13 | export const CONTRACT = 'contract' 14 | export const CONTRACT_OVERLAY = 'contract-overlay' 15 | export const EXPAND_LEFT = 'expand-left' 16 | export const EXPAND_RIGHT = 'expand-right' 17 | export const EXPAND_UP = 'expand-up' 18 | export const EXPAND_DOWN = 'expand-down' 19 | export const SLIDE_LEFT = 'slide-left' 20 | export const SLIDE_RIGHT = 'slide-right' 21 | export const SLIDE_UP = 'slide-up' 22 | export const SLIDE_DOWN = 'slide-down' 23 | export const ZOOM_IN = 'zoom-in' 24 | export const ZOOM_OUT = 'zoom-out' 25 | 26 | export const STYLES = [ 27 | CONTRACT, 28 | CONTRACT_OVERLAY, 29 | EXPAND_LEFT, 30 | EXPAND_RIGHT, 31 | EXPAND_UP, 32 | EXPAND_DOWN, 33 | SLIDE_LEFT, 34 | SLIDE_RIGHT, 35 | SLIDE_UP, 36 | SLIDE_DOWN, 37 | ZOOM_IN, 38 | ZOOM_OUT, 39 | ] 40 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import LaddaButton from './LaddaButton' 2 | 3 | export default LaddaButton 4 | export * from './constants' 5 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | "rules": { 7 | "no-unused-expressions": 0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/.setup.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(); 2 | const { configure } = require('enzyme'); 3 | const Adapter = require('enzyme-adapter-react-16'); 4 | 5 | configure({ adapter: new Adapter() }); 6 | var jsdom = require('jsdom').jsdom; 7 | 8 | var exposedProperties = ['window', 'navigator', 'document']; 9 | 10 | global.document = jsdom(''); 11 | global.window = document.defaultView; 12 | Object.keys(document.defaultView).forEach((property) => { 13 | if (typeof global[property] === 'undefined') { 14 | exposedProperties.push(property); 15 | global[property] = document.defaultView[property]; 16 | } 17 | }); 18 | 19 | global.navigator = { 20 | userAgent: 'node.js' 21 | }; 22 | 23 | documentRef = document; 24 | 25 | var chai = require('chai'); 26 | chai.use(require('chai-enzyme')()); 27 | chai.use(require('sinon-chai')); 28 | -------------------------------------------------------------------------------- /test/LaddaButton-test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { findDOMNode } from 'react-dom' 3 | import { expect } from 'chai' 4 | import { render, mount } from 'enzyme' 5 | import sinon from 'sinon' 6 | import Ladda from 'ladda' 7 | 8 | import LaddaButton from '../src/LaddaButton' 9 | import { XL, SLIDE_UP } from '../src/constants' 10 | 11 | describe('LaddaButton', () => { 12 | it('should render the elements correctly', () => { 13 | // The correct markup that Ladda expects is defined here: 14 | // https://github.com/hakimel/Ladda#html 15 | const wrapper = mount(child) 16 | const button = wrapper.find('button.ladda-button') 17 | expect(button).to.be.present() 18 | const label = button.find('span.ladda-label') 19 | expect(label).to.be.present() 20 | expect(label).to.have.text('child') 21 | }) 22 | 23 | it('should pass data attributes down to the button', () => { 24 | const wrapper = render( 25 | 33 | ) 34 | 35 | expect(wrapper).to.have.attr('data-color').equal('#eee') 36 | expect(wrapper).to.have.attr('data-size').equal(XL) 37 | expect(wrapper).to.have.attr('data-style').equal(SLIDE_UP) 38 | expect(wrapper).to.have.attr('data-spinner-size').equal('30') 39 | expect(wrapper).to.have.attr('data-spinner-color').equal('#ddd') 40 | expect(wrapper).to.have.attr('data-spinner-lines').equal('12') 41 | }) 42 | 43 | it('should not pass blacklisted props to the Ladda button', () => { 44 | const wrapper = mount( 45 | 49 | ) 50 | const button = wrapper.find('button') 51 | expect(button).to.not.have.prop('loading') 52 | expect(button).to.not.have.prop('progress') 53 | }) 54 | 55 | it('should combine classNames correctly', () => { 56 | const wrapper = mount( 57 | 58 | ) 59 | 60 | expect( 61 | wrapper.find('button.ladda-button.custom') 62 | ).to.be.present() 63 | }) 64 | 65 | it('should pass props down to the button', () => { 66 | const handler = () => {} 67 | const wrapper = mount() 68 | expect(wrapper.find('button')).prop('onClick').to.eq(handler) 69 | }) 70 | 71 | it('should allow `loading` and `progress` to be changed in the same state update', () => { 72 | const wrapper = mount() 73 | 74 | // Ladda depends on a set `offsetWidth` for calculations. 75 | const node = findDOMNode(wrapper.instance()) 76 | node.offsetWidth = 200 77 | 78 | expect(wrapper.html()).not.to.contain('ladda-progress') 79 | wrapper.setProps({ loading: true, progress: 0.4 }) 80 | expect(wrapper.html()).to.contain('ladda-progress') 81 | }) 82 | 83 | it('should not disable the button if `props.loading` is falsey', () => { 84 | const wrapper = mount() 85 | expect(wrapper.find('button').prop('disabled')).to.eq(undefined) 86 | }) 87 | 88 | it('should disable the button if the `props.disabled` is set', () => { 89 | const wrapper = mount() 90 | expect(wrapper.find('button').prop('disabled')).to.eq(true) 91 | }) 92 | 93 | it('should disable the button if `props.loading` is truthy', () => { 94 | const wrapper = mount() 95 | expect(wrapper.find('button').prop('disabled')).to.eq(true) 96 | }) 97 | 98 | it('should keep the attribute `disabled` after loading', () => { 99 | const wrapper = mount() 100 | expect(wrapper.find('button')).to.have.attr('disabled') 101 | wrapper.setProps({ loading: true }) 102 | wrapper.setProps({ loading: false }) 103 | expect(wrapper.find('button')).to.have.attr('disabled') 104 | }) 105 | 106 | describe('ladda instance', () => { 107 | let createStub 108 | let laddaInstance 109 | 110 | beforeEach(() => { 111 | createStub = sinon.stub(Ladda, 'create') 112 | laddaInstance = { 113 | remove: sinon.spy(), 114 | setProgress: sinon.spy(), 115 | start: sinon.spy(), 116 | stop: sinon.spy(), 117 | } 118 | createStub.returns(laddaInstance) 119 | }) 120 | 121 | afterEach(() => { 122 | createStub.restore() 123 | }) 124 | 125 | it('should be maintained for the entire lifecycle of the component', () => { 126 | const wrapper = mount() 127 | const node = findDOMNode(wrapper.instance()) 128 | expect(createStub).to.have.been.calledWithExactly(node) 129 | wrapper.unmount() 130 | expect(laddaInstance.remove).to.have.been.calledWithExactly() 131 | }) 132 | 133 | it('should receive setProgress call when progress is set', () => { 134 | const wrapper = mount() 135 | expect(laddaInstance.setProgress).not.to.have.been.called 136 | 137 | wrapper.setProps({ progress: 0.5 }) 138 | expect(laddaInstance.setProgress).to.have.been.calledWithExactly(0.5) 139 | laddaInstance.setProgress.reset() 140 | 141 | wrapper.setProps({ progress: 0.6 }) 142 | expect(laddaInstance.setProgress).to.have.been.calledWithExactly(0.6) 143 | laddaInstance.setProgress.reset() 144 | 145 | wrapper.setProps({ progress: 0.6 }) 146 | expect(laddaInstance.setProgress).not.to.have.been.called 147 | }) 148 | 149 | it('should receive start and stop calls when loading is set', () => { 150 | const wrapper = mount() 151 | expect(laddaInstance.stop).not.to.have.been.called 152 | expect(laddaInstance.start).not.to.have.been.called 153 | 154 | wrapper.setProps({ loading: true }) 155 | expect(laddaInstance.start).to.have.been.calledWithExactly() 156 | laddaInstance.start.reset() 157 | 158 | wrapper.setProps({ loading: true }) 159 | expect(laddaInstance.start).not.to.have.been.called 160 | 161 | wrapper.setProps({ loading: false }) 162 | expect(laddaInstance.stop).to.have.been.calledWithExactly() 163 | laddaInstance.stop.reset() 164 | 165 | wrapper.setProps({ loading: false }) 166 | expect(laddaInstance.stop).not.to.have.been.called 167 | }) 168 | 169 | context('when `props.progress` is initially set', () => { 170 | beforeEach(() => { 171 | mount() 172 | }) 173 | 174 | it('should receive a setProgress call ', () => { 175 | expect(laddaInstance.setProgress).to.have.been.calledWithExactly(0.3) 176 | }) 177 | }) 178 | 179 | context('when `props.loading` is initially set', () => { 180 | beforeEach(() => { 181 | mount() 182 | }) 183 | 184 | it('should receive a start call', () => { 185 | expect(laddaInstance.start).to.have.been.calledWithExactly() 186 | }) 187 | }) 188 | }) 189 | }) 190 | --------------------------------------------------------------------------------