├── .gitignore
├── HISTORY.md
├── README.md
├── examples
├── mobile
│ ├── .gitignore
│ ├── .meteor
│ │ ├── .finished-upgraders
│ │ ├── .gitignore
│ │ ├── .id
│ │ ├── packages
│ │ ├── platforms
│ │ ├── release
│ │ └── versions
│ └── client
│ │ ├── main.coffee
│ │ ├── main.html
│ │ └── style
│ │ ├── 0global.coffee
│ │ ├── buttons.coffee
│ │ ├── normalize.css
│ │ └── text.coffee
└── reactive
│ ├── .gitignore
│ ├── .meteor
│ ├── .finished-upgraders
│ ├── .gitignore
│ ├── .id
│ ├── packages
│ ├── platforms
│ ├── release
│ └── versions
│ └── client
│ ├── main.coffee
│ └── main.html
├── lib
├── css.coffee
├── helpers.coffee
└── mixins.coffee
└── package.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | .versions
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # 1.0.4
2 | 1.0.2 broke css::nested
3 |
4 | # 1.0.2
5 |
6 | Added support for `css.prototype.stop` to top autorun computations associated with that object. This should help with template styles.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reactive CSS
2 |
3 | This package allows you to define all your CSS rules in a Javascript or Coffeescript and with reactive bindings to Tracker-aware functions.
4 |
5 | Check out the [live demo](http://reactive-css.meteor.com). It demonstrates:
6 |
7 | - reactive-responsive deisgn: changes layout when window resizes
8 | - reactively updating color scheme
9 | - platform-specific css for Android vs iOS
10 |
11 | ## Getting Started
12 |
13 | meteor add ccorcos:reactive-css
14 |
15 | The API is highly flexible so choose whatever syntax works best for you.
16 |
17 | ### Basics
18 |
19 | The easiest way to get started is by defining nested rules they way you are probably comfortable with in whatever CSS preprocessing language you use.
20 |
21 | css
22 | '.page':
23 | '.nav':
24 | 'height': '90px'
25 | 'width': '100%'
26 | '.content':
27 | 'paddingTop': '90px'
28 | 'paddingBottom': '50px'
29 | 'color': 'blue'
30 | '&:hover':
31 | 'color': 'red'
32 | '.toolbar':
33 | 'height': '50px'
34 |
35 |
36 | The other way is more object oriented.
37 |
38 | page = css('.page')
39 |
40 | nav = page.child('.nav').height('90px').width('100%')
41 |
42 | content = page.child('.content')
43 | content.paddingTop('90px')
44 | content.paddingBottom('20px')
45 | content.color('blue')
46 |
47 | hoveredContent = content.also(':hover')
48 | hoveredContent.color('red')
49 |
50 | toolbar = page.child('.toolbar').height('50px')
51 |
52 | Every function returns `this` so you can chain them, or not.
53 |
54 | ### Units
55 |
56 | Units are handled in a few ways. For nested objects, it is sometimes convenient to leave everything as numbers so you can add and subtract them. Thus you can specify the unit by the postfix 2 letters in the CSS rule. For example:
57 |
58 | navHeightpx = 90
59 | toolbarHeightpx = 50
60 | css
61 | '.page':
62 | '.nav':
63 | 'heightpx': navHeightpx
64 | 'widthpc': 100
65 | '.content':
66 | 'paddingToppx': navHeightpx
67 | 'paddingBottompx': toolbarHeightpx
68 | 'color': 'blue'
69 | '&:hover':
70 | 'color': 'red'
71 | '.toolbar':
72 | 'heightpx': toolbarHeightpx
73 |
74 | Valid postfixes are 'px', 'pc', 'vh', 'vw', and 'em'.
75 |
76 | The object oriented way is to pass a second string for the units.
77 |
78 | page = css('.page')
79 |
80 | nav = page.child('.nav').height(navHeightpx, 'px').width(100, 'pc')
81 |
82 | content = page.child('.content')
83 | content.paddingTop(navHeightpx)
84 | content.paddingBottom(toolbarHeightpx)
85 | content.color('blue')
86 |
87 | hoveredContent = content.also(':hover')
88 | hoveredContent.color('red')
89 |
90 | toolbar = page.child('.toolbar').height(toolbarHeightpx)
91 |
92 | The defualt unit is 'px' so you don't necessarily have to specify it.
93 |
94 | ### Reactivity
95 |
96 | This package is "Tracker-aware". So if you pass a function, it will evaluate the function with `Tracker.autorun`. This allows you to reactively update CSS rules! Suppose you parameterize your whole app within a reactive dictionary:
97 |
98 | styles = new ReactiveDict()
99 | styles.set('primary', 'blue')
100 | styles.set('background', 'white')
101 | styles.set('navHeightpx', 90)
102 | styles.set('toolbarHeightpx', 50)
103 |
104 | Then for the nested object you could use:
105 |
106 | css
107 | '.page':
108 | '.nav':
109 | 'backgroundColor': -> styles.get('primary')
110 | 'heightpx': -> styles.get('navHeightpx')
111 | '.content':
112 | 'paddingToppx': -> styles.get('navHeightpx')
113 | 'paddingBottompx': -> styles.get('toolbarHeightpx')
114 | 'backgroundColor': -> styles.get('background')
115 | '.toolbar':
116 | 'backgroundColor': -> styles.get('primary')
117 | 'heightpx': -> styles.get('toolbarHeightpx')
118 |
119 | The object oriented way is the same idea, only the units will be the first arguement as opposed to the second. This makes your coffeescript a lot nicer :)
120 |
121 | page = css('.page')
122 |
123 | nav = page
124 | .child '.nav'
125 | .height 'px', -> styles.get('navHeightpx')
126 | .backgroundColor -> styles.get('primary')
127 |
128 | content = page
129 | .child '.content'
130 | .paddingTop 'px', -> styles.get('navHeightpx')
131 | .paddingBottom 'px', -> styles.get('toolbarHeightpx')
132 | .backgroundColor -> styles.get('background')
133 |
134 | toolbar = page
135 | .child '.toolbar'
136 | .height 'px', -> styles.get('toolbarHeightpx')
137 | .backgroundColor -> styles.get('primary')
138 |
139 | ### Mixins
140 |
141 | This library is clearly incomplete and it would be concenient if you could extend it nicely. Mixins attach to the css object AND the css prototype. Here's how you define one:
142 |
143 | css.mixin 'fullPage', ->
144 | position: 'absolute'
145 | top: 0
146 | bottom: 0
147 | left: 0
148 | right: 0
149 |
150 | css.mixin 'boxSizing', (args...) ->
151 | obj = {}
152 | value = args.join(' ')
153 | obj['boxSizing'] = value
154 | obj["Webkit"+capitalize('boxSizing')] = value
155 | obj["Moz"+capitalize('boxSizing')] = value
156 | obj["Ms"+capitalize('boxSizing')] = value
157 | return obj
158 |
159 | css.mixin 'borderRadius', (args...) ->
160 | obj = {}
161 | # args could be [10, 'em']
162 | value = args.join(' ')
163 | obj['borderRadius'] = value
164 | obj["Webkit"+capitalize('borderRadius')] = value
165 | obj["Moz"+capitalize('borderRadius')] = value
166 | obj["Ms"+capitalize('borderRadius')] = value
167 | return obj
168 |
169 | This allows you to add vendor prefixes as you like as well as create convenient helpers which you could use in a few ways:
170 |
171 | css '*': css.boxSizing('border-box')
172 |
173 | css
174 | '.page': _.extend css.fullPage(),
175 | '.nav':
176 | 'backgroundColor': -> styles.get('primary')
177 | 'heightpx': -> styles.get('navHeightpx')
178 |
179 | page = css('.page').fullPage()
180 |
181 | Now, all that typing can be a pain, especially 'backgroundColor'. So there's an alias function to alias mixins.
182 |
183 | css.alias('backgroundColor', 'bg')
184 |
185 | Ahh... Much better. No help me expand this package! Or build a responsive framework using [reactive window size](https://github.com/gadicc/meteor-reactive-window)! Create different styles easily whether on Android or iOS. Or, as in the demo, create a rotating color scheme for your app!
186 |
187 | ## Pros and Cons
188 |
189 | There is obviously going to be a tradeoff here. So lets enumerate them.
190 |
191 | Pros
192 |
193 | - Create your CSS styles using a turing complete language you are familiar with.
194 | - Reactively update your CSS styles.
195 |
196 | Cons
197 |
198 | - Browsers cannot cache your CSS stylesheets.
199 | - `Tracker.autorun` everywhere, but wait, you don't have to use it.
200 |
201 | I think its worth it. I hate "battling the framework" when it comes to CSS.
202 |
203 | ## To Do
204 | - tests
205 | - template-specific css using `Template.name.css(...)`.
206 | - can we set inline styles using @find on created?
207 | - we also need to stop all the associated autorun methods
--------------------------------------------------------------------------------
/examples/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | packages/
2 |
--------------------------------------------------------------------------------
/examples/mobile/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 |
--------------------------------------------------------------------------------
/examples/mobile/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/examples/mobile/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 7qheeahbcy04m3kr1g
8 |
--------------------------------------------------------------------------------
/examples/mobile/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | meteor-platform
8 | autopublish
9 | insecure
10 | coffeescript
11 | ccorcos:reactive-css
12 | pagebakers:ionicons
13 | d3js:d3
14 |
--------------------------------------------------------------------------------
/examples/mobile/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/mobile/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.0.3.1
2 |
--------------------------------------------------------------------------------
/examples/mobile/.meteor/versions:
--------------------------------------------------------------------------------
1 | application-configuration@1.0.4
2 | autopublish@1.0.2
3 | autoupdate@1.1.5
4 | base64@1.0.2
5 | binary-heap@1.0.2
6 | blaze@2.0.4
7 | blaze-tools@1.0.2
8 | boilerplate-generator@1.0.2
9 | callback-hook@1.0.2
10 | ccorcos:reactive-css@1.0.4
11 | check@1.0.4
12 | coffeescript@1.0.5
13 | d3js:d3@3.5.5
14 | ddp@1.0.14
15 | deps@1.0.6
16 | ejson@1.0.5
17 | fastclick@1.0.2
18 | follower-livedata@1.0.3
19 | geojson-utils@1.0.2
20 | html-tools@1.0.3
21 | htmljs@1.0.3
22 | http@1.0.10
23 | id-map@1.0.2
24 | insecure@1.0.2
25 | jquery@1.11.3
26 | json@1.0.2
27 | launch-screen@1.0.1
28 | livedata@1.0.12
29 | logging@1.0.6
30 | meteor@1.1.4
31 | meteor-platform@1.2.1
32 | minifiers@1.1.3
33 | minimongo@1.0.6
34 | mobile-status-bar@1.0.2
35 | mongo@1.0.11
36 | observe-sequence@1.0.4
37 | ordered-dict@1.0.2
38 | pagebakers:ionicons@2.0.0
39 | random@1.0.2
40 | reactive-dict@1.0.5
41 | reactive-var@1.0.4
42 | reload@1.1.2
43 | retry@1.0.2
44 | routepolicy@1.0.4
45 | session@1.0.5
46 | spacebars@1.0.5
47 | spacebars-compiler@1.0.4
48 | templating@1.0.11
49 | tracker@1.0.5
50 | ui@1.0.5
51 | underscore@1.0.2
52 | url@1.0.3
53 | webapp@1.1.6
54 | webapp-hashing@1.0.2
55 |
--------------------------------------------------------------------------------
/examples/mobile/client/main.coffee:
--------------------------------------------------------------------------------
1 |
2 | page = css('.page')
3 | .position('absolute')
4 | .top(0)
5 | .bottom(0)
6 | .left(0)
7 | .right(0)
8 |
9 | nav = page.child('.nav')
10 | .position('absolute')
11 | .top(0).left(0).right(0)
12 | .textOverflow('ellipsis')
13 | .height(style.navHeight)
14 | .lineHeight(style.navHeight-style.statusBarHeight)
15 | .pt(style.statusBarHeight)
16 | .fontSize(style.navFontSize)
17 | .textAlign('center')
18 | .color(colors.primaryText)
19 | .bg(colors.primary)
20 |
21 | left = nav.child('.left')
22 | .position('absolute')
23 | .top(style.statusBarHeight)
24 | .left(0)
25 | .height(style.navHeight-style.statusBarHeight)
26 | .pl(style.contentPadding)
27 |
28 | right = nav.child('.right')
29 | .position('absolute')
30 | .top(style.statusBarHeight)
31 | .right(0)
32 | .height(style.navHeight-style.statusBarHeight)
33 | .pr(style.contentPadding)
34 |
35 | statusBar = nav.child('.statusbar')
36 | .position('absolute')
37 | .top(0)
38 | .right(0)
39 | .left(0)
40 | .height(style.statusBarHeight)
41 | .bg('black')
42 |
43 | content = page.child('.content')
44 | .position('absolute')
45 | .top(style.navHeight)
46 | .bottom(0)
47 | .left(0)
48 | .right(0)
49 | .color(colors.backgroundText)
50 | .bg(colors.background)
51 | .padding(style.contentPadding)
--------------------------------------------------------------------------------
/examples/mobile/client/main.html:
--------------------------------------------------------------------------------
1 |
2 | {{>main}}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Nav Title
10 |
11 |
12 |
13 |
14 |
H1 Heading
15 |
H2 Heading
16 |
H3 Heading
17 |
H4 Heading
18 |
H5 Heading
19 |
H6 Heading
20 |
A simple paragraph with a link!
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/mobile/client/style/0global.coffee:
--------------------------------------------------------------------------------
1 | # darker to lighter color scheme using HCL
2 | scheme = ['#21313E', '#20575F', '#268073', '#53A976', '#98CF6F', '#EFEE69']
3 | # scheme = d3.scale.linear()
4 | # .domain([0,1])
5 | # .range(["#21313E", "#EFEE69"])
6 | # .interpolate(d3.interpolateHcl)
7 |
8 | @colors =
9 | primary: scheme[0]
10 | primaryText: scheme[3]
11 | secondary: scheme[1]
12 | secondaryText: scheme[4]
13 | tertiary: scheme[2]
14 | tertiaryText: scheme[5]
15 | background: '#dddddd'
16 | backgroundText: '#222222'
17 |
18 | @style =
19 | statusBarHeight: 10
20 | navHeight: 49
21 | navFontSize: 24
22 | contentPadding: 10
--------------------------------------------------------------------------------
/examples/mobile/client/style/buttons.coffee:
--------------------------------------------------------------------------------
1 | button = css('button')
2 | .margin(0)
3 | .pl(12).pr(12)
4 | .minWidth(52)
5 | .minHeight(47)
6 | .borderWidth(1)
7 | .borderStyle('solid')
8 | .borderColor('#555555')
9 | .borderRadius(2)
10 | .textAlign('center')
11 | .textOverflow('ellipsis')
12 | .fontSize(16)
13 | .lineHeight(42)
14 | .cursor('pointer')
15 |
16 |
17 | button.also(':hover').textDecoration('none')
18 | button.also('.block').w(100, 'pc')
19 |
20 | button.also('.bar')
21 | .w("calc(100% + #{style.contentPadding*2}px)")
22 | .margin("10px -#{style.contentPadding}px")
23 | .borderRadius(0)
24 | .borderLeft('none')
25 | .borderRight('none')
--------------------------------------------------------------------------------
/examples/mobile/client/style/normalize.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, i, u, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, fieldset, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
2 | margin: 0;
3 | padding: 0;
4 | border: 0;
5 | vertical-align: baseline;
6 | font: inherit;
7 | font-size: 100%; }
8 |
9 | /**
10 | * 1. Set default font family to sans-serif.
11 | * 2. Prevent iOS text size adjust after orientation change, without disabling
12 | * user zoom.
13 | */
14 | html {
15 | -webkit-user-select: none;
16 | -moz-user-select: none;
17 | -ms-user-select: none;
18 | user-select: none;
19 | font-family: sans-serif;
20 | /* 1 */
21 | -webkit-text-size-adjust: 100%;
22 | -ms-text-size-adjust: 100%;
23 | /* 2 */
24 | -webkit-text-size-adjust: 100%;
25 | /* 2 */ }
26 |
27 | /**
28 | * Remove default margin.
29 | */
30 | body {
31 | margin: 0;
32 | line-height: 1; }
33 |
34 | /**
35 | * Remove default outlines.
36 | */
37 | a, button, :focus, a:focus, button:focus, a:active, a:hover {
38 | outline: 0;
39 | text-decoration: none;}
40 |
41 | /* *
42 | * Remove tap highlight color
43 | */
44 | a {
45 | -webkit-user-drag: none;
46 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
47 | -webkit-tap-highlight-color: transparent; }
48 | a[href]:hover {
49 | cursor: pointer; }
50 |
51 |
52 | /**
53 | * 1. Correct font family not being inherited in all browsers.
54 | * 2. Correct font size not being inherited in all browsers.
55 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
56 | * 4. Remove any default :focus styles
57 | * 5. Make sure webkit font smoothing is being inherited
58 | * 6. Remove default gradient in Android Firefox / FirefoxOS
59 | */
60 | button, input, select, textarea {
61 | margin: 0;
62 | /* 3 */
63 | font-size: 100%;
64 | /* 2 */
65 | font-family: inherit;
66 | /* 1 */
67 | outline-offset: 0;
68 | /* 4 */
69 | outline-style: none;
70 | /* 4 */
71 | outline-width: 0;
72 | /* 4 */
73 | -webkit-font-smoothing: inherit;
74 | /* 5 */
75 | background-image: none;
76 | /* 6 */ }
77 |
78 | /**
79 | * Address Firefox 4+ setting `line-height` on `input` using `importnt` in
80 | * the UA stylesheet.
81 | */
82 | button, input {
83 | line-height: normal; }
84 |
85 | /**
86 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
87 | * All other form control elements do not inherit `text-transform` values.
88 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
89 | * Correct `select` style inheritance in Firefox 4+ and Opera.
90 | */
91 | button, select {
92 | text-transform: none; }
93 |
94 | /**
95 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
96 | * and `video` controls.
97 | * 2. Correct inability to style clickable `input` types in iOS.
98 | * 3. Improve usability and consistency of cursor style between image-type
99 | * `input` and others.
100 | */
101 | button, html input[type="button"], input[type="reset"], input[type="submit"] {
102 | cursor: pointer;
103 | /* 3 */
104 | -webkit-appearance: button;
105 | /* 2 */ }
106 |
107 | /**
108 | * Re-set default cursor for disabled elements.
109 | */
110 | button[disabled], html input[disabled] {
111 | cursor: default; }
112 |
113 | /**
114 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
115 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
116 | * (include `-moz` to future-proof).
117 | */
118 | input[type="search"] {
119 | -webkit-box-sizing: content-box;
120 | /* 2 */
121 | -moz-box-sizing: content-box;
122 | box-sizing: content-box;
123 | -webkit-appearance: textfield;
124 | /* 1 */ }
125 |
126 | /**
127 | * Remove inner padding and search cancel button in Safari 5 and Chrome
128 | * on OS X.
129 | */
130 | input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {
131 | -webkit-appearance: none; }
132 |
133 | /**
134 | * Remove inner padding and border in Firefox 4+.
135 | */
136 | button::-moz-focus-inner, input::-moz-focus-inner {
137 | padding: 0;
138 | border: 0; }
139 |
140 | /**
141 | * 1. Remove default vertical scrollbar in IE 8/9.
142 | * 2. Improve readability and alignment in all browsers.
143 | */
144 | textarea {
145 | overflow: auto;
146 | /* 1 */
147 | vertical-align: top;
148 | /* 2 */ }
149 |
150 | img {
151 | -webkit-user-drag: none; }
152 |
153 |
154 | *, *:before, *:after {
155 | -webkit-box-sizing: border-box;
156 | -moz-box-sizing: border-box;
157 | box-sizing: border-box; }
158 |
159 | html {
160 | overflow: hidden;
161 | -ms-touch-action: pan-y;
162 | touch-action: pan-y; }
163 |
164 | body, .ionic-body {
165 | -webkit-touch-callout: none;
166 | -webkit-font-smoothing: antialiased;
167 | font-smoothing: antialiased;
168 | -webkit-text-size-adjust: none;
169 | -moz-text-size-adjust: none;
170 | text-size-adjust: none;
171 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
172 | -webkit-tap-highlight-color: transparent;
173 | -webkit-user-select: none;
174 | -moz-user-select: none;
175 | -ms-user-select: none;
176 | user-select: none;
177 | top: 0;
178 | right: 0;
179 | bottom: 0;
180 | left: 0;
181 | overflow: hidden;
182 | margin: 0;
183 | padding: 0;
184 | color: #000;
185 | word-wrap: break-word;
186 | font-size: 14px;
187 | font-family: "Helvetica Neue", "Roboto", sans-serif;
188 | line-height: 20px;
189 | text-rendering: optimizeLegibility;
190 | -webkit-backface-visibility: hidden;
191 | -webkit-user-drag: none; }
192 |
193 |
194 |
--------------------------------------------------------------------------------
/examples/mobile/client/style/text.coffee:
--------------------------------------------------------------------------------
1 |
2 | css('h1, h2, h3, h4, h5, h6')
3 | .fontWeight(500)
4 | .fontFamily('sans-serif')
5 |
6 | css('h1, h2, h3').mt(20).mb(10)
7 | css('h4, h5, h6').mt(10).mb(10)
8 |
9 | css('h1').fontSize(36)
10 | css('h2').fontSize(30)
11 | css('h3').fontSize(24)
12 | css('h4').fontSize(18)
13 | css('h5').fontSize(14)
14 | css('h6').fontSize(12)
15 |
16 | css('p').mb(10)
--------------------------------------------------------------------------------
/examples/reactive/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | packages/
--------------------------------------------------------------------------------
/examples/reactive/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 |
--------------------------------------------------------------------------------
/examples/reactive/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/examples/reactive/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 6b8ws2bzaslg1akqwvx
8 |
--------------------------------------------------------------------------------
/examples/reactive/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | meteor-platform
8 | autopublish
9 | insecure
10 | coffeescript
11 | reactive-dict
12 | reactive-var
13 | rcy:nouislider
14 | d3js:d3
15 | ccorcos:reactive-css
16 |
--------------------------------------------------------------------------------
/examples/reactive/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/reactive/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.0.3.1
2 |
--------------------------------------------------------------------------------
/examples/reactive/.meteor/versions:
--------------------------------------------------------------------------------
1 | application-configuration@1.0.4
2 | autopublish@1.0.2
3 | autoupdate@1.1.5
4 | base64@1.0.2
5 | binary-heap@1.0.2
6 | blaze@2.0.4
7 | blaze-tools@1.0.2
8 | boilerplate-generator@1.0.2
9 | callback-hook@1.0.2
10 | ccorcos:reactive-css@1.0.4
11 | check@1.0.4
12 | coffeescript@1.0.5
13 | d3js:d3@3.5.3
14 | ddp@1.0.14
15 | deps@1.0.6
16 | ejson@1.0.5
17 | fastclick@1.0.2
18 | follower-livedata@1.0.3
19 | geojson-utils@1.0.2
20 | html-tools@1.0.3
21 | htmljs@1.0.3
22 | http@1.0.10
23 | id-map@1.0.2
24 | insecure@1.0.2
25 | jquery@1.11.3
26 | json@1.0.2
27 | launch-screen@1.0.1
28 | livedata@1.0.12
29 | logging@1.0.6
30 | meteor@1.1.4
31 | meteor-platform@1.2.1
32 | minifiers@1.1.3
33 | minimongo@1.0.6
34 | mobile-status-bar@1.0.2
35 | mongo@1.0.11
36 | observe-sequence@1.0.4
37 | ordered-dict@1.0.2
38 | random@1.0.2
39 | rcy:nouislider@7.0.7_2
40 | reactive-dict@1.0.5
41 | reactive-var@1.0.4
42 | reload@1.1.2
43 | retry@1.0.2
44 | routepolicy@1.0.4
45 | session@1.0.5
46 | spacebars@1.0.5
47 | spacebars-compiler@1.0.4
48 | templating@1.0.11
49 | tracker@1.0.5
50 | ui@1.0.5
51 | underscore@1.0.2
52 | url@1.0.3
53 | webapp@1.1.6
54 | webapp-hashing@1.0.2
55 |
--------------------------------------------------------------------------------
/examples/reactive/client/main.coffee:
--------------------------------------------------------------------------------
1 | # Define some styles
2 | @styles = new ReactiveDict()
3 | styles.set 'navHeightpx', 90
4 | styles.set 'toolbarHeightpx', 50
5 | styles.set 'contentMaxWidthem', 40
6 |
7 | # reactive sizing
8 | adjustSize = ->
9 | width = window.innerWidth
10 | height = window.innerHeight
11 | styles.set 'size.width', width
12 | styles.set 'size.height', height
13 | if width < 1200
14 | styles.set 'size', 'mobile'
15 | else
16 | styles.set 'size', 'desktop'
17 |
18 | adjustSize()
19 | window.addEventListener "resize", adjustSize
20 |
21 | # React to Andoid vs iOS
22 | ios = !!navigator.userAgent.match(/iPad/i) or !!navigator.userAgent.match(/iPhone/i) or !!navigator.userAgent.match(/iPod/i)
23 | android = navigator.userAgent.indexOf('Android') > 0
24 |
25 | styles.set 'platform', do ->
26 | if ios then return 'ios'
27 | if android then return 'android'
28 | return 'desktop'
29 |
30 | # Using D3 to define an HCL color interpolation
31 | @color = d3.scale.linear()
32 | .domain([0,1])
33 | .range(["#F3F983", "#373A49"])
34 | .interpolate(d3.interpolateHcl);
35 |
36 | # @color = d3.scale.linear()
37 | # .domain([0,1])
38 | # .range(["#4D261F","#573649","#395267","#206B5D","#5E793B","#A97839"])
39 | # .interpolate(d3.interpolateHcl);
40 |
41 | # Rotate the color scheme with a reactive var and a setInterval
42 | @rotate = new ReactiveVar(0.0)
43 |
44 | desaturate = (x) ->
45 | c = d3.hcl(x)
46 | c.c *= 0.7
47 | return c.toString()
48 |
49 | lighten = (x) ->
50 | c = d3.hcl(x)
51 | c.l /= 0.7
52 | return c.toString()
53 |
54 | darken = (x) ->
55 | c = d3.hcl(x)
56 | c.l *= 0.7
57 | return c.toString()
58 |
59 | Tracker.autorun ->
60 | r = rotate.get()
61 | styles.set 'primary', color(2/6 + r)
62 | styles.set 'secondary', color(1/6 + r)
63 | styles.set 'tertiary', color(0/6 + r)
64 | styles.set 'primary.text', lighten(desaturate(color(0/6+r)))
65 | styles.set 'secondary.text', lighten(desaturate(color(0/6+r)))
66 | styles.set 'tertiary.text', darken(desaturate(color(2/6+r)))
67 |
68 | inc = 1/6/10
69 | rotateColors = ->
70 | r = rotate.get()
71 | if r+inc+2/6 >= 1.0
72 | inc *= -1
73 | else if r+inc <= 0
74 | inc *= -1
75 | r += inc
76 | rotate.set(r)
77 |
78 | Meteor.setInterval(rotateColors, '100')
79 |
80 |
81 | # Define all the styles
82 | css '*': css.boxSizing('border-box')
83 |
84 | page = css('.page')
85 | .fp()
86 | .bg -> styles.get('tertiary')
87 | .c -> styles.get('tertiary.text')
88 |
89 | nav = css('.nav')
90 | .w 100, 'pc'
91 | .h 'px', -> styles.get('navHeightpx')
92 | .bg -> styles.get('primary')
93 | .c -> styles.get('primary.text')
94 |
95 |
96 | title = nav.child('.title')
97 | .w 100, 'pc'
98 | .h styles.get('navHeightpx')
99 | .lineHeight styles.get('navHeightpx')
100 | .fontSize 2, 'em'
101 | .c -> styles.get('primary.text')
102 |
103 | # Make this whole chunk reactive
104 | # Android titles pull left, while iOS is centered
105 | Tracker.autorun ->
106 | if styles.equals('platform', 'android')
107 | title
108 | .pl 10, 'px'
109 | .margin 'auto'
110 | .textAlign 'left'
111 | else
112 | title
113 | .margin '0 auto'
114 | .pl 0
115 | .textAlign 'center'
116 |
117 | content = css('.content')
118 | .position 'absolute'
119 | .top -> styles.get('navHeightpx')
120 | .pt 10, 'px'
121 | .bottom 'px', ->
122 | if styles.equals('size','mobile')
123 | return styles.get('toolbarHeightpx')
124 | else
125 | return 0
126 | .pb 10, 'px'
127 | .pl 10
128 | .pr 10
129 | .left 0
130 | .right 0
131 | .overflowY 'scroll'
132 | .maxWidth 'em', -> styles.get('contentMaxWidthem')
133 | .margin '0 auto'
134 |
135 | toolbar = css('.toolbar')
136 | .position('absolute')
137 | .bg -> styles.get('secondary')
138 | .c -> styles.get('secondary.text')
139 |
140 | # On a larger layout, pull the toolbar to the left
141 | Tracker.autorun ->
142 | device = styles.get('size')
143 | if device is 'mobile'
144 | toolbar
145 | .bottom 0
146 | .height styles.get('toolbarHeightpx')
147 | .left 0
148 | .right 0
149 | .top 'auto'
150 | .w 100, 'pc'
151 | else
152 | toolbar
153 | .height 'auto'
154 | .left "calc(50% - #{styles.get('contentMaxWidthem')/2}em - 210px)"
155 | .right 'auto'
156 | .width 200
157 | .top styles.get('navHeightpx') + 10
158 | .bottom styles.get('toolbarHeightpx')
159 |
--------------------------------------------------------------------------------
/examples/reactive/client/main.html:
--------------------------------------------------------------------------------
1 |
2 | {{> main}}
3 |
4 |
5 |
6 |
7 | {{>nav}}
8 | {{>content}}
9 | {{>toolbar}}
10 |
11 |
12 |
13 |
14 |
15 |
16 | Reactive CSS
17 |
18 |
19 |
20 |
21 |
22 |
23 |
This Meteor app is built with Reactive CSS
24 |
Resize the window and see some *reactive* responsive design at work.
25 |
Wait a little bit and you'll also notice the color scheme changing slowly over time.
26 |
27 |
28 |
29 |
30 |
31 | toolbar items...
32 |
33 |
--------------------------------------------------------------------------------
/lib/css.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | # Note
3 | a = ->
4 | if not (this instanceof a) then return new a()
5 | @something = 10
6 | return this
7 |
8 | a.f = -> 21
9 | a::f = -> @something * 10
10 |
11 | console.log a.f() # => 21
12 | console.log a().f() # => 100
13 | ###
14 |
15 | # Some helper functions
16 | isFunction = (value) -> (typeof value is 'function')
17 | isPlainObject = (value) -> (typeof value is 'object') and not Array.isArray(value)
18 | isArray = (value) -> Array.isArray(value)
19 | isString = (value) -> (typeof value == 'string')
20 | isNumber = (value) -> (typeof value == 'number')
21 | capitalize = (string) -> string and (string.charAt(0).toUpperCase() + string.slice(1))
22 |
23 | # Create the Stylesheet in the DOM
24 | Stylesheet = ->
25 | if not (this instanceof Stylesheet) then return new Stylesheet()
26 | @styles = {}
27 | style = document.createElement('style')
28 | style.setAttribute('media', 'screen')
29 | style.appendChild(document.createTextNode(''))
30 | document.head.appendChild(style)
31 | @sheet = style.sheet
32 | return this
33 |
34 | # Add a CSS rule to the stylesheet
35 | Stylesheet::rule = (selector, key, value) ->
36 | # check if the selector is already in the stylesheet
37 | idx = _.map(@sheet.cssRules, (rule) -> rule.selectorText).indexOf(selector)
38 | # else make a new rule
39 | unless idx >= 0
40 | idx = @sheet.insertRule("#{selector} {}", 0)
41 |
42 | unless selector of @styles
43 | @styles[selector] = {}
44 |
45 | # check if the rule ends with px, pc, or em for translating values
46 | ending = key[-2..-1]
47 | if ending is 'px'
48 | key = key[0...-2]
49 | value = "#{value}px"
50 | else if ending is 'pc'
51 | key = key[0...-2]
52 | value = "#{value}%"
53 | else if ending is 'em'
54 | key = key[0...-2]
55 | value = "#{value}em"
56 | else if ending is 'vh'
57 | key = key[0...-2]
58 | value = "#{value}vh"
59 | else if ending is 'vw'
60 | key = key[0...-2]
61 | value = "#{value}vw"
62 |
63 | # write the rule
64 | # console.log "#{key}: #{value}"
65 | @styles[selector][key] = value
66 | @sheet.cssRules[idx].style[key] = value
67 |
68 | # EXAMPLE: Make a style sheet and add rules to it:
69 | # stylesheet().rule('.page', 'widthpc', 100)
70 |
71 | # Create a stylesheet for everything else to use
72 | @stylesheet = Stylesheet()
73 | rule = (args...) -> stylesheet.rule.apply(stylesheet, args)
74 |
75 | # if input is a...
76 | # object: then parse out the nested CSS rules
77 | # string: set the selector and the prototype function create rules on that selector
78 | @css = (input) ->
79 | # safeguard so you dont have to use new
80 | if not (this instanceof css) then return new css(input)
81 | @autoruns = []
82 | if isString(input)
83 | @selector = input
84 | return this
85 | else if isPlainObject(input)
86 | @nested(input)
87 | return
88 | else
89 | console.log "WARNING CoffeeCSS: calling css() doesn't do anything.", input
90 | return
91 |
92 | # Recursively parse a nested object of CSS rules
93 | css::nested = (obj, selector="") ->
94 | self = this
95 | for key, value of obj
96 | # if its a string, then its a CSS rule
97 | # if its an object, then recursively update the selector
98 | # if its a function, run reactively
99 | if isString(value) or isNumber(value)
100 | if selector is ""
101 | console.log("WARNING CoffeeCSS: No selector in nested CSS object?!")
102 | return
103 | rule(selector, key, value)
104 |
105 | else if isPlainObject(value)
106 | nextSelector = ""
107 | if selector is ""
108 | nextSelector = key
109 | else if key[0] is '&'
110 | nextSelector = "#{selector}#{key}"
111 | else
112 | nextSelector = "#{selector} #{key}"
113 | @nested(value, nextSelector)
114 |
115 | else if isFunction(value)
116 | do (key, value) ->
117 | self.autoruns.push Tracker.autorun ->
118 | rule(selector, key, value())
119 |
120 | else
121 | console.log "WARNING CoffeeCSS: Not a valid CSS rule: #{selector} {#{key}: #{value}};"
122 |
123 | # return a new css instance with the selector appended
124 | css::child = (string) ->
125 | return css(@selector + " #{string}")
126 |
127 | # return a new css instance with the selector appended
128 | css::also = (string) ->
129 | return css(@selector + "#{string}")
130 |
131 | # manual specification
132 | # css('.page').rule('overflowX', 'scroll')
133 | # => writes the rule, .page {overflow-x: scroll}
134 | css::rule = (name, value) ->
135 | if isFunction(value)
136 | self = this
137 | # run the function in a reactive context
138 | @autoruns.push Tracker.autorun ->
139 | rule(self.selector, name, value())
140 | else
141 | rule(@selector, name, value)
142 | return this
143 |
144 | css::rules = (obj) ->
145 | @nested(obj, @selector)
146 |
147 | css::stop = ->
148 | for autorun in @autoruns
149 | autorun.stop()
150 | return
151 |
152 | # define a method for creating mixins.
153 | css.mixin = (name, func) ->
154 | css[name] = func
155 | css::[name] = (args...) ->
156 | last = args.length-1
157 | if isFunction(args[last])
158 | self = this
159 | # your first input can be a unit, with the second being the function
160 | # but we need to reverse them into the mixin function because we
161 | # typically just join the args.
162 | if args.length > 1
163 | args.unshift(args[last])
164 | args.pop()
165 | # run the function in a reactive context
166 | f = args[0]
167 | @autoruns.push Tracker.autorun ->
168 | args[0] = f()
169 | obj = func.apply(self, args)
170 | for k, v of obj
171 | rule(self.selector, k, v)
172 | else
173 | obj = func.apply(@, args)
174 | for k, v of obj
175 | rule(@selector, k, v)
176 |
177 | return this
178 |
179 | css.alias = (name, aliases...) ->
180 | for alias in aliases
181 | css[alias] = css[name]
182 | css::[alias] = css::[name]
--------------------------------------------------------------------------------
/lib/helpers.coffee:
--------------------------------------------------------------------------------
1 | # Helpers
2 | css.mixin 'fullPage', ->
3 | position: 'absolute'
4 | top: 0
5 | bottom: 0
6 | left: 0
7 | right: 0
8 |
9 | css.mixin 'noPadding', ->
10 | paddingTop: 0
11 | paddingBottom: 0
12 | paddingLeft: 0
13 | paddingRight: 0
14 |
15 | css.mixin 'noMargin', ->
16 | marginTop: 0
17 | marginBottom: 0
18 | marginLeft: 0
19 | marginRight: 0
20 |
21 |
22 | css.mixin 'fullWidth', -> {width: '100%'}
23 | css.mixin 'fullHeight', -> {height: '100%'}
24 |
25 | css.alias('fullPage', 'fp')
26 | css.alias('fullWidth', 'fw')
27 | css.alias('fullHeight', 'fh')
28 | css.alias('fullPage', 'fp')
29 | css.alias('noMargin', 'nm')
30 | css.alias('noPadding', 'np')
31 |
32 |
--------------------------------------------------------------------------------
/lib/mixins.coffee:
--------------------------------------------------------------------------------
1 |
2 | isString = (value) -> (typeof value == 'string')
3 | capitalize = (string) -> string and (string.charAt(0).toUpperCase() + string.slice(1))
4 |
5 |
6 | # parse curried arguments for single units if necessary
7 | # default px
8 | argsToUnits = (args...) ->
9 | if args.length is 1
10 | value = args[0]
11 | if isString(value)
12 | return value
13 | else
14 | return "#{value}px"
15 | else if args.length is 2
16 | if args[1] is 'pc' then args[1] = '%'
17 | return "#{args[0]}#{args[1]}"
18 |
19 | # simple meaning no units or anything to parse
20 | simpleMixins = [
21 | 'color'
22 | 'backgroundColor'
23 | 'position'
24 | 'overflow'
25 | 'overflowX'
26 | 'overflowY'
27 | 'margin'
28 | 'zIndex'
29 | 'fontWeight'
30 | 'fontFamily'
31 | 'textOverflow'
32 | 'borderStyle'
33 | 'cursor'
34 | 'textDecoration'
35 | 'borderColor'
36 | 'borderLeft'
37 | 'borderRight'
38 | ]
39 |
40 | simplePrefixMixins = [
41 | 'boxSizing'
42 | 'userSelect'
43 | 'opacity'
44 | 'textAlign'
45 | 'transform'
46 | ]
47 |
48 | # one unit meaning theres only one posisble value input
49 | # but it could be px, em, %, etc.
50 | oneUnitMixins = [
51 | 'height'
52 | 'maxHeight'
53 | 'minHeight'
54 | 'width'
55 | 'maxWidth'
56 | 'minWidth'
57 | 'paddingTop'
58 | 'paddingBottom'
59 | 'paddingRight'
60 | 'paddingLeft'
61 | 'marginTop'
62 | 'marginBottom'
63 | 'marginRight'
64 | 'marginLeft'
65 | 'top'
66 | 'bottom'
67 | 'left'
68 | 'right'
69 | 'lineHeight'
70 | 'fontSize'
71 | 'padding'
72 | 'borderWidth'
73 | ]
74 |
75 | oneUnitPrefixMixins = [
76 | 'borderRadius'
77 | ]
78 |
79 | for name in simpleMixins
80 | do (name) ->
81 | css.mixin name, (args...) ->
82 | obj = {}
83 | value = args.join(' ')
84 | obj[name] = value
85 | return obj
86 |
87 | for name in simplePrefixMixins
88 | do (name) ->
89 | css.mixin name, (args...) ->
90 | obj = {}
91 | value = args.join(' ')
92 | obj[name] = value
93 | obj["Webkit"+capitalize(name)] = value
94 | obj["Moz"+capitalize(name)] = value
95 | obj["Ms"+capitalize(name)] = value
96 | return obj
97 |
98 | for name in oneUnitMixins
99 | do (name) ->
100 | css.mixin name, (args...) ->
101 | obj = {}
102 | obj[name] = argsToUnits.apply({}, args)
103 | return obj
104 |
105 | for name in oneUnitPrefixMixins
106 | do (name) ->
107 | css.mixin name, (args...) ->
108 | obj = {}
109 | unit = argsToUnits.apply({}, args)
110 | obj[name] = unit
111 | obj["Webkit"+capitalize(name)] = unit
112 | obj["Moz"+capitalize(name)] = unit
113 | obj["Ms"+capitalize(name)] = unit
114 | return obj
115 |
116 | # Create some aliases of the mixins :)
117 | css.alias('color', 'c')
118 | css.alias('backgroundColor', 'bg')
119 | css.alias('height', 'h')
120 | css.alias('width', 'w')
121 | css.alias('paddingTop', 'pt')
122 | css.alias('paddingBottom', 'pb')
123 | css.alias('paddingLeft', 'pl')
124 | css.alias('paddingRight', 'pr')
125 | css.alias('marginTop', 'mt')
126 | css.alias('marginBottom', 'mb')
127 | css.alias('marginLeft', 'ml')
128 | css.alias('marginRight', 'mr')
129 |
130 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: 'ccorcos:reactive-css',
3 | summary: 'Define reactive CSS rules in Javascript or (preferably ;) Coffeescript.',
4 | version: '1.0.5',
5 | git: 'https://github.com/ccorcos/meteor-reactive-css'
6 | });
7 |
8 | Package.onUse(function(api) {
9 | api.versionsFrom('METEOR@1');
10 |
11 | api.use([
12 | 'coffeescript',
13 | 'underscore'
14 | ], 'client');
15 |
16 | api.addFiles([
17 | 'lib/css.coffee',
18 | 'lib/mixins.coffee',
19 | 'lib/helpers.coffee',
20 | ], 'client');
21 |
22 | });
--------------------------------------------------------------------------------