Skip to content

Commit 28ef546

Browse files
authored
Merge pull request #61 from rackerlabs/SURF-509-build-checkbox
surf-509-build-checkbox
2 parents ede3226 + 0c5c384 commit 28ef546

File tree

13 files changed

+613
-110
lines changed

13 files changed

+613
-110
lines changed

source/_data/icons.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- kbd-space
3232
- kbd-tab
3333
- lock
34+
- minus
3435
- phone
3536
- plus-or-minus
3637
- search

source/_data/nav.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ components:
66
Blockquote: blockquote
77
Breadcrumb: breadcrumb
88
Button: button
9+
Checkbox: checkbox
910
Code: code
1011
Dropdown: dropdown
1112
Form: form
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
const KEY = require('../../lib/KEY');
2+
/*
3+
* See "Using the checkbox role" (https://goo.gl/jDZFpH)
4+
*/
5+
6+
window.addEventListener('WebComponentsReady', function () {
7+
const template = document.createElement('template');
8+
9+
template.innerHTML = `
10+
<style>
11+
:host {
12+
background-color: #ffffff;
13+
border-color: currentColor;
14+
border-radius: 2px;
15+
border-style: solid;
16+
border-width: 1px;
17+
color: #bdbdbd;
18+
display: inline-block;
19+
height: 1rem;
20+
vertical-align: middle;
21+
width: 1rem;
22+
}
23+
24+
:host([hidden]) { display: none; }
25+
26+
/* default unchecked */
27+
28+
:host(:hover) {
29+
background-color: #e4f9f9;
30+
color: #16b9d4;
31+
}
32+
33+
/* default checked */
34+
35+
:host([checked]) {
36+
color: #0c7c84;
37+
}
38+
39+
:host([checked]:hover) {
40+
background-color: #e4f9f9;
41+
color: #16b9d4;
42+
}
43+
44+
/* default indeterminate (checked or unchecked) */
45+
46+
:host([indeterminate]) {
47+
color: #0c7c84;
48+
}
49+
50+
:host([indeterminate]:hover) {
51+
color: #16b9d4;
52+
}
53+
54+
/* invalid unchecked */
55+
56+
:host([invalid]) {
57+
border-width: 2px;
58+
color: #d32f2f;
59+
}
60+
61+
:host([invalid]:hover) {
62+
background-color: #FFCDD2;
63+
color: #d32f2f;
64+
}
65+
66+
/* invalid checked */
67+
68+
/* invalid indeterminate (checked or unchecked) */
69+
70+
/* disabled unchecked */
71+
72+
:host([disabled]) {
73+
background-color: #eeeeee;
74+
color: #bdbdbd;
75+
cursor: not-allowed;
76+
}
77+
78+
:host([disabled]:hover) {
79+
background-color: #eeeeee;
80+
color: #bdbdbd;
81+
}
82+
83+
/* disabled checked */
84+
85+
/* disabled indeterminate (checked or unchecked) */
86+
:host([disabled][indeterminate]) {
87+
color: #bdbdbd;
88+
}
89+
90+
/* ^^ light dom overridable ^^ */
91+
#container {
92+
align-content: center;
93+
align-items: center;
94+
display: flex;
95+
font-size: 0.625em; /* ~10px */
96+
height: 100%;
97+
justify-content: center;
98+
width: 100%;
99+
}
100+
101+
#minus,
102+
#tick {
103+
display: none;
104+
height: 1em;
105+
line-height: 1;
106+
width: 1em;
107+
}
108+
109+
/* FIXME: redefine due to bug in hxIcon */
110+
hx-icon svg {
111+
fill: currentColor;
112+
stroke: none;
113+
}
114+
115+
:host([checked]:not([indeterminate])) #tick {
116+
display: block;
117+
}
118+
119+
:host([indeterminate]) #minus {
120+
display: block;
121+
}
122+
</style>
123+
124+
<div id="container">
125+
<hx-icon type="checkmark" id="tick"></hx-icon>
126+
<hx-icon type="minus" id="minus"></hx-icon>
127+
</div>
128+
`;
129+
130+
function _preventScroll (event) {
131+
if (event.keyCode == KEY.Space) {
132+
event.preventDefault();
133+
}
134+
}
135+
136+
class HxCheckbox extends HTMLElement {
137+
static get is () {
138+
return 'hx-checkbox';
139+
}
140+
141+
static get observedAttributes () {
142+
return [
143+
'checked',
144+
'disabled',
145+
'indeterminate'
146+
];
147+
}
148+
149+
constructor () {
150+
super();
151+
this.attachShadow({mode: 'open'});
152+
if (window.ShadyCSS) {
153+
ShadyCSS.prepareTemplate(template, 'hx-checkbox');
154+
ShadyCSS.styleElement(this);
155+
}
156+
this.shadowRoot.appendChild(template.content.cloneNode(true));
157+
}
158+
159+
connectedCallback () {
160+
if (!this.hasAttribute('role')) {
161+
this.setAttribute('role', 'checkbox');
162+
}
163+
if (!this.hasAttribute('tabindex') && !this.disabled) {
164+
this.setAttribute('tabindex', 0);
165+
}
166+
167+
this._upgradeProperty('checked');
168+
this._upgradeProperty('disabled');
169+
this._upgradeProperty('indeterminate');
170+
171+
this.addEventListener('keydown', _preventScroll);
172+
this.addEventListener('keyup', this._onKeyUp);
173+
this.addEventListener('click', this._onClick);
174+
}
175+
176+
disconnectedCallback () {
177+
this.removeEventListener('keydown', _preventScroll);
178+
this.removeEventListener('keyup', this._onKeyUp);
179+
this.removeEventListener('click', this._onClick);
180+
}
181+
182+
set checked (value) {
183+
if (Boolean(value)) {
184+
this.setAttribute('checked', '');
185+
} else {
186+
this.removeAttribute('checked');
187+
}
188+
}
189+
190+
get checked () {
191+
return this.hasAttribute('checked');
192+
}
193+
194+
set disabled (value) {
195+
if (Boolean(value)) {
196+
this.setAttribute('disabled', '');
197+
} else {
198+
this.removeAttribute('disabled');
199+
}
200+
}
201+
202+
get disabled () {
203+
return this.hasAttribute('disabled');
204+
}
205+
206+
set indeterminate (value) {
207+
if (Boolean(value)) {
208+
this.setAttribute('indeterminate', '');
209+
} else {
210+
this.removeAttribute('indeterminate');
211+
}
212+
}
213+
214+
get indeterminate () {
215+
return this.hasAttribute('indeterminate');
216+
}
217+
218+
attributeChangedCallback (name, oldValue, newValue) {
219+
const hasValue = newValue !== null;
220+
switch (name) {
221+
case 'indeterminate':
222+
if (hasValue) {
223+
this.setAttribute('aria-checked', 'mixed');
224+
}
225+
break;
226+
227+
case 'checked':
228+
if (!this.indeterminate) {
229+
this.setAttribute('aria-checked', hasValue);
230+
}
231+
break;
232+
233+
case 'disabled':
234+
this.setAttribute('aria-disabled', hasValue);
235+
if (hasValue) {
236+
this.removeAttribute('tabindex');
237+
this.blur();
238+
} else {
239+
this.setAttribute('tabindex', '0');
240+
}
241+
break;
242+
}
243+
}//attributeChangedCallback()
244+
245+
_onKeyUp (event) {
246+
if (event.altKey) {
247+
return;
248+
}
249+
250+
if (event.keyCode == KEY.Space) {
251+
event.preventDefault();
252+
this._toggleChecked();
253+
}
254+
}
255+
256+
_onClick (event) {
257+
this._toggleChecked();
258+
}
259+
260+
_toggleChecked () {
261+
if (this.disabled) {
262+
return;
263+
}
264+
this.indeterminate = false;
265+
this.checked = !this.checked;
266+
267+
let changeEvent = new CustomEvent('change', {
268+
detail: {
269+
checked: this.checked
270+
},
271+
bubbles: true
272+
});
273+
274+
this.dispatchEvent(changeEvent);
275+
}
276+
277+
// A user may set a property on an _instance_ of an element, before its
278+
// prototype has been connected to this class. The `_upgradeProperty()`
279+
// method will check for any instance properties and run them through
280+
// the proper class setters.
281+
_upgradeProperty (prop) {
282+
if (this.hasOwnProperty(prop)) {
283+
let value = this[prop];
284+
delete this[prop];
285+
this[prop] = value;
286+
}
287+
}
288+
}
289+
customElements.define(HxCheckbox.is, HxCheckbox)
290+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
hx-checkbox {
2+
& + * {
3+
margin-left: 0.5rem;
4+
}
5+
6+
&[disabled] + label {
7+
&:extend(.hxSubdued);
8+
}
9+
}

0 commit comments

Comments
 (0)