hootstrap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +54 -0
  4. data/Rakefile +33 -0
  5. data/assets/javascripts/hootstrap/modal.js +795 -0
  6. data/assets/javascripts/hootstrap/rails.js +28 -0
  7. data/assets/javascripts/hootstrap/toast.js +166 -0
  8. data/assets/javascripts/hootstrap/utils/dynamicListener.js +80 -0
  9. data/assets/javascripts/hootstrap.js +3 -0
  10. data/assets/stylesheets/hootstrap/base/_base.scss +5 -0
  11. data/assets/stylesheets/hootstrap/base/_variables.scss +868 -0
  12. data/assets/stylesheets/hootstrap/base/mixins/_background.scss +8 -0
  13. data/assets/stylesheets/hootstrap/base/mixins/_button.scss +42 -0
  14. data/assets/stylesheets/hootstrap/base/mixins/_toast.scss +16 -0
  15. data/assets/stylesheets/hootstrap/base/mixins/_tooltip.scss +29 -0
  16. data/assets/stylesheets/hootstrap/components/_alert.scss +7 -0
  17. data/assets/stylesheets/hootstrap/components/_badge.scss +3 -0
  18. data/assets/stylesheets/hootstrap/components/_base.scss +6 -0
  19. data/assets/stylesheets/hootstrap/components/_button.scss +9 -0
  20. data/assets/stylesheets/hootstrap/components/_forms.scss +64 -0
  21. data/assets/stylesheets/hootstrap/components/_loader.scss +27 -0
  22. data/assets/stylesheets/hootstrap/components/_tooltip.scss +274 -0
  23. data/assets/stylesheets/hootstrap/patterns/_base.scss +5 -0
  24. data/assets/stylesheets/hootstrap/patterns/_card.scss +8 -0
  25. data/assets/stylesheets/hootstrap/patterns/_list-group.scss +8 -0
  26. data/assets/stylesheets/hootstrap/patterns/_navbar.scss +3 -0
  27. data/assets/stylesheets/hootstrap/patterns/_search.scss +45 -0
  28. data/assets/stylesheets/hootstrap/patterns/_toast.scss +34 -0
  29. data/assets/stylesheets/hootstrap/reset/_base.scss +1 -0
  30. data/assets/stylesheets/hootstrap/reset/_html.scss +4 -0
  31. data/assets/stylesheets/hootstrap/utils/_background.scss +3 -0
  32. data/assets/stylesheets/hootstrap/utils/_base.scss +5 -0
  33. data/assets/stylesheets/hootstrap/utils/_flex.scss +3 -0
  34. data/assets/stylesheets/hootstrap/utils/_opacity.scss +3 -0
  35. data/assets/stylesheets/hootstrap/utils/_sizing.scss +16 -0
  36. data/assets/stylesheets/hootstrap/utils/_transition.scss +0 -0
  37. data/assets/stylesheets/hootstrap/utils/_typography.scss +49 -0
  38. data/assets/stylesheets/hootstrap.scss +9 -0
  39. data/lib/hootstrap/engine.rb +9 -0
  40. data/lib/hootstrap/railtie.rb +10 -0
  41. data/lib/hootstrap/version.rb +3 -0
  42. data/lib/hootstrap.rb +20 -0
  43. metadata +141 -0
