25 |
26 | ## How to open
27 |
28 | Auto open:
29 |
30 |
...
31 |
32 | Open via JavaScript:
33 |
34 |
...
35 | $('.pony').trigger('open.modal.tamia');
36 |
37 | Open via link:
38 |
39 |
Open
40 |
41 |
42 | ## Events
43 |
44 | ### open.modal.tamia
45 |
46 | Fire this event on `.modal` DOM node to open popup.
47 |
48 | ### close.modal.tamia
49 |
50 | Fire this event on `.modal` DOM node to close popup.
51 |
52 | ### commit.modal.tamia
53 |
54 | Fires when user clicks Save button.
55 |
56 | You can prevent popup from closing:
57 |
58 | $('.js-popup').on('commit.modal.tamia', function() {
59 | return false;
60 | });
61 |
62 | ### dismiss.modal.tamia
63 |
64 | Fires when user click Cancel button
65 |
66 |
67 | ## Skin
68 |
69 | Set `modal_default_skin` or `modules_default_skin` to `true` to enable default skin.
70 |
71 |
72 | ## Configuration
73 |
74 | ### modal_shade_color
75 |
76 | Type: CSS color value.
77 |
78 | Color of popup shade layer.
79 |
--------------------------------------------------------------------------------
/tamia/modules/modal/example.html:
--------------------------------------------------------------------------------
1 |
Open popup
2 |
15 |
--------------------------------------------------------------------------------
/tamia/modules/modal/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Modal
3 |
4 | modal_shade_color ?= black(.4)
5 |
6 | // Body class to disable page scroll when modal is open
7 | .modal-opened
8 | overflow: hidden
9 |
10 | // Modal shade
11 | .modal-shade
12 | position fixed
13 | top: 0
14 | left: 0
15 | bottom: 0
16 | right: 0
17 | padding: 1em
18 | background: modal_shade_color
19 | z-index: 99999
20 |
21 | &.is-switching
22 | background: none
23 |
24 | .modal-shade,
25 | .modal
26 | transition: opacity .15s ease-out
27 |
28 | &.is-hidden
29 | opacity: 0
30 |
31 |
32 | // Bones
33 | .modal
34 | &_fluid
35 | width: 100%
36 | max-width: 800px
37 |
38 | .l-center &
39 | display: inline-block
40 |
41 |
42 | // Default skin
43 |
44 | modules_default_skin ?= true
45 | modal_default_skin ?= false
46 |
47 | if modules_default_skin or modal_default_skin
48 | .modal
49 | background: #fff
50 | border: 1px solid #ccc
51 | border-radius: .3em
52 | box-shadow: 0 0 3em black(.3)
53 |
54 | &__header,
55 | &__body,
56 | &__footer
57 | padding: 3*spacer 2*spacer
58 | border: 0 solid #ddd
59 |
60 | &__header
61 | position: relative
62 | padding-top: 1.5*spacer
63 | padding-bottom: @padding-top
64 | border-bottom-width: 1px
65 |
66 | &__body
67 | position: relative
68 | padding-bottom: 4*spacer
69 |
70 | &__footer
71 | text-align: right
72 | background: #fafafa
73 | border-top-width: 1px
74 |
75 | &__title
76 | margin-bottom: -.1em
77 | line-height: 1.2
78 | font-weight: normal
79 | font-size: 20px
80 |
81 | &__close
82 | position: absolute
83 | top: 1.5*spacer - 2px
84 | right: 2*spacer
85 |
--------------------------------------------------------------------------------
/tamia/modules/modal/script.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Modal
3 |
4 | /*global tamia:false*/
5 | ;(function(window, $, undefined) {
6 | 'use strict';
7 |
8 | var _body = $('body');
9 | var _doc = $(document);
10 |
11 | var _bodyClass = 'modal-opened';
12 | var _switchingState = 'switching';
13 | var _hiddenState = 'hidden';
14 | var _opened = null;
15 |
16 | tamia.Modal = tamia.extend(tamia.Component, {
17 | displayName: 'tamia.Modal',
18 | binded: 'commit dismiss keyup shadeClick',
19 | wrapperTemplate: {
20 | block: 'modal-shade',
21 | states: 'hidden',
22 | content: {
23 | block: 'l-center',
24 | content: {
25 | block: 'l-center',
26 | inner: true,
27 | js: 'modal',
28 | content: {
29 | node: true
30 | }
31 | }
32 | }
33 | },
34 |
35 | init: function() {
36 | this.elem.data('modal', this);
37 | this.elem.on('click', '.js-modal-commit', this.commit_);
38 | this.elem.on('click', '.js-modal-dismiss', this.dismiss_);
39 | if (this.elem.data('modal-open')) {
40 | this.open();
41 | }
42 | },
43 |
44 | initWrapperHtml: function() {
45 | if (this.wrapper) return;
46 | this.wrapper = tamia.oporNode(this.wrapperTemplate, this.elem);
47 | this.wrapper.on('click', this.shadeClick_);
48 | _body.append(this.wrapper);
49 | this.elem.removeState('hidden');
50 | },
51 |
52 | open: function() {
53 | if (this === _opened) return;
54 |
55 | var opened = _opened;
56 | this.initWrapperHtml();
57 | _body.addClass(_bodyClass);
58 | if (opened) {
59 | opened.close({hide: true});
60 | this.wrapper.addState(_switchingState);
61 | this.wrapper.on('appeared.tamia', function() {
62 | this.wrapper.removeState(_switchingState);
63 | opened.wrapper.addState(_hiddenState);
64 | opened.elem.removeState(_hiddenState);
65 | }.bind(this));
66 | }
67 | this.wrapper.trigger('appear.tamia');
68 | _doc.on('keyup', this.keyup_);
69 | _opened = this;
70 |
71 | // Set focus to element with autofocus attribute
72 | var autofocus = this.elem.find('[autofocus]');
73 | if (autofocus.length) {
74 | autofocus.focus();
75 | }
76 | },
77 |
78 | close: function(params) {
79 | if (params === undefined) params = {hide: false};
80 |
81 | var elem = params.hide ? this.elem : this.wrapper;
82 | elem.trigger('disappear.tamia');
83 | if (!params.hide) _body.removeClass(_bodyClass);
84 | _doc.off('keyup', this.keyup_);
85 | _opened = null;
86 | },
87 |
88 | commit: function(event) {
89 | this.done(event, 'commit');
90 | },
91 |
92 | dismiss: function(event) {
93 | this.done(event, 'dismiss');
94 | },
95 |
96 | done: function(event, type) {
97 | if (event) event.preventDefault();
98 |
99 | var typeEvent = $.Event(type + '.modal.tamia');
100 | this.elem.trigger(typeEvent);
101 | if (typeEvent.isDefaultPrevented()) return;
102 |
103 | this.close();
104 | },
105 |
106 | keyup: function(event) {
107 | if (event.which === 27) { // Escape
108 | this.dismiss(event);
109 | }
110 | },
111 |
112 | shadeClick: function(event) {
113 | if ($(event.target).hasClass('js-modal') && this.elem.data('modal-close-on-shade') !== 'no') {
114 | this.dismiss(event);
115 | }
116 | }
117 | });
118 |
119 | // Events
120 | tamia.registerEvents({
121 | 'open.modal': function(elem) {
122 | var container = $(elem);
123 | var modal = container.data('modal');
124 | if (!modal) modal = new tamia.Modal(elem);
125 | modal.open();
126 | },
127 | 'close.modal': function(elem) {
128 | var container = $(elem);
129 | var modal = container.data('modal');
130 | if (!modal) return;
131 | modal.close();
132 | }
133 | });
134 |
135 | }(window, jQuery));
136 |
--------------------------------------------------------------------------------
/tamia/modules/password/Readme.md:
--------------------------------------------------------------------------------
1 | # Password field
2 |
3 | Password field with toggle to show characters.
4 |
5 |
6 | ## Markup
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## States
14 |
15 | ### .password.is-ok
16 |
17 | Browser is supported.
18 |
19 | ### .password.is-unlocked
20 |
21 | Password characters are visible.
22 |
23 |
24 | ## Caveats
25 |
26 | IE9+.
27 |
28 |
29 | ## Skin
30 |
31 | Set `password_default_skin` or `modules_default_skin` to `true` to enable default skin.
32 |
--------------------------------------------------------------------------------
/tamia/modules/password/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tamia/modules/password/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Password field with toggle to show characters
3 | // Dependencies: form
4 |
5 | // Bones
6 |
7 | .password
8 | position: relative
9 | display: block
10 |
11 | &_inline
12 | display: inline-block
13 |
14 | &__field
15 | position:relative;
16 | width: 100%
17 | z-index: 90
18 |
19 | &__toggle
20 | display: none
21 | &.is-ok &__toggle
22 | position: absolute
23 | display: block
24 | top: 0
25 | bottom: 0
26 | right: 0
27 | cursor: pointer
28 | z-index: 100
29 | &:before
30 | content: ""
31 | position: absolute
32 | top: 50%
33 | left: 50%
34 | transform: translate(-50%,-50%)
35 |
36 | &.is-disabled &__toggle
37 | opacity: .4
38 |
39 | // Hide IE10 password visibility toggle
40 | &::-ms-reveal
41 | size: 0 // Not display:none because: http://bit.ly/1h3UlAH
42 |
43 |
44 | // Default skin
45 |
46 | modules_default_skin ?= true
47 | password_default_skin ?= false
48 |
49 | if modules_default_skin or password_default_skin
50 | .password
51 | &.is-ok &__toggle
52 | width: 1.8em
53 | &:before
54 | tweak-inverted-text()
55 | content: "abc"
56 | font-size: .75em
57 | letter-spacing: .1ex
58 | &.is-ok.is-unlocked &__toggle:before
59 | content: "●●●"
60 | letter-spacing: 0
61 |
--------------------------------------------------------------------------------
/tamia/modules/password/script.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Password field with toggle to show characters
3 |
4 | /*global tamia:false*/
5 | ;(function(window, $, undefined) {
6 | 'use strict';
7 |
8 | var _unlockedState = 'unlocked';
9 | var _disabledState = 'disabled';
10 | var _inputSyncEvent = 'input.sync.password';
11 |
12 | tamia.Password = tamia.extend(tamia.Component, {
13 | displayName: 'tamia.Password',
14 | binded: 'toggle focus',
15 | template: {
16 | block: 'password',
17 | node: 'root',
18 | content: [
19 | {
20 | block: 'password',
21 | elem: 'toggle',
22 | link: 'toggleElem'
23 | },
24 | {
25 | block: 'password',
26 | elem: 'field',
27 | mix: {
28 | block: 'field'
29 | },
30 | node: '.js-field',
31 | link: 'fieldElem',
32 | attrs: {
33 | autocapitalize: 'off',
34 | autocomplete: 'off',
35 | autocorrect: 'off',
36 | spellcheck: 'false'
37 | }
38 | }
39 | ]
40 | },
41 |
42 | init: function() {
43 | // Mousedown instead of click to catch focused field
44 | this.toggleElem.on('mousedown', this.toggle_);
45 |
46 | if (this.elem.hasState(_disabledState)) {
47 | this.fieldElem.prop(_disabledState, true);
48 | }
49 | },
50 |
51 | toggle: function() {
52 | var focused = document.activeElement === this.fieldElem[0];
53 | var locked = !this.isLocked();
54 |
55 | this.elem.toggleState(_unlockedState);
56 |
57 | // Create hidden input[type=password] element to invoke password saving in browser
58 | if (!locked) {
59 | this.cloneElem = this.fieldElem.clone();
60 | this.cloneElem.hide();
61 | this.fieldElem.after(this.cloneElem);
62 | this.fieldElem.name = '';
63 | this.fieldElem.on(_inputSyncEvent, this.syncWith.bind(this, this.cloneElem));
64 | this.cloneElem.on(_inputSyncEvent, this.syncWith.bind(this, this.fieldElem));
65 | }
66 | else if (this.cloneElem) {
67 | this.fieldElem.off(_inputSyncEvent);
68 | this.fieldElem.name = this.cloneElem.name;
69 | this.cloneElem.remove();
70 | }
71 |
72 | this.fieldElem.attr('type', locked ? 'password' : 'text');
73 |
74 | if (focused) {
75 | setTimeout(this.focus_, 0);
76 | }
77 | },
78 |
79 | focus: function() {
80 | this.fieldElem.focus();
81 | },
82 |
83 | isLocked: function() {
84 | return !this.elem.hasState(_unlockedState);
85 | },
86 |
87 | syncWith: function(receiver, event) {
88 | receiver.val(event.target.value);
89 | }
90 | });
91 |
92 | tamia.initComponents({password: tamia.Password});
93 |
94 | }(window, jQuery));
95 |
--------------------------------------------------------------------------------
/tamia/modules/preload/Readme.md:
--------------------------------------------------------------------------------
1 | # Preload
2 |
3 | Image preload.
4 |
5 |
6 | ## Usage
7 |
8 | preload(
9 | [
10 | '../images/homepage-iphone.png',
11 | '../images/homepage-iphone-screenshot.png'
12 | ],
13 | function(err) {
14 | // `err` contains array of not loaded images or null
15 | console.log('Images preloaded. Errors:', err);
16 | }
17 | );
18 |
--------------------------------------------------------------------------------
/tamia/modules/preload/script.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Image preload
3 |
4 | /*global tamia:false*/
5 | ;(function(window, $, undefined) {
6 | 'use strict';
7 |
8 | var preload = function (images, callback) {
9 | var done = function() {
10 | counter--;
11 | if (counter === 0) {
12 | callback(errors.length ? errors : null);
13 | }
14 | };
15 | var error = function() {
16 | errors.push(this.src);
17 | done();
18 | };
19 |
20 | images = parse(images);
21 | var counter = images.length;
22 | var errors = [];
23 | for (var imageIdx = 0; imageIdx < images.length; imageIdx++) {
24 | var img = new Image();
25 | img.onload = done;
26 | img.onerror = error;
27 | img.src = images[imageIdx];
28 | }
29 | };
30 |
31 | var parse = function(images) {
32 | if (!$.isArray(images)) images = [images];
33 | // TODO: img.attr('src')
34 | return images;
35 | };
36 |
37 | tamia.preload = preload;
38 |
39 | }(window, jQuery));
40 |
--------------------------------------------------------------------------------
/tamia/modules/print/Readme.md:
--------------------------------------------------------------------------------
1 | # Print
2 |
3 | Print stylesheet.
4 |
5 |
6 | ## Configuration
7 |
8 | ### site_domain
9 |
10 | Type: String.
11 |
12 | Site domain (for example, `example.com`) that will be printed.
13 |
14 |
15 | ## Caveats
16 |
17 | Use `.no-print` class to hide elements in print version and `.print` to show (hidden on screen).
18 |
--------------------------------------------------------------------------------
/tamia/modules/print/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Print stylesheet
3 |
4 | // Site domain (for example, `example.com`) that will be printed.
5 | site_domain ?= false
6 |
7 | @media print
8 |
9 | @page
10 | margin: .5cm
11 |
12 | nav,
13 | .social-likes,
14 | .no-print
15 | display: none
16 |
17 | .print
18 | display: block
19 |
20 | *
21 | font-family: Cambria, Georgia, serif
22 | color: #000 !important
23 | background: transparent !important
24 | float: none !important
25 | width: auto !important
26 | margin-side: 0 !important
27 | padding-side: 0 !important
28 | text-shadow: none !important
29 | box-shadow: none !important
30 |
31 | body
32 | padding-bottom: 0
33 |
34 | a
35 | border: none !important
36 |
37 | h1,
38 | h2,
39 | h3,
40 | h4,
41 | h5,
42 | h6
43 | font-family: Corbel, 'Helvetica Neue', Arial, sans-serif
44 | page-break-inside: avoid
45 | page-break-after: avoid
46 |
47 | a[href^="http"]:link:after,
48 | a[href^="http"]:visited:after
49 | content: " (" attr(href) ")"
50 | font-size: .9em
51 |
52 | if site_domain
53 | a[href^="/"]:link:after,
54 | a[href^="/"]:visited:after
55 | content: (" (http://" + site_domain) attr(href) ")"
56 | font-size: .9em
57 |
58 | .header
59 | margin-bottom: 2*spacer
60 | border-bottom: 1pt solid #000
61 | height: auto
62 |
63 | .footer
64 | position: static
65 | margin-top: 2*spacer
66 | border-top: 1pt solid #000
67 | height: auto
68 | a
69 | text-decoration: none
70 |
71 | if site_domain
72 | .logo:before
73 | content: url(/favicon.ico)
74 | padding-right: 2px
75 | vertical-align: middle
76 |
77 | .logo a:link:after,
78 | .logo a:visited:after
79 | content: " — " + site_domain
80 |
81 | p,
82 | blockquote,
83 | ul,
84 | ol,
85 | dl,
86 | tr,
87 | img
88 | page-break-inside: avoid
89 |
90 | p,
91 | h2,
92 | h3
93 | orphans: 3
94 | widows: 3
95 |
96 | ul
97 | margin-left: 1.2em !important
98 |
--------------------------------------------------------------------------------
/tamia/modules/richtypo/Readme.md:
--------------------------------------------------------------------------------
1 | # Rich typograhy
2 |
3 | Classes and tweaks for better typography.
4 |
5 |
6 | ## Markup
7 |
8 | ### Abbreviations with spacing
9 |
10 |
11 |
PNG, GIF (animated or not) and JPEG formats.
12 |
13 |
14 | ### The best ampersand
15 |
16 | Nuts
& Bolts.
17 |
18 | ### Hanging punctuation
19 |
20 | Awesome Web Typography with
“Richtypo”'
21 |
22 |
23 | ## Configuration
24 |
25 | ### richtypo_global_ligatures
26 |
27 | Type: Boolean. Default: `true`.
28 |
29 | Enables ligatures on body (just headers otherwise).
30 |
31 | ### richtypo_extra_features
32 |
33 | Type: Boolean or List. Default: `false`.
34 |
35 | Extra OpenType features, eg. `"ss01", "ss03", "salt"` (`richtypo_global_ligatures` should be enabled).
36 |
37 | ### richtypo_figures
38 |
39 | Type: String. Default: `proportional`.
40 |
41 | Figures type: `proportional`, `oldstyle` (`richtypo_global_ligatures` should be enabled).
42 |
43 | ### richtypo_proper_abbr
44 |
45 | Type: Boolean. Default: `true`.
46 |
47 | Enables small caps in abbreviations (if your font supports them).
48 |
49 |
50 | ## Tools
51 |
52 | * [richtypo.js](https://github.com/sapegin/richtypo.js) for Node.js.
53 | * [wp-typohelper](https://github.com/sapegin/wp-typohelper) for Wordpress.
54 |
--------------------------------------------------------------------------------
/tamia/modules/richtypo/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2015 Artem Sapegin http://sapegin.me
2 | // Rich typograhy
3 | //
4 | // Syntax for OpenType features in CSS:
5 | // http://help.typekit.com/customer/portal/articles/1789736
6 | // Death to typewriters by Medium. Technical supplement:
7 | // https://medium.com/@mwichary/death-to-typewriters-technical-supplement-8f3c754626f2
8 |
9 | // Enables ligatures on body (just headers otherwise).
10 | richtypo_global_ligatures ?= true
11 |
12 | // Extra OpenType features, eg. "ss01", "ss03", "salt" (richtypo_global_ligatures should be enabled).
13 | richtypo_extra_features ?= false
14 |
15 | // Figures type: proportional, oldstyle (richtypo_global_ligatures should be enabled).
16 | richtypo_figures ?= "proportional"
17 |
18 | // Enables small caps in abbreviations.
19 | richtypo_proper_abbr ?= true
20 |
21 | _ligatures = common-ligatures discretionary-ligatures contextual historical-ligatures
22 | _base_features = "kern", "liga", "dlig", "hlig", "clig"
23 | if richtypo_extra_features
24 | _base_features = _base_features, richtypo_extra_features
25 | if richtypo_figures == "proportional"
26 | _base_features = _base_features, "pnum"
27 | _figures_variant = "proportional-nums"
28 | else if richtypo_figures == "oldstyle"
29 | _base_features = _base_features, "onum"
30 | _figures_variant = "oldstyle-nums"
31 |
32 | // Global font settings.
33 | if richtypo_global_ligatures
34 | body
35 | font-kerning: normal
36 | font-variant-numeric: _figures_variant
37 | font-variant-ligatures: _ligatures
38 | font-feature-settings: _base_features
39 |
40 | .table,
41 | code,
42 | pre
43 | font-variant-numeric: tabular-nums
44 | font-variant-ligatures: none
45 | font-feature-settings: "thum"
46 |
47 | // Abbreviations with spacing.
48 | .abbr,
49 | .text abbr
50 | letter-spacing: 0.1em
51 | margin-right: -0.1em
52 | if richtypo_proper_abbr
53 | font-variant-caps: all-small-caps
54 | font-feature-settings: _base_features, "c2sc", "smcp"
55 |
56 | // The best ampersand.
57 | .amp
58 | font-family: Baskerville, Constantia, Palatino, "Palatino Linotype", "Book Antiqua", serif
59 | font-style: italic
60 |
61 | // Hanging punctuation.
62 | .sbrace
63 | margin-right: 0.3em
64 | .hbrace
65 | margin-left: -0.3em
66 | .slaquo
67 | margin-right: 0.42em
68 | .hlaquo
69 | margin-left: -0.42em
70 |
71 | // Headings with ligatures.
72 | if not richtypo_global_ligatures
73 | .text h1, .alpha,
74 | .text h2, .beta,
75 | .text h3, .gamma,
76 | .text h4, .delta,
77 | .text h5, .epsilon,
78 | .text h6, .zeta
79 | font-variant-ligatures: _ligatures
80 | font-feature-settings: _base_features
81 |
--------------------------------------------------------------------------------
/tamia/modules/rouble/Readme.md:
--------------------------------------------------------------------------------
1 | # Rouble ß
2 |
3 | Russian Rouble sign.
4 |
5 |
6 | ## Markup
7 |
8 |
100 Р
9 |
10 |
11 | ## Caveats
12 |
13 | * IE9+.
14 | * Only one font (Arial) is available now.
15 |
--------------------------------------------------------------------------------
/tamia/modules/rouble/example.html:
--------------------------------------------------------------------------------
1 |
499 Р
2 |
--------------------------------------------------------------------------------
/tamia/modules/rouble/fonts/rouble-arial-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sapegin/react-text-stats/a03153185bd19cd3a41db61a8369f5ef4d1e3a1f/tamia/modules/rouble/fonts/rouble-arial-regular.woff
--------------------------------------------------------------------------------
/tamia/modules/rouble/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Russian Rouble sign ß
3 |
4 | // IE9+
5 | // Details: http://artgorbunov.ru/bb/soviet/20120223/
6 | // Fonts from http://www.artlebedev.ru/kovodstvo/sections/159/
7 |
8 | @font-face
9 | font-family: 'ALSRubl-Arial'
10 | src: embedurl('fonts/rouble-arial-regular.woff') format('woff')
11 | font-weight: normal
12 | font-style: normal
13 |
14 | .rub,
15 | .rub_arial
16 | font-family: 'ALSRubl-Arial'
17 | line-height: normal
18 |
--------------------------------------------------------------------------------
/tamia/modules/select/Readme.md:
--------------------------------------------------------------------------------
1 | # Select
2 |
3 | Select with custom design
4 |
5 |
6 | ## Markup
7 |
8 |
9 |
16 |
17 |
18 |
19 | ## Skin
20 |
21 | Set `select_default_skin` or `modules_default_skin` to `true` to enable default skin.
22 |
--------------------------------------------------------------------------------
/tamia/modules/select/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tamia/modules/select/example.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/tamia/modules/select/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Select with custom design
3 | // Dependencies: form
4 |
5 | // Bones
6 |
7 | .select
8 | no-select()
9 | position: relative
10 | display: block
11 | overflow: hidden
12 | font-size: 1em
13 | height: 2em;
14 |
15 | &_inline
16 | display: inline-block
17 |
18 | &__select
19 | position: absolute
20 | bottom: 0
21 | left: 0
22 | width: 100%
23 | opacity: 0
24 | z-index: 2
25 | cursor: default
26 | transform: scaleY(2) // Select height could be smaller than box height
27 | transform-origin: 0 100% // Fixes dropdown position on Windows
28 |
29 | &__box
30 | display: block
31 | vertical-align: middle
32 | line-height: 1
33 | white-space: nowrap
34 | overflow: hidden
35 | text-overflow: ellipsis
36 |
37 | &:after
38 | content: ""
39 | position: absolute
40 | top: 0
41 | bottom: 0
42 | right: 0
43 | z-index: 1
44 |
45 | &.is-disabled
46 | opacity: .4
47 |
48 |
49 | // Default skin
50 |
51 | modules_default_skin ?= true
52 | select_default_skin ?= false
53 |
54 | if modules_default_skin or select_default_skin
55 | .select
56 | border: 1px solid #bbb
57 | border-bottom-color: #aaa
58 | border-radius: form_border_radius
59 | transition: border-color .1s ease-in-out, box-shadow .1s ease-in-out
60 |
61 | &__box
62 | height: 2em
63 | padding: .45em 1.5em .5em .5em
64 | background: #f4f4f4
65 | background: linear-gradient(to bottom, #fefefe, #f4f4f4)
66 | line-height: 1
67 | color: #555
68 |
69 | &:after
70 | top: 50%
71 | right: .3em
72 | width: .8em
73 | height: .7em
74 | margin-top: -.3em
75 | background: embedurl("arrow.svg") no-repeat
76 | background-size: 100% 100%
77 | .no-svg &:after
78 | content: "▼"
79 | padding-right: .2em
80 | text-align: center
81 | vertical-align: middle
82 | font-size: 1em
83 | line-height: 2em
84 |
85 | &.is-focused
86 | border-color: form_focus_color
87 | box-shadow: 0 0 .4em rgba(form_focus_color, .75)
88 |
--------------------------------------------------------------------------------
/tamia/modules/select/script.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Select with custom design
3 |
4 | /*global tamia:false*/
5 | ;(function(window, $, undefined) {
6 | 'use strict';
7 |
8 | var _disabledState = 'disabled';
9 |
10 | tamia.Select = tamia.extend(tamia.Component, {
11 | displayName: 'tamia.Select',
12 | binded: 'focus blur change',
13 | template: {
14 | block: 'select',
15 | node: 'root',
16 | content: [
17 | {
18 | block: 'select',
19 | elem: 'box',
20 | link: 'boxElem'
21 | },
22 | {
23 | block: 'select',
24 | elem: 'select',
25 | node: '.js-select',
26 | link: 'selectElem'
27 | }
28 | ]
29 | },
30 |
31 | init: function() {
32 | this.selectElem.on({
33 | focus: this.focus_,
34 | blur: this.blur_,
35 | change: this.change_
36 | });
37 |
38 | if (this.elem.hasState(_disabledState)) {
39 | this.selectElem.prop(_disabledState, true);
40 | }
41 |
42 | this.change();
43 | },
44 |
45 | focus: function() {
46 | this.toggleFocused(true);
47 | },
48 |
49 | blur: function() {
50 | this.toggleFocused(false);
51 | },
52 |
53 | toggleFocused: function(toggle) {
54 | this.elem.toggleState('focused', toggle);
55 | },
56 |
57 | change: function() {
58 | this.boxElem.text(this.selectElem.find(':selected').text());
59 | }
60 | });
61 |
62 | tamia.initComponents({select: tamia.Select});
63 |
64 | }(window, jQuery));
65 |
--------------------------------------------------------------------------------
/tamia/modules/spinner/Readme.md:
--------------------------------------------------------------------------------
1 | # Spinner
2 |
3 | Loading indicator (spinner) with animation.
4 |
5 |
6 | ## Markup
7 |
8 |
9 |
10 |
13 |
14 | `.loader` is the same as `.spinner` but it’s hidden by default. It’s visible only when `.is-loading` state is set on ancestor element.
15 |
16 | ## Modifiers
17 |
18 | ### .spinner.spinner_big
19 |
20 | Bigger size.
21 |
22 |
23 | ## More sizes
24 |
25 | You can set any spinner size changing `font-size` property.
26 |
27 | .spinner_huge
28 | font-size: 64px
29 |
30 |
31 |
32 |
33 | ## Component loading indicator
34 |
35 | $('.pony').trigger('loading-start.tamia'); // Show loader
36 | $('.pony').trigger('loading-stop.tamia'); // Hide loader
37 |
38 | That will blocks all container’s content with a semi transparent layer and shows spinner in the middle.
39 |
40 | To change shade layer’s color set `loader_shade_color` variable.
41 |
42 |
43 | ## IE 8—9 callback
44 |
45 | Copy `spinner.gif` to your images folder and set `spinner_fallback_gif` variable to its URL.
46 |
--------------------------------------------------------------------------------
/tamia/modules/spinner/example.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tamia/modules/spinner/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Spinner
3 | // Based on http://codepen.io/beben-koben/pen/JcGnK
4 |
5 | spinner_fallback_gif ?= false
6 | loader_shade_color ?= white(.8)
7 |
8 |
9 | @keyframes spinner-rotate
10 | 0%
11 | transform: rotate(0deg)
12 | 100%
13 | transform: rotate(360deg)
14 |
15 |
16 | // Regular spinner
17 | .spinner,
18 | .loader
19 | display: inline-block
20 | width: .25em
21 | height: .25em
22 | margin: .5em .2em
23 | opacity: .7
24 | border-radius: 50%
25 | font-size: 16px
26 | box-shadow: 0 -.4em 0 0 rgba(0,0,0,1), -.28em -.28em 0 0 rgba(0,0,0,.75), -.4em 0 0 0 rgba(0,0,0,.5), -.28em .28em 0 0 rgba(0,0,0,.25)
27 |
28 | &_big
29 | font-size: 32px
30 |
31 |
32 | // Loader
33 | .loader
34 | visibility: hidden
35 | opacity: 0
36 | transition: visibility 0s .3s, opacity .3s ease-out
37 |
38 | .is-loading &
39 | visibility: visible
40 | opacity: 1
41 | transition: opacity .5s ease-out
42 |
43 |
44 | // Enable animation only when spinner is visible to increase page performance
45 | .spinner,
46 | .is-loading .loader
47 | animation: .85s spinner-rotate steps(9) infinite
48 |
49 | .spinner.is-hidden,
50 | .loader.is-hidden
51 | animation: none
52 |
53 |
54 | // Loader shade
55 | .loader-wrapper
56 | position: relative
57 |
58 | .loader-shade
59 | position: absolute
60 | top: 0
61 | left: 0
62 | width: 100%
63 | height: 100%
64 | opacity: 0
65 | background: loader_shade_color
66 | border-radius: inherit
67 | z-index: 99999
68 | transition: opacity .15s ease-out
69 |
70 | .is-loading &
71 | opacity: 1
72 |
73 |
74 | // Fallback GIF image for browsers that don’t support CSS animations
75 | if spinner_fallback_gif
76 | .no-cssanimations .spinner
77 | width: 16px
78 | height: 16px
79 | margin: 0
80 | opacity: 1
81 | background: url(spinner_fallback_gif)
82 |
--------------------------------------------------------------------------------
/tamia/modules/spinner/script.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Spinner
3 |
4 | /*global tamia:false*/
5 | ;(function(window, $, undefined) {
6 | 'use strict';
7 |
8 | var _wrapperClass = 'loader-wrapper';
9 | var _shadeSelector = '.loader-shade';
10 |
11 | tamia.Loader = tamia.extend(tamia.Component, {
12 | displayName: 'tamia.Loader',
13 | template: {
14 | block: 'loader-shade',
15 | content: {
16 | block: 'l-center',
17 | content: {
18 | block: 'l-center',
19 | inner: true,
20 | content: {
21 | block: 'spinner',
22 | mods: 'big'
23 | }
24 | }
25 | }
26 | },
27 |
28 | init: function() {
29 | tamia.delay(this.elem.addState, this.elem, 0, 'loading');
30 | },
31 |
32 | destroy: function() {
33 | tamia.delay(function() {
34 | this.elem.removeState('loading');
35 | this.elem.find(_shadeSelector).afterTransition(function() {
36 | this.elem.removeClass(_wrapperClass);
37 | this.loader.remove();
38 | }.bind(this));
39 | }, this, 0);
40 | },
41 |
42 | initHtml: function() {
43 | this.elem.addClass(_wrapperClass);
44 | this.loader = tamia.oporNode(this.template);
45 | this.elem.append(this.loader);
46 | }
47 | });
48 |
49 |
50 | // Events
51 | tamia.registerEvents({
52 | 'loading-start': function(elem) {
53 | var container = $(elem);
54 | if (container.data('loader')) return;
55 | container.data('loader', new tamia.Loader(elem));
56 | },
57 |
58 | 'loading-stop': function(elem) {
59 | var container = $(elem);
60 | var loader = container.data('loader');
61 | if (!loader) return;
62 | loader.destroy();
63 | container.removeData('loader');
64 | },
65 | });
66 |
67 | }(window, jQuery));
68 |
--------------------------------------------------------------------------------
/tamia/modules/spinner/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sapegin/react-text-stats/a03153185bd19cd3a41db61a8369f5ef4d1e3a1f/tamia/modules/spinner/spinner.gif
--------------------------------------------------------------------------------
/tamia/modules/switcher/Readme.md:
--------------------------------------------------------------------------------
1 | # Switcher
2 |
3 | Nice looking radio buttons group.
4 |
5 |
6 | ## Markup
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ## Skin
19 |
20 | Set `switcher_default_skin` or `modules_default_skin` to `true` to enable default skin.
21 |
--------------------------------------------------------------------------------
/tamia/modules/switcher/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tamia/modules/switcher/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Switcher
3 | // Dependencies: form
4 |
5 | // Bones
6 |
7 | .switcher
8 | clearfix()
9 | no-select()
10 |
11 | &__label,
12 | &__input
13 | // For prehistoric browsers
14 | vertical-align: text-bottom
15 | padding-right: .5em
16 | font-size: 1em
17 |
18 | &__input:checked,
19 | &__input:not(:checked)
20 | position: absolute
21 | opacity: 0
22 |
23 | &__input:checked + &__label,
24 | &__input:not(:checked) + &__label
25 | position: relative
26 | float: left
27 | box-sizing: border-box
28 | vertical-align: top
29 | cursor: pointer
30 | z-index: 1
31 | margin-left: -1px
32 | user-select: none
33 |
34 | &__label:nth-of-type(1)
35 | margin-left: 0
36 |
37 | &__input:checked + &__label
38 | cursor: default
39 | z-index: 2
40 |
41 | &.is-disabled
42 | opacity: .4
43 |
44 |
45 | // Default skin
46 |
47 | modules_default_skin ?= true
48 | switcher_default_skin ?= false
49 |
50 | if modules_default_skin or switcher_default_skin
51 | .switcher
52 | &__input:checked + &__label,
53 | &__input:not(:checked) + &__label
54 | height: 2em
55 | padding: .4em 1em .6em
56 | background: #f4f4f4
57 | background: linear-gradient(to bottom, #fefefe, #f4f4f4)
58 | border: 1px solid #bbb
59 | line-height: 1
60 | color: #555
61 | transition: border-color .1s ease-in-out, box-shadow .1s ease-in-out
62 |
63 | &__label:nth-of-type(1)
64 | border-radius: form_border_radius 0 0 form_border_radius
65 | &__label:nth-last-of-type(1)
66 | border-radius: 0 form_border_radius form_border_radius 0
67 |
68 | &__input:checked + &__label
69 | background: #ccc
70 | background: linear-gradient(to bottom, #bbb, #eee)
71 | border-color: #999
72 | box-shadow: inset 0 .1em .2em black(.2)
73 | text-shadow: 0 1px 0 black(.1)
74 |
75 | &__input:focus + &__label
76 | border-color: form_focus_color
77 | box-shadow: 0 0 .4em rgba(form_focus_color, .75)
78 |
--------------------------------------------------------------------------------
/tamia/modules/table/Readme.md:
--------------------------------------------------------------------------------
1 | # Tables
2 |
3 | Basic tables styles.
4 |
5 |
6 | ## Markup
7 |
8 |
9 |
10 |
11 | Header |
12 | ...
13 |
14 |
15 |
16 |
17 | Data |
18 | ...
19 |
20 | ...
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tamia/modules/table/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | |
5 | |
6 | |
7 |
8 |
9 |
10 |
11 | |
12 | |
13 | |
14 |
15 |
16 | |
17 | |
18 | |
19 |
20 |
21 | |
22 | |
23 | |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tamia/modules/table/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Table
3 | // Based on https://github.com/twbs/bootstrap/blob/master/less/tables.less
4 |
5 | table_border_color ?= #ddd
6 |
7 |
8 | .table
9 | space(2)
10 | width: 100%;
11 | font-size: .8em
12 | font-size: .8rem
13 |
14 | th
15 | text-align: left
16 |
17 | // Cells
18 | > thead,
19 | > tbody,
20 | > tfoot
21 | > tr
22 | > th,
23 | > td
24 | padding: 8px
25 | vertical-align: top
26 | border-top: 1px solid table_border_color
27 | > th:first-child,
28 | > td:first-child
29 | padding-left: 0
30 | > th:last-child,
31 | > td:last-child
32 | padding-right: 0
33 |
34 | // Bottom align for column headings
35 | > thead > tr > th
36 | vertical-align: bottom
37 | border-bottom: 2px solid table_border_color
38 |
39 | // Remove top border from thead by default
40 | > caption + thead,
41 | > colgroup + thead,
42 | > thead:first-child
43 | > tr:first-child
44 | > th,
45 | > td
46 | border-top: 0
47 |
48 | // Account for multiple tbody instances
49 | > tbody + tbody
50 | border-top: 2px solid table_border_color
51 |
--------------------------------------------------------------------------------
/tamia/modules/text/Readme.md:
--------------------------------------------------------------------------------
1 | # Basic text styles
2 |
3 | Headings, paragraphs, etc.
4 |
5 |
6 | ## Markup
7 |
8 |
9 |
Down the Rabbit-Hole
10 |
Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, “and what is the use of a book,” thought Alice “without pictures or conversation?”
11 |
So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her.
12 |
13 |
14 |
15 | ## Configuration
16 |
17 | ### text_default_headings
18 |
19 | Type: Boolean. Default: `true`.
20 |
21 | Enables default headings (H1—H5) styles.
22 |
--------------------------------------------------------------------------------
/tamia/modules/text/example.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/tamia/modules/text/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2015 Artem Sapegin http://sapegin.me
2 | // Basic text styles
3 |
4 | text_default_headings ?= true
5 |
6 | .text
7 | responsive-images()
8 |
9 | word-wrap: break-word
10 | hyphens: auto
11 |
12 | p,
13 | ul,
14 | ol,
15 | li,
16 | pre,
17 | blockquote
18 | space(2)
19 |
20 | blockquote
21 | font-size: .8em
22 | padding: 0 2em
23 |
24 | a
25 | @extend .link
26 |
27 | if text_default_headings
28 | .alpha,
29 | .text h1,
30 | .beta,
31 | .text h2,
32 | .gamma,
33 | .text h3,
34 | .delta,
35 | .text h4,
36 | .epsilon,
37 | .text h5
38 | margin-top: 4*spacer
39 | margin-bottom: 2*spacer
40 | line-height: 1.2
41 | font-weight: normal
42 |
43 | .alpha,
44 | .text h1
45 | heading-font-size: 44px
46 |
47 | .beta,
48 | .text h2
49 | heading-font-size: 34px
50 |
51 | .gamma,
52 | .text h3
53 | heading-font-size: 28px
54 |
55 | .delta,
56 | .text h4
57 | heading-font-size: 20px
58 |
59 | .epsilon,
60 | .text h5
61 | heading-font-size: 1em
62 | font-weight: bold
63 |
64 | // Collapse margin between headings and before first heading.
65 | .text h1 + h2,
66 | .text h2 + h3,
67 | .text h3 + h4,
68 | .text h4 + h5,
69 | .alpha + .beta,
70 | .beta + .gamma,
71 | .gamma + .delta,
72 | .delta + .epsilon,
73 | h1:first-child,
74 | h2:first-child,
75 | h3:first-child,
76 | h4:first-child,
77 | h5:first-child,
78 | .alpha:first-child,
79 | .beta:first-child,
80 | .gamma:first-child,
81 | .delta:first-child
82 | margin-top: 0
83 |
--------------------------------------------------------------------------------
/tamia/modules/toggle/Readme.md:
--------------------------------------------------------------------------------
1 | # Toggle ß
2 |
3 | Magic content toggler.
4 |
5 |
6 | ## Markup
7 |
8 |
28 |
29 |
30 | ## States
31 |
32 | There are two kind of states:
33 |
34 | * Wrapper states: define different look of your UI. You can switch wrapper sates with togglers (see below).
35 | * Element states: define look of particular elements (hidden, disabled, etc.) as a reaction for wrapper state switching.
36 |
37 | For every wrapper state you could define which element states should be added or removed.
38 |
39 | For example this element will be hidden at `restore` state:
40 |
41 |
Log in
42 |
43 | But this one will be visible:
44 |
45 |
Restore
46 |
47 | You could react to or change any number of states:
48 |
49 |
50 |
51 | You should set initial states for wrapper (`
`) and elements (`
Restore
`) in your markup.
52 |
53 | You can use wrapper state classes (`.state-restore`) to add custom CSS.
54 |
55 |
56 | ### Elements States
57 |
58 | Basically element state is an `.is-statename` CSS class. But some states imply additional magic:
59 |
60 | * `hidden` will also trigger `appear.tamia`/`disappear.tamia` events.
61 | * `disabled` will also add/remove `disabled` attribute.
62 |
63 |
64 | ## Togglers
65 |
66 | You can toggle state with a special link:
67 |
68 |
Toggle
69 |
70 | Or several states at once:
71 |
72 |
Toggle
73 |
74 |
75 | ## Transitions
76 |
77 | By default element states change with fade transition (crossfade when necessary) but you can use different transition:
78 |
79 |
80 |
81 |
82 | ### Available Transitions
83 |
84 | * `toggle-transition-slide`: kind of slide transition (unfinished).
85 |
86 |
87 | ## Configuration
88 |
89 | ### toggle_transition_time
90 |
91 | Type: CSS duration value.
92 |
93 | Duration of appearing/disappearing animation.
94 |
--------------------------------------------------------------------------------
/tamia/modules/toggle/example.html:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/tamia/modules/toggle/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Toggle
3 |
4 | toggle_transition_time ?= .3s
5 |
6 | .toggle-element
7 | transition: all toggle_transition_time ease-in-out-cubic
8 |
9 | &.is-hidden
10 | opacity: 0
11 |
12 | &.is-transit
13 | display: block
14 | transform: translateZ(0)
15 |
16 | &.toggle-transition-slide
17 | transition: all toggle_transition_time linear
18 | backface-visibility: hidden
19 |
20 | &.toggle-transition-slide.is-hidden
21 | opacity: 1
22 | transform: translateZ(0) rotateX(90deg)
23 |
24 | .toggle-crossfade-wrapper
25 | & > *
26 | position: absolute
27 |
--------------------------------------------------------------------------------
/tamia/modules/toggle/script.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // Toggle
3 |
4 | // @todo Global events
5 | // @todo Proper slide transition
6 | // @todo Real list of available actions
7 |
8 | /*global tamia:false*/
9 | ;(function(window, $, undefined) {
10 | 'use strict';
11 |
12 | var _body = $('body');
13 | var _doc = $(document);
14 |
15 | var togglers = {
16 | hidden: {
17 | set: function(elem) {
18 | elem.trigger('disappear.tamia');
19 | },
20 | clear: function(elem) {
21 | elem.trigger('appear.tamia');
22 | }
23 | },
24 | disabled: {
25 | set: function(elem) {
26 | elem.addState('disabled');
27 | elem.prop('disabled', true);
28 | },
29 | clear: function(elem) {
30 | elem.removeState('disabled');
31 | elem.prop('disabled', false);
32 | }
33 | },
34 | _default: {
35 | set: function(elem, name) {
36 | elem.addState(name);
37 | },
38 | clear: function(elem, name) {
39 | elem.removeState(name);
40 | }
41 | }
42 | };
43 |
44 | var hiddenEvents = 'appeared.tamia disappeared.tamia';
45 |
46 |
47 | function Toggle(wrapper) {
48 | this.wrapper = wrapper;
49 | this.elems = this.findElems();
50 | }
51 |
52 | Toggle.prototype = {
53 | findElems: function() {
54 | var elems = [];
55 | var tags = this.wrapper.find('[data-states]');
56 | tags.each(function(index, elem) {
57 | elem = $(elem);
58 | elem.addClass('toggle-element');
59 |
60 | elems.push({
61 | elem: elem,
62 | states: this.parseState(elem.data('states'))
63 | });
64 |
65 | // Detect crossfade
66 | var prev = elems[index-1];
67 | if (prev) {
68 | var parent = elem.parent();
69 | // Have the common parent and no other siblings
70 | if (parent.children().length === 2 && parent[0] === prev.elem.parent()[0]) {
71 | prev.crossfade = true;
72 | }
73 | }
74 | }.bind(this));
75 | return elems;
76 | },
77 |
78 | parseState: function(data) {
79 | var states = data.replace(/\s+/g, '').split(';');
80 | var statesData = {};
81 | for (var stateIdx = 0; stateIdx < states.length; stateIdx++) {
82 | var stateData = states[stateIdx].split(':');
83 | var name = stateData[0];
84 | var elemStates = stateData[1].split(',');
85 | statesData[name] = {};
86 | for (var elemStateIdx = 0; elemStateIdx < elemStates.length; elemStateIdx++) {
87 | var state = elemStates[elemStateIdx];
88 | var set = true;
89 | if (state[0] === '-') { // Negative state: visible:-hidden
90 | set = false;
91 | state = state.slice(1);
92 | }
93 | statesData[name][state] = set;
94 | }
95 | }
96 | return statesData;
97 | },
98 |
99 | toggle: function(link) {
100 | var actions = link.data('toggle');
101 | actions = this.parseToggle(actions);
102 | for (var action in actions) {
103 | var func = action + 'States';
104 | if (!this[func]) throw new tamia.Error('Toggle: wrong action ' + action + '.', 'Available actions: toggle, set, clear.');
105 | this[func](actions[action]);
106 | }
107 | },
108 |
109 | parseToggle: function(data) {
110 | var actions = data.replace(/\s+/g, '').split(';');
111 | var toggleData = {};
112 | for (var actionIdx = 0; actionIdx < actions.length; actionIdx++) {
113 | var action = actions[actionIdx].split(':');
114 | var name = action[0];
115 | var elemStates = action[1].split(',');
116 | toggleData[name] = elemStates;
117 | }
118 | return toggleData;
119 | },
120 |
121 | hasStateClass: function(state) {
122 | return this.wrapper.hasClass('state-' + state);
123 | },
124 |
125 | toggleStateClass: function(state, set) {
126 | return this.wrapper.toggleClass('state-' + state, set);
127 | },
128 |
129 | toggleStates: function(states) {
130 | for (var stateIdx = 0; stateIdx < states.length; stateIdx++) {
131 | this.toggleState(states[stateIdx]);
132 | }
133 | },
134 |
135 | toggleState: function(state) {
136 | var cleanCrossfade = function(elem, next, parent) {
137 | elem.elem.off(hiddenEvents);
138 | setTimeout(function() {
139 | parent.height('');
140 | position = {left: '', top: '', width: ''};
141 | elem.elem.css(position);
142 | next.elem.css(position);
143 | parent.removeClass('toggle-crossfade-wrapper');
144 | }, 100);
145 | };
146 |
147 | var set = !this.hasStateClass(state);
148 | this.toggleStateClass(state, set);
149 |
150 | for (var elemIdx = 0; elemIdx < this.elems.length; elemIdx++) {
151 | var elem = this.elems[elemIdx];
152 | var elemStates = elem.states[state];
153 | if (!elemStates) continue;
154 |
155 | for (var elemState in elemStates) {
156 | var toggler = this.getToggler(elemState);
157 | var action = set === elemStates[elemState] ? 'set' : 'clear';
158 |
159 | // Crossfade
160 | if (elemState === 'hidden' && elem.crossfade) {
161 | var next = this.elems[elemIdx+1];
162 | if (next && next.states[state][elemState] !== elemStates[elemState]) {
163 | var visibleElem, hiddenElem;
164 | if (elem.elem[0].offsetHeight) {
165 | visibleElem = elem.elem;
166 | hiddenElem = next.elem;
167 | }
168 | else {
169 | visibleElem = next.elem;
170 | hiddenElem = elem.elem;
171 | }
172 |
173 | var position = visibleElem.position();
174 |
175 | // Find width
176 | var hiddenClone = hiddenElem.clone();
177 | hiddenClone.css({position: 'absolute', display: 'block', top: -9999, left: -999});
178 | _body.append(hiddenClone);
179 | position.width = Math.max(visibleElem.width(), hiddenClone.width());
180 | hiddenClone.remove();
181 |
182 | var parent = elem.elem.parent();
183 | parent.height(parent.height());
184 | elem.elem.css(position);
185 | next.elem.css(position);
186 | parent.addClass('toggle-crossfade-wrapper');
187 | elem.elem.one(hiddenEvents, cleanCrossfade.bind(this, elem, next, parent));
188 | }
189 | }
190 |
191 | toggler[action](elem.elem, elemState);
192 | }
193 | }
194 | },
195 |
196 | getToggler: function(state) {
197 | return togglers[state] || togglers._default;
198 | }
199 | };
200 |
201 |
202 | _doc.on('click', '[data-toggle]', function(event) {
203 | var elem = $(event.currentTarget);
204 | var wrapper = elem.closest('.js-toggle-wrapper');
205 | if (!wrapper.length) throw new tamia.Error('Toggle: .js-toggle-wrapper not found.', elem);
206 |
207 | var toggle = wrapper.data('tamia-toggle');
208 | if (!toggle) {
209 | toggle = new Toggle(wrapper);
210 | wrapper.data('tamia-toggle', toggle);
211 | }
212 |
213 | toggle.toggle(elem);
214 |
215 | event.preventDefault();
216 | });
217 |
218 | }(window, jQuery));
219 |
--------------------------------------------------------------------------------
/tamia/modules/tooltip/Readme.md:
--------------------------------------------------------------------------------
1 | # Tooltip
2 |
3 | Simple tooltips.
4 |
5 |
6 | ## Markup
7 |
8 |
I’m tooltip!
9 |
10 |
+7 495 212-85-06
11 |
12 |
13 | ## Skin
14 |
15 | Set `tooltip_default_skin` or `modules_default_skin` to `true` to enable default skin.
16 |
--------------------------------------------------------------------------------
/tamia/modules/tooltip/example.html:
--------------------------------------------------------------------------------
1 |
+7 495 212-85-06
2 |
--------------------------------------------------------------------------------
/tamia/modules/tooltip/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Tooltip
3 |
4 | // Bones
5 |
6 | .tooltip,
7 | .has-tooltip:before
8 | position: relative
9 | display: inline-block
10 | text-decoration: none
11 | white-space: nowrap
12 |
13 | .tooltip:after,
14 | .has-tooltip:before,
15 | .has-tooltip:after
16 | content: ""
17 | position: absolute
18 | left: 50%
19 | transform: translateX(-50%)
20 |
21 | .has-tooltip
22 | position: relative
23 |
24 | &:before
25 | content: attr(data-tooltip);
26 |
27 | &:before,
28 | &:after
29 | top: 100%
30 | display: none
31 |
32 | &:hover:before,
33 | &:hover:after
34 | display: block
35 |
36 |
37 | // Default skin
38 |
39 | modules_default_skin ?= true
40 | tooltip_default_skin ?= false
41 |
42 | if modules_default_skin or tooltip_default_skin
43 |
44 | _tooltip_arrow = 5px
45 | _tooltip_bg = black(0.9)
46 |
47 | .tooltip,
48 | .has-tooltip:before
49 | padding: .2em .5em
50 | background: _tooltip_bg
51 | color: #fff
52 | font-size: 14px
53 | border-radius: form_border_radius
54 |
55 | .tooltip:after,
56 | .has-tooltip:after
57 | triangle("up", _tooltip_arrow, _tooltip_bg)
58 |
59 | .tooltip:after
60 | top: (-(_tooltip_arrow))
61 |
62 | .has-tooltip:before
63 | margin-top: _tooltip_arrow
64 |
--------------------------------------------------------------------------------
/tamia/tamia/bootstrap.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Base CSS
3 |
4 | // Text selection background color.
5 | selection_color ?= null
6 |
7 | // Global reset.
8 | *
9 | padding: 0
10 | margin: 0
11 | box-sizing: border-box
12 | article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary
13 | display: block
14 | sup, sub, small, code
15 | line-height: 0
16 | img
17 | vertical-align: middle
18 | a img, a.img
19 | border: none !important
20 | background-image: none !important
21 | abbr
22 | border-bottom: none
23 | ul
24 | list-style: none
25 | table
26 | border-collapse: collapse
27 | border-spacing: 0
28 |
29 | html
30 | // Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g
31 | -webkit-text-size-adjust: 100%
32 | -ms-text-size-adjust: 100%
33 | if sticky_footer_height
34 | position: relative
35 | min-height: 100%
36 |
37 | // Sticky footer: mystrd.at/modern-clean-css-sticky-footer/.
38 | if sticky_footer_height
39 | body
40 | padding-bottom: sticky_footer_height
41 | .footer
42 | sticky-footer(sticky_footer_height)
43 | if max_width
44 | &-i
45 | max-width: max_width
46 | margin-side: auto
47 | padding: 0 spacer
48 | else
49 | padding: 0 spacer
50 |
51 | // www.aestheticallyloyal.com/public/optimize-legibility/
52 | h1, h2, h3
53 | text-rendering: optimizeLegibility
54 |
55 | // Hand cursor on clickable input elements
56 | input[type="button"],
57 | input[type="submit"],
58 | input[type="image"],
59 | button
60 | cursor: pointer
61 | button[disabled],
62 | input[disabled]
63 | cursor: default
64 |
65 | // Forms: github.com/necolas/normalize.css/blob/master/normalize.css.
66 | button
67 | overflow: visible
68 | button,
69 | input
70 | line-height: normal
71 | button,
72 | input,
73 | select,
74 | textarea
75 | font: inherit
76 | color: inherit
77 | button,
78 | input[type="button"],
79 | input[type="reset"],
80 | input[type="submit"]
81 | -webkit-appearance: button
82 | input[type="search"]
83 | -webkit-appearance: textfield
84 | input[type="search"]::-webkit-search-decoration,
85 | input[type="search"]::-webkit-search-cancel-button
86 | -webkit-appearance: none
87 | button::-moz-focus-inner,
88 | input::-moz-focus-inner
89 | border: 0
90 | textarea
91 | overflow: auto
92 | vertical-align: top
93 | resize: vertical
94 | fieldset,
95 | legend
96 | border: 0
97 | [hidden]
98 | display: none
99 |
100 | // Hide the spin-button from input[type="number"]
101 | input[type="number"]
102 | -moz-appearance: textfield
103 | input[type="number"]::-webkit-outer-spin-button,
104 | input[type="number"]::-webkit-inner-spin-button
105 | -webkit-appearance: none
106 | margin: 0
107 |
108 | // Pre with wrapping.
109 | pre
110 | white-space: pre-wrap
111 |
112 | // Addresses outline inconsistency between Chrome and other browsers.
113 | a:focus
114 | outline: thin dotted
115 |
116 | // Improve readability when focused and also mouse hovered in all browsers.
117 | a:hover,
118 | a:active
119 | outline: 0
120 |
121 | // Text selection and iOS tap highlighting.
122 | if selection_color
123 | _selection_color = selection_color
124 | else if light(bg_color)
125 | _selection_color = rgba(darken(bg_color,60),.5)
126 | else
127 | _selection_color = rgba(lighten(bg_color,60),.5)
128 | ::selection
129 | color: base_color
130 | background: _selection_color
131 | text-shadow: none
132 | a:link
133 | -webkit-tap-highlight-color: rgba(_selection_color,.25)
134 |
135 | // Hide phone links from desktop browsers.
136 | .no-touch a[href^="tel"]
137 | &:link,
138 | &:visited,
139 | &:hover,
140 | &:active,
141 | &:focus
142 | position: inherit
143 | text-decoration: inherit
144 | border: inherit
145 | color: inherit
146 | background-image: none
147 | cursor: default
148 |
149 | if content_max_width
150 | .content
151 | max-width: content_max_width
152 | margin: 0 auto
153 |
--------------------------------------------------------------------------------
/tamia/tamia/classes.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2015 Artem Sapegin http://sapegin.me
2 | // Helper classes
3 |
4 | // Element is hidden.
5 | .is-hidden
6 | display: none !important
7 |
8 | // Element is invisible (but occupies place on page).
9 | .is-invisible
10 | visibility: hidden
11 |
12 | /// Element is in transit between hidden (.is-hidden) and visible.
13 | .is-transit
14 | display: block !important
15 | &.l-center
16 | display: table !important
17 |
18 | // Clearfix.
19 | .group
20 | clearfix()
21 |
--------------------------------------------------------------------------------
/tamia/tamia/component.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // https://github.com/sapegin/tamia
3 | // JS component base class
4 |
5 | /*global DEBUG:false, tamia:false*/
6 | ;(function(window, $, undefined) {
7 | 'use strict';
8 |
9 | /**
10 | * @class JS component base class.
11 | *
12 | * Elements: any HTML element with class name that follow a pattern `.js-name` where `name` is an element name.
13 | *
14 | * States: any class on component root HTML node that follow a pattern `.is-state` where `state` is a state name.
15 | * After initialization all components will have `ok` state.
16 | *
17 | * Example:
18 | *
19 | * var Pony = tamia.extend(tamia.Component, {
20 | * binded: 'toggle',
21 | * init: function() {
22 | * this.elem.on('click', '.js-toggle', this.toggle_);
23 | * },
24 | * toggle: function() {
25 | * this.elem.toggleState('pink');
26 | * }
27 | * });
28 | *
29 | * tamia.initComponents({pony: Pony});
30 | *
31 | *
32 | *
33 | *
34 | */
35 | function Component(elem) {
36 | if (!elem || elem.nodeType !== 1) throw new ReferenceError('No DOM node passed to Component constructor.');
37 |
38 | // Bind methods to `this`
39 | if (this.binded) {
40 | if (typeof this.binded === 'string') this.binded = this.binded.split(' ');
41 | this.bindAll.apply(this, this.binded);
42 | }
43 |
44 | this.elemNode = elem;
45 | this.elem = $(elem);
46 | this.initializable = this.isInitializable();
47 | if (!this.initializable) return;
48 |
49 | if (this.isSupported()) {
50 | this.handlers = {};
51 | this.initHtml();
52 | this.init();
53 | this.elem.addState('ok');
54 | }
55 | else {
56 | this.fallback();
57 | this.elem.addState('unsupported');
58 | }
59 | }
60 |
61 | Component.prototype = {
62 | __tamia_cmpnt__: true,
63 | displayName: 'tamia.Component',
64 |
65 | /**
66 | * List of methods that should be binded to `this` (see `bindAll` method).
67 | *
68 | * @type {String|Array}
69 | */
70 | binded: null,
71 |
72 | /**
73 | * Component’s OPORJSON template (see `initHtml` method).
74 | */
75 | template: null,
76 |
77 | /**
78 | * Put all your initialization code in this method.
79 | */
80 | init: function() {
81 | // Should be implemented
82 | },
83 |
84 | /**
85 | * You can implement this method to do destroy component.
86 | */
87 | destroy: function() {
88 | // Could be implemented
89 | },
90 |
91 | /**
92 | * Implement this method if you want to check whether browser is good for your component or not.
93 | *
94 | * @return {Boolean}
95 | */
96 | isSupported: function() {
97 | return true;
98 | },
99 |
100 | /**
101 | * Implement this method if you want to check whether component could be initialized.
102 | *
103 | * Example:
104 | *
105 | * isInitializable: function() {
106 | * // Do not initialize component if it's not visible
107 | * return this.isVisible();
108 | * }
109 | *
110 | * @return {Boolean}
111 | */
112 | isInitializable: function() {
113 | return true;
114 | },
115 |
116 | /**
117 | * Implement this method to do some fallbacks. It will be called if isSupported() returns false.
118 | */
119 | fallback: function() {
120 | // Could be implemented
121 | },
122 |
123 | /**
124 | * Initialize HTML using OPORJSON stored in `this.template`.
125 | */
126 | initHtml: function() {
127 | if (!this.template) return;
128 | if (DEBUG && !tamia.oporNode) throw new tamia.Error('Component.initHtml: Tâmia OPOR API not found. Please include tamia/tamia/opor.js.');
129 | var opor = tamia.oporNode(this.template, {
130 | root: this.elem
131 | });
132 | $.extend(this, opor.data('links'));
133 | },
134 |
135 | /**
136 | * Binds all specified methods to this. Binded method names have `_` at the end.
137 | *
138 | * @param {String} method1... Method names
139 | *
140 | * Example:
141 | *
142 | * this.bindAll('toggle');
143 | * this.elem.on('click', this.toggle_);
144 | */
145 | bindAll: function() {
146 | if (arguments.length === 0) throw new tamia.Error('Component.bindAll: no method names passed.');
147 | for (var funcIdx = 0; funcIdx < arguments.length; funcIdx++) {
148 | var func = arguments[funcIdx];
149 | if (DEBUG && !this[func] || !$.isFunction(this[func])) throw new tamia.Error('Component.bindAll: method ' + func + ' not exists or not a function.');
150 | this[func + '_'] = this[func].bind(this);
151 | }
152 | },
153 |
154 | /**
155 | * Returns component visibility.
156 | *
157 | * @return {Boolean}
158 | */
159 | isVisible: function() {
160 | return !!(this.elemNode.offsetWidth || this.elemNode.offsetHeight);
161 | }
162 | };
163 |
164 | tamia.Component = Component;
165 |
166 | }(window, jQuery));
167 |
--------------------------------------------------------------------------------
/tamia/tamia/functions.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Functions
3 |
4 | // White color with transparency.
5 | //
6 | // opacity - Opacity value (0—1).
7 | //
8 | // Returns color.
9 | white(opacity)
10 | rgba(255, 255, 255, opacity)
11 |
12 | // Black color with transparency.
13 | //
14 | // opacity - Opacity value (0—1).
15 | //
16 | // Returns color.
17 | black(opacity)
18 | rgba(0, 0, 0, opacity)
19 |
--------------------------------------------------------------------------------
/tamia/tamia/images.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2015 Artem Sapegin http://sapegin.me
2 | // Images, sprites, Retina support
3 |
4 | //
5 | // Configuration
6 | //
7 |
8 | // Enable Retina support (default: false).
9 | retinafy ?= false
10 |
11 | // Path to calculate file system image path based on CSS path (default: .).
12 | images_relative_root ?= '.'
13 |
14 | // Sprite fingerprint, string that will be appended to URL to flush browser cache.
15 | sprite_fingerprint ?= null
16 |
17 | // Default sprite image file (default: ../build/sprite.png).
18 | // Sprite should be generated by [grunt-tamia-sprite](https://github.com/tamiadev/grunt-tamia-sprite).
19 | sprite_image ?= '../build/sprite.png'
20 | sprite_image = sprite_image + '?' + sprite_fingerprint if sprite_fingerprint
21 |
22 |
23 | //
24 | // Functions
25 | //
26 |
27 | // background-image with Retina variant.
28 | //
29 | // path - Image file path.
30 | // x - background-position x (default: 0).
31 | // y - background-position y (default: 0).
32 | //
33 | // Retina image file should be named path@2x.png.
34 | image(path, x=0, y=0)
35 | if retinafy
36 | background: url(path) x y
37 | +retina()
38 | ext = extname(path)
39 | hdpath = dirname(path) + '/' + basename(path, ext) + '@2x' + ext
40 | background: url(hdpath) x y
41 | background-size: image-size(pathjoin(images_relative_root, path))
42 | else
43 | background: url(path) x y
44 |
45 |
46 | // Sprite (to use in pseudo element).
47 | //
48 | // img - Sprite image variable.
49 | //
50 | // Example:
51 | //
52 | // .elem
53 | // sprite(sprite_pony);
54 | sprite(img)
55 | if not @content and match(":(before|after)", selector())
56 | content: ""
57 | if not @display
58 | display: inline-block
59 | image(sprite_image, img[0], img[1])
60 | width: img[2]
61 | height: img[3]
62 |
63 | // background-position for sprite.
64 | //
65 | // img - Sprite image variable.
66 | //
67 | // Example:
68 | //
69 | // .elem
70 | // sprite-bg()
71 | // sprite-pos(sprite_pony-hover)
72 | sprite-pos(img)
73 | background-position: img[0] img[1]
74 |
75 | // background-image for sprite.
76 | sprite-bg()
77 | image(sprite_image)
78 |
79 | // Element width for sprite.
80 | //
81 | // img - Sprite image variable.
82 | //
83 | // Returns pixels
84 | sprite-width(img)
85 | img[2]
86 |
87 | // Element height for sprite.
88 | //
89 | // img - Sprite image variable.
90 | //
91 | // Returns pixels
92 | sprite-height(img)
93 | img[3]
94 |
95 | // Element width/height for sprite.
96 | //
97 | // img - Sprite image variable.
98 | //
99 | // Example:
100 | //
101 | // .elem
102 | // sprite-size(sprite_pony);
103 | sprite-size(img)
104 | width: sprite-width(img)
105 | height: sprite-height(img)
106 |
107 | // Shifts element half height to top.
108 | //
109 | // img - Sprite image variable.
110 | sprite-shift-top(img)
111 | margin-top: (-(round(sprite-height(img)/2)))
112 |
--------------------------------------------------------------------------------
/tamia/tamia/index.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Configuration
3 |
4 | /// Debug mode.
5 | DEBUG ?= false
6 |
7 | // Default spacer (default: 10px).
8 | spacer ?= 10px;
9 |
10 | // Max page with.
11 | max_width ?= null;
12 |
13 | // Max content block (.content) with.
14 | content_max_width ?= null;
15 |
16 | // Sticky footer height (default: no sticky footer).
17 | //
18 | // Example:
19 | //
24 | sticky_footer_height ?= null;
25 |
26 | // Background color (default: white).
27 | bg_color ?= #fff;
28 |
29 | // Base text color (default: #111).
30 | base_color ?= #111;
31 |
32 | // Form focus outline color (default: hsl(204,68%,69%)).
33 | form_focus_color ?= hsl(204, 68%, 69%)
34 |
35 | // Form border radius (default: .15em).
36 | form_border_radius ?= .15em
37 |
38 |
39 | @import "./misc"
40 | @import "./functions"
41 | @import "./layout"
42 | @import "./mediaqueries"
43 | @import "./images"
44 | @import "./bootstrap"
45 | @import "./classes"
46 | @import "./links"
47 |
--------------------------------------------------------------------------------
/tamia/tamia/layout.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Grids and layouts
3 |
4 | //
5 | // Configuration
6 | //
7 |
8 | // Grid gutter size, in pixels (default: 2×spacer).
9 | grid_gutter ?= 2*spacer
10 |
11 | // Number of columns in grid (default: 12).
12 | grid_columns ?= 12
13 |
14 |
15 | //
16 | // Grid
17 | //
18 |
19 | /// Inspired by http://zengrids.com
20 |
21 | // Columns wrapper.
22 | grid-row()
23 | clearfix()
24 | grid-margin()
25 | content: "tamia__grid-row" if DEBUG
26 |
27 | // Column (grid).
28 | //
29 | // position - Start column number (default: 1).
30 | // span - Width, in culumns (default: 1).
31 | // context - Width of parent column (default: null).
32 | //
33 | // Example:
34 | //
35 | //
39 | //
40 | // .row
41 | // grid-row()
42 | // .col1
43 | // grid-col(1,2)
44 | // .col2
45 | // grid-col(3,10)
46 | grid-col(position=1, span=1, context=null)
47 | _grid-column(position, span, context)
48 | _grid-float()
49 | grid-padding()
50 |
51 | // Column and wrapper for nested columns (grid).
52 | //
53 | // position - Start column number (default: 1).
54 | // span - Width, in culumns (default: 1).
55 | // context - Width of parent column (default: null).
56 | //
57 | // Example:
58 | //
59 | //
66 | //
67 | // .row
68 | // grid-row()
69 | // .col1
70 | // grid-col-row(1,8)
71 | // .col2, .col3, .col4
72 | // grid-col(...)
73 | grid-col-row(position=1, span=1, context=null)
74 | _grid-column(position, span, context)
75 |
76 | // Gutter size negative margin to make column with different background.
77 | //
78 | // full - Explode to all sides if true, left/right otherwise (default: false).
79 | grid-explode(full=false)
80 | grid-padding(full)
81 | if full
82 | margin: -(grid_gutter)
83 | else
84 | margin-side: -(grid_gutter)
85 |
86 | // Width of column, in percent.
87 | //
88 | // span - Width, in culumns (default: 1).
89 | // context - Width of parent column (default: null).
90 | //
91 | // Returns percent.
92 | grid-width(span=1, context=null)
93 | context = grid_columns if not context
94 | unit-width = 100% / context
95 | span * unit-width
96 |
97 | // Column left/right padding.
98 | //
99 | // full - Padding on all sides if true, left/right otherwise (default: false).
100 | grid-padding(full=false)
101 | side-gutter = grid_gutter / 2;
102 | if full
103 | padding: side-gutter
104 | else
105 | padding-side: side-gutter
106 |
107 | // Column wrapper left/right negative margin.
108 | grid-margin()
109 | side-gutter = (-(grid_gutter / 2))
110 | margin-side: side-gutter
111 |
112 | _grid-column(position=1, span=1, context=null)
113 | context = grid_columns if not context
114 | unit-width = 100% / context
115 | width: span * unit-width
116 | margin-left: (position - 1) * unit-width
117 | content: "tamia__grid-col" if DEBUG
118 |
119 | _grid-float()
120 | float: left
121 | margin-right: -100%
122 |
123 |
124 | //
125 | // Layouts
126 | //
127 |
128 | // Layout wrapper (without margins).
129 | layout-wrapper()
130 | display: flex
131 | flex-flow: row wrap
132 | /// IE8-9
133 | .no-flexbox.no-flexboxlegacy &
134 | display: block
135 | letter-spacing: -0.31em
136 | content: "tamia__layout-row" if DEBUG
137 |
138 | // Layout row wrapper (with margins).
139 | layout-row()
140 | grid-margin()
141 | layout-wrapper()
142 |
143 |
144 | // Column (simple layout).
145 | //
146 | // part - 1/Nth part of wrapper.
147 | //
148 | // Example:
149 | //
150 | //
151 | //
152 | //
153 | //
154 | //
155 | // .row
156 | // layout-row()
157 | // .col
158 | // layout-nth(1/2) // Half
159 | layout-nth(part)
160 | _layout-col()
161 | _layout-part(part)
162 |
163 | // Change number of columns.
164 | //
165 | // Use it to change number of columns (defined via layout-nth()) inside media queries.
166 | //
167 | // part - 1/Nth part of wrapper.
168 | layout-change(part)
169 | _layout-part(part)
170 |
171 | // Disable columns.
172 | //
173 | // Alias for layout-change(1).
174 | layout-stop()
175 | _layout-part(1)
176 |
177 | _layout-col()
178 | display: block
179 | grid-padding()
180 | /// IE8-9
181 | .no-flexbox.no-flexboxlegacy &
182 | display: inline-block
183 | vertical-align: top
184 | letter-spacing: normal
185 | content: "tamia__layout-col" if DEBUG
186 |
187 | _layout-part(part)
188 | width: (100% / (1 / part))
189 |
190 |
191 | //
192 | // Misc
193 | //
194 |
195 | // Center element with specified width and hegith.
196 | //
197 | // width - Width of an element.
198 | // height - Height of an element (default: null).
199 | //
200 | // Example:
201 | //
202 | // .checkbox:before
203 | // center(.8em, .65em)
204 | // background-image: url("check.svg")
205 | center(width, height=null)
206 | size(width, height)
207 | left: 50%
208 | top: 50%
209 | margin-left: (-(@width / 2))
210 | margin-top: (-(@height / 2))
211 |
212 |
213 | //
214 | // Classes
215 | //
216 |
217 | // Column wrapper.
218 | .l-row
219 | layout-row()
220 |
221 | .l-sixth,
222 | .l-quarter,
223 | .l-third,
224 | .l-half,
225 | .l-three-quarters,
226 | .l-two-thirds
227 | _layout-col()
228 |
229 | // Sixth (to use inside .l-row).
230 | .l-sixth
231 | _layout-part(1/6)
232 |
233 | // Quarter (to use inside .l-row).
234 | .l-quarter
235 | _layout-part(1/4)
236 |
237 | // Three quarters (to use inside .l-row).
238 | .l-three-quarters
239 | _layout-part(3/4)
240 |
241 | // Third (to use inside .l-row).
242 | .l-third
243 | _layout-part(1/3)
244 |
245 | // Two thirds (to use inside .l-row).
246 | .l-two-thirds
247 | _layout-part(2/3)
248 |
249 | // Half (to use inside .l-row).
250 | .l-half
251 | _layout-part(1/2)
252 |
253 | // Wrapper for .l-left/.l-right.
254 | .l-wrap
255 | layout-wrapper()
256 |
257 | // Flexible columns with left text alignment (to use inside .l-wrap).
258 | //
259 | // Example:
260 | //
261 | // .block
262 | // layout-wrapper()
263 | // &__first
264 | // layout-left()
265 | // &__second
266 | // layout-right()
267 | layout-left()
268 | flex: 1
269 | .no-flexbox.no-flexboxlegacy
270 | float: left
271 |
272 | // Flexible columns with right text alignment (to use inside .l-wrap).
273 | layout-right()
274 | flex: 1
275 | text-align: right
276 | .no-flexbox.no-flexboxlegacy
277 | float: right
278 |
279 | // Flexible columns with left text alignment (to use inside .l-wrap).
280 | //
281 | // Example:
282 | //
283 | //
284 | //
Left
285 | //
Left
286 | //
287 | .l-left
288 | layout-left()
289 |
290 | // Flexible columns with right text alignment (to use inside .l-wrap).
291 | .l-right
292 | layout-right()
293 |
294 | // Pull left (float: left).
295 | .l-pull-left
296 | float: left !important
297 |
298 | // Pull right (float: right).
299 | .l-pull-right
300 | float: right !important
301 |
302 | // Centered content (vertically and horizontally).
303 | //
304 | // Example:
305 | //
306 | //
307 | //
308 | // Centered content
309 | //
310 | //
311 | //
312 | .l-center
313 | display: table
314 | width: 100%
315 | height: 100%
316 | &-i
317 | display: table-cell
318 | text-align: center
319 | vertical-align: middle
320 | &__wrap
321 | display: inline-block
322 |
323 | // Horizontally centered block.
324 | .l-centered
325 | display: block
326 | margin-side: auto
327 |
328 | // Centered text.
329 | .l-text-centered
330 | text-align: center
331 |
332 | // Panel with a header (sticked to top) and a body (occupies all available space).
333 | // (No fallback for non-flexbox browsers.)
334 | //
335 | // .l-panel__centered can be applied to either header or body and will center content horizontally and vertically.
336 | //
337 | // Example:
338 | //
339 | //
340 | //
341 | //
342 | //
343 | .l-panel
344 | display: flex
345 | flex-direction: column
346 |
347 | &__body
348 | flex: 2
349 |
350 | &__centered
351 | display: flex
352 | align-items: center
353 | justify-content: center
354 |
355 | //
356 | // Spacing
357 | //
358 |
359 | // Space after block.
360 | //
361 | // rows - Number of “rows” (one row = default spacer).
362 | space(rows=1)
363 | margin-bottom: rows*spacer
364 |
365 | // Standard bottom margin.
366 | .l-space
367 | space()
368 |
369 | // Double bottom margin.
370 | .l-double-space
371 | space(2)
372 |
373 | // Triple bottom margin.
374 | .l-triple-space
375 | space(3)
376 |
377 | // Quadruple bottom margin.
378 | .l-quad-space
379 | space(4)
380 |
381 | // Quintuple bottom margin.
382 | .l-quint-space
383 | space(5)
384 |
385 | // Sextuple bottom margin.
386 | .l-sext-space
387 | space(6)
388 |
389 | ///
390 | /// Debug stuff
391 | ///
392 |
393 | if DEBUG
394 | .tamia__show-help
395 | overflow: hidden
396 |
397 | .tamia__help
398 | display: flex
399 | align-items: center
400 | justify-content: center
401 | position: fixed
402 | left: 0
403 | top: 0
404 | right: 0
405 | bottom: 0
406 | *
407 | background: #fff
408 | color: #000
409 | text-shadow: none
410 | font: 16px/20px "Helvetica Neue", Arial, sans-serif
411 | ul
412 | padding: 2*spacer 2*spacer spacer
413 | border-radius: .25em
414 | box-shadow: 0 .2em .1em black(.15)
415 | li
416 | margin-bottom: spacer
417 | kbd
418 | display: inline-block
419 | margin-right: .3em
420 | padding: .1em .3em
421 | background: #ccc
422 | border-radius: .3em
423 | text-shadow: 0 1px 0 white(.4)
424 |
425 | .tamia__show-layout-outlines
426 | .tamia__grid-col,
427 | .tamia__layout-col
428 | transition: outline .15s ease-in-out
429 | .tamia__grid-col
430 | outline: 1px solid #fc6 !important
431 | .tamia__layout-col
432 | outline: 1px solid #6cc !important
433 |
434 | .tamia__show-all-outlines
435 | *:before,
436 | *:after
437 | outline: 1px solid #d381c3 !important
438 | *
439 | outline: 1px solid #fb0120 !important
440 | *
441 | outline: 1px solid #fc6d24 !important
442 | *
443 | outline: 1px solid #fda331 !important
444 | *
445 | outline: 1px solid #a1c659 !important
446 | *
447 | outline: 1px solid #76c7b7 !important
448 | *
449 | outline: 1px solid #6fb3d2 !important
450 | *
451 | outline: 1px solid #be643c !important
452 |
453 | .tamia__grid-row,
454 | .tamia__layout-row
455 | position: relative
456 |
457 | .tamia__grid-debugger
458 | position: absolute
459 | left: 0
460 | right: 0
461 | z-index: 10000
462 | overflow: hidden
463 | pointer-events: none
464 | transition: opacity .15s ease-in-out
465 | &.is-hidden
466 | opacity: 0
467 |
468 | .tamia__grid-debugger-col
469 | grid-padding()
470 | float: left
471 | width: grid-width()
472 | height: inherit
473 | &:before
474 | content: ""
475 | display: block
476 | height: inherit
477 | background: hsla(220,40%,50%,.25)
478 |
--------------------------------------------------------------------------------
/tamia/tamia/links.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2015 Artem Sapegin http://sapegin.me
2 | // Links
3 |
4 | //
5 | // Configuration
6 | //
7 |
8 | // Link underline style (default: underline).
9 | //
10 | // Values:
11 | // - none - no underline.
12 | // - underline - text-decoration:underline.
13 | // - gradient - Uses CSS gradients to position line under text (cannot change line color on hover).
14 | link_style ?= "underline"
15 |
16 | _is_underline = (link_style == "underline")
17 | _is_gradient = (link_style == "gradient")
18 |
19 | // Link color.
20 | link_color ?= #1978c8
21 |
22 | // Visited link color (only for underline links because image backgrounds don’t work on :visited links).
23 | visited_color ?= #a963b8
24 | if _is_gradient
25 | visited_color = link_color
26 |
27 | // Hovered link color.
28 | hover_color ?= #f28a25
29 |
30 | // Link underline color (for gradient links).
31 | link_underline_color ?= currentColor
32 |
33 | // Hovered link underline color (for gradient links).
34 | hover_underline_color ?= currentColor
35 |
36 | // Link underline position (only for gradient links, default: 90%)
37 | link_underline_position ?= 90%
38 | link_underline_position = 85% if _is_underline
39 |
40 |
41 | //
42 | // Mixins
43 | //
44 |
45 | // Adds link underline (for gradient links).
46 | link-underline(color)
47 | background-image: linear-gradient(to left, color 50%, color 50%)
48 |
49 | // Adds fake link underline
50 | link-fake-underline(color)
51 | background-image: linear-gradient(to right, color, color 50%, transparent 50%)
52 |
53 | // Link underline position (for gradient and fake links).
54 | link-underline-position(position)
55 | background-position: 0 position
56 |
57 | // Link underline size (for gradient and fake links).
58 | link-underline-size(height, step=1px)
59 | background-size: step height
60 |
61 | // :hover, :active and :focus states of the link.
62 | //
63 | // Example:
64 | //
65 | // +link-hovers()
66 | // color: #bada55
67 | link-hovers()
68 | &:hover,
69 | &:active,
70 | &:focus
71 | {block}
72 |
73 | // :link and :visited states of the link.
74 | //
75 | // Example:
76 | //
77 | // +link-non-hovers()
78 | // color: #bada55
79 | link-non-hovers()
80 | &,
81 | &:link,
82 | &:visited
83 | {block}
84 |
85 | // All states of the link.
86 | //
87 | // Example:
88 | //
89 | // +link-all-states()
90 | // color: @color
91 | link-all-states()
92 | &,
93 | &:link,
94 | &:visited,
95 | &:hover,
96 | &:active,
97 | &:focus
98 | {block}
99 |
100 | // Clear link descenders.
101 | link-clear-descenders(bg_color)
102 | text-shadow: 3px 0 bg_color, 2px 0 bg_color, 1px 0 bg_color, -1px 0 bg_color, -2px 0 bg_color, -3px 0 bg_color
103 |
104 | // Pressable element: pointer cursor + disable text selection.
105 | pressable()
106 | no-select()
107 | cursor: pointer
108 |
109 | // Non-pressable element: cancels .pressable.
110 | no-pressable()
111 | cursor: default
112 | pointer-events: none
113 |
114 | // Remove underline.
115 | no-underline()
116 | +link-all-states()
117 | text-decoration: none
118 | background-image: none
119 |
120 |
121 | //
122 | // Classes
123 | //
124 |
125 | // Link.
126 | .link
127 | &,
128 | & u
129 | transition: color .2s ease-in-out, background .2s ease-in-out
130 | cursor: pointer
131 | if _is_underline
132 | text-decoration: underline
133 | else
134 | text-decoration: none
135 | if _is_gradient
136 | background-repeat: repeat-x
137 | link-underline-position: link_underline_position
138 | link-underline-size: 1px
139 | &:hover,
140 | &:hover u
141 | transition: none
142 |
143 | &,
144 | &:link,
145 | & u,
146 | &:link u
147 | color: link_color
148 | if _is_gradient
149 | link-underline(link_underline_color)
150 |
151 | if _is_underline
152 | &:visited,
153 | &:visited u
154 | color: visited_color
155 |
156 | &:hover,
157 | &:active,
158 | &:focus,
159 | &:hover u,
160 | &:active u,
161 | &:focus u
162 | color: hover_color
163 | if _is_gradient and hover_underline_color != currentColor
164 | link-underline(hover_underline_color)
165 |
166 | // Pseudo link with dashed underline.
167 | .link_fake
168 | link-underline-size: 1px 5px
169 | if _is_underline
170 | text-decoration: none
171 | background-repeat: repeat-x
172 | link-underline-position: link_underline_position
173 |
174 | &,
175 | &:link,
176 | &:visited
177 | link-fake-underline(link_underline_color)
178 | color: link_color
179 |
180 | &:hover,
181 | &:active,
182 | &:focus
183 | if hover_underline_color != currentColor
184 | link-fake-underline(hover_underline_color)
185 | color: hover_color
186 |
187 | // Link in quotes (quotes should be colored but not underlined).
188 | //
189 | // Example:
190 | //
191 | //
“My Little Pony”
192 | .link_quoted
193 | no-underline()
194 |
195 | .no-cssgradients
196 | .link
197 | text-decoration: underline
198 | .link_fake
199 | text-decoration: none
200 | border-bottom: 1px dotted
201 |
202 | @media print
203 | .link,
204 | .link u,
205 | .link_fake
206 | background-image: none
207 | text-decoration: underline
208 |
--------------------------------------------------------------------------------
/tamia/tamia/mediaqueries.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // Media queries
3 |
4 | /// Based on https://github.com/jenius/rupture
5 |
6 | // Media query shortcut: viewport width is greater than or equal to specified value.
7 | //
8 | // Example:
9 | //
10 | // .pony
11 | // color: #c0ffee
12 | // +above(320px)
13 | // color: #bada55
14 | //
15 | // min - Min width.
16 | above(min)
17 | condition = 'only screen and (min-width: %s)' % min
18 | @media condition
19 | {block}
20 |
21 | // Media query shortcut: viewport width is less than or equal to specified value.
22 | //
23 | // max - Max width.
24 | below(max)
25 | condition = 'only screen and (max-width: %s)' % max
26 | @media condition
27 | {block}
28 |
29 | // Media query shortcut: viewport width is in between specified values.
30 | //
31 | // min - Min width.
32 | // max - Max width.
33 | between(min, max)
34 | condition = 'only screen and (min-width: %s) and (max-width: %s)' % (min max)
35 | @media condition
36 | {block}
37 |
38 | // Media query shortcut: retina screen.
39 | retina()
40 | @media (min-resolution: 1.5dppx), (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (min-resolution: 144dpi)
41 | {block}
42 |
--------------------------------------------------------------------------------
/tamia/tamia/misc.styl:
--------------------------------------------------------------------------------
1 | // Tâmia © 2015 Artem Sapegin http://sapegin.me
2 | // Misc functions and mixins
3 |
4 | /// Colors for debug() function.
5 | _debug_colors = cyan magenta orange green blue red
6 |
7 | //
8 | // Easings
9 | //
10 |
11 | /// More here: https://github.com/thoughtbot/bourbon/blob/master/app/assets/stylesheets/addons/_timing-functions.scss
12 |
13 | // Ease in sine.
14 | ease-in-sine = cubic-bezier(0.47, 0, 0.745, 0.715)
15 |
16 | // Ease out quint.
17 | ease-out-quint = cubic-bezier(0.23, 1, 0.32, 1)
18 |
19 | // Ease out back.
20 | ease-out-back = cubic-bezier(0.175, 0.885, 0.32, 1.275)
21 |
22 | // Ease in out cubic.
23 | ease-in-out-cubic = cubic-bezier(0.645, 0.045, 0.355, 1)
24 |
25 |
26 | //
27 | // Typography and text
28 | //
29 |
30 | // Ellipsis text overflow.
31 | ellipsis()
32 | overflow: hidden
33 | text-overflow: ellipsis
34 | white-space: nowrap
35 |
36 | // Tweak inverted text (light on dark) for OS X.
37 | tweak-inverted-text()
38 | -webkit-font-smoothing: antialiased
39 | -moz-osx-font-smoothing: grayscale
40 |
41 | // Disables text selection.
42 | no-select()
43 | -webkit-touch-callout: none
44 | user-select: none
45 |
46 | // Enables text selection.
47 | enable-select()
48 | -webkit-touch-callout: default
49 | user-select: all
50 |
51 | // Hides text (for image replacement).
52 | hide-text()
53 | /// https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
54 | font: "0/0" a
55 | color: transparent
56 | text-shadow: none
57 | background-color: transparent
58 | border: 0
59 |
60 | // Heading font size with negative left margin to better align it with the column.
61 | heading-font-size(size)
62 | font-size: size
63 | margin-left: -(size / 20)
64 |
65 |
66 | //
67 | // Misc.
68 | //
69 |
70 | // Clearfix.
71 | clearfix()
72 | &:before
73 | &:after
74 | content: ""
75 | display: table
76 | &:after
77 | clear: both
78 |
79 | // Set element width and height.
80 | //
81 | // width - Width.
82 | // [height] - Height (the same as width if not passed).
83 | size(width, height=null)
84 | width: width
85 | if height
86 | height: height
87 | else
88 | height: width
89 |
90 | // Set element’s left and right margins.
91 | margin-side(width)
92 | margin-left: width
93 | margin-right: width
94 |
95 | // Set element’s left and right paddings.
96 | padding-side(width)
97 | padding-left: width
98 | padding-right: width
99 |
100 | // Make all images inside responsive.
101 | responsive-images()
102 | img
103 | max-width: 100%
104 | height: auto
105 |
106 | // Disables columns.
107 | no-cols()
108 | position: static
109 | display: block
110 | float: none
111 | width: auto
112 | margin-left: 0
113 | margin-right: 0
114 |
115 | // Disables transitions.
116 | //
117 | // imp - Important? (default: false).
118 | no-transition(imp=false)
119 | /// It is the shortest crossbrowser way
120 | if imp
121 | transition 0s !important
122 | else
123 | transition 0s
124 |
125 | // Draws triangle.
126 | //
127 | // direction - Triangle direction (up, down, left, right).
128 | // size - Size (default: 10px).
129 | // color - Color (default: black).
130 | triangle(direction=up, size=10px, color=#000)
131 | /// Borrowed from https://github.com/jenius/roots-css/blob/master/roots-css/utilities.styl.
132 | width: 0
133 | height: 0
134 | if direction is up
135 | border-left: size solid transparent
136 | border-right: size solid transparent
137 | border-bottom: size solid color
138 | else if direction is down
139 | border-left: size solid transparent
140 | border-right: size solid transparent
141 | border-top: size solid color
142 | else if direction is left
143 | border-top: size solid transparent
144 | border-bottom: size solid transparent
145 | border-right: size solid color
146 | else if direction is right
147 | border-top: size solid transparent
148 | border-bottom: size solid transparent
149 | border-left: size solid color
150 |
151 |
152 |
153 | // Draws color outline around an element.
154 | //
155 | // Works only when DEBUG == true.
156 | debug()
157 | outline: 1px solid pop(_debug_colors) if DEBUG
158 |
159 |
160 | ///
161 | /// Internal stuff
162 | ///
163 |
164 | /// Sticky footer wrapper.
165 | ///
166 | /// h - Footer height.
167 | sticky-footer-wrapper(h)
168 | position: relative
169 | min-height: 100%
170 | margin-bottom: h
171 |
172 | /// Sticky footer.
173 | ///
174 | /// h - Footer height.
175 | sticky-footer(h)
176 | position: absolute
177 | left: 0
178 | right: 0
179 | bottom: 0
180 | height: h
181 |
--------------------------------------------------------------------------------
/tamia/tamia/opor.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2014 Artem Sapegin http://sapegin.me
2 | // https://github.com/sapegin/tamia
3 | // OPOR API
4 |
5 | /*global DEBUG:false, tamia:false*/
6 | ;(function(window, $, undefined) {
7 | 'use strict';
8 |
9 | var _defaultTag = 'div';
10 | var _elemSeparator = '__';
11 | var _modSeparator = '_';
12 | var _statePrefix = 'is-';
13 | var _jsPrefix = 'js-';
14 |
15 | /**
16 | * OPORJSON to DOM tree.
17 | *
18 | * Example:
19 | *
20 | * var select = tamia.oporNode({
21 | * block: 'select',
22 | * mods: ['big', 'awesome'],
23 | * states: 'hidden',
24 | * content: {
25 | * block: 'select',
26 | * elem: 'box',
27 | * js: 'select-box',
28 | * link: 'boxElem',
29 | * content: 'Choose semething'
30 | * }
31 | * });
32 | * this.elem.append(select);
33 | * $.extend(this, select.data('links')); // {boxElem: $('.js-select-box')}
34 | *
35 | * @param {Object} json OPORJSON (`block` or `node` is required).
36 | * @param {String} [json.tag=div] Tag name.
37 | * @param {String} [json.block] Block name (.block).
38 | * @param {String} [json.elem] Element name (.block__elem).
39 | * @param {String|Array} [json.mods] Modifier(s) (.block_mod).
40 | * @param {String|Array} [json.states] State(s) (.is-state).
41 | * @param {String|Array} [json.js] JS class(es) (.js-hook).
42 | * @param {OPORJSON|Array} [json.mix] Blocks to mix ({block: 'scrollable'}).
43 | * @param {Object} [json.attrs] HTML attributes ({href: '/about'}).
44 | * @param {String|HTMLElement|jQuery} [json.node] Key in `nodes` object, DOM/jQuery node or selector.
45 | * @param {String} [json.link] Element link key (see example).
46 | * @param {Object|String|Array} [json.content] Child node(s) or text content.
47 | * @param {Object|HTMLElement|jQuery} [nodes] Existing DOM nodes or node (to use in `node` parameter of OPORJSON).
48 | * @return {jQuery}
49 | */
50 | tamia.oporNode = function(json, nodes, links) {
51 | if (nodes === undefined) nodes = {};
52 | var isRoot = links === undefined;
53 | if (isRoot) links = {};
54 |
55 | var elem;
56 | if (json.node) {
57 | // Use existent node
58 | if (typeof json.node === 'string') {
59 | if (nodes[json.node]) {
60 | elem = nodes[json.node];
61 | }
62 | else {
63 | if (DEBUG && (!nodes || !nodes.root)) throw new tamia.Error('tamia.oporNode: `nodes.root` is required to use selectors in `node` parameter.');
64 | elem = nodes.root[0].querySelector(json.node);
65 | }
66 | }
67 | else {
68 | elem = nodes;
69 | }
70 | // jQuery object
71 | if (elem && elem.jquery && elem.length) {
72 | elem = elem[0];
73 | }
74 | if (DEBUG && (!elem || elem.nodeType !== 1)) throw new tamia.Error('tamia.oporNode: node `' + json.node + '` not found.', json);
75 | // Detach node
76 | if (!isRoot && elem.parentNode) {
77 | elem.parentNode.removeChild(elem);
78 | }
79 | }
80 | else {
81 | // Create new node
82 | elem = document.createElement(json.tag || _defaultTag);
83 | }
84 |
85 | // Classes
86 | var newClasses = tamia.oporClass(json);
87 | if (newClasses) {
88 | var classes = elem.className;
89 | if (classes) {
90 | newClasses = _uniqueArray((classes + ' ' + newClasses).split(' ')).join(' ');
91 | }
92 | elem.className = newClasses;
93 | }
94 |
95 | // Attributes
96 | if (json.attrs) {
97 | for (var name in json.attrs) {
98 | elem.setAttribute(name, json.attrs[name]);
99 | }
100 | }
101 |
102 | // Store link
103 | if (json.link) {
104 | links[json.link] = $(elem);
105 | }
106 |
107 | // Append content
108 | if (json.content) {
109 | var child;
110 | if ($.isArray(json.content)) {
111 | child = document.createDocumentFragment();
112 | var children = _ensureArray(json.content);
113 | for (var childIdx = 0; childIdx < children.length; childIdx++) {
114 | child.appendChild(_createChildNode(children[childIdx], nodes, links));
115 | }
116 | }
117 | else {
118 | child = _createChildNode(json.content, nodes, links);
119 | }
120 | elem.appendChild(child);
121 | }
122 |
123 | if (isRoot) {
124 | elem = $(elem);
125 | elem.data('links', links);
126 | return elem;
127 | }
128 | return elem;
129 | };
130 |
131 | /**
132 | * Generates class names for given OPORJSON element.
133 | *
134 | * Example:
135 | *
136 | * tamia.oporClass({block: 'select', elem: 'box', js: 'box'}); // "select__box js-box"
137 | *
138 | * @param {Object} json OPORJSON
139 | * @return {String}
140 | */
141 | tamia.oporClass = function(json) {
142 | var cls = [];
143 |
144 | if (json.block) {
145 | var base = json.block + (json.inner ? '-i' : '') + (json.elem ? _elemSeparator + json.elem : '');
146 | cls.push(base);
147 |
148 | if (json.mods) {
149 | var mods = _ensureArray(json.mods);
150 | for (var modIdx = 0; modIdx < mods.length; modIdx++) {
151 | cls.push(base + _modSeparator + mods[modIdx]);
152 | }
153 | }
154 | }
155 |
156 | if (json.mix) {
157 | var mixes = _ensureArray(json.mix);
158 | for (var mixIdx = 0; mixIdx < mixes.length; mixIdx++) {
159 | cls.push(tamia.oporClass(mixes[mixIdx]));
160 | }
161 | }
162 |
163 | if (json.states) {
164 | var states = _ensureArray(json.states);
165 | for (var stateIdx = 0; stateIdx < states.length; stateIdx++) {
166 | cls.push(_statePrefix + states[stateIdx]);
167 | }
168 | }
169 |
170 | if (json.js) {
171 | var js = _ensureArray(json.js);
172 | for (var jsIdx = 0; jsIdx < js.length; jsIdx++) {
173 | cls.push(_jsPrefix + js[jsIdx]);
174 | }
175 | }
176 |
177 | return cls.join(' ');
178 | };
179 |
180 | function _ensureArray(array) {
181 | return $.isArray(array) ? array : [array];
182 | }
183 |
184 | function _uniqueArray(array) {
185 | return $.grep(array, function(value, key) {
186 | return $.inArray(value, array) === key;
187 | });
188 | }
189 |
190 | function _createChildNode(child, nodes, links) {
191 | if (typeof child === 'string') {
192 | return document.createTextNode(child);
193 | }
194 | else {
195 | return tamia.oporNode(child, nodes, links);
196 | }
197 | }
198 |
199 | }(window, jQuery));
200 |
--------------------------------------------------------------------------------
/tamia/tamia/tamia.js:
--------------------------------------------------------------------------------
1 | // Tâmia © 2013 Artem Sapegin http://sapegin.me
2 | // https://github.com/sapegin/tamia
3 | // JS core
4 | // jQuery is required. Modernizr and jQuery.Transitions aren’t required.
5 |
6 | /*jshint newcap:false*/
7 | /*global DEBUG:true, console:false, ga:false, mixpanel:false*/
8 |
9 | // Debug mode is ON by default
10 | if (typeof window.DEBUG === 'undefined') window.DEBUG = true;
11 |
12 | ;(function(window, jQuery, Modernizr, undefined) {
13 | 'use strict';
14 |
15 | // IE8+
16 | if (!document.querySelectorAll) return;
17 |
18 | // Namespace
19 | var tamia = window.tamia = {};
20 |
21 | // Shortcuts
22 | var $ = jQuery;
23 | var slice = Array.prototype.slice;
24 | var hasOwnProperty = Object.prototype.hasOwnProperty;
25 |
26 | // Custom exception
27 | tamia.Error = function(message) {
28 | if (DEBUG) warn.apply(null, arguments);
29 | this.name = 'TamiaError';
30 | this.message = message;
31 | };
32 | tamia.Error.prototype = new Error();
33 |
34 |
35 | /**
36 | * Debugging.
37 | *
38 | * You can use DEBUG global variable in your scripts to hide some code from minified production version of JavaScript.
39 | *
40 | * To make it work add to your Gruntfile:
41 | *
42 | * uglify: {
43 | * options: {
44 | * compress: {
45 | * global_defs: {
46 | * DEBUG: !!grunt.option('debug')
47 | * }
48 | * }
49 | * },
50 | * // ...
51 | * }
52 | *
53 | * Then if you run `grunt --debug` DEBUG variable will be true and false if you run just `grunt`.
54 | */
55 | if (DEBUG) {
56 | // Debug logger
57 | var addBadge = function(args, name, bg) {
58 | // Color console badge
59 | // Based on https://github.com/jbail/lumberjack
60 | var ua = navigator.userAgent.toLowerCase();
61 | if (ua.indexOf('chrome') !== -1 || ua.indexOf('firefox') !== -1) {
62 | var format = '%c %s %c ' + args.shift();
63 | args.unshift(format, 'background:' + bg + '; color:#fff', name, 'background:inherit; color:inherit');
64 | }
65 | else {
66 | args[0] = name + ': ' + args[0];
67 | }
68 | return args;
69 | };
70 | var logger = function() {
71 | var args = slice.call(arguments);
72 | var func = args.shift();
73 | console[func].apply(console, addBadge(args, 'Tâmia', '#aa759f'));
74 | };
75 | var log = tamia.log = logger.bind(null, 'log');
76 | var warn = tamia.warn = logger.bind(null, 'warn');
77 |
78 | /**
79 | * Traces all object’s method calls and arguments.
80 | *
81 | * @param {Object} object Object.
82 | * @param {String} [name=object.displayName] Object name.
83 | *
84 | * Example:
85 | *
86 | * init: function() {
87 | * tamia.trace(this, 'ClassName');
88 | * // ...
89 | * }
90 | */
91 | tamia.trace = function(object, name) {
92 | if (name === undefined) name = object.displayName || 'Object';
93 | var level = 0;
94 |
95 | var wrap = function(funcName) {
96 | var func = object[funcName];
97 |
98 | object[funcName] = function() {
99 | pre(funcName, slice.call(arguments));
100 | var result = func.apply(this, arguments);
101 | post();
102 | return result;
103 | };
104 | };
105 |
106 | var pre = function(funcName, args) {
107 | level++;
108 | var padding = new Array(level).join('. ');
109 | console.log.apply(console, addBadge([padding + funcName, args || []], name, '#d73737'));
110 | };
111 |
112 | var post = function() {
113 | level--;
114 | };
115 |
116 | for (var funcName in object) {
117 | if ($.isFunction(object[funcName])) {
118 | wrap(funcName);
119 | }
120 | }
121 | };
122 |
123 | // Check dependencies
124 | if (!jQuery) throw tamia.Error('jQuery not found.');
125 |
126 | // Check optional dependencies
127 | if (jQuery && !jQuery.Transitions) warn('jQuery Transition Events plugin (tamia/vendor/transition-events.js) not found.');
128 | if (!Modernizr) warn('Modernizr not found.');
129 |
130 | // Check required Modernizr features
131 | if (Modernizr) $.each([
132 | 'csstransitions',
133 | 'cssgradients',
134 | 'flexbox',
135 | 'touch',
136 | ], function(idx, feature) {
137 | if (!(feature in Modernizr)) warn('Modernizr should be built with "' + feature + '" feautre.');
138 | });
139 | }
140 | else {
141 | tamia.log = tamia.warn = tamia.trace = function() {};
142 | }
143 |
144 |
145 | var _containersCache;
146 | var _components = {};
147 | var _initializedAttribute = '_tamia-yep';
148 |
149 | function _getContainers(parent) {
150 | return (parent || document).querySelectorAll('[data-component]');
151 | }
152 |
153 |
154 | /**
155 | * Components management.
156 | */
157 |
158 | /**
159 | * Initialize components.
160 | *
161 | * @param {Object} components Initializers for each component.
162 | *
163 | * @attribute data-component
164 | *
165 | * Examples:
166 | *
167 | *
168 | *
169 | * tamia.initComponents({
170 | * // New style component
171 | * pony: Pony, // var Pony = tamia.extend(tamia.Component, { ... })
172 | * // Plain initializer
173 | * pony: function(elem) {
174 | * // $(elem) ===
175 | * },
176 | * // Initialize jQuery plugins (plain initializer)
177 | * jquerypony: function(elem) {
178 | * $(elem).pluginmethod({option1: 'val1', options2: 'val2'});
179 | * $(elem).pluginmethod2();
180 | * },
181 | * // Initialize jQuery plugins (shortcut)
182 | * jquerypony: {
183 | * pluginmethod: {option1: 'val1', options2: 'val2'},
184 | * pluginmethod2: ['attr1', 'attr2', 'attr3'],
185 | * pluginmethod3: null
186 | * }
187 | * }
188 | *
189 | * Caveats:
190 | *
191 | * 1. To initialize components inside container that was hidden or inside dynamically created container use
192 | * init.tamia event: `$('.js-container').trigger('init.tamia');`
193 | * 2. No components will be initialized twice. It’s safe to trigger init.tamia event multiple times: only new nodes
194 | * or nodes that was hidden before will be affected.
195 | */
196 | tamia.initComponents = function(components, parent) {
197 | var containers;
198 | if (parent === undefined) {
199 | containers = _containersCache || (_containersCache = _getContainers());
200 | }
201 | else {
202 | // Init all components inside DOM node
203 | containers = _getContainers(parent);
204 | components = _components;
205 | }
206 |
207 | // Init components
208 | for (var containerIdx = 0, containerCnt = containers.length; containerIdx < containerCnt; containerIdx++) {
209 | var container = containers[containerIdx];
210 | var $container = $(container);
211 | var componentNames = container.getAttribute('data-component').split(' ');
212 | for (var componentIdx = 0; componentIdx < componentNames.length; componentIdx++) {
213 | var componentName = componentNames[componentIdx];
214 | var component = components[componentName];
215 | var initializedNames = $container.data(_initializedAttribute) || {};
216 |
217 | if (!component || initializedNames[componentName]) continue;
218 |
219 | var initialized = true;
220 | if (component.prototype && component.prototype.__tamia_cmpnt__) {
221 | // New style component
222 | initialized = (new component(container)).initializable;
223 | }
224 | else if (typeof component === 'function') {
225 | // Old style component
226 | initialized = component(container);
227 | }
228 | else if (jQuery) {
229 | // jQuery plugins shortcut
230 | for (var method in component) {
231 | var params = component[method];
232 | var elem = $(container);
233 | if (DEBUG && !$.isFunction(elem[method])) warn('jQuery method "%s" not found (used in "%s" component).', method, componentName);
234 | if ($.isArray(params)) {
235 | elem[method].apply(elem, params);
236 | }
237 | else {
238 | elem[method](params);
239 | }
240 | }
241 | }
242 |
243 | if (initialized !== false) {
244 | initializedNames[componentName] = true;
245 | $container.data(_initializedAttribute, initializedNames);
246 | }
247 | }
248 | }
249 |
250 | // Add new components to all components array
251 | for (var name in components) {
252 | _components[name] = components[name];
253 | }
254 | };
255 |
256 |
257 | /**
258 | * Common functions
259 | */
260 |
261 | /**
262 | * JavaScript inheritance.
263 | *
264 | * @param {Object} parent Parent object.
265 | * @param {Object} props Child properties.
266 | *
267 | * Example:
268 | *
269 | * var Pony = tamia.extend(tamia.Component, {
270 | * init: function() {
271 | * // ...
272 | * }
273 | * });
274 | */
275 | tamia.extend = function(parent, props) {
276 | // Adapted from Backbone
277 | var child;
278 |
279 | // The constructor function for the new subclass is either defined by you (the "constructor" property
280 | // in your `extend` definition), or defaulted by us to simply call the parent's constructor.
281 | if (props && hasOwnProperty.call(props, 'constructor')) {
282 | child = props.constructor;
283 | }
284 | else {
285 | child = function() { return parent.apply(this, arguments); };
286 | }
287 |
288 | // Set the prototype chain to inherit from `parent`, without calling `parent`'s constructor function.
289 | var Ctor = function() { this.constructor = child; };
290 | Ctor.prototype = parent.prototype;
291 | child.prototype = new Ctor();
292 |
293 | // Add prototype properties (instance properties) to the subclass, if supplied.
294 | if (props) {
295 | for (var prop in props) {
296 | child.prototype[prop] = props[prop];
297 | }
298 | }
299 |
300 | // Copy displayName
301 | if (DEBUG && props.displayName) {
302 | child.displayName = props.displayName;
303 | }
304 |
305 | // Set a convenience property in case the parent's prototype is needed later.
306 | child.__super__ = parent.prototype;
307 |
308 | return child;
309 | };
310 |
311 |
312 | /**
313 | * Delays a function for the given number of milliseconds, and then calls it with the specified arguments.
314 | *
315 | * @param {Function} func Function to call.
316 | * @param {Object} [context] Function context (default: global).
317 | * @param {Number} [wait] Time to wait, milliseconds (default: 0).
318 | * @param {Mixed} [param1...] Any params to pass to function.
319 | * @return {TimeoutId} Timeout handler.
320 | */
321 | tamia.delay = function(func, context, wait) {
322 | var args = slice.call(arguments, 3);
323 | return setTimeout(function delayedFunc() { return func.apply(context || null, args); }, wait || 0);
324 | };
325 |
326 |
327 | var _doc = $(document);
328 | var _hiddenState = 'hidden';
329 | var _transitionState = 'transit';
330 | var _statePrefix = 'is-';
331 | var _statesData = 'tamia-states';
332 | var _appear = 'appear';
333 | var _disappear = 'disappear';
334 | var _appearedEvent = 'appeared.tamia';
335 | var _disappearedEvent = 'disappeared.tamia';
336 | var _toggledEvent = 'toggled.tamia';
337 |
338 |
339 | /**
340 | * Animations management
341 | */
342 | var _animations = {};
343 |
344 | /**
345 | * Registers an animation.
346 | *
347 | * @param {Object} animations Animations list.
348 | *
349 | * Example:
350 | *
351 | * tamia.registerAnimations({
352 | * slide: function(elem, done) {
353 | * $(elem).slideDown(done);
354 | * }
355 | * });
356 | * $('.js-elem').trigger('runanimation.tamia', 'slide');
357 | */
358 | tamia.registerAnimations = function(animations) {
359 | $.extend(_animations, animations);
360 | };
361 |
362 |
363 | /**
364 | * Events management
365 | */
366 |
367 | /**
368 | * Registers Tâmia events (eventname.tamia) on document.
369 | *
370 | * @param {Object} handlers Handlers list.
371 | *
372 | * Example:
373 | *
374 | * // Registers enable.tamia event.
375 | * tamia.registerEvents({
376 | * enable: function(elem) {
377 | * }
378 | * });
379 | */
380 | tamia.registerEvents = function(handlers) {
381 | var events = $.map(handlers, _tamiaze).join(' ');
382 | _doc.on(events, function onRegisterEventsEvent(event) {
383 | var eventName = [event.type, event.namespace].join('.').replace(/.tamia$/, '');
384 | var args = slice.call(arguments);
385 | args[0] = event.target;
386 | var handler = handlers[eventName];
387 | if (DEBUG) handler.displayName = eventName + '.tamia event handler';
388 | if (DEBUG) log('Event "%s":', eventName, args);
389 | handler.apply(null, args);
390 | });
391 | };
392 |
393 | var _tamiaze = function(handler, name) {
394 | return name + '.tamia';
395 | };
396 |
397 |
398 | /**
399 | * Events
400 | */
401 | var _handlers = {};
402 |
403 | /**
404 | * Init components inside any jQuery node.
405 | *
406 | * @event init.tamia
407 | *
408 | * Examples:
409 | *
410 | * $(document).trigger('init.tamia');
411 | * $('.js-container').trigger('init.tamia');
412 | */
413 | _handlers.init = function(elem) {
414 | tamia.initComponents(undefined, elem);
415 | };
416 |
417 | /**
418 | * Show element with CSS transition.
419 | *
420 | * @event appear.tamia
421 | *
422 | * appeared.tamia and toggled.tamia events will be fired the moment transition ends.
423 | *
424 | * Example:
425 | *
426 | * .dialog
427 | * transition: opacity .5s ease-in-out
428 | * &.is-hidden
429 | * opacity: 0
430 | *
431 | *
...
432 | *
433 | * $('.js-dialog').trigger('appear.tamia');
434 | */
435 | _handlers.appear = function(elem) {
436 | elem = $(elem);
437 | if (Modernizr && Modernizr.csstransitions) {
438 | if (elem.data(_transitionState) === _appear) return;
439 | elem.data(_transitionState, _appear);
440 | elem.addState(_transitionState);
441 | setTimeout(function appearDelayed() {
442 | if (elem.data(_transitionState) !== _appear) return;
443 | elem.removeState(_hiddenState);
444 | elem.afterTransition(function appearAfterTransition() {
445 | elem.removeData(_transitionState);
446 | elem.removeState(_transitionState);
447 | elem.trigger(_appearedEvent);
448 | elem.trigger(_toggledEvent, true);
449 | });
450 | }, 0);
451 | }
452 | else {
453 | elem.removeState(_hiddenState);
454 | elem.trigger(_appearedEvent);
455 | elem.trigger(_toggledEvent, true);
456 | }
457 | };
458 |
459 | /**
460 | * Hide element with CSS transition.
461 | *
462 | * @event disappear.tamia
463 | *
464 | * disappeared.tamia and toggled.tamia events will be fired the moment transition ends.
465 | *
466 | * Opposite of `appear.tamia` event.
467 | */
468 | _handlers.disappear = function(elem) {
469 | elem = $(elem);
470 | if (Modernizr && Modernizr.csstransitions) {
471 | var transitionState = elem.data(_transitionState);
472 | if (transitionState === _disappear || (!transitionState && elem.hasState(_hiddenState))) return;
473 | elem.data(_transitionState, _disappear);
474 | elem.addState(_transitionState);
475 | elem.addState(_hiddenState);
476 | elem.afterTransition(function disappearAfterTransition() {
477 | elem.removeData(_transitionState);
478 | elem.removeState(_transitionState);
479 | elem.trigger(_disappearedEvent);
480 | elem.trigger(_toggledEvent, false);
481 | });
482 | }
483 | else {
484 | elem.addState(_hiddenState);
485 | elem.trigger(_disappearedEvent);
486 | elem.trigger(_toggledEvent, false);
487 | }
488 | };
489 |
490 | /**
491 | * Toggles element’s visibility with CSS transition.
492 | *
493 | * @event toggle.tamia
494 | *
495 | * See `appear.tamia` event for details.
496 | */
497 | _handlers.toggle = function(elem) {
498 | elem = $(elem);
499 | if (elem.hasState(_hiddenState)) {
500 | _handlers.appear(elem);
501 | }
502 | else {
503 | _handlers.disappear(elem);
504 | }
505 | };
506 |
507 | /**
508 | * Runs animation on an element.
509 | *
510 | * @event runanimation.tamia
511 | *
512 | * @param {String|Function} animation Animation name, CSS class name or JS function.
513 | * @param {Function} [done] Animation end callback.
514 | *
515 | * Animation name should be registered via `tamia.registerAnimations` function.
516 | */
517 | _handlers.runanimation = function(elem, animation, done) {
518 | if (done === undefined) done = function() {};
519 | setTimeout(function runAnimationDelayed() {
520 | if (_animations[animation]) {
521 | _animations[animation](elem, done);
522 | }
523 | else if ($.isFunction(animation)) {
524 | animation(elem, done);
525 | }
526 | else {
527 | var animationDone = function() {
528 | elem.removeClass(animation);
529 | done();
530 | };
531 | elem = $(elem);
532 | elem.addClass(animation);
533 | if (elem.css('animation')) {
534 | elem.one('animationend webkitAnimationEnd MSAnimationEnd', animationDone);
535 | }
536 | else {
537 | elem.afterTransition(done);
538 | }
539 | }
540 | }, 0);
541 | };
542 |
543 | tamia.registerEvents(_handlers);
544 |
545 |
546 | /**
547 | * Controls.
548 | *
549 | * Fires jQuery event to specified element on click at this element.
550 | *
551 | * @attribute data-fire
552 | *
553 | * @param data-fire Event name.
554 | * @param [data-target] Target element selector.
555 | * @param [data-closest] Target element selector: search only through element ancestors.
556 | * @param [data-attrs] Comma separated attributes list.
557 | *
558 | * Either of data-target or data-closest is required.
559 | *
560 | * Example:
561 | *
562 | *
Next
563 | *
564 | * $('.portfolio').trigger('slider-next', [1, 2, 3]);
565 | */
566 | _doc.on('click', '[data-fire]', function onDataFireClick(event) {
567 | var elem = $(event.currentTarget);
568 |
569 | var data = elem.data();
570 | if (DEBUG) if (!data.target && !data.closest) return log('You should define either data-target or data-closest on', elem[0]);
571 |
572 | var target = data.target && $(data.target) || elem.closest(data.closest);
573 | if (DEBUG) if (!target.length) return log('Target element %s not found for', data.target || data.closest, elem[0]);
574 |
575 | var attrs = data.attrs;
576 | if (DEBUG) log('Fire "%s" with attrs [%s] on', data.fire, attrs || '', target);
577 | target.trigger(data.fire, attrs ? attrs.split(/[;, ]/) : undefined);
578 |
579 | event.preventDefault();
580 | });
581 |
582 |
583 | /**
584 | * States management
585 | */
586 |
587 | /**
588 | * Toggles specified state on an element.
589 | *
590 | * State is a special CSS class: .is-name.
591 | *
592 | * @param {String} name State name.
593 | * @param {Boolean} [value] Add/remove state.
594 | * @return {jQuery}
595 | */
596 | jQuery.fn.toggleState = function(name, value) {
597 | return this.each(function eachToggleState() {
598 | var elem = $(this);
599 | var states = _getStates(elem);
600 | if (value === undefined) value = !states[name];
601 | else if (value === states[name]) return;
602 | states[name] = value;
603 | elem.toggleClass(_statePrefix + name, value);
604 | });
605 | };
606 |
607 | /**
608 | * Adds specified state to an element.
609 | *
610 | * @param {String} name State name.
611 | * @return {jQuery}
612 | */
613 | jQuery.fn.addState = function(name) {
614 | return this.toggleState(name, true);
615 | };
616 |
617 | /**
618 | * Removes specified state from an element.
619 | *
620 | * @param {String} name State name.
621 | * @return {jQuery}
622 | */
623 | jQuery.fn.removeState = function(name) {
624 | return this.toggleState(name, false);
625 | };
626 |
627 | /**
628 | * Returns whether an element has specified state.
629 | *
630 | * @param {String} name State name.
631 | * @return {Boolean}
632 | */
633 | jQuery.fn.hasState = function(name) {
634 | var states = _getStates(this);
635 | return !!states[name];
636 | };
637 |
638 | var _getStates = function(elem) {
639 | var states = elem.data(_statesData);
640 | if (!states) {
641 | states = {};
642 | var classes = elem[0].classList || elem[0].className.split(' ');
643 | for (var classIdx = 0; classIdx < classes.length; classIdx++) {
644 | var cls = classes[classIdx];
645 | if (cls.slice(0, 3) === _statePrefix) {
646 | states[cls.slice(3)] = true;
647 | }
648 | }
649 | elem.data(_statesData, states);
650 | }
651 | return states;
652 | };
653 |
654 |
655 | /**
656 | * Templates
657 | */
658 |
659 | /**
660 | * Simplest template.
661 | *
662 | * Just replaces {something} with data.something.
663 | *
664 | * @param {String} tmpl Template.
665 | * @param {String} data Template context.
666 | * @return {String} HTML.
667 | */
668 | tamia.stmpl = function(tmpl, data) {
669 | return tmpl.replace(/\{([^\}]+)\}/g, function stmplReplace(m, key) {
670 | return data[key] || '';
671 | });
672 | };
673 |
674 | var _templates = window.__templates;
675 | if (_templates) {
676 | /**
677 | * Invokes precompiled template.
678 | *
679 | * Templates should be stored in window.__templates.
680 | *
681 | * @param {String} tmplId Template ID.
682 | * @param {String} data Template context.
683 | * @return {String} HTML.
684 | */
685 | tamia.tmpl = function(tmplId, data) {
686 | var tmpl = _templates[tmplId];
687 | if (DEBUG) if (!tmpl) warn('Template %s not found.', tmplId);
688 | return tmpl(data);
689 | };
690 |
691 | /**
692 | * Replaces jQuery node’s content with the result of template invocation.
693 | *
694 | * @param {String} tmplId Template ID.
695 | * @param {String} data Template context.
696 | * @return {jQuery}
697 | */
698 | jQuery.fn.tmpl = function(tmplId, data) {
699 | var html = tamia.tmpl(tmplId, data);
700 | return $(this).html(html);
701 | };
702 | }
703 |
704 |
705 | /**
706 | * Google Analytics or Mixpanel events tracking.
707 | *
708 | * @param data-track Event name ('link' if empty).
709 | * @param [data-track-extra] Extra data.
710 | *
711 | * @attribute data-track
712 | *
713 | * Examples:
714 | *
715 | *
GitHub
716 | * Next
717 | */
718 | if ('ga' in window || 'mixpanel' in window) _doc.on('click', '[data-track]', function onDataTrackClick(event) {
719 | var mp = 'mixpanel' in window;
720 | var elem = $(event.currentTarget);
721 | var eventName = elem.data('track') || (mp ? 'Link clicked' : 'link');
722 | var eventExtra = elem.data('track-extra') || (mp ? undefined : 'click');
723 | var url = elem.attr('href');
724 | var link = url && !event.metaKey && !event.ctrlKey;
725 | var callback;
726 | if (link) {
727 | event.preventDefault();
728 | callback = function() {
729 | document.location = url;
730 | };
731 | }
732 | if (mp) {
733 | var props = {URL: url};
734 | if (eventExtra) props.Extra = eventExtra;
735 | mixpanel.track(eventName, props, callback);
736 | }
737 | else {
738 | ga('send', 'event', eventName, eventExtra, url, {hitCallback: callback});
739 | }
740 | });
741 |
742 |
743 | /**
744 | * Grid debugger.
745 | *
746 | * Hotkeys:
747 | *
748 | * - ? - Toggle help.
749 | * - g - Toggle grid.
750 | * - o - Toggle layout outlines.
751 | * - a - Toggle all elements outlines.
752 | */
753 | if (DEBUG) {
754 | var layoutClassesAdded = false;
755 | var gridDebugger;
756 |
757 | var toggleGrid = function() {
758 | addLayoutClasses();
759 | addGrid();
760 | gridDebugger.trigger('toggle.tamia');
761 | };
762 |
763 | var toggleOutline = function() {
764 | addLayoutClasses();
765 | $('body').toggleClass('tamia__show-layout-outlines');
766 | };
767 |
768 | var toggleAllOutline = function() {
769 | $('body').toggleClass('tamia__show-all-outlines');
770 | };
771 |
772 | var toggleHelp = function() {
773 | var cls = 'tamia__show-help';
774 | var body = $('body');
775 | body.toggleClass(cls);
776 | if (body.hasClass(cls)) {
777 | body.append($('').html('
' +
778 | '- G Toggle grid
' +
779 | '- O Toggle columns outlines
' +
780 | '- A Toggle all elements outlines
' +
781 | '
'));
782 | }
783 | else {
784 | $('.tamia__help').remove();
785 | }
786 | };
787 |
788 | var addLayoutClasses = function() {
789 | if (layoutClassesAdded) return;
790 | $('*').each(function() {
791 | var elem = $(this);
792 | var content = elem.css('content');
793 | if (/^tamia__/.test(content)) {
794 | elem.addClass(content);
795 | }
796 | });
797 | layoutClassesAdded = true;
798 | };
799 |
800 | var addGrid = function() {
801 | var firstRow = $('.tamia__grid-row:visible,.tamia__layout-row:visible').first();
802 | if (!firstRow.length) return;
803 |
804 | if (!gridDebugger) {
805 | var columns = 12; // @todo Use real number of columns
806 | gridDebugger = $('
', {'class': 'tamia__grid-debugger is-hidden'});
807 | gridDebugger.html(new Array(columns + 1).join('
'));
808 | firstRow.prepend(gridDebugger);
809 | }
810 |
811 | gridDebugger.css({
812 | 'margin-top': -(firstRow.offset().top + parseInt(firstRow.css('padding-top') || 0, 10)),
813 | 'height': $(document).height()
814 | });
815 | };
816 |
817 | _doc.on('keydown', function(event) {
818 | var activeTag = document.activeElement.tagName;
819 | if (activeTag === 'INPUT' || activeTag === 'TEXTAREA') return;
820 |
821 | var keycode = event.which;
822 | var func = {
823 | 71: toggleGrid, // G
824 | 79: toggleOutline, // O
825 | 65: toggleAllOutline, // A
826 | 191: toggleHelp // ?
827 | }[keycode];
828 | if (!func) return;
829 |
830 | func();
831 | event.preventDefault();
832 | });
833 |
834 | }
835 |
836 | }(window, window.jQuery, window.Modernizr));
837 |
--------------------------------------------------------------------------------
/tamia/vendor/transition-events.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Andrey “A.I.” Sitnik
,
3 | * sponsored by Evil Martians.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Lesser General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | ;(function($) {
20 | "use strict";
21 |
22 | // Common methods and properties for jQuery Transition Events plugin.
23 | // Mostly for internal usage, but maybe helpful for some hack stuff:
24 | //
25 | // if ( $.Transitions.isSupported() ) {
26 | // // CSS Transitions is supported
27 | // }
28 | $.Transitions = {
29 |
30 | // Hash of property name to event name with vendor prefixes.
31 | // It is used to detect prefix.
32 | _names: {
33 | // Webkit must be on bottom, because Opera try to use webkit
34 | // prefix.
35 | 'transition': 'transitionend',
36 | 'OTransition': 'oTransitionEnd',
37 | 'WebkitTransition': 'webkitTransitionEnd',
38 | 'MozTransition': 'transitionend'
39 | },
40 |
41 | // Return array of milliseconds for CSS value of `transition-duration`.
42 | // It’s used in `$.fn.afterTransition`.
43 | _parseTimes: function (string) {
44 | var value, array = string.split(/,\s*/);
45 | for (var i = 0; i < array.length; i++) {
46 | value = array[i];
47 | array[i] = parseFloat(value);
48 | if ( value.match(/\ds/) ) {
49 | array[i] = array[i] * 1000;
50 | }
51 | }
52 | return array;
53 | },
54 |
55 | // Autodetect vendor prefix and return `transitionend` event name.
56 | //
57 | // If browser didn’t support CSS Transitions it will return `false`.
58 | getEvent: function () {
59 | var finded = false;
60 | for ( var prop in this._names ) {
61 | if ( typeof(document.body.style[prop]) != 'undefined' ) {
62 | finded = this._names[prop];
63 | break;
64 | }
65 | }
66 |
67 | this.getEvent = function () {
68 | return finded;
69 | };
70 |
71 | return finded;
72 | },
73 |
74 | // Alias to vendor prefixed `requestAnimationFrame`. Will be replace
75 | // by native function after first call.
76 | animFrame: function (callback) {
77 | var raf = window.requestAnimationFrame ||
78 | window.webkitRequestAnimationFrame ||
79 | window.mozRequestAnimationFrame ||
80 | window.msRequestAnimationFrame;
81 | if ( raf ) {
82 | this.animFrame = function (callback) {
83 | return raf.call(window, callback);
84 | };
85 | } else {
86 | this.animFrame = function (callback) {
87 | return setTimeout(callback, 10);
88 | };
89 | }
90 | return this.animFrame(callback);
91 | },
92 |
93 | // Return `true` if browser support CSS Transitions.
94 | isSupported: function () {
95 | return this.getEvent() !== false;
96 | }
97 |
98 | }
99 |
100 | // jQuery node methods.
101 | $.extend($.fn, {
102 |
103 | // Call `callback` after CSS Transition finish
104 | // `delay + (durationPart * duration)`. It will call `callback` only
105 | // once, in difference from `transitionEnd`.
106 | //
107 | // $('.show-video').click(function () {
108 | // $('.slider').addClass('video-position').afterTransition(
109 | // function () { autoPlayVideo(); });
110 | // });
111 | //
112 | // You can set `durationPart` to call `callback` in the middle of
113 | // transition:
114 | //
115 | // $('.fliper').addClass('rotate').afterTransition(0.5, function () {
116 | // $(this).find('.backface').show();
117 | // });
118 | //
119 | // Callback will get object with `propertyName` and `elapsedTime`
120 | // properties. If transition is set to difference properties, it will
121 | // be called on every property.
122 | //
123 | // This method doesn’t check, that transition is really finished (it can
124 | // be canceled in the middle).
125 | afterTransition: function (durationPart, callback) {
126 | if ( typeof(callback) == 'undefined' ) {
127 | callback = durationPart;
128 | durationPart = 1;
129 | }
130 |
131 | if ( !$.Transitions.isSupported() ) {
132 | for (var i = 0; i < this.length; i++) {
133 | callback.call(this[i], {
134 | type: 'aftertransition',
135 | elapsedTime: 0,
136 | propertyName: '',
137 | currentTarget: this[i]
138 | });
139 | }
140 | return this;
141 | }
142 |
143 | for (var i = 0; i < this.length; i++) {
144 | var el = $(this[i]);
145 | var props = el.css('transition-property').split(/,\s*/);
146 | var durations = el.css('transition-duration');
147 | var delays = el.css('transition-delay');
148 |
149 | durations = $.Transitions._parseTimes(durations);
150 | delays = $.Transitions._parseTimes(delays);
151 |
152 | var prop, duration, delay, after, elapsed;
153 | for (var j = 0; j < props.length; j++) {
154 | prop = props[j];
155 | duration = durations[ durations.length == 1 ? 0 : j ];
156 | delay = delays[ delays.length == 1 ? 0 : j ];
157 | after = delay + (duration * durationPart);
158 | elapsed = duration * durationPart / 1000;
159 |
160 | (function (el, prop, after, elapsed) {
161 | setTimeout(function () {
162 | $.Transitions.animFrame(function () {
163 | callback.call(el[0], {
164 | type: 'aftertransition',
165 | elapsedTime: elapsed,
166 | propertyName: prop,
167 | currentTarget: el[0]
168 | });
169 | });
170 | }, after);
171 | })(el, prop, after, elapsed);
172 | }
173 | }
174 | return this;
175 | },
176 |
177 | // Set `callback` to listen every CSS Transition finish.
178 | // It will call `callback` on every finished transition,
179 | // in difference from `afterTransition`.
180 | //
181 | // It just bind to `transitionend` event, but detect vendor prefix.
182 | //
183 | // Callback will get event object with `propertyName` and `elapsedTime`
184 | // properties. If transition is set to difference properties, it will
185 | // be called on every property.
186 | //
187 | // Note, that `callback` will get original event object, not from
188 | // jQuery.
189 | //
190 | // var slider = $('.slider').transitionEnd(function () {
191 | // if ( slider.hasClass('video-position') ) {
192 | // autoPlayVideo();
193 | // }
194 | // });
195 | //
196 | // $('.show-video').click(function () {
197 | // slider.addClass('video-position');
198 | // });
199 | //
200 | // If transition will be canceled before finish, event won’t be fired.
201 | transitionEnd: function (callback) {
202 | for (var i = 0; i < this.length; i++) {
203 | this[i].addEventListener($.Transitions.getEvent(), function (e) {
204 | callback.call(this, e);
205 | });
206 | }
207 | return this;
208 | }
209 |
210 | });
211 |
212 | }).call(this, jQuery);
213 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var webpack = require('webpack');
5 | var nodePath = path.join(__dirname, 'node_modules');
6 |
7 | var isDevelopment = process.env.NODE_ENV !== 'production';
8 |
9 | var plugins = [
10 | new webpack.ProvidePlugin({
11 | React: 'react'
12 | }),
13 | new webpack.DefinePlugin({
14 | DEBUG: isDevelopment
15 | })
16 | ];
17 |
18 | if (!isDevelopment) {
19 | plugins.push(new webpack.optimize.UglifyJsPlugin({
20 | compress: {
21 | warnings: false
22 | },
23 | output: {
24 | comments: false
25 | }
26 | }));
27 | plugins.push(new webpack.optimize.DedupePlugin());
28 | }
29 |
30 | module.exports = {
31 | debug: isDevelopment,
32 | cache: isDevelopment,
33 | devtool: isDevelopment ? 'eval' : false,
34 | watch: false,
35 | entry: './app/app.js',
36 | stats: {
37 | colors: true,
38 | reasons: isDevelopment
39 | },
40 | resolveLoader: {
41 | root: [nodePath]
42 | },
43 | plugins: plugins,
44 | output: {
45 | path: path.join(__dirname, 'build/'),
46 | filename: 'bundle.js'
47 | },
48 | module: {
49 | noParse: [
50 | /\.min\.js/
51 | ],
52 | preLoaders: [
53 | {
54 | test: /\.js$/,
55 | exclude: [nodePath],
56 | loader: 'eslint-loader'
57 | }
58 | ],
59 | loaders: [
60 | {
61 | test: /\.js$/,
62 | exclude: [nodePath],
63 | loader: 'babel-loader'
64 | }
65 | ]
66 | }
67 | };
68 |
--------------------------------------------------------------------------------