uniform-ui 2.4.1 → 3.0.0.beta8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/assets/javascripts/uniform.js +13 -13
  3. data/lib/assets/javascripts/uniform/checkbox.js +59 -16
  4. data/lib/assets/javascripts/uniform/component.js +27 -4
  5. data/lib/assets/javascripts/uniform/dropdown.js +78 -209
  6. data/lib/assets/javascripts/uniform/floating-label-input.js +65 -0
  7. data/lib/assets/javascripts/uniform/icons.js +12 -3
  8. data/lib/assets/javascripts/uniform/modal.js +29 -30
  9. data/lib/assets/javascripts/uniform/popover.js +26 -24
  10. data/lib/assets/javascripts/uniform/resizer.js +26 -30
  11. data/lib/assets/javascripts/uniform/select.js +188 -222
  12. data/lib/assets/javascripts/uniform/tooltip.js +11 -11
  13. data/lib/assets/stylesheets/uniform.scss +3 -7
  14. data/lib/assets/stylesheets/uniform/base.scss +20 -1
  15. data/lib/assets/stylesheets/uniform/components/buttons.scss +171 -184
  16. data/lib/assets/stylesheets/uniform/components/checkbox.scss +104 -0
  17. data/lib/assets/stylesheets/uniform/components/container.scss +3 -2
  18. data/lib/assets/stylesheets/uniform/components/dropdown.scss +8 -5
  19. data/lib/assets/stylesheets/uniform/components/floating-label-input.scss +29 -0
  20. data/lib/assets/stylesheets/uniform/components/input-group.scss +39 -0
  21. data/lib/assets/stylesheets/uniform/components/label.scss +21 -16
  22. data/lib/assets/stylesheets/uniform/components/loaders.scss +28 -54
  23. data/lib/assets/stylesheets/uniform/components/modal.scss +21 -36
  24. data/lib/assets/stylesheets/uniform/components/nav.scss +50 -87
  25. data/lib/assets/stylesheets/uniform/components/pointer.scss +83 -0
  26. data/lib/assets/stylesheets/uniform/components/select.scss +97 -107
  27. data/lib/assets/stylesheets/uniform/components/table.scss +31 -138
  28. data/lib/assets/stylesheets/uniform/components/thumb.scss +40 -25
  29. data/lib/assets/stylesheets/uniform/components/z-input.scss +26 -0
  30. data/lib/assets/stylesheets/uniform/defaults.scss +31 -10
  31. data/lib/assets/stylesheets/uniform/functions.scss +32 -7
  32. data/lib/assets/stylesheets/uniform/mixins.scss +110 -57
  33. data/lib/assets/stylesheets/uniform/utilities.scss +55 -0
  34. data/lib/assets/stylesheets/uniform/utilities/background.scss +9 -0
  35. data/lib/assets/stylesheets/uniform/utilities/borders.scss +84 -0
  36. data/lib/assets/stylesheets/uniform/utilities/effects.scss +172 -0
  37. data/lib/assets/stylesheets/uniform/utilities/layout.scss +181 -0
  38. data/lib/assets/stylesheets/uniform/utilities/position.scss +42 -0
  39. data/lib/assets/stylesheets/uniform/utilities/sizing.scss +60 -0
  40. data/lib/assets/stylesheets/uniform/utilities/spacing.scss +68 -0
  41. data/lib/assets/stylesheets/uniform/utilities/svg.scss +5 -0
  42. data/lib/assets/stylesheets/uniform/utilities/text.scss +158 -0
  43. data/lib/assets/stylesheets/uniform/variables.scss +113 -42
  44. data/lib/uniform/version.rb +1 -1
  45. metadata +22 -45
  46. data/lib/assets/javascripts/uniform.jquery.js +0 -152
  47. data/lib/assets/javascripts/uniform/dom-helpers.js +0 -158
  48. data/lib/assets/javascripts/uniform/floating-label.js +0 -54
  49. data/lib/assets/stylesheets/uniform-print.scss +0 -1
  50. data/lib/assets/stylesheets/uniform/components.scss +0 -11
  51. data/lib/assets/stylesheets/uniform/components/alert.scss +0 -72
  52. data/lib/assets/stylesheets/uniform/components/card.scss +0 -93
  53. data/lib/assets/stylesheets/uniform/components/form.scss +0 -149
  54. data/lib/assets/stylesheets/uniform/components/form/checkbox-collection.scss +0 -103
  55. data/lib/assets/stylesheets/uniform/components/form/checkbox.scss +0 -58
  56. data/lib/assets/stylesheets/uniform/components/form/floating-label.scss +0 -65
  57. data/lib/assets/stylesheets/uniform/components/form/input-group.scss +0 -56
  58. data/lib/assets/stylesheets/uniform/components/form/tristate.scss +0 -88
  59. data/lib/assets/stylesheets/uniform/components/grid.scss +0 -179
  60. data/lib/assets/stylesheets/uniform/components/row.scss +0 -67
  61. data/lib/assets/stylesheets/uniform/components/tooltip.scss +0 -41
  62. data/lib/assets/stylesheets/uniform/helpers.scss +0 -133
  63. data/lib/assets/stylesheets/uniform/helpers/border.scss +0 -28
  64. data/lib/assets/stylesheets/uniform/helpers/colors.scss +0 -24
  65. data/lib/assets/stylesheets/uniform/helpers/margin.scss +0 -27
  66. data/lib/assets/stylesheets/uniform/helpers/padding.scss +0 -9
  67. data/lib/assets/stylesheets/uniform/helpers/position.scss +0 -20
  68. data/lib/assets/stylesheets/uniform/helpers/sizes.scss +0 -38
  69. data/lib/assets/stylesheets/uniform/helpers/text.scss +0 -152
  70. data/lib/assets/stylesheets/uniform/print/grid.scss +0 -50