@@ -0,0 +1,28 @@
1
+ //= require ./utils/dynamicListener
2
+
3
+ document.addEventListener('turbolinks:load', () => {
4
+ let confirmed = false;
5
+
6
+ addDynamicEventListener(document.body, 'click', '[data-prompt]', handleClick);
7
+
8
+ function handleClick(event) {
9
+ if (confirmed) {
10
+ confirmed = false;
11
+ return;
12
+ }
13
+
14
+ Rails.stopEverything(event);
15
+
16
+ const target = event.target;
17
+
18
+ new Toast({
19
+ message: target.getAttribute('data-prompt'),
20
+ type: 'danger',
21
+ action: 'OK',
22
+ onClick: () => {
23
+ confirmed = true;
24
+ target.click({ something: 'hello' });
25
+ }
26
+ });
27
+ }
28
+ });
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Toast
3
+ *
4
+ * API
5
+ *
6
+ * @param {string} message - The text to be displayed inside the Toast.
7
+ * @param {string} type - The context: success, danger, primary, or warning.
8
+ * @param {string} [action] - The text inside the actionable button.
9
+ * @param {function} [onClick] - The callback function after actionable button is clicked.
10
+ *
11
+ */
12
+
13
+ function Toast(options) {
14
+ if (!options.message) {
15
+ throw new Error('You need a message to use a Toast');
16
+ return;
17
+ }
18
+
19
+ if (!options.type) {
20
+ throw new Error('You need a type to use Toast');
21
+ return;
22
+ }
23
+
24
+ const types = ['success', 'danger', 'primary', 'warning'];
25
+ if (!types.includes(options.type)) {
26
+ throw new Error(
27
+ 'You need a valid type to use Toast: `success`, `danger`, `primary`, `warning`'
28
+ );
29
+ return;
30
+ }
31
+
32
+ if (options.action && !options.onClick) {
33
+ throw new Error('You need an onClick function to use `action`');
34
+ return;
35
+ }
36
+
37
+ this.options = options;
38
+ this.containerEl = document.querySelector('.toast-container');
39
+ this.el = document.querySelector('.toast');
40
+ this.handleKeyup = this.handleKeyup.bind(this);
41
+
42
+ this.init();
43
+ }
44
+
45
+ Toast.prototype._createElements = function() {
46
+ return new Promise((resolve, reject) => {
47
+ this.containerEl = document.createElement('div');
48
+ this.containerEl.classList.add('toast-container');
49
+ this.containerEl.setAttribute('role', 'alert');
50
+ this.containerEl.setAttribute('aria-hidden', true);
51
+
52
+ this.el = document.createElement('div');
53
+ this.el.classList.add('toast');
54
+
55
+ this.containerEl.appendChild(this.el);
56
+ document.body.appendChild(this.containerEl);
57
+
58
+ resolve();
59
+ });
60
+ };
61
+
62
+ Toast.prototype.bindEventListeners = function() {
63
+ const dismissEl = document.querySelector('[data-dismiss="toast"]');
64
+
65
+ dismissEl.addEventListener('click', () => {
66
+ this.close();
67
+ });
68
+
69
+ if (this.options.action) {
70
+ const actionButton = document.querySelector(
71
+ '[data-behavior="toast-action"]'
72
+ );
73
+
74
+ actionButton.addEventListener('click', event => {
75
+ this.options.onClick(event);
76
+ this.close();
77
+ });
78
+ }
79
+
80
+ document.addEventListener('keyup', this.handleKeyup);
81
+ };
82
+
83
+ Toast.prototype.handleKeyup = function(event) {
84
+ if (event.keyCode == 27) {
85
+ this.close();
86
+ }
87
+ };
88
+
89
+ Toast.prototype.close = function() {
90
+ return new Promise((resolve, reject) => {
91
+ this.containerEl.setAttribute('aria-hidden', true);
92
+
93
+ this.el.innerHTML = '';
94
+ this.el.classList.remove(
95
+ 'toast-success',
96
+ 'toast-danger',
97
+ 'toast-primary',
98
+ 'toast-warning'
99
+ );
100
+
101
+ if (this.focusedElBeforeOpen) {
102
+ this.focusedElBeforeOpen.focus();
103
+ }
104
+
105
+ document.removeEventListener('keyup', this.handleKeyup);
106
+
107
+ resolve();
108
+ });
109
+ };
110
+
111
+ Toast.prototype._open = function() {
112
+ this.el.classList.add(`toast-${this.options.type}`);
113
+ this.containerEl.setAttribute('aria-hidden', false);
114
+
115
+ let actionButton = '';
116
+ if (this.options.action) {
117
+ actionButton = `
118
+ <button type="button"
119
+ class="btn btn-${this.options.type} btn-sm"
120
+ data-behavior="toast-action">
121
+ ${this.options.action}
122
+ </button>
123
+ `;
124
+ }
125
+
126
+ const textClass = this.options.action ? '' : 'mb-0';
127
+
128
+ this.el.innerHTML = `
129
+ <button type="button" class="close" data-dismiss="toast" aria-label="Close">
130
+ <span aria-hidden="true">&times;</span>
131
+ </button>
132
+
133
+ <p class="${textClass}">${this.options.message}</p>
134
+
135
+ ${actionButton}
136
+ `;
137
+
138
+ this.focusedElBeforeOpen = document.activeElement;
139
+
140
+ if (this.options.action) {
141
+ document.querySelector('[data-behavior="toast-action"]').focus();
142
+ } else {
143
+ document.querySelector('[data-dismiss="toast"]').focus();
144
+ }
145
+ };
146
+
147
+ Toast.prototype.init = function() {
148
+ Promise.resolve()
149
+ .then(() => {
150
+ if (this.containerEl) {
151
+ return Promise.resolve();
152
+ }
153
+
154
+ return this._createElements();
155
+ })
156
+ .then(() => {
157
+ if (this.containerEl.getAttribute('aria-hidden') == 'false') {
158
+ return this.close();
159
+ }
160
+ return Promise.resolve();
161
+ })
162
+ .then(() => {
163
+ this._open();
164
+ this.bindEventListeners();
165
+ });
166
+ };
@@ -0,0 +1,80 @@
1
+ // Credits
2
+ // https://github.com/Cristy94/dynamic-listener
3
+ //
4
+ // Usage
5
+ //
6
+ // addDynamicEventListener(document.body, 'click', '[data-prompt]', event => {
7
+ // console.log(event);
8
+ // });
9
+ (function(globalSope) {
10
+ 'use strict';
11
+
12
+ /**
13
+ * Including this file adds the `addDynamicListener` to the ELement prototype.
14
+ *
15
+ * The dynamic listener gets an extra `selector` parameter that only calls the callback
16
+ * if the target element matches the selector.
17
+ *
18
+ * The listener has to be added to the container/root element and the selector should match
19
+ * the elements that should trigger the event.
20
+ *
21
+ * Browser support: IE9+
22
+ */
23
+
24
+ // Polyfil Element.matches
25
+ // https://developer.mozilla.org/en/docs/Web/API/Element/matches#Polyfill
26
+ if (!Element.prototype.matches) {
27
+ Element.prototype.matches =
28
+ Element.prototype.matchesSelector ||
29
+ Element.prototype.mozMatchesSelector ||
30
+ Element.prototype.msMatchesSelector ||
31
+ Element.prototype.oMatchesSelector ||
32
+ Element.prototype.webkitMatchesSelector ||
33
+ function(s) {
34
+ var matches = (this.document || this.ownerDocument).querySelectorAll(s),
35
+ i = matches.length;
36
+ while (--i >= 0 && matches.item(i) !== this) {}
37
+ return i > -1;
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Returns a modified callback function that calls the
43
+ * initial callback function only if the target element matches the given selector
44
+ *
45
+ * @param {string} selector
46
+ * @param {function} callback
47
+ */
48
+ function getConditionalCallback(selector, callback) {
49
+ return function(e) {
50
+ if (!e.target) return;
51
+ if (!e.target.matches(selector)) return;
52
+ callback.apply(this, arguments);
53
+ };
54
+ }
55
+
56
+ /**
57
+ *
58
+ *
59
+ * @param {Element} rootElement The root element to add the linster too.
60
+ * @param {string} eventType The event type to listen for.
61
+ * @param {string} selector The selector that should match the dynamic elements.
62
+ * @param {function} callback The function to call when an event occurs on the given selector.
63
+ * @param {boolean|object} options Passed as the regular `options` parameter to the addEventListener function
64
+ * Set to `true` to use capture.
65
+ * Usually used as an object to add the listener as `passive`
66
+ */
67
+ globalSope.addDynamicEventListener = function(
68
+ rootElement,
69
+ eventType,
70
+ selector,
71
+ callback,
72
+ options
73
+ ) {
74
+ rootElement.addEventListener(
75
+ eventType,
76
+ getConditionalCallback(selector, callback),
77
+ options
78
+ );
79
+ };
80
+ })(this);
@@ -0,0 +1,3 @@
1
+ //= require hootstrap/modal
2
+ //= require hootstrap/rails
3
+ //= require hootstrap/toast
@@ -0,0 +1,5 @@
1
+ @import "hootstrap/base/variables";
2
+ @import "hootstrap/base/mixins/background";
3
+ @import "hootstrap/base/mixins/button";
4
+ @import "hootstrap/base/mixins/toast";
5
+ @import "hootstrap/base/mixins/tooltip";