├── .gitignore ├── .npmignore ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── dialog.css ├── index.js ├── package.json ├── template.html └── test ├── index.html └── reuse.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | components 3 | build 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.0 / 2015-06-04 3 | ================== 4 | 5 | * add support reusing dialog with overlay 6 | * updates in package.json to make it possible to use with browserify 7 | 8 | 0.4.0 / 2015-02-16 9 | ================== 10 | 11 | * add: use pure-css positioning of the dialog. 12 | * This change is minor because no API was changed nor was a regression observed. 13 | 14 | 0.3.1 / 2015-02-06 15 | ================== 16 | 17 | * fix: don't use bind as they are not available in legacy browsers 18 | 19 | 0.3.0 / 2014-08-25 20 | ================== 21 | 22 | * fix CSS for test/demo 23 | * removed generic style rules which break user styles 24 | * reduced specificity: Changed #dialog to .dialog 25 | * component.json: Pin and update deps 26 | 27 | 0.2.1 / 2014-06-16 28 | ================== 29 | 30 | * c8: Update overlay dep 31 | 32 | 0.2.0 / 2014-04-05 33 | ================== 34 | 35 | * add Dialog.fixed function to allow for an alternate centering algorithm 36 | * fix 'dialog not centered in some cases' issue 37 | 38 | 0.1.0 / 2014-04-03 39 | ================== 40 | 41 | * remove jQuery dependency 42 | * fix problems with reopening dialog - remove 'hide' class on show 43 | * support passing options in overlay() to overlay component 44 | * make escapable() public 45 | * use component templates to translate HTML 46 | 47 | 0.0.7 / 2013-05-27 48 | ================== 49 | 50 | * pin deps 51 | 52 | 0.0.6 / 2013-02-12 53 | ================== 54 | 55 | * remove redundant 'show' event 56 | * fix gap between x and "close" 57 | 58 | 0.0.5 / 2012-09-21 59 | ================== 60 | 61 | * add hidden "close" em within .close 62 | * remove default box-shadow on .modal. Closes #3 63 | 64 | 0.0.4 / 2012-08-31 65 | ================== 66 | 67 | * add `.addClass()`. Closes #2 68 | * add template.js 69 | 70 | 0.0.3 / 2012-08-03 71 | ================== 72 | 73 | * add component.json 74 | * fix some event race-conditions 75 | 76 | 0.0.2 / 2012-07-05 77 | ================== 78 | 79 | * fix dialog.effect support 80 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: build 3 | 4 | components: component.json 5 | component install --dev 6 | 7 | build: index.js dialog.css template.html | components 8 | component build --dev 9 | 10 | clean: 11 | rm -rf components build 12 | 13 | test: test-phantom 14 | 15 | test-phantom: | build 16 | component test phantom 17 | 18 | test-browser: | build 19 | component test browser 20 | 21 | 22 | .PHONY: all clean test test-phantom test-browser 23 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Dialog 3 | 4 | Dialog component with structural styling to give you a clean slate. 5 | 6 | ![js dialog component](http://f.cl.ly/items/0r140j3W323T3c3i2H3a/Screen%20Shot%202012-07-26%20at%203.11.20%20PM.png) 7 | 8 | Live demo is [here](http://component.github.io/dialog/) 9 | 10 | ## Installation 11 | 12 | ``` 13 | $ npm install dialog-component 14 | ``` 15 | 16 | ## Features 17 | 18 | - events for composition 19 | - structural CSS letting you decide on style 20 | - overlay support 21 | - modal overlay support 22 | - escapable (esc key support) 23 | - fluent API 24 | 25 | ## Events 26 | 27 | - `show` the dialog is shown 28 | - `hide` the dialog is hidden 29 | - `escape` the dialog was closed via the escape key 30 | - `close` the dialog was closed via the close button 31 | 32 | ## API 33 | 34 | ### dialog(msg) 35 | 36 | Display a dialog with a `msg` only. 37 | 38 | ### dialog(title, msg) 39 | 40 | Display a dialog with `title` and `msg`. 41 | 42 | ### Dialog#closable() 43 | 44 | Make the dialog closable, this adds a × 45 | that users make click to forcefully close 46 | the dialog. 47 | 48 | ### Dialog#effect(name) 49 | 50 | Assign the effect name, driven by CSS transitions. 51 | Out of the box the following are available: 52 | 53 | - `slide` 54 | - `fade` 55 | - `scale` 56 | 57 | ### Dialog#overlay() 58 | 59 | Add a clickable overlay, which closes the dialog. 60 | 61 | ### Dialog#modal() 62 | 63 | Add a non-clickable overlay making it modal. 64 | 65 | ### Dialog#fixed() 66 | 67 | Dialogs are centered by default. If you'd rather use CSS to position the dialog make it `fixed`; 68 | no per element CSS properties are added to such dialogs. 69 | 70 | ### Dialog#escapable() 71 | 72 | This is __private__ as it is implied by other options. 73 | If no overlay is used, or the overlay is non-modal 74 | then a user may close the dialog by pressing the escape key. 75 | 76 | ### Dialog#show() 77 | 78 | Show the dialog. 79 | 80 | ### Dialog#hide([ms]) 81 | 82 | Hide the dialog immediately or wait `ms`. 83 | 84 | ### Dialog#addClass(name) 85 | 86 | Add class `name`, useful for styling dialogs differently. 87 | 88 | ## License 89 | 90 | MIT 91 | 92 | ## Developers 93 | 94 | Install [component-test](https://github.com/MatthewMueller/component-test) 95 | globally in order to run unit tests: 96 | 97 | ```bash 98 | sudo npm install -g component-test2 99 | ``` 100 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dialog", 3 | "description": "Dialog component", 4 | "version": "1.0.0", 5 | "keywords": [ 6 | "dialog", 7 | "ui", 8 | "modal" 9 | ], 10 | "dependencies": { 11 | "component/emitter": "1.1.3", 12 | "component/overlay": "0.3.5", 13 | "component/domify": "1.3.1", 14 | "component/event": "0.1.4", 15 | "component/classes": "1.2.1", 16 | "component/query": "0.0.3" 17 | }, 18 | "development": { 19 | "visionmedia/mocha": "*", 20 | "dominicbarnes/expect.js": "*" 21 | }, 22 | "scripts": [ 23 | "index.js" 24 | ], 25 | "styles": [ 26 | "dialog.css" 27 | ], 28 | "templates": [ 29 | "template.html" 30 | ], 31 | "demo": "http://component.github.io/dialog/" 32 | } -------------------------------------------------------------------------------- /dialog.css: -------------------------------------------------------------------------------- 1 | .dialog { 2 | position: fixed; 3 | top: 150px; 4 | left: 5%; 5 | width: 90%; 6 | z-index: 1000; 7 | text-align: center; 8 | } 9 | 10 | .dialog .content { 11 | box-sizing: border-box; 12 | position: relative; 13 | background: white; 14 | max-width: 600px; 15 | min-width: 252px; 16 | border: 1px solid #eee; 17 | padding: 15px 20px; 18 | display: inline-block; 19 | text-align: left; 20 | } 21 | 22 | /* close */ 23 | 24 | .dialog .close { 25 | position: absolute; 26 | top: 3px; 27 | right: 10px; 28 | text-decoration: none; 29 | color: #888; 30 | font-size: 16px; 31 | font-weight: bold; 32 | display: none; 33 | } 34 | 35 | .dialog .close em { 36 | display: none; 37 | } 38 | 39 | .dialog.closable .close { 40 | display: block; 41 | } 42 | 43 | .dialog .close:hover { 44 | color: black; 45 | } 46 | 47 | .dialog .close:active { 48 | margin-top: 1px; 49 | } 50 | 51 | /* slide */ 52 | 53 | .dialog.slide { 54 | -webkit-transition: opacity 300ms, top 300ms; 55 | -moz-transition: opacity 300ms, top 300ms; 56 | } 57 | 58 | .dialog.slide.hide { 59 | opacity: 0; 60 | top: -500px; 61 | } 62 | 63 | /* fade */ 64 | 65 | .dialog.fade { 66 | -webkit-transition: opacity 300ms; 67 | -moz-transition: opacity 300ms; 68 | } 69 | 70 | .dialog.fade.hide { 71 | opacity: 0; 72 | } 73 | 74 | /* scale */ 75 | 76 | .dialog.scale { 77 | -webkit-transition: -webkit-transform 300ms; 78 | -moz-transition: -moz-transform 300ms; 79 | -webkit-transform: scale(1); 80 | -moz-transform: scale(1); 81 | } 82 | 83 | .dialog.scale.hide { 84 | -webkit-transform: scale(0); 85 | -moz-transform: scale(0); 86 | } 87 | 88 | /* off-center */ 89 | 90 | .dialog.off-center { 91 | text-align: left; 92 | width: inherit; 93 | } 94 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Emitter = require('emitter') 7 | , overlay = require('overlay') 8 | , domify = require('domify') 9 | , events = require('event') 10 | , classes = require('classes') 11 | , query = require('query'); 12 | 13 | /** 14 | * Active dialog. 15 | */ 16 | 17 | var active; 18 | 19 | /** 20 | * Expose `dialog()`. 21 | */ 22 | 23 | exports = module.exports = dialog; 24 | 25 | /** 26 | * Expose `Dialog`. 27 | */ 28 | 29 | exports.Dialog = Dialog; 30 | 31 | /** 32 | * Return a new `Dialog` with the given 33 | * (optional) `title` and `msg`. 34 | * 35 | * @param {String} title or msg 36 | * @param {String} msg 37 | * @return {Dialog} 38 | * @api public 39 | */ 40 | 41 | function dialog(title, msg){ 42 | switch (arguments.length) { 43 | case 2: 44 | return new Dialog({ title: title, message: msg }); 45 | case 1: 46 | return new Dialog({ message: title }); 47 | } 48 | }; 49 | 50 | /** 51 | * Initialize a new `Dialog`. 52 | * 53 | * Options: 54 | * 55 | * - `title` dialog title 56 | * - `message` a message to display 57 | * 58 | * Emits: 59 | * 60 | * - `show` when visible 61 | * - `hide` when hidden 62 | * 63 | * @param {Object} options 64 | * @api public 65 | */ 66 | 67 | function Dialog(options) { 68 | Emitter.call(this); 69 | options = options || {}; 70 | this.template = require('./template.html'); 71 | this.el = domify(this.template); 72 | this._classes = classes(this.el); 73 | this.render(options); 74 | if (active && !active.hiding) active.hide(); 75 | if (exports.effect) this.effect(exports.effect); 76 | 77 | active = this; 78 | this.on('escape', function(){ 79 | active.hide(); 80 | }); 81 | }; 82 | 83 | /** 84 | * Inherit from `Emitter.prototype`. 85 | */ 86 | 87 | Dialog.prototype = new Emitter; 88 | 89 | /** 90 | * Render with the given `options`. 91 | * 92 | * @param {Object} options 93 | * @api public 94 | */ 95 | 96 | Dialog.prototype.render = function(options){ 97 | var self = this 98 | , el = self.el 99 | , title = options.title 100 | , titleEl = query('.title', el) 101 | , pEl = query('p', el) 102 | , msg = options.message; 103 | 104 | events.bind(query('.close', el), 'click', function (ev) { 105 | ev.preventDefault(); 106 | self.emit('close'); 107 | self.hide(); 108 | }); 109 | 110 | if (titleEl) { 111 | if (!title) { 112 | titleEl.parentNode.removeChild(titleEl); 113 | } else { 114 | titleEl.textContent = title; 115 | } 116 | } 117 | 118 | // message 119 | if ('string' == typeof msg) { 120 | pEl.textContent = msg; 121 | } else if (msg) { 122 | pEl.parentNode.insertBefore(msg.el || msg, pEl); 123 | pEl.parentNode.removeChild(pEl); 124 | } 125 | }; 126 | 127 | /** 128 | * Enable the dialog close link. 129 | * 130 | * @return {Dialog} for chaining 131 | * @api public 132 | */ 133 | 134 | Dialog.prototype.closable = function(){ 135 | return this.addClass('closable'); 136 | }; 137 | 138 | /** 139 | * Add class `name`. 140 | * 141 | * @param {String} name 142 | * @return {Dialog} 143 | * @api public 144 | */ 145 | 146 | Dialog.prototype.addClass = function(name){ 147 | this._classes.add(name); 148 | return this; 149 | }; 150 | 151 | /** 152 | * Set the effect to `type`. 153 | * 154 | * @param {String} type 155 | * @return {Dialog} for chaining 156 | * @api public 157 | */ 158 | 159 | Dialog.prototype.effect = function(type){ 160 | this._effect = type; 161 | this.addClass(type); 162 | return this; 163 | }; 164 | 165 | /** 166 | * Make it modal! 167 | * 168 | * @return {Dialog} for chaining 169 | * @api public 170 | */ 171 | 172 | Dialog.prototype.modal = function(){ 173 | this.overlay(); 174 | return this; 175 | }; 176 | 177 | /** 178 | * Add an overlay. 179 | * 180 | * @return {Dialog} for chaining 181 | * @api public 182 | */ 183 | 184 | Dialog.prototype.overlay = function(opts){ 185 | this._overlayOptions = opts || { closable: true }; 186 | return this; 187 | }; 188 | 189 | /** 190 | * Close the dialog when the escape key is pressed. 191 | * 192 | * @api public 193 | */ 194 | 195 | Dialog.prototype.escapable = function(){ 196 | var self = this; 197 | // Save reference to remove listener later 198 | self._escKeyCallback = self._escKeyCallback || function (e) { 199 | e.which = e.which || e.keyCode; 200 | if (27 !== e.which) return; 201 | self.emit('escape'); 202 | }; 203 | events.bind(document, 'keydown', self._escKeyCallback); 204 | return this; 205 | }; 206 | 207 | /** 208 | * Fixed dialogs position can be manipulated through CSS. 209 | * 210 | * @return {Dialog} for chaining 211 | * @api public 212 | */ 213 | 214 | Dialog.prototype.fixed = function(){ 215 | this._fixed = true; 216 | return this; 217 | } 218 | 219 | /** 220 | * Show the dialog. 221 | * 222 | * Emits "show" event. 223 | * 224 | * @return {Dialog} for chaining 225 | * @api public 226 | */ 227 | 228 | Dialog.prototype.show = function(){ 229 | var overlay = this._overlay; 230 | 231 | // overlay 232 | this.showOverlay(); 233 | 234 | // escape 235 | if (!overlay || overlay.closable) this.escapable(); 236 | 237 | // position 238 | document.body.appendChild(this.el); 239 | this._classes.remove('hide'); 240 | this.emit('show'); 241 | return this; 242 | }; 243 | 244 | /** 245 | * Hide the overlay. 246 | * 247 | * @api private 248 | */ 249 | 250 | Dialog.prototype.hideOverlay = function(){ 251 | if (!this._overlay) return; 252 | this._overlay.off('hide'); 253 | this._overlay.hide(); 254 | this._overlay = null; 255 | }; 256 | 257 | /** 258 | * Show the overlay. 259 | * 260 | * @api private 261 | */ 262 | 263 | Dialog.prototype.showOverlay = function(){ 264 | var self = this; 265 | 266 | if (!self._overlayOptions) return; 267 | self._overlay = overlay(self._overlayOptions) 268 | .once('hide', function(){ 269 | self._overlay = null; 270 | self.hide(); 271 | }) 272 | .show(); 273 | self._classes.add('modal'); 274 | }; 275 | 276 | /** 277 | * Hide the dialog with optional delay of `ms`, 278 | * otherwise the dialog is removed immediately. 279 | * 280 | * Emits "hide" event. 281 | * 282 | * @return {Number} ms 283 | * @return {Dialog} for chaining 284 | * @api public 285 | */ 286 | 287 | Dialog.prototype.hide = function(ms){ 288 | var self = this; 289 | 290 | if (self._escKeyCallback) { 291 | events.unbind(document, 'keydown', self._escKeyCallback); 292 | } 293 | 294 | // prevent thrashing 295 | self.hiding = true; 296 | 297 | // duration 298 | if (ms) { 299 | setTimeout(function(){ 300 | self.hide(); 301 | }, ms); 302 | return self; 303 | } 304 | 305 | // hide / remove 306 | self._classes.add('hide'); 307 | if (self._effect) { 308 | setTimeout(function(){ 309 | self.remove(); 310 | }, 500); 311 | } else { 312 | self.remove(); 313 | } 314 | 315 | // overlay 316 | self.hideOverlay(); 317 | 318 | return self; 319 | }; 320 | /** 321 | * Hide the dialog without potential animation. 322 | * 323 | * @return {Dialog} for chaining 324 | * @api public 325 | */ 326 | 327 | Dialog.prototype.remove = function(){ 328 | if (this.el.parentNode) { 329 | this.emit('hide'); 330 | this.el.parentNode.removeChild(this.el); 331 | } 332 | return this; 333 | }; 334 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dialog-component", 3 | "description": "Dialog component", 4 | "version": "1.0.0", 5 | "keywords": [ 6 | "dialog", 7 | "ui" 8 | ], 9 | "bugs": { 10 | "url": "https://github.com/component/dialog/issues" 11 | }, 12 | "homepage": "https://github.com/component/dialog", 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:component/dialog.git" 16 | }, 17 | "license": "MIT", 18 | "browser": { 19 | "emitter": "component-emitter", 20 | "overlay": "overlay-component", 21 | "event": "component-event", 22 | "classes": "component-classes", 23 | "query": "component-query" 24 | }, 25 | "browserify": { 26 | "transform": [ 27 | "stringify" 28 | ] 29 | }, 30 | "dependencies": { 31 | "component-query": "^0.0.3", 32 | "component-event": "^0.1.0", 33 | "component-emitter": "^1.1.1", 34 | "domify": "^1.1.1", 35 | "overlay-component": "^0.3.5", 36 | "component-classes": "^1.1.3", 37 | "stringify": "^3.1.0" 38 | }, 39 | "style": "./dialog.css" 40 | } -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Title 4 | ×close 5 |
6 |

Message

7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dialog 5 | 6 | 7 | 27 | 28 | 29 | Dialog 30 | 31 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /test/reuse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dialog 5 | 6 | 7 | 27 | 28 | 29 | Dialog 30 | 31 | 46 | 47 | 48 | --------------------------------------------------------------------------------