@@ -0,0 +1,65 @@
1
+ import Component from './component';
2
+ import { isVisible, isFocus, css, createElement } from 'dolla';
3
+
4
+ export default class FloatingLabel extends Component {
5
+
6
+ initialize(options){
7
+ if(options.input instanceof Element) {
8
+ this.input = options.input
9
+ } else {
10
+ this.input = createElement('input', Object.assign({}, {
11
+ type: this.constructor.type
12
+ }, options.input)) // TODO filter options to dolla.HTML_ATTRIBUTES
13
+ }
14
+ this.label = createElement('label', {
15
+ for: this.input.id,
16
+ children: [options.label]
17
+ });
18
+ this.input.setAttribute('aria-label', options.label);
19
+
20
+ this.el.classList.add('uniformFloatingLabelInput');
21
+
22
+ this.listenTo(this.input, 'focus', this.focus);
23
+ this.listenTo(this.input, 'blur', this.blur);
24
+ this.listenTo(this.input, 'revealed', this.render);
25
+ }
26
+
27
+ render () {
28
+ if(!isVisible(this.input)) return this;
29
+
30
+ let internalHeight = parseInt(css(this.input, 'height')) - parseInt(css(this.input, 'borderTopWidth')) - parseInt(css(this.input, 'borderBottomWidth'));
31
+ this.input.style.lineHeight = 1;
32
+ let lineHeight = parseInt(css(this.input, 'lineHeight'));
33
+ let fontSize = parseInt(css(this.input, 'fontSize'));
34
+ let padding = internalHeight - lineHeight;
35
+
36
+ this.label.style.setProperty('--font-size', css(this.input, 'fontSize'))
37
+ this.label.style.paddingLeft = css(this.input, 'paddingLeft');
38
+ this.label.style.lineHeight = lineHeight + "px";
39
+ this.label.style.paddingTop = (internalHeight/2 - lineHeight) + "px";
40
+ this.label.style.paddingBottom = (internalHeight/2 - lineHeight) + "px";
41
+
42
+ this.input.style.paddingTop = internalHeight/2 - (lineHeight - fontSize) + "px";
43
+ this.input.style.paddingBottom = (internalHeight/2 - lineHeight) + (lineHeight - fontSize) + "px";
44
+
45
+ this.input.parentNode.insertBefore(this.el, this.input.nextSibling);
46
+ this.el.append(this.input);
47
+ this.el.append(this.label);
48
+
49
+ if(this.input.value != ""){
50
+ this.focus()
51
+ }
52
+
53
+ return this;
54
+ }
55
+
56
+ focus (e) {
57
+ this.el.classList.add('present');
58
+ }
59
+
60
+ blur (e) {
61
+ if(this.input.value == ""){
62
+ this.el.classList.remove('present');
63
+ }
64
+ }
65
+ }
@@ -1,16 +1,25 @@
1
1
  let check = `
2
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1em" height="1em" viewBox="0 0 32 32">
2
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">
3
3
  <path d="M28.998 8.531l-2.134-2.134c-0.394-0.393-1.030-0.393-1.423 0l-12.795 12.795-6.086-6.13c-0.393-0.393-1.029-0.393-1.423 0l-2.134 2.134c-0.393 0.394-0.393 1.030 0 1.423l8.924 8.984c0.393 0.393 1.030 0.393 1.423 0l15.648-15.649c0.393-0.392 0.393-1.030 0-1.423z"></path>
4
4
  </svg>
5
5
  `.trim()
6
6
 
7
7
  let arrow_down = `
8
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1em" height="1em" viewBox="0 0 20 20">
8
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 20">
9
9
  <path d="M13.418 7.601c0.271-0.268 0.709-0.268 0.979 0s0.271 0.701 0 0.969l-3.907 3.83c-0.271 0.268-0.709 0.268-0.979 0l-3.908-3.83c-0.27-0.268-0.27-0.701 0-0.969s0.708-0.268 0.979 0l3.418 3.14 3.418-3.14z"></path>
10
10
  </svg>
11
11
  `.trim()
12
12
 
13
+ let x = `
14
+ <svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" xml:space="preserve">
15
+ <g><rect x="-2.352" y="29.385" transform="matrix(0.7071 0.7071 -0.7071 0.7071 32.3545 -14.3899)" width="71.799" height="4.95"/></g>
16
+ <g><rect x="-2.374" y="29.376" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -12.7023 33.0352)" width="71.799" height="4.95"/></g>
17
+ </svg>
18
+
19
+ `.trim()
20
+
13
21
  export {
14
22
  check,
15
- arrow_down
23
+ arrow_down,
24
+ x
16
25
  }
@@ -1,5 +1,5 @@
1
1
  import Component from './component';
2
- import * as Helpers from './dom-helpers';
2
+ import {css, trigger, append, createElement} from 'dolla';
3
3
 
4
4
  /* UniformModal.initialize
5
5
  Options
@@ -14,8 +14,9 @@ export default class Modal extends Component {
14
14
  this.options = {};
15
15
  this.options.klass = options.klass || false;
16
16
  this.content = options.content;
17
+ this.el.removeAttribute('content');
17
18
 
18
- Helpers.addClass(this.el, 'uniformModal');
19
+ this.el.classList.add('uniformModal');
19
20
  this.listenTo(document, 'keyup', this.keyup);
20
21
  this.listenTo(this.el, 'click', this.checkCloseButton);
21
22
  }
@@ -27,27 +28,21 @@ export default class Modal extends Component {
27
28
 
28
29
  render () {
29
30
  var that = this;
30
- var content = typeof this.content == 'function' ? this.content() : this.content;
31
31
 
32
32
  this.highest_z_index = 0;
33
- this.overlay = document.createElement('div');
34
- Helpers.addClass(this.overlay, 'uniformModal-overlay');
35
-
36
- this.blur = document.createElement('div');
37
- Helpers.addClass(this.blur, 'uniformModal-blur');
33
+ this.overlay = createElement('div', {class: 'uniformModal-overlay'});
34
+ this.blur = createElement('div', {class: 'uniformModal-blur'});
38
35
 
39
36
  this.original_scroll = window.scrollY;
40
37
  this.blur.style.top = 0 - this.original_scroll + "px";
41
38
 
42
39
  if (document.body.querySelectorAll('.uniformModal').length > 0) {
43
40
  this.highest_z_index = Math.max(Array.prototype.map.call(document.body.querySelectorAll('.uniformModal'), function(el){
44
- return parseInt(Helpers.css(el, 'zIndex'));
41
+ return parseInt(css(el, 'zIndex'));
45
42
  }));
46
43
  this.el.style.zIndex = this.highest_z_index + 2;
47
44
  }
48
45
 
49
- this.el.appendChild(this.overlay);
50
-
51
46
  let next_element = document.body.children[0]
52
47
  while(next_element){
53
48
  const element = next_element;
@@ -57,40 +52,44 @@ export default class Modal extends Component {
57
52
  }
58
53
  }
59
54
 
60
- Helpers.addClass(document.body, 'uniformModal-active');
55
+ document.body.classList.add('uniformModal-active');
61
56
  document.body.appendChild(this.blur);
62
57
  document.body.appendChild(this.el);
63
58
 
64
- var container = document.createElement('div');
65
- Helpers.addClass(container, 'uniformModal-container');
66
- if (content instanceof Node) {
67
- container.appendChild(content);
68
- } else {
69
- container.innerHTML = content;
70
- }
71
-
72
- var closeButton = document.createElement('div');
73
- Helpers.addClass(closeButton, 'uniformModal-close');
74
- this.el.appendChild(closeButton);
75
-
76
59
  this.el.style.top = window.scrollY;
77
60
  this.listenTo(this.overlay, 'click', this.close);
78
- this.el.appendChild(container);
61
+
62
+ const container = createElement('div', {
63
+ class: 'uniformModal-container',
64
+ children: this.content
65
+ });
66
+
67
+ const closeButton = createElement('div', {
68
+ class: 'uniformModal-close-container',
69
+ children: {
70
+ class: 'uniformModal-close'
71
+ }
72
+ });
73
+
74
+ this.el.append(this.overlay);
75
+ this.el.append(container);
76
+ this.el.append(closeButton);
79
77
 
80
- if (this.options.klass) Helpers.addClass(container, this.options.klass);
81
- if (content.innerHTML) Helpers.trigger(content, 'rendered');
78
+ if (this.options.klass) container.classList.add(this.options.klass);
79
+ if (this.content.innerHTML) trigger(this.content, 'rendered');
82
80
  this.trigger('rendered');
83
81
 
84
82
  return this;
85
83
  }
86
84
 
87
85
  checkCloseButton (e) {
88
- if(Helpers.hasClass(e.target, 'uniformModal-close'))
89
- this.close();
86
+ if(e.target.classList.contains('uniformModal-close')){
87
+ this.close();
88
+ }
90
89
  }
91
90
 
92
91
  close () {
93
- Helpers.removeClass(document.querySelectorAll('uniformModal-active'), 'uniformModal-active');
92
+ document.querySelectorAll('uniformModal-active').forEach(el => el.classList.remove('uniformModal-active'));
94
93
  var elements = this.blur.children;
95
94
  var elementCount = elements.length
96
95
  for(var i=0; i < elementCount; i++){
@@ -1,5 +1,5 @@
1
1
  import Component from './component';
2
- import * as Helpers from './dom-helpers';
2
+ import { offset, outerWidth, outerHeight, append } from 'dolla';
3
3
 
4
4
  /*
5
5
  Requirements
@@ -12,6 +12,7 @@ import * as Helpers from './dom-helpers';
12
12
  align: [left|right|center|#px] [top|center|bottom|#px] | default: 'center bottom'
13
13
  zIndex: # | default: unset
14
14
  offset: {left: 0, top: 0}
15
+ container: element to append popover to. default: document
15
16
  */
16
17
  export default class Popover extends Component {
17
18
  initialize (options) {
@@ -26,7 +27,9 @@ export default class Popover extends Component {
26
27
  offset: {left: 0, top: 0}
27
28
  };
28
29
  Object.assign(this.options, this.pick(options, Object.keys(this.options)));
30
+ this.el.removeAttribute('content');
29
31
 
32
+ this.options.anchor.popover = this
30
33
  this.listenTo(document, 'click', this.checkFocus);
31
34
  this.listenTo(document, 'focusin', this.checkFocus);
32
35
  this.listenTo(document, 'keyup', this.checkEscape);
@@ -35,9 +38,10 @@ export default class Popover extends Component {
35
38
  });
36
39
 
37
40
  if(typeof this.options.container == "string"){
38
- this.options.container = Helpers.closest(this.options.anchor, this.options.container)
39
- this.options.container = this.options.container || document.body
41
+ this.options.container = this.options.anchor.closest(this.options.container)
40
42
  }
43
+ this.options.container = this.options.container || document.body
44
+
41
45
  }
42
46
 
43
47
  render () {
@@ -45,10 +49,7 @@ export default class Popover extends Component {
45
49
  this.el.style.position = 'absolute';
46
50
  this.el.style.zIndex = this.options.zIndex;
47
51
 
48
- if(this.options.content instanceof Node)
49
- this.el.appendChild(this.options.content);
50
- else
51
- this.el.innerHTML = this.options.content;
52
+ append(this.el, this.options.content)
52
53
 
53
54
  this.options.container.appendChild(this.el);
54
55
  this.resize();
@@ -101,43 +102,44 @@ export default class Popover extends Component {
101
102
  var [leftAlign, topAlign] = align.split(" ");
102
103
  leftAlign = leftAlign || "bottom";
103
104
 
104
- var offset = Helpers.offset(this.options.anchor);
105
+ var anchorOffset = offset(this.options.anchor);
105
106
  var container = this.options.container;
106
107
  if(getComputedStyle(container)['position'] == "static") container = container.offsetParent;
107
108
  if(!container) container = document.body;
108
109
 
109
- var containerOffset = Helpers.offset(container);
110
- offset = {
111
- top: offset.top - containerOffset.top,
112
- left: offset.left - containerOffset.left
110
+ var containerOffset = offset(container);
111
+ anchorOffset = {
112
+ top: anchorOffset.top - containerOffset.top,
113
+ left: anchorOffset.left - containerOffset.left
113
114
  }
114
115
 
115
116
  var position = {};
116
117
  if(leftAlign == 'left'){
117
- position.right = Helpers.outerWidth(container) - offset.left;
118
+ position.right = outerWidth(container) - anchorOffset.left;
118
119
  } else if(leftAlign == 'center'){
119
- position.left = offset.left + Helpers.outerWidth(this.options.anchor) / 2 - Helpers.outerWidth(this.el) / 2;
120
+ position.left = anchorOffset.left + outerWidth(this.options.anchor) / 2;
121
+ position.transform = "translateX(-50%)";
120
122
  } else if (leftAlign == 'right'){
121
- position.left = offset.left + Helpers.outerWidth(this.options.anchor);
123
+ position.left = anchorOffset.left + outerWidth(this.options.anchor);
122
124
  } else if (leftAlign.includes("px")){
123
- position.left = offset.left + parseInt(leftAlign);
125
+ position.left = anchorOffset.left + parseInt(leftAlign);
124
126
  }
125
127
 
126
128
  if(topAlign == 'top'){
127
- let height = Helpers.outerHeight(container);
129
+ let height = outerHeight(container);
128
130
  if(container == document.body && getComputedStyle(container)['position'] == "static"){
129
131
  height = window.innerHeight;
130
132
  } else if (container == document.body) {
131
133
  height = Math.max(height, document.body.clientHeight);
132
134
  }
133
- position.bottom = height - offset.top;
135
+ position.bottom = height - anchorOffset.top;
134
136
  } else if(topAlign == 'center'){
135
- position.top = offset.top + Helpers.outerHeight(this.options.anchor) / 2;
137
+ position.top = anchorOffset.top + outerHeight(this.options.anchor) / 2;
136
138
  position.transform = "translateY(-50%)";
137
139
  } else if (topAlign == 'bottom'){
138
- position.top = offset.top + Helpers.outerHeight(this.options.anchor);
140
+ position.top = anchorOffset.top + outerHeight(this.options.anchor);
139
141
  } else if (topAlign.includes("px")){
140
- position.top = offset.top + parseInt(topAlign);
142
+ position.top = anchorOffset.top + parseInt(topAlign);
141
143
  }
142
144
 
143
145
  if(this.options.offset.left) position.left += parseInt(this.options.offset.left);
@@ -150,9 +152,9 @@ export default class Popover extends Component {
150
152
  this.el.style.top = null;
151
153
  this.el.style.bottom = null;
152
154
  this.el.style.transform = null;
153
- Helpers.removeClass(this.el, 'popover-left popover-right popover-center popover-top popover-bottom');
154
- Helpers.addClass(this.el, 'popover-' + topAlign);
155
- Helpers.addClass(this.el, 'popover-' + leftAlign);
155
+ this.el.classList.remove('popover-left', 'popover-right', 'popover-center', 'popover-top', 'popover-bottom');
156
+ this.el.classList.add('popover-' + topAlign);
157
+ this.el.classList.add('popover-' + leftAlign);
156
158
  Object.keys(position).forEach(function(key){
157
159
  this.el.style[key] = position[key] + (key != "transform" ? "px" : "");
158
160
  }, this);
@@ -1,43 +1,39 @@
1
1
  import Component from './component';
2
- import * as Helpers from './dom-helpers';
2
+ import { trigger } from 'dolla';
3
3
 
4
4
  export default class Resizer extends Component {
5
5
 
6
6
  initialize () {
7
+ const breakpoints = getComputedStyle(window.document.body).getPropertyValue('--breakpoints')
8
+ this.breakpoints = {}
9
+ breakpoints.split(",").forEach(breakpoint => {
10
+ const [key, value] = breakpoint.split("/")
11
+ this.breakpoints[key.trim()] = value;
12
+ })
13
+
7
14
  this.listenTo(window, 'resize', this.resize);
8
- Helpers.trigger(window, 'resize');
15
+ this.resize();
9
16
  }
10
17
 
11
18
  resize () {
12
- // breakpoints at 720, 1080, 1440
13
- var width = this.el.offsetWidth;
14
-
15
- if(width > 720 && !Helpers.hasClass(this.el, 'md-size')) {
16
- Helpers.addClass(this.el, 'md-size');
17
- Helpers.trigger(window, 'resized-md');
18
- } else if (width < 720 && Helpers.hasClass(this.el, 'md-size')) {
19
- Helpers.removeClass(this.el, 'md-size');
20
- }
21
-
22
- if(width > 1080 && !Helpers.hasClass(this.el, 'lg-size')) {
23
- Helpers.addClass(this.el, 'lg-size');
24
- Helpers.trigger(window, 'resized-lg');
25
- } else if (width < 1080 && Helpers.hasClass(this.el, 'lg-size')) {
26
- Helpers.removeClass(this.el, 'lg-size');
19
+ const width = this.el.offsetWidth;
20
+ Object.keys(this.breakpoints).forEach(size => {
21
+ const query = this.breakpoints[size]
22
+ const css_class = size + '-container'
23
+ let [attribute, value] = query.split(":")
24
+ if(value.match("px")){
25
+ value = parseInt(value)
26
+ } else {
27
+ throw "unsupported media units"
27
28
  }
28
-
29
- if(width > 1440 && !Helpers.hasClass(this.el, 'xl-size')) {
30
- Helpers.addClass(this.el, 'xl-size');
31
- Helpers.trigger(window, 'resized-xl');
32
- } else if (width < 1440 && Helpers.hasClass(this.el, 'xl-size')) {
33
- Helpers.removeClass(this.el, 'xl-size');
34
- }
35
-
36
- if(width < 720 && !Helpers.hasClass(this.el, 'sm-size')) {
37
- Helpers.addClass(this.el, 'sm-size');
38
- Helpers.trigger(window, 'resized-sm');
39
- } else if (width > 720 && Helpers.hasClass(this.el, 'sm-size')) {
40
- Helpers.removeClass(this.el, 'sm-size');
29
+
30
+ if(attribute == "min-width") {
31
+ this.el.classList.toggle(css_class, width > value)
32
+ } else if (attribute == "max-width") {
33
+ this.el.classList.toggle(css_class, width < value)
34
+ } else {
35
+ throw "unsupported media feature"
41
36
  }
37
+ });
42
38
  }
43
39
  }
@@ -1,252 +1,218 @@
1
1
  import Component from './component';
2
2
  import Popover from './popover';
3
3
  import Modal from './modal';
4
- import { check as checkIcon, arrow_down as arrowIcon } from './icons';
5
- import * as Helpers from './dom-helpers';
4
+ import { check as checkIcon, arrow_down as arrowIcon, x as xIcon } from './icons';
5
+ import { createElement, HTML_ATTRIBUTES, filter, css, isEmpty, trigger } from 'dolla';
6
6
 
7
7
  /*
8
- options
9
- class: String, appended to uniformSelect-edit button as class
10
- limit: int | false - number of options to limit to, or false to not limit
11
- showAll: function(button_options) to run if/when "Show All" is clicked
12
- label: string, used for mobile menu
13
- container: selector for where to render dropdown
8
+ options: array of html options, each item can be string | array | object
9
+ ex. ["Employee", "Manager", "General Manager"]
10
+ ex. [
11
+ ["Employee", "employee", false],
12
+ ["Manager", "manager", false],
13
+ ["General Manager", "general_manager", true],
14
+ ]
15
+ ex. [
16
+ {value: "employee", text: 'Employee', selected: false},
17
+ {value: "manager", text: 'Manager', selected: false},
18
+ {value: "general_manager", text: 'General Manager', selected: true}
19
+ ]
20
+ limit: int | false - number of options to limit to, or false to not limit
21
+ container: selector for where to render dropdown
22
+ multiple: false
14
23
  */
15
24
  export default class Select extends Component {
16
25
 
17
- initialize (options = {}) {
18
- this.options = {
19
- label: false,
20
- class: "",
21
- showAll: function (button_options){
22
- Helpers.removeClass(button_options.querySelectorAll('button.hide'), 'hide');
23
- var button = button_options.querySelector('.uniformSelect-show-all');
24
- button.parentNode.removeChild(button);
25
-
26
- return false;
27
- },
28
- limit: 8,
29
- container: false
26
+ initialize (options = {}) {
27
+ this.htmlOptions = options.options.map(option => {
28
+ if(typeof option == "string") {
29
+ return {
30
+ value: option,
31
+ text: option
30
32
  }
33
+ } else if (Array.isArray(option)){
34
+ return {
35
+ value: option[1],
36
+ text: option[0],
37
+ selected: option[2]
38
+ }
39
+ } else if (typeof option == "object") {
40
+ return option
41
+ } else {
42
+ throw "option of unexpected type"
43
+ }
44
+ });
45
+ this.options = {
46
+ multiple: false,
47
+ limit: 8,
48
+ container: false
49
+ }
50
+ Object.assign(this.options, this.pick(options, Object.keys(this.options)));
31
51
 
32
- Object.assign(this.options, this.pick(options, Object.keys(this.options)));
52
+ this.el_options = Object.assign({}, this.pick(options, HTML_ATTRIBUTES));
53
+ this.el = createElement('button', this.el_options);
54
+ this.el.classList.add('uniformSelect');
33
55
 
34
- this.listenTo(this.el, 'change', this.renderSelected);
35
- this.listenTo(this.el, 'close', this.hideOptions);
36
- this.el.uniformSelect = this;
56
+ this.listenTo(this.el, 'click', this.toggleOptions);
57
+ this.listenTo(this.el, 'click', '.uniformSelect-remove', this.removeSelection);
58
+ this.listenTo(this.el, 'change', 'select', this.renderValue);
59
+ this.listenTo(this.el, 'close', 'select', this.removeOptions);
60
+ }
61
+
62
+ render () {
63
+ this.valueEl = createElement('span');
64
+ this.valueEl.classList.add('uniformSelect-value')
65
+ this.el.append(this.valueEl);
37
66
 
38
- this.activeIcon = document.createElement('span');
39
- this.activeIcon.innerHTML = checkIcon;
40
- Helpers.addClass(this.activeIcon, 'uniformSelect-option-icon');
41
- }
67
+ this.indicatorEl = createElement('span', {children: arrowIcon})
68
+ this.indicatorEl.classList.add('uniformSelect-indicator')
69
+ this.el.append(this.indicatorEl);
42
70
 
43
- remove () {
44
- Component.prototype.remove.apply(this, arguments);
45
- this.edit_button.parentNode.removeChild(this.edit_button);
46
- delete this.this.edit_button;
71
+ this.select = createElement('select', this.el_options);
72
+ this.htmlOptions.forEach(option => {
73
+ this.select.append(createElement('option', Object.assign({}, {children: option.text}, option)))
74
+ });
75
+ this.el.append(this.select);
47
76
 
48
- this.activeIcon.parentNode.removeChild(this.activeIcon);
49
- delete this.activeIcon;
50
77
 
51
- if(this.button_options_popover) this.button_options_popover.remove();
52
- if(this.button_options_modal) this.button_options_modal.remove();
78
+ // Append placeholder of longest option, to set width
79
+ const longestText = this.htmlOptions.map(x => x.text).sort((a, b) => a.length < b.length)[0]
80
+ const placeholder = createElement('span', {class: 'uniformSelect-placeholder', children: longestText})
81
+ this.el.append(placeholder);
53
82
 
54
- if(this.button_options) {
55
- this.button_options.parentNode.removeChild(this.button_options);
56
- delete this.button_options;
57
- }
58
- }
83
+ this.renderValue();
84
+ return this;
85
+ }
59
86
 
60
- render () {
61
- this.edit_button = Helpers.createElement(`
62
- <button type='button' class='uniformSelect-edit uniformInput outline block ${this.options.class}'>
63
- <span class="text-js"></span>
64
- <span class="uniformSelect-edit-icon">${arrowIcon}</span>
65
- </button>
66
- `);
67
-
68
- if (this.el.name) {
69
- Helpers.addClass(this.edit_button, this.el.name.toLowerCase().replace(/[^a-z0-9\-_]+/g, '-'));
70
- }
71
- this.el.style.display = "none";
72
- this.el.insertAdjacentElement('beforebegin', this.edit_button);
73
-
74
- if(!this.options.container) {
75
- this.options.container = this.edit_button.parentElement
76
- }
77
-
78
- // Set Min-Width for when empty
87
+ renderValue () {
88
+ const selectedOptions = filter(this.select.querySelectorAll("option"), el => el.selected);
89
+ const html = selectedOptions.map(el => this.options.multiple ? `
90
+ <span class="uniformSelect-selection">
91
+ <span>${el.textContent}</span><span class="uniformSelect-remove">${xIcon}</span>
92
+ </span>
93
+ ` : el.textContent).join(" ");
79
94
 
80
- const option = [...this.el.querySelectorAll("option")].sort((a, b) => {
81
- return a.textContent.length > b.textContent.length
82
- }).reverse()[0];
83
- this.edit_button.querySelector('.text-js').style.opacity = 0;
84
- this.edit_button.querySelector('.text-js').innerHTML = option.textContent;
85
- const min_width = this.edit_button.querySelector('.text-js').offsetWidth;
86
- this.edit_button.style.minWidth = min_width + "px";
87
- this.edit_button.querySelector('.text-js').innerHTML = "";
88
- this.edit_button.querySelector('.text-js').style.opacity = null;
89
-
90
- this.renderSelected();
91
-
92
- this.listenTo(this.edit_button, 'click', this.showOptions);
93
- this.listenTo(this.edit_button, 'click', '.uniformSelect-remove', this.removeSelection);
94
-
95
- return this;
95
+ this.valueEl.innerHTML = html;
96
+ }
97
+
98
+ selectOption (e) {
99
+ const makeActive = !e.target.option.selected;
100
+ if (!this.options.multiple && makeActive) {
101
+ e.target.offsetParent.querySelectorAll('.active').forEach(el => el.classList.remove('active'));
96
102
  }
97
-
98
- renderOptions () {
99
- this.button_options = Helpers.createElement("<div class='uniformSelect-options'>");
100
- if (this.el.name) {
101
- Helpers.addClass(this.button_options, this.el.name.toLowerCase().replace(/[^a-z0-9\-_]+/g, '-'));
102
- }
103
- this.button_options.style.fontSize = Helpers.css(this.el, 'font-size');
103
+ e.target.classList.toggle('active', makeActive);
104
+ e.target.option.selected = makeActive;
104
105
 
105
- Helpers.each(this.el.querySelectorAll('option'), function(el, index){
106
- var button = Helpers.createElement("<button type='button' class='uniformSelect-option block outline text-left'>");
107
- button.option = el;
108
- button.textContent = el.textContent;
109
- button.value = el.value;
110
- if (button.textContent == "") button.innerHTML = "<span class='text-italic text-muted'>None</span>";
111
- if(el.selected){
112
- Helpers.addClass(button, 'active');
113
- button.append(this.activeIcon.cloneNode(true));
114
- } else if (this.options.limit && index > this.options.limit) {
115
- Helpers.addClass(button, 'hide');
116
- }
117
- this.button_options.append(button);
118
- }.bind(this));
119
-
120
- this.listenTo(this.button_options, 'click', '.uniformSelect-option', this.selectOption);
121
-
122
- const actions_el = Helpers.createElement('<div class="uniformSelect-options-actions"></div>');
123
- if (this.options.limit && this.el.querySelectorAll('option').length > this.options.limit) {
124
- const show_all_button = Helpers.createElement("<button type='button' class='uniformSelect-show-all outline blue' style='display: block; border: none'>Show All</button>");
125
- this.listenTo(show_all_button, 'click', function(e){
126
- Helpers.trigger(this.el, 'show_all');
127
- if (this.options.showAll) this.options.showAll(this.button_options);
128
- e.preventDefault();
129
- e.stopPropagation();
130
- })
131
- actions_el.appendChild(show_all_button);
132
- }
133
- if (this.el.multiple) {
134
- var done_button = Helpers.createElement("<button type='button' class='uniformSelect-done block outline blue'>Done</button>");
135
- this.listenTo(done_button, 'click', this.hideOptions);
136
- actions_el.appendChild(done_button);
137
- }
138
- if (!Helpers.is_empty(actions_el)) {
139
- this.button_options.appendChild(actions_el);
140
- }
106
+ if (!this.options.multiple) {
107
+ this.removeOptions();
141
108
  }
142
-
143
- renderSelected () {
144
- const selected_options = Helpers.filter(this.el.querySelectorAll("option"), function(el){
145
- return el.selected;
146
- });
147
- const html = Helpers.map(selected_options, function(el){
148
- return this.el.multiple ? `<span class="uniformSelect-selection">${el.textContent}<span class="uniformSelect-remove"></span></span>` : el.textContent;
149
- }.bind(this)).join(" ");
150
109
 
151
- if (html == "") {
152
- this.edit_button.querySelector('.text-js').innerHTML = "&nbsp;"
153
- } else {
154
- this.edit_button.querySelector('.text-js').innerHTML = html;
155
- }
110
+ trigger(this.select, 'change');
111
+ }
112
+
113
+ removeSelection (e) {
114
+ e.preventDefault();
115
+ e.stopPropagation();
116
+ var option = filter(this.select.querySelectorAll("option"), function(el){
117
+ return el.innerText.trim() == e.target.closest('.uniformSelect-selection').innerText.trim();
118
+ })[0];
119
+ if(!option) return;
120
+ option.selected = false;
121
+ option.button.classList.remove('active');
156
122
 
157
- if(this.button_options) {
158
- Helpers.each(this.button_options.querySelectorAll('.uniformSelect-option'), function(el) {
159
- if(el.option.selected){
160
- Helpers.addClass(el, 'active');
161
- el.append(this.activeIcon.cloneNode(true));
162
- } else {
163
- Helpers.removeClass(el, 'active');
164
- Helpers.each(el.querySelectorAll('.uniformSelect-option-icon'), Helpers.remove);
165
- }
166
- }.bind(this))
167
- }
123
+ trigger(this.select, 'change');
124
+ }
125
+
126
+ toggleOptions (e) {
127
+ if(e && (e.target.matches('.uniformSelect-remove') || e.target.closest('.uniformSelect-remove'))){
128
+ return;
168
129
  }
169
-
170
- hideOptions () {
171
- if(!this.button_options) return;
172
- if(this.button_options_modal) this.button_options_modal.close();
173
- if(this.button_options_popover) {
174
- this.button_options_popover.remove();
175
- delete this.button_options_popover;
176
- }
177
- Helpers.removeClass(this.edit_button, 'active');
130
+ this.el.classList.toggle('active')
131
+ if(this.el.contains('active')){
132
+ this.renderOptions()
133
+ } else {
134
+ this.removeOptions()
178
135
  }
136
+ }
179
137
 
180
- showOptions(e) {
181
- if(Helpers.hasClass(e.target, 'uniformSelect-remove')) return;
182
- if(this.button_options_modal) return this.hideOptions();
183
- if(this.button_options_popover) return this.hideOptions();
184
- if(!this.button_options) this.renderOptions();
185
- Helpers.addClass(this.edit_button, 'active');
186
- // For Mobile: Render Full Screen
187
- if(window.innerWidth < 720) {
188
- const content = Helpers.createElement('<div class="uniformSelect-modal">');
189
- content.append(this.button_options);
190
- if (this.options.label) {
191
- content.append(`<div class="uniformSelect-label margin-bottom text-bold">${this.options.label}</div>`);
192
- }
193
- this.button_options_modal = new Modal({
194
- content: content,
195
- klass: '-reset'
196
- }).render();
197
- this.button_options_modal.on('closed', () => {
198
- Helpers.removeClass(this.edit_button, 'active');
199
- delete this.button_options_modal;
200
- });
201
- this.listenTo(content, 'click', function(e){
202
- if(e.target == content) {
203
- this.button_options_modal.close()
204
- }
205
- });
206
- // For Larger: Render Popover
207
- } else {
208
- this.button_options.style.minWidth = this.edit_button.offsetWidth + "px";
209
- this.button_options_popover = new Popover({
210
- offset: {top: 1},
211
- anchor: this.edit_button,
212
- align: '0px bottom',
213
- content: this.button_options,
214
- container: this.options.container
215
- }).render()
216
- this.button_options_popover.on('hidden', () => {
217
- Helpers.removeClass(this.edit_button, 'active');
218
- this.button_options_popover.remove();
219
- delete this.button_options_popover;
220
- })
221
- }
222
- }
138
+ renderOptions () {
139
+ const options = createElement("div", {
140
+ class: 'uniformSelect-options'
141
+ });
142
+
143
+ options.style.fontSize = css(this.el, 'font-size')
144
+
145
+ this.select.querySelectorAll('option').forEach(function(option, index){
146
+ var button = createElement("button", {
147
+ type: 'button',
148
+ class: 'uniformSelect-option'
149
+ });
150
+ button.option = option;
151
+ option.button = button;
152
+ button.textContent = option.textContent;
153
+ button.value = option.value;
154
+ if (button.textContent == "") button.innerHTML = "<span class='text-italic text-muted'>None</span>";
155
+ button.classList.toggle('active', option.selected);
156
+
157
+ console.log(this.options.limit, index);
158
+ if (this.options.limit && (index + 1) > this.options.limit) {
159
+ button.classList.add('hide')
160
+ }
161
+ options.append(button);
162
+ }, this);
163
+
164
+ this.listenTo(options, 'click', '.uniformSelect-option', this.selectOption);
223
165
 
224
- selectOption(e) {
225
- const button = e.target;
226
- if (!this.el.multiple) {
227
- Helpers.each(this.button_options.querySelectorAll('.uniformSelect-option.active .uniformSelect-option-icon'), Helpers.remove);
228
- Helpers.removeClass(this.button_options.querySelectorAll('.uniformSelect-option.active'), 'active');
229
- }
230
- Helpers.toggleClass(e.target, 'active');
231
- e.target.option.selected = Helpers.hasClass(e.target, 'active');
232
- if (Helpers.hasClass(e.target, 'active')) {
233
- e.target.append(this.activeIcon.cloneNode(true));
234
- } else {
235
- Helpers.each(e.target.querySelectorAll('.uniformSelect-option-icon'), Helpers.remove);
236
- }
237
- if (!this.el.multiple) {
238
- this.hideOptions();
239
- }
240
- Helpers.trigger(this.el, 'change');
241
- }
166
+
167
+ const actions = createElement('div', {
168
+ class: 'uniformSelect-actions'
169
+ });
242
170
 
243
- removeSelection(e) {
244
- e.preventDefault();
245
- var target = Helpers.filter(this.el.querySelectorAll("option"), function(el){
246
- return el.innerText.trim() == e.target.parentNode.innerText.trim();
247
- })[0];
248
- if(!target) return;
249
- target.selected = false;
250
- Helpers.trigger(this.el, 'change');
171
+ if (this.options.limit && this.htmlOptions.length > this.options.limit) {
172
+ const button = createElement('button', {
173
+ type: 'button',
174
+ class: 'uniformSelect-show-all',
175
+ children: 'Show All'
176
+ });
177
+ this.listenTo(button, 'click', this.showAllOptions.bind(this))
178
+ actions.append(button);
179
+ }
180
+ if (this.options.multiple) {
181
+ const button = createElement('button', {
182
+ type: 'button',
183
+ class: 'uniformSelect-done',
184
+ children: ['Done']
185
+ });
186
+ this.listenTo(button, 'click', this.removeOptions.bind(this));
187
+ actions.append(button);
251
188
  }
189
+ if (!isEmpty(actions)) {
190
+ options.append(actions);
191
+ }
192
+
193
+ this.popover = new Popover({
194
+ offset: {top: 1},
195
+ align: '0px bottom',
196
+ anchor: this.el,
197
+ content: options,
198
+ container: this.options.container
199
+ }).render()
200
+
201
+ this.listenTo(this.popover, 'hidden', this.removeOptions);
202
+ }
203
+
204
+ removeOptions () {
205
+ this.el.classList.remove('active')
206
+ if(!this.popover) return;
207
+ this.popover.remove()
208
+ }
209
+
210
+ showAllOptions (e) {
211
+ e.preventDefault();
212
+ e.stopPropagation();
213
+ if(!this.popover) return;
214
+ this.popover.el.querySelectorAll('button.hide').forEach(el => el.classList.remove('hide'));
215
+ var button = this.popover.el.querySelector('.uniformSelect-show-all');
216
+ button.parentNode.removeChild(button);
217
+ }
252
218
  }