├── .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
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 |
--------------------------------------------------------------------------------