├── .gitignore ├── .npmignore ├── README.md ├── gulpfile.coffee ├── package.json ├── src ├── demo │ ├── main.jsx │ └── theme.css ├── dialog.coffee ├── dialog.less ├── fade.less ├── file-modal.coffee ├── file-modal.less ├── index.coffee ├── mixin-layered.coffee ├── modal.coffee ├── modal.less ├── overlay.coffee ├── overlay.less ├── popover.coffee ├── popover.less ├── reader-modal.coffee ├── reader-modal.less ├── style.less └── transition.coffee ├── template.cirru ├── webpack.config.coffee └── webpack.min.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | 3 | build 4 | node_modules 5 | index.html 6 | lib 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | /build 3 | /gulpfile.coffee 4 | /webpack.* 5 | /template.cirru 6 | /index.html 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | React Lite Dialog, Modals, Popover, Overlay 3 | ---- 4 | 5 | Dialog, Modals, Popover, Overlay components from Talk by Teambition. 6 | 7 | Demo http://ui.talk.ai/react-lite-layered/ 8 | 9 | Inspired by http://stackoverflow.com/a/26789089/883571 10 | 11 | > Notice, `0.1.x` is using React `0.14.x`, while `0.0.x` was using React `0.13`. 12 | 13 | ### Properties 14 | 15 | This module contains 6 layered components. There are some **common properties**: 16 | 17 | * `show`(`bool.isRequired`) controls visibility 18 | * `name`(`string`, defaults to `default`), CSS hook `"is-for-#{name}"` 19 | 20 | ##### Modal 21 | 22 | ```coffee 23 | T = React.PropTypes 24 | 25 | propTypes: 26 | # this component accepts children 27 | name: T.string 28 | title: T.string 29 | onCloseClick: T.func.isRequired 30 | showCornerClose: T.bool 31 | show: T.bool.isRequired 32 | ``` 33 | 34 | ##### Popover 35 | 36 | ```coffee 37 | propTypes: 38 | # this component accepts children 39 | title: T.string 40 | name: T.string 41 | onPopoverClose: T.func 42 | positionAlgorithm: T.func # could be customized 43 | baseArea: T.object.isRequired # top, right, down, left 44 | showClose: T.bool.isRequired 45 | show: T.bool.isRequired 46 | ``` 47 | 48 | * `baseArea`(`object.isRequired`) positions get from `Element.getBoundingClientRect` 49 | 50 | * `positionAlgorithm`(`func`, optional) if given, takes in `baseArea` and returns CSS styles 51 | 52 | 53 | ##### Overlay 54 | 55 | ```coffee 56 | propTypes: 57 | # this component accepts children 58 | show: T.bool.isRequired 59 | name: T.string 60 | ``` 61 | 62 | > Notice: click content to close overlay, not the black area. 63 | 64 | ##### Reader Modal 65 | 66 | ```coffee 67 | T = React.PropTypes 68 | 69 | propTypes: 70 | # this component accepts children 71 | name: T.string 72 | title: T.string 73 | onCloseClick: T.func.isRequired 74 | showCornerClose: T.bool 75 | show: T.bool.isRequired 76 | ``` 77 | > Child structure 78 | ``` 79 | div 80 | .header (fixed at top) 81 | .content (scrollable) 82 | .footer (fixed at bottom) 83 | ``` 84 | 85 | ##### File Modal 86 | 87 | ```coffee 88 | T = React.PropTypes 89 | 90 | propTypes: 91 | # this component accepts children 92 | name: T.string 93 | title: T.string 94 | onCloseClick: T.func.isRequired 95 | showCornerClose: T.bool 96 | show: T.bool.isRequired 97 | ``` 98 | 99 | ##### Transition 100 | 101 | Transition component with timeout. 102 | Read more at: https://github.com/facebook/react/issues/1326 103 | 104 | ### Supposition 105 | 106 | These modules contains bussiness logics of Teambition. 107 | I will suggest copy code and make create you own. 108 | 109 | And the `layered` mixin is tricky. I hope it improved in the future. 110 | 111 | ### Usage 112 | 113 | ```bash 114 | npm i --save react-lite-layered 115 | ``` 116 | 117 | Read [src/demo/main.jsx](main)(compiles with Babel) for details: 118 | 119 | [main]: https://github.com/teambition/react-lite-layered/blob/master/src/demo/main.jsx 120 | 121 | ```jsx 122 | import {default as React} from 'react'; 123 | 124 | import {Popover, Modal, Overlay} from 'react-lite-layered'; 125 | 126 | import './modal.css'; 127 | import './popover.css'; 128 | import './overlay.css'; 129 | import './fade.css'; 130 | import './demo.css'; 131 | 132 | var App = React.createClass({ 133 | displayName: 'page-app', 134 | 135 | getInitialState: function () { 136 | return { 137 | showModal: false, 138 | showPopover: false, 139 | showOverlay: false 140 | }; 141 | }, 142 | 143 | componentDidMount: function() { 144 | this._areaEl = this.refs.area 145 | }, 146 | 147 | getTriggerArea: function() { 148 | if (this._areaEl) { 149 | return this._areaEl.getBoundingClientRect() 150 | } else { 151 | return {} 152 | } 153 | }, 154 | 155 | onModalShow: function() { 156 | this.setState({showModal: true}) 157 | }, 158 | 159 | onModalHide: function() { 160 | this.setState({showModal: false}) 161 | }, 162 | 163 | onPopoverToggle: function(event){ 164 | event.stopPropagation() 165 | this.setState({showPopover: !this.state.showPopover}) 166 | }, 167 | 168 | onPopoverClose: function() { 169 | this.setState({showPopover: false}) 170 | }, 171 | 172 | onOverlayToggle: function() { 173 | this.setState({showOverlay: !this.state.showOverlay}) 174 | }, 175 | 176 | onOverlayClose: function() { 177 | this.setState({showOverlay: false}) 178 | }, 179 | 180 | renderModal: function() { 181 | return 184 |
{"Content of Modal, style this for yor self."}
185 |
186 | }, 187 | 188 | renderPopover: function() { 189 | return 195 |
Some content of popover
196 |
197 | }, 198 | 199 | renderOverlay: function() { 200 | return 201 |
{"Content in Overlay"}
202 |
203 | }, 204 | 205 | render: function() { 206 | return
207 | 208 | 209 | 210 | {this.renderModal()} 211 | {this.renderPopover()} 212 | {this.renderOverlay()} 213 |
214 | } 215 | }); 216 | 217 | var PageApp = React.createFactory(App); 218 | 219 | var demo = document.querySelector('.demo'); 220 | 221 | React.render(PageApp(), demo); 222 | ``` 223 | 224 | ### Develop 225 | 226 | ```text 227 | npm i 228 | ``` 229 | 230 | You need a static file server for the HTML files. Personally I suggest using Nginx. 231 | 232 | Develop: 233 | 234 | ```bash 235 | gulp html # regenerate index.html 236 | webpack-dev-server --hot # enable live-reloading 237 | ``` 238 | 239 | or simply 240 | ```bash 241 | npm run dev 242 | ``` 243 | 244 | Build (Pack and optimize js, reivision js and add entry in `index.html`): 245 | 246 | ```bash 247 | gulp build 248 | ``` 249 | 250 | ### License 251 | 252 | MIT 253 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | gulp = require('gulp') 4 | sequence = require('run-sequence') 5 | exec = require('child_process').exec 6 | env = 7 | dev: true 8 | main: 'http://localhost:8080/build/main.js' 9 | 10 | gulp.task 'script', -> 11 | coffee = require('gulp-coffee') 12 | gulp 13 | .src 'src/*.coffee' 14 | .pipe coffee() 15 | .pipe gulp.dest('lib/') 16 | 17 | gulp.task 'rsync', (cb) -> 18 | wrapper = require 'rsyncwrapper' 19 | wrapper.rsync 20 | ssh: true 21 | src: ['index.html', 'build'] 22 | recursive: true 23 | args: ['--verbose'] 24 | dest: 'talk-ui:/teambition/server/talk-ui/react-lite-layered' 25 | deleteAll: true 26 | , (error, stdout, stderr, cmd) -> 27 | if error? 28 | throw error 29 | console.error stderr 30 | console.log cmd 31 | cb() 32 | 33 | gulp.task 'html', (cb) -> 34 | require('cirru-script/lib/register') 35 | html = require('./template.cirru') 36 | fs = require('fs') 37 | assets = undefined 38 | unless env.dev 39 | assets = require('./build/assets.json') 40 | env.main = './build/' + assets.main 41 | 42 | fs.writeFile 'index.html', html(env), cb 43 | 44 | gulp.task 'del', (cb) -> 45 | del = require('del') 46 | del [ 'build' ], cb 47 | 48 | gulp.task 'webpack', (cb) -> 49 | if env.dev 50 | command = 'webpack' 51 | else 52 | command = 'webpack --config webpack.min.coffee --progress' 53 | exec command, (err, stdout, stderr) -> 54 | console.log stdout 55 | console.log stderr 56 | cb err 57 | 58 | gulp.task 'build', (cb) -> 59 | env.dev = false 60 | sequence 'del', 'webpack', 'html', cb 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-lite-layered", 3 | "version": "0.2.3", 4 | "description": "Dropdown menu from Talk by teambition", 5 | "main": "lib/index.js", 6 | "dependencies": { 7 | "bowser": "^0.7.2", 8 | "classnames": "^2.2.3", 9 | "keycode": "^2.0.0", 10 | "react": "^0.14.7", 11 | "react-addons-transition-group": "^0.14.7", 12 | "react-dom": "^0.14.7", 13 | "volubile-ui": "^0.1.2" 14 | }, 15 | "devDependencies": { 16 | "autoprefixer-loader": "^3.1.0", 17 | "babel-core": "^5.1.10", 18 | "babel-loader": "^5.0.0", 19 | "cirru-script": "^0.2.3", 20 | "coffee-loader": "^0.7.2", 21 | "coffee-script": "^1.9.1", 22 | "css-loader": "^0.10.1", 23 | "del": "^1.1.1", 24 | "gulp": "^3.8.11", 25 | "gulp-coffee": "^2.3.1", 26 | "less": "^2.5.1", 27 | "less-loader": "^2.2.0", 28 | "node-libs-browser": "^0.5.2", 29 | "rsyncwrapper": "^0.4.3", 30 | "run-sequence": "^1.0.2", 31 | "stir-template": "^0.1.4", 32 | "style-loader": "^0.10.2", 33 | "url-loader": "^0.5.6", 34 | "webpack": "^1.8.4", 35 | "webpack-dev-server": "^1.8.0" 36 | }, 37 | "scripts": { 38 | "test": "echo \"Error: no test specified\" && exit 1", 39 | "dev": "gulp html && webpack-dev-server --hot --host=0.0.0.0" 40 | }, 41 | "keywords": [ 42 | "react-component", 43 | "dropdown", 44 | "modal", 45 | "dialog", 46 | "popover" 47 | ], 48 | "author": "Teambition jiyinyiyong", 49 | "license": "MIT", 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/teambition/react-lite-layered.git" 53 | }, 54 | "bugs": { 55 | "url": "https://github.com/teambition/react-lite-layered/issues" 56 | }, 57 | "homepage": "https://github.com/teambition/react-lite-layered" 58 | } 59 | -------------------------------------------------------------------------------- /src/demo/main.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | 5 | import {Dialog, Popover, Modal, Overlay, ReaderModal, FileModal} from '../index' 6 | 7 | import './theme.css' 8 | import '../style.less' 9 | 10 | var App = React.createClass({ 11 | displayName: 'page-app', 12 | 13 | getInitialState: function () { 14 | return { 15 | showDialog: false, 16 | showModal: false, 17 | showPopover: false, 18 | showOverlay: false, 19 | showReaderModal: false, 20 | showFileModal: false 21 | } 22 | }, 23 | 24 | componentDidMount: function() { 25 | this._areaEl = this.refs.area 26 | }, 27 | 28 | getTriggerArea: function() { 29 | if (this._areaEl) { 30 | return this._areaEl.getBoundingClientRect() 31 | } else { 32 | return {} 33 | } 34 | }, 35 | 36 | onDialogShow: function() { 37 | this.setState({showDialog: true}) 38 | }, 39 | 40 | onDialogClose: function() { 41 | this.setState({showDialog: false}) 42 | }, 43 | 44 | onModalShow: function() { 45 | this.setState({showModal: true}) 46 | }, 47 | onReaderModalShow: function() { 48 | this.setState({showReaderModal: true}) 49 | }, 50 | onFileModalShow: function() { 51 | this.setState({showFileModal: true}) 52 | }, 53 | 54 | onModalHide: function() { 55 | this.setState({showModal: false}) 56 | }, 57 | onReaderModalHide: function() { 58 | this.setState({showReaderModal: false}) 59 | }, 60 | onFileModalHide: function() { 61 | this.setState({showFileModal: false}) 62 | }, 63 | onPopoverToggle: function(event){ 64 | event.stopPropagation() 65 | this.setState({showPopover: !this.state.showPopover}) 66 | }, 67 | 68 | onPopoverClose: function() { 69 | this.setState({showPopover: false}) 70 | }, 71 | 72 | onOverlayToggle: function() { 73 | this.setState({showOverlay: !this.state.showOverlay}) 74 | }, 75 | 76 | onOverlayClose: function() { 77 | this.setState({showOverlay: false}) 78 | }, 79 | 80 | renderDialog: function() { 81 | return 85 |
{"hey"}
86 |
87 | }, 88 | 89 | renderModal: function() { 90 | return 93 |
{"Content of Modal, style this for yor self."}
94 |
{"Better if you add some padding~"}
95 |
96 | }, 97 | 98 | renderPopover: function() { 99 | return 105 |
106 | {'Some content of popover'} 107 |
108 | {'This can be a list'} 109 |
110 | {'Add padding as you want'} 111 |
112 |
113 | }, 114 | 115 | renderOverlay: function() { 116 | return 117 |
118 | {"Content in Overlay. Click here to close."} 119 |
120 |
121 | }, 122 | 123 | renderReaderModal: function() { 124 | return 127 |
128 |
This is reader demo
129 |
130 |

The paragraph is 500px high

131 |
132 |
End of reader modal
133 |
134 |
135 | }, 136 | 137 | renderFileModal: function() { 138 | return 141 |
142 |
This is file modal demo
143 |
144 |

The paragraph is 500px high

145 |
146 |
End of file modal
147 |
148 |
149 | }, 150 | 151 | render: function() { 152 | return
153 |
Show Dialog
154 |
Show Modal
155 |
Show Popover
156 |
Show Overlay
157 |
Show Reader Modal
158 |
Show File Modal
159 | 160 | {this.renderDialog()} 161 | {this.renderModal()} 162 | {this.renderPopover()} 163 | {this.renderOverlay()} 164 | {this.renderReaderModal()} 165 | {this.renderFileModal()} 166 |
167 | } 168 | }) 169 | 170 | var PageApp = React.createFactory(App) 171 | 172 | var demo = document.querySelector('.demo') 173 | 174 | ReactDOM.render(PageApp(), demo) 175 | -------------------------------------------------------------------------------- /src/demo/theme.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | padding: 100px 100px 300px 100px; 4 | font-family: Palatino, Optima, Georgia, serif; 5 | font-size: 16px; 6 | line-height: 1.6em; 7 | } 8 | 9 | .title { 10 | font-weight: bold; 11 | font-size: 20px; 12 | margin: 20px 0; 13 | } 14 | 15 | .content { 16 | padding: 10px; 17 | background-color: white; 18 | } 19 | 20 | .button { 21 | background-color: #ccf; 22 | outline: none; 23 | display: inline-block; 24 | text-align: center; 25 | font-size: 14px; 26 | color: white; 27 | white-space: nowrap; 28 | cursor: pointer; 29 | touch-action: manipulation; 30 | border-radius: 3px; 31 | border: none; 32 | text-transform: none; 33 | -webkit-user-select: none; 34 | height: 40px; 35 | line-height: 40px; 36 | padding: 0 10px; 37 | vertical-align: middle; 38 | min-width: 80px; 39 | margin-right: 20px; 40 | } 41 | 42 | .button.is-danger { 43 | color: white; 44 | background-color: #ff7043; 45 | 46 | } 47 | 48 | .button.is-danger:hover { 49 | color: white; 50 | background-color: darken(#ff7043, 5%) 51 | } 52 | 53 | .button.is-danger { 54 | color: white; 55 | background-color: #ff7043; 56 | } 57 | 58 | .button.is-link { 59 | background-color: transparent; 60 | color: #808080;; 61 | } 62 | 63 | .demo { 64 | margin: 40px 0; 65 | } 66 | 67 | .demo .button { 68 | padding: 0 20px; 69 | } 70 | 71 | .lite-overlay .content { 72 | width: 300px; 73 | height: 20px; 74 | margin: auto; 75 | position: absolute; 76 | left: 0; 77 | right: 0; 78 | top: 0; 79 | bottom: 0; 80 | } 81 | 82 | .lite-reader-modal .header{ 83 | padding: 17px 20px; 84 | border-bottom: 1px solid #efefef; 85 | text-align: center; 86 | 87 | } 88 | .lite-reader-modal .footer{ 89 | padding: 17px 20px; 90 | border-top: 1px solid #efefef; 91 | text-align: center; 92 | } 93 | -------------------------------------------------------------------------------- /src/dialog.coffee: -------------------------------------------------------------------------------- 1 | 2 | React = require 'react' 3 | keycode = require 'keycode' 4 | 5 | mixinLayered = require './mixin-layered' 6 | 7 | Transition = React.createFactory require './transition' 8 | 9 | div = React.createFactory 'div' 10 | span = React.createFactory 'span' 11 | a = React.createFactory 'a' 12 | button = React.createFactory 'button' 13 | 14 | T = React.PropTypes 15 | cx = require 'classnames' 16 | 17 | module.exports = React.createClass 18 | displayName: 'lite-dialog' 19 | mixins: [mixinLayered] 20 | 21 | propTypes: 22 | # this components accepts children 23 | cancel: T.string 24 | confirm: T.string 25 | content: T.string 26 | flexible: T.bool 27 | show: T.bool.isRequired 28 | onCloseClick: T.func.isRequired 29 | onConfirm: T.func.isRequired 30 | 31 | bindWindowEvents: -> 32 | window.addEventListener 'keydown', @onWindowKeydown 33 | 34 | unbindWindowEvents: -> 35 | window.removeEventListener 'keydown', @onWindowKeydown 36 | 37 | onWindowKeydown: (event) -> 38 | if keycode(event.keyCode) is 'esc' 39 | @onCloseClick() 40 | 41 | onCloseClick: -> 42 | @props.onCloseClick() 43 | 44 | onConfirmClick: -> 45 | @props.onConfirm() 46 | @props.onCloseClick() 47 | 48 | onBackdropClick: (event) -> 49 | if event.target is event.currentTarget 50 | @onCloseClick() 51 | 52 | renderActions: -> 53 | div className: 'actions line', 54 | button className: 'button is-link', onClick: @onCloseClick, 55 | @props.cancel 56 | button className: 'button is-danger', onClick: @onConfirmClick, 57 | @props.confirm 58 | 59 | renderLayer: (afterTransition) -> 60 | className = "lite-dialog is-for-#{@props.name}" 61 | boxClassName = cx 'box', 'flex': @props.flexible 62 | Transition transitionName: 'fade', enterTimeout: 200, leaveTimeout: 350, 63 | if @props.show and afterTransition 64 | div className: className, onClick: @onBackdropClick, 65 | div className: 'wrapper', onClick: @onBackdropClick, 66 | div className: boxClassName, 67 | div className: 'content', 68 | div className: 'inner', @props.content 69 | @props.children 70 | @renderActions() 71 | 72 | render: -> 73 | div() 74 | -------------------------------------------------------------------------------- /src/dialog.less: -------------------------------------------------------------------------------- 1 | @black: #000000; 2 | @light-black: lighten(@black, 22%); // #383838 3 | 4 | .lite-dialog { 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | right: 0; 9 | bottom: 0; 10 | /* tricky, search is 200 increase this*/ 11 | z-index: 210; 12 | 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | align-items: stretch; 17 | 18 | height: 100%; 19 | line-height: 1.6px; 20 | overflow: auto; 21 | background-color: fadeout(@black, 50%); 22 | color: @light-black; 23 | -webkit-transform: translateZ(0); 24 | -webkit-backface-visibility: hidden; 25 | 26 | > .wrapper { 27 | padding: 60px 0; 28 | overflow: auto; 29 | text-align: center; 30 | } 31 | 32 | > .wrapper > .box { 33 | max-width: 600px; 34 | min-width: 350px; 35 | display: inline-block; 36 | width: auto; 37 | background-color: white; 38 | border-radius: 3px; 39 | text-align: left; 40 | 41 | .content { 42 | padding: 20px; 43 | line-height: 28px; 44 | 45 | .inner { 46 | margin-bottom: 20px; 47 | } 48 | } 49 | 50 | .actions { 51 | text-align: right; 52 | } 53 | } 54 | } 55 | 56 | // In & Out transition 57 | .lite-dialog.fade-enter .box { 58 | .translate(0, -40px); 59 | } 60 | 61 | .lite-dialog.fade-enter-active .box { 62 | .translate(0, 0); 63 | .scale(1); 64 | .transition-transform(200ms ease-in-out); 65 | } 66 | 67 | .lite-dialog.fade-leave .box { 68 | .transition(all 150ms ease-in-out); 69 | } 70 | 71 | .lite-dialog.fade-leave-active .box { 72 | .opacity(0.01); 73 | .translate(0, -40px); 74 | } 75 | -------------------------------------------------------------------------------- /src/fade.less: -------------------------------------------------------------------------------- 1 | .fade-enter { 2 | .opacity(0.01); 3 | 4 | &.fade-enter-active { 5 | .opacity(1); 6 | .transition(opacity 150ms ease-in-out 50ms); 7 | } 8 | } 9 | 10 | .fade-leave { 11 | .opacity(1); 12 | 13 | &.fade-leave-active { 14 | .opacity(0.01); 15 | .transition(opacity 150ms ease-in-out 200ms); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/file-modal.coffee: -------------------------------------------------------------------------------- 1 | 2 | React = require 'react' 3 | keycode = require 'keycode' 4 | 5 | mixinLayered = require './mixin-layered' 6 | 7 | Transition = React.createFactory require './transition' 8 | 9 | div = React.createFactory 'div' 10 | span = React.createFactory 'span' 11 | a = React.createFactory 'a' 12 | 13 | T = React.PropTypes 14 | cx = require 'classnames' 15 | 16 | module.exports = React.createClass 17 | displayName: 'file-modal' 18 | mixins: [mixinLayered] 19 | 20 | propTypes: 21 | # this components accepts children 22 | name: T.string 23 | title: T.string 24 | onCloseClick: T.func.isRequired 25 | showCornerClose: T.bool 26 | show: T.bool.isRequired 27 | 28 | bindWindowEvents: -> 29 | window.addEventListener 'keydown', @onWindowKeydown 30 | 31 | unbindWindowEvents: -> 32 | window.removeEventListener 'keydown', @onWindowKeydown 33 | 34 | onWindowKeydown: (event) -> 35 | if keycode(event.keyCode) is 'esc' 36 | @onCloseClick() 37 | 38 | onCloseClick: -> 39 | @props.onCloseClick() 40 | 41 | onBackdropClick: (event) -> 42 | if not @props.showCornerClose && event.target is event.currentTarget 43 | @onCloseClick() 44 | 45 | renderLayer: (afterTransition) -> 46 | className = "lite-file-modal is-for-#{@props.name}" 47 | Transition transitionName: 'fade', enterTimeout: 200, leaveTimeout: 350, 48 | if @props.show and afterTransition 49 | div className: className, onMouseDown: @onBackdropClick, 50 | div className: 'wrapper', onMouseDown: @onBackdropClick, 51 | div className: 'box', 52 | @props.children 53 | 54 | 55 | render: -> 56 | div() 57 | -------------------------------------------------------------------------------- /src/file-modal.less: -------------------------------------------------------------------------------- 1 | @black: #000000; 2 | @light-black: lighten(@black, 22%); // #383838 3 | .lite-file-modal { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | right: 0; 8 | bottom: 0; 9 | /* tricky, search is 200 increase this*/ 10 | z-index: 210; 11 | background-color: fadeout(@black, 50%); 12 | height:100%; 13 | 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | 18 | -webkit-transform: translateZ(0); 19 | -webkit-backface-visibility: hidden; 20 | line-height: 1.6em; 21 | color: @light-black; 22 | 23 | > .wrapper { 24 | padding:60px; 25 | box-sizing: border-box; 26 | height: 100%; 27 | > .box { 28 | width: 100%; 29 | height:100%; 30 | max-width:1300px; 31 | margin:auto; 32 | border-radius: 3px; 33 | text-align: left; 34 | overflow:hidden; 35 | background-color: #fff; 36 | } 37 | } 38 | } 39 | 40 | // In & Out transition 41 | .lite-file-modal.fade-enter .box { 42 | .translate(0, -40px); 43 | } 44 | 45 | .lite-file-modal.fade-enter-active .box { 46 | .translate(0, 0); 47 | .scale(1); 48 | .transition-transform(200ms ease-in-out); 49 | } 50 | 51 | .lite-file-modal.fade-leave .box { 52 | .transition(all 150ms ease-in-out); 53 | } 54 | 55 | .lite-file-modal.fade-leave-active .box { 56 | .opacity(0.01); 57 | .translate(0, -40px); 58 | } 59 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | exports.Dialog = require('./dialog') 3 | exports.Modal = require('./modal') 4 | exports.Overlay = require('./overlay') 5 | exports.Popover = require('./popover') 6 | exports.Transition = require('./transition') 7 | exports.ReaderModal = require('./reader-modal') 8 | exports.FileModal = require('./file-modal') -------------------------------------------------------------------------------- /src/mixin-layered.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Code mostly done at: 3 | # http://stackoverflow.com/a/26789089/883571 4 | 5 | React = require 'react' 6 | ReactDOM = require 'react-dom' 7 | # bowser = require 'bowser' 8 | 9 | div = React.createFactory 'div' 10 | 11 | module.exports = 12 | 13 | # needs to implement 14 | 15 | # renderLayer: (afterTransition) -> 16 | # use afterTransition to control initial animation 17 | 18 | # bindWindowEvents: -> 19 | 20 | componentWillUnmount: -> 21 | return unless @_target? 22 | @_unrenderLayer() 23 | document.body.removeChild @_target 24 | @unbindWindowEvents?() 25 | 26 | componentDidUpdate: -> 27 | @_renderLayer() 28 | 29 | _renderLayer: -> 30 | if @_target? 31 | @_renderChildren() 32 | return 33 | if (not @props.show) and (not @_target?) 34 | return 35 | # so show but found no target 36 | @_target = document.createElement 'div' 37 | document.body.appendChild @_target 38 | @bindWindowEvents?() 39 | tree = @renderLayer false 40 | ReactDOM.render tree, @_target 41 | 42 | # use delay to create transition 43 | # more delay to fix in safari 44 | setTimeout => 45 | @_renderChildren() 46 | , 100 47 | 48 | _renderChildren: -> 49 | tree = @renderLayer true 50 | ReactDOM.render tree, @_target 51 | 52 | _unrenderLayer: -> 53 | ReactDOM.unmountComponentAtNode @_target 54 | -------------------------------------------------------------------------------- /src/modal.coffee: -------------------------------------------------------------------------------- 1 | 2 | React = require 'react' 3 | keycode = require 'keycode' 4 | 5 | mixinLayered = require './mixin-layered' 6 | 7 | Transition = React.createFactory require './transition' 8 | 9 | div = React.createFactory 'div' 10 | span = React.createFactory 'span' 11 | a = React.createFactory 'a' 12 | 13 | T = React.PropTypes 14 | cx = require 'classnames' 15 | 16 | module.exports = React.createClass 17 | displayName: 'lite-modal' 18 | mixins: [mixinLayered] 19 | 20 | propTypes: 21 | # this components accepts children 22 | name: T.string 23 | title: T.string 24 | onCloseClick: T.func.isRequired 25 | showCornerClose: T.bool 26 | show: T.bool.isRequired 27 | 28 | bindWindowEvents: -> 29 | window.addEventListener 'keydown', @onWindowKeydown 30 | 31 | unbindWindowEvents: -> 32 | window.removeEventListener 'keydown', @onWindowKeydown 33 | 34 | onWindowKeydown: (event) -> 35 | if keycode(event.keyCode) is 'esc' 36 | @onCloseClick() 37 | 38 | onCloseClick: ()-> 39 | @props.onCloseClick() 40 | 41 | onBackdropClick: (event) -> 42 | event.stopPropagation() 43 | #Object.keys(event).forEach (key) -> 44 | # console.log key,event[key] 45 | if not @props.showCornerClose && event.target is event.currentTarget 46 | @onCloseClick() 47 | 48 | renderLayer: (afterTransition) -> 49 | className = "lite-modal is-for-#{@props.name}" 50 | 51 | Transition transitionName: 'fade', enterTimeout: 200, leaveTimeout: 350, 52 | if @props.show and afterTransition 53 | div className: className, onMouseDown: @onBackdropClick, 54 | div className: 'wrapper', onMouseDown: @onBackdropClick, 55 | div className: 'box', 56 | if @props.title? 57 | div className: 'title', 58 | span className: 'name', @props.title 59 | span className: 'icon icon-remove', onClick: @onCloseClick 60 | @props.children 61 | 62 | render: -> 63 | div() 64 | -------------------------------------------------------------------------------- /src/modal.less: -------------------------------------------------------------------------------- 1 | @black: #000000; 2 | @light-black: lighten(@black, 22%); // #383838 3 | 4 | .lite-modal { 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | right: 0; 9 | bottom: 0; 10 | /* tricky, search is 200 increase this*/ 11 | z-index: 210; 12 | 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | align-items: stretch; 17 | 18 | height: 100%; 19 | line-height: 1.6em; 20 | overflow: auto; 21 | background-color: fadeout(@black, 50%); 22 | color: @light-black; 23 | -webkit-transform: translateZ(0); 24 | -webkit-backface-visibility: hidden; 25 | 26 | > .wrapper { 27 | padding: 60px 0; 28 | overflow: auto; 29 | } 30 | 31 | > .wrapper > .box { 32 | width: 600px; 33 | background-color: white; 34 | border-radius: 3px; 35 | text-align: left; 36 | margin: 0 auto; 37 | 38 | & > .title { 39 | padding: 17px 20px; 40 | border-bottom: 1px solid lighten(@black, 90%); 41 | text-align: center; 42 | 43 | .name { 44 | font-size: 18px; 45 | color: @light-black; 46 | } 47 | 48 | .icon-remove { 49 | float: right; 50 | font-size: 14px; 51 | cursor: pointer; 52 | } 53 | } 54 | } 55 | } 56 | 57 | // In & Out transition 58 | .lite-modal.fade-enter .box { 59 | .translate(0, -40px); 60 | } 61 | 62 | .lite-modal.fade-enter-active .box { 63 | .translate(0, 0); 64 | .scale(1); 65 | .transition-transform(200ms ease-in-out); 66 | } 67 | 68 | .lite-modal.fade-leave .box { 69 | .transition(all 150ms ease-in-out); 70 | } 71 | 72 | .lite-modal.fade-leave-active .box { 73 | .opacity(0.01); 74 | .translate(0, -40px); 75 | } 76 | -------------------------------------------------------------------------------- /src/overlay.coffee: -------------------------------------------------------------------------------- 1 | 2 | React = require 'react' 3 | 4 | div = React.createFactory 'div' 5 | 6 | mixinLayered = require './mixin-layered' 7 | 8 | Transition = React.createFactory (require './transition') 9 | 10 | T = React.PropTypes 11 | 12 | module.exports = React.createClass 13 | displayName: 'lite-overlay' 14 | mixins: [mixinLayered] 15 | 16 | propTypes: 17 | show: T.bool.isRequired 18 | name: T.string 19 | 20 | getDefaultProps: -> 21 | name: 'default' 22 | 23 | renderLayer: (afterTransition) -> 24 | Transition transitionName: 'fade', enterTimeout: 200, leaveTimeout: 350, 25 | if @props.show and afterTransition 26 | div className: "lite-overlay is-for-#{@props.name}", 27 | @props.children 28 | 29 | render: -> 30 | div() 31 | -------------------------------------------------------------------------------- /src/overlay.less: -------------------------------------------------------------------------------- 1 | .lite-overlay { 2 | position: fixed; 3 | top: 0px; 4 | bottom: 0px; 5 | left: 0px; 6 | right: 0px; 7 | background-color: black; 8 | z-index: 220; 9 | overflow: auto; 10 | 11 | img { 12 | position: absolute; 13 | top: 0; 14 | bottom: 0; 15 | left: 0; 16 | right: 0; 17 | margin: auto; 18 | cursor: zoom-out; 19 | cursor: -webkit-zoom-out; 20 | } 21 | 22 | img.is-large { 23 | position: absolute; 24 | left: 0; 25 | top: 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/popover.coffee: -------------------------------------------------------------------------------- 1 | 2 | React = require 'react' 3 | 4 | mixinLayered = require './mixin-layered' 5 | 6 | div = React.createFactory 'div' 7 | span = React.createFactory 'span' 8 | a = React.createFactory 'a' 9 | 10 | T = React.PropTypes 11 | 12 | PopoverMenu = React.createFactory React.createClass 13 | displayName: 'lite-popover-menu' 14 | 15 | propTypes: 16 | # accepts children 17 | onClose: T.func.isRequired 18 | style: T.object.isRequired 19 | decorator: T.string.isRequired 20 | 21 | componentDidMount: -> 22 | event = new MouseEvent 'click', 23 | view: window 24 | bubbles: true 25 | cancelable: true 26 | window.dispatchEvent event 27 | window.addEventListener 'click', @onWindowClick 28 | 29 | componentWillUnmount: -> 30 | window.removeEventListener 'click', @onWindowClick 31 | 32 | onWindowClick: -> 33 | @props.onClose() 34 | 35 | onClick: (event) -> 36 | event.stopPropagation() 37 | 38 | render: -> 39 | div className: "lite-popover #{@props.decorator}", style: @props.style, onClick: @onClick, 40 | @props.children 41 | 42 | module.exports = React.createClass 43 | displayName: 'lite-popover' 44 | mixins: [mixinLayered] 45 | 46 | propTypes: 47 | # this component accepts children 48 | title: T.string 49 | name: T.string 50 | onPopoverClose: T.func 51 | positionAlgorithm: T.func # could be customized 52 | baseArea: T.object.isRequired # top, right, down, left 53 | showClose: T.bool.isRequired 54 | show: T.bool.isRequired 55 | 56 | computePosition: -> 57 | if @props.positionAlgorithm? 58 | return @props.positionAlgorithm @props.baseArea 59 | width = 240 # card default width 60 | supposeHeight = 200 61 | half = width / 2 62 | maxTop = innerHeight - supposeHeight 63 | top = Math.min @props.baseArea.bottom, maxTop 64 | xCenter = (@props.baseArea.left + @props.baseArea.right) / 2 65 | left = xCenter - half 66 | if left < 20 then left = 20 # mind the left edge 67 | if (left + width + 20) > innerWidth 68 | left = innerWidth - width - 20 69 | # return as style 70 | top: "#{top}px" 71 | left: "#{left}px" 72 | 73 | onPopoverClose: -> 74 | @props.onPopoverClose() 75 | 76 | renderLayer: (afterTransition) -> 77 | decorator = "is-#{@props.name or 'default'}" 78 | div null, 79 | if @props.show and afterTransition 80 | style = @computePosition() 81 | PopoverMenu style: style, decorator: decorator, onClose: @onPopoverClose, 82 | if @props.title? 83 | div className: 'header', 84 | span className: 'title', @props.title 85 | if @props.showClose 86 | a className: 'icon icon-remove', onClick: @onPopoverClose 87 | div className: "body #{decorator}", 88 | @props.children 89 | 90 | render: -> 91 | div() 92 | -------------------------------------------------------------------------------- /src/popover.less: -------------------------------------------------------------------------------- 1 | @black: #000000; 2 | @light-black: lighten(black, 22%); // #383838 3 | 4 | .lite-popover { 5 | background: white; 6 | position: fixed; 7 | z-index: 230; 8 | box-shadow: 0 7px 21px rgba(0,0,0,.1); 9 | min-width: 240px; 10 | line-height: 30px; 11 | border-radius: 3px; 12 | } 13 | 14 | // header 15 | 16 | .lite-popover > .header { 17 | text-align: center; 18 | position: relative; 19 | padding: 10px 0; 20 | margin: 0 15px; 21 | border-bottom: 1px solid rgba(0, 0, 0, .07); 22 | color: @light-black; 23 | } 24 | 25 | .lite-popover > .header .title { 26 | font-size: 15px; 27 | } 28 | 29 | .lite-popover > .icon-remove { 30 | position: absolute; 31 | right: 19px; 32 | top: 11px; 33 | } 34 | 35 | // body 36 | 37 | .lite-popover > .body { 38 | color: #808080; 39 | padding: 5px 0; 40 | } 41 | 42 | .lite-popover > .body .item { 43 | padding: 5px 15px; 44 | cursor: pointer; 45 | } 46 | 47 | .lite-popover > .body .icon { 48 | float: left; 49 | } 50 | 51 | .lite-popover > .body [class*="icon-char"] { 52 | margin: 3px 0 0 -4px; 53 | } 54 | 55 | .lite-popover > .body .icon.is-big { 56 | margin-left: -4px; 57 | } 58 | 59 | .lite-popover .line > .icon + * { 60 | margin-left: 15px; 61 | } 62 | 63 | .lite-popover .line > .icon-char + *, 64 | .lite-popover .line > .icon.is-big + * { 65 | margin-left: 10px; 66 | } 67 | 68 | .lite-popover > .body .item:hover { 69 | background-color: #f7f7f7; 70 | } 71 | 72 | .lite-popover > .body.is-member-menu { 73 | 74 | .contact-name .name { 75 | max-width: 180px; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/reader-modal.coffee: -------------------------------------------------------------------------------- 1 | 2 | React = require 'react' 3 | keycode = require 'keycode' 4 | 5 | mixinLayered = require './mixin-layered' 6 | 7 | Transition = React.createFactory require './transition' 8 | 9 | div = React.createFactory 'div' 10 | span = React.createFactory 'span' 11 | a = React.createFactory 'a' 12 | 13 | T = React.PropTypes 14 | cx = require 'classnames' 15 | 16 | module.exports = React.createClass 17 | displayName: 'reader-modal' 18 | mixins: [mixinLayered] 19 | 20 | propTypes: 21 | # this components accepts children 22 | name: T.string 23 | title: T.string 24 | onCloseClick: T.func.isRequired 25 | showCornerClose: T.bool 26 | show: T.bool.isRequired 27 | 28 | bindWindowEvents: -> 29 | window.addEventListener 'keydown', @onWindowKeydown 30 | 31 | unbindWindowEvents: -> 32 | window.removeEventListener 'keydown', @onWindowKeydown 33 | 34 | onWindowKeydown: (event) -> 35 | if keycode(event.keyCode) is 'esc' 36 | @onCloseClick() 37 | 38 | onCloseClick: -> 39 | @props.onCloseClick() 40 | 41 | onBackdropClick: (event) -> 42 | if not @props.showCornerClose && event.target is event.currentTarget 43 | @onCloseClick() 44 | 45 | renderLayer: (afterTransition) -> 46 | className = "lite-reader-modal is-for-#{@props.name}" 47 | Transition transitionName: 'fade', enterTimeout: 200, leaveTimeout: 350, 48 | if @props.show and afterTransition 49 | div className: className, onMouseDown: @onBackdropClick, 50 | div className: 'box', onMouseDown: @onBackdropClick, 51 | @props.children 52 | 53 | 54 | render: -> 55 | div() 56 | -------------------------------------------------------------------------------- /src/reader-modal.less: -------------------------------------------------------------------------------- 1 | @black: #000000; 2 | @light-black: lighten(@black, 22%); // #383838 3 | .lite-reader-modal { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | right: 0; 8 | bottom: 0; 9 | /* tricky, search is 200 increase this*/ 10 | z-index: 210; 11 | background-color: fadeout(@black, 50%); 12 | 13 | > .box { 14 | height:100%; 15 | 16 | display: flex; 17 | flex-direction: column; 18 | justify-content: center; 19 | 20 | -webkit-transform: translateZ(0); 21 | -webkit-backface-visibility: hidden; 22 | line-height: 1.6em; 23 | color: @light-black; 24 | 25 | > div { 26 | height:100%; 27 | margin: auto; 28 | width: 800px; 29 | background-color: white; 30 | border-radius: 3px; 31 | text-align: left; 32 | display: flex; 33 | flex-direction: column; 34 | 35 | .content { 36 | flex: 1; 37 | overflow-y: auto; 38 | } 39 | 40 | .header { 41 | flex-shrink: 0; 42 | } 43 | 44 | .footer { 45 | flex-shrink: 0; 46 | } 47 | } 48 | } 49 | } 50 | 51 | // In & Out transition 52 | .lite-reader-modal.fade-enter .box { 53 | .translate(0, -40px); 54 | } 55 | 56 | .lite-reader-modal.fade-enter-active .box { 57 | .translate(0, 0); 58 | .scale(1); 59 | .transition-transform(200ms ease-in-out); 60 | } 61 | 62 | .lite-reader-modal.fade-leave .box { 63 | .transition(all 150ms ease-in-out); 64 | } 65 | 66 | .lite-reader-modal.fade-leave-active .box { 67 | .opacity(0.01); 68 | .translate(0, -40px); 69 | } 70 | -------------------------------------------------------------------------------- /src/style.less: -------------------------------------------------------------------------------- 1 | @import '~volubile-ui/ui/mixins.less'; 2 | 3 | @import './dialog.less'; 4 | @import './fade.less'; 5 | @import './modal.less'; 6 | @import './overlay.less'; 7 | @import './popover.less'; 8 | @import './reader-modal.less'; 9 | @import './file-modal.less'; 10 | -------------------------------------------------------------------------------- /src/transition.coffee: -------------------------------------------------------------------------------- 1 | 2 | # https://github.com/facebook/react/issues/1326 3 | # compiled by JSX compiler and js2coffee 4 | # rewrite to remove jQuery dependency 5 | 6 | React = require("react") 7 | ReactDOM = require("react-dom") 8 | ReactTransitionGroup = require('react-addons-transition-group') 9 | 10 | animationSupported = -> 11 | endEvents.length isnt 0 12 | 13 | removeClass = (node, x) -> 14 | classList = node.className.split(' ') 15 | classList = classList.filter (name) -> name isnt x 16 | node.className = classList.join(' ') 17 | 18 | addClass = (node, x) -> 19 | classList = node.className.split(' ') 20 | classList = classList.concat [x] 21 | node.className = classList.join(' ') 22 | 23 | TICK = 17 24 | EVENT_NAME_MAP = 25 | transitionend: 26 | transition: "transitionend" 27 | WebkitTransition: "webkitTransitionEnd" 28 | MozTransition: "mozTransitionEnd" 29 | OTransition: "oTransitionEnd" 30 | msTransition: "MSTransitionEnd" 31 | 32 | animationend: 33 | animation: "animationend" 34 | WebkitAnimation: "webkitAnimationEnd" 35 | MozAnimation: "mozAnimationEnd" 36 | OAnimation: "oAnimationEnd" 37 | msAnimation: "MSAnimationEnd" 38 | 39 | endEvents = [] 40 | (detectEvents = -> 41 | return if typeof window is "undefined" 42 | testEl = document.createElement("div") 43 | style = testEl.style 44 | delete EVENT_NAME_MAP.animationend.animation unless "AnimationEvent" of window 45 | delete EVENT_NAME_MAP.transitionend.transition unless "TransitionEvent" of window 46 | for baseEventName of EVENT_NAME_MAP 47 | if EVENT_NAME_MAP.hasOwnProperty(baseEventName) 48 | baseEvents = EVENT_NAME_MAP[baseEventName] 49 | for styleName of baseEvents 50 | if styleName of style 51 | endEvents.push baseEvents[styleName] 52 | break 53 | return 54 | )() 55 | 56 | TimeoutTransitionGroupChild = React.createClass 57 | displayName: "TimeoutTransitionGroupChild" 58 | transition: (animationType, finishCallback) -> 59 | node = ReactDOM.findDOMNode @ 60 | className = @props.name + "-" + animationType 61 | activeClassName = className + "-active" 62 | endListener = -> 63 | removeClass node, className 64 | removeClass node, activeClassName 65 | 66 | # Usually this optional callback is used for informing an owner of 67 | # a leave animation and telling it to remove the child. 68 | finishCallback and finishCallback() 69 | return 70 | 71 | unless animationSupported() 72 | endListener() 73 | else 74 | if animationType is "enter" 75 | @animationTimeout = setTimeout(endListener, @props.enterTimeout) 76 | else @animationTimeout = setTimeout(endListener, @props.leaveTimeout) if animationType is "leave" 77 | addClass node, className 78 | 79 | # Need to do this to actually trigger a transition. 80 | @queueClass activeClassName 81 | return 82 | 83 | queueClass: (className) -> 84 | @classNameQueue.push className 85 | @timeout = setTimeout(@flushClassNameQueue, TICK) unless @timeout 86 | return 87 | 88 | flushClassNameQueue: -> 89 | if @isMounted() 90 | addClass (ReactDOM.findDOMNode @), @classNameQueue.join(" ") 91 | @classNameQueue.length = 0 92 | @timeout = null 93 | return 94 | 95 | componentWillMount: -> 96 | @classNameQueue = [] 97 | return 98 | 99 | componentWillUnmount: -> 100 | clearTimeout @timeout if @timeout 101 | clearTimeout @animationTimeout if @animationTimeout 102 | return 103 | 104 | componentWillEnter: (done) -> 105 | if @props.enter 106 | @transition "enter", done 107 | else 108 | done() 109 | return 110 | 111 | componentWillLeave: (done) -> 112 | if @props.leave 113 | @transition "leave", done 114 | else 115 | done() 116 | return 117 | 118 | render: -> 119 | React.Children.only @props.children 120 | 121 | TimeoutTransitionGroup = React.createClass 122 | displayName: "TimeoutTransitionGroup" 123 | propTypes: 124 | enterTimeout: React.PropTypes.number.isRequired 125 | leaveTimeout: React.PropTypes.number.isRequired 126 | transitionName: React.PropTypes.string.isRequired 127 | transitionEnter: React.PropTypes.bool 128 | transitionLeave: React.PropTypes.bool 129 | 130 | getDefaultProps: -> 131 | transitionEnter: true 132 | transitionLeave: true 133 | 134 | _wrapChild: (child) -> 135 | React.createElement TimeoutTransitionGroupChild, 136 | enterTimeout: @props.enterTimeout 137 | leaveTimeout: @props.leaveTimeout 138 | name: @props.transitionName 139 | enter: @props.transitionEnter 140 | leave: @props.transitionLeave 141 | , child 142 | 143 | render: -> 144 | React.createElement ReactTransitionGroup, React.__spread({}, @props, 145 | childFactory: @_wrapChild 146 | ) 147 | 148 | module.exports = TimeoutTransitionGroup 149 | -------------------------------------------------------------------------------- /template.cirru: -------------------------------------------------------------------------------- 1 | 2 | var 3 | stir $ require :stir-template 4 | 5 | var 6 | (object~ html head title body meta script link div a span) stir 7 | 8 | var 9 | line $ \ (text) 10 | return $ div null text 11 | 12 | = module.exports $ \ (data) 13 | return $ stir.render 14 | stir.doctype 15 | html null 16 | head null 17 | title null ":React Lite Layered" 18 | meta $ object (:charset :utf-8) 19 | link $ object (:rel :icon) 20 | :href :http://tp4.sinaimg.cn/5592259015/180/5725970590/1 21 | script $ object (:src data.main) (:defer true) 22 | body null 23 | div 24 | object (:class :intro) 25 | div (object (:class :title)) ":demo of Lite Layered Components" 26 | line ":Add your own style in your projects." 27 | div null 28 | span null ":Read more at " 29 | a 30 | object (:href :http://github.com/teambition/react-lite-layered) 31 | , :github.com/teambition/react-lite-layered 32 | span null :. 33 | div 34 | object (:class :demo) 35 | -------------------------------------------------------------------------------- /webpack.config.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require('fs') 3 | 4 | module.exports = 5 | entry: 6 | main: [ 7 | 'webpack-dev-server/client?http://0.0.0.0:8080' 8 | 'webpack/hot/dev-server' 9 | './src/demo/main' 10 | ] 11 | output: 12 | path: 'build/' 13 | filename: '[name].js' 14 | publicPath: 'http://localhost:8080/build/' 15 | resolve: extensions: ['.jsx', '.js', '.coffee', ''] 16 | module: 17 | loaders: [ 18 | {test: /\.jsx$/, exclude: /node_modules/, loader: 'babel-loader'} 19 | {test: /\.coffee$/, loader: 'coffee'} 20 | {test: /\.css$/, loader: 'style!css!autoprefixer'} 21 | {test: /\.less$/, loader: 'style!css!autoprefixer!less'} 22 | ] 23 | plugins: [] 24 | -------------------------------------------------------------------------------- /webpack.min.coffee: -------------------------------------------------------------------------------- 1 | 2 | webpack = require('webpack') 3 | config = require('./webpack.config') 4 | fs = require('fs') 5 | 6 | module.exports = 7 | entry: 8 | main: [ './src/demo/main' ] 9 | output: 10 | path: 'build/' 11 | filename: '[name].[chunkhash:8].js' 12 | publicPath: './build/' 13 | resolve: config.resolve 14 | module: config.module 15 | plugins: [ 16 | new webpack.optimize.UglifyJsPlugin sourceMap: false 17 | -> 18 | @plugin 'done', (stats) -> 19 | content = JSON.stringify(stats.toJson().assetsByChunkName, null, 2) 20 | fs.writeFileSync 'build/assets.json', content 21 | ] 22 | --------------------------------------------------------------------------------