Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Toggle Button component (resolves #336) #338

Merged
merged 4 commits into from
May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions src/assets/scripts/Pinecone/RadioGroup/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Radio Grou class.
*/
class RadioGroup {
/**
* Constructor.
*
* @param {DomNode} container
* @param {Object} options
*/
constructor( container, options ) {
this.radioGroup = container;
this.keyCode = Object.freeze( {
'LEFT': 37,
'UP': 38,
'RIGHT': 39,
'DOWN': 40
} );

this.config = {
...{
groupSelector: '.radio-group'
},
...options
};

this.handleClick = this.handleClick.bind( this );
this.handleKeyDown = this.handleKeyDown.bind( this );
this.setCheckedToPreviousItem = this.setCheckedToPreviousItem.bind( this );
this.setCheckedToNextItem = this.setCheckedToNextItem.bind( this );
this.addEventListeners();
}

/**
* Handle click.
*
* @param {Event} event
*/
handleClick( event ) {
if ( ! event.target.closest( this.config.groupSelector ) ) return;
if ( 'BUTTON' !== event.target.nodeName ) return;

const checkedItem = this.radioGroup.querySelector( '[aria-checked="true"]' );

if ( 'false' === event.target.getAttribute( 'aria-checked' ) && checkedItem ) {
checkedItem.setAttribute( 'aria-checked', false );
event.target.setAttribute( 'aria-checked', true );
}
}

/**
* Handle keydown.
*
* @param {Event} event
*/
handleKeyDown( event ) {
if ( ! document.activeElement.closest( this.config.groupSelector ) ) return;

switch ( event.keyCode ) {
case this.keyCode.UP:
this.setCheckedToPreviousItem( document.activeElement );
break;

case this.keyCode.DOWN:
this.setCheckedToNextItem( document.activeElement );
break;

case this.keyCode.LEFT:
this.setCheckedToPreviousItem( document.activeElement );
break;

case this.keyCode.RIGHT:
this.setCheckedToNextItem( document.activeElement );
break;

default:
break;
}
}

/**
* Move checked state to previous item.
*/
setCheckedToPreviousItem( ) {
let previousItem = document.activeElement.previousElementSibling;
if ( !previousItem ) {
const buttons = this.radioGroup.querySelectorAll( 'button' );
previousItem = buttons[buttons.length - 1];
}
const checkedItem = this.radioGroup.querySelector( '[aria-checked="true"]' );

checkedItem.setAttribute( 'aria-checked', false );
previousItem.setAttribute( 'aria-checked', true );
previousItem.focus();
}

/**
* Move checked state to next item.
*/
setCheckedToNextItem() {
let nextItem = document.activeElement.nextElementSibling;
if ( !nextItem ) {
const button = this.radioGroup.querySelector( 'button' );
nextItem = button;
}
const checkedItem = this.radioGroup.querySelector( '[aria-checked="true"]' );
checkedItem.setAttribute( 'aria-checked', false );
nextItem.setAttribute( 'aria-checked', true );
nextItem.focus();
}

/**
* Add event listeners.
*/
addEventListeners() {
document.addEventListener( 'click', this.handleClick );
document.addEventListener( 'keydown', this.handleKeyDown );
}
}

export default RadioGroup;
43 changes: 43 additions & 0 deletions src/assets/scripts/Pinecone/ToggleButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Toggle Button class.
*/
class ToggleButton {
/**
* Constructor.
*
* @param {Object} options
*/
constructor( options ) {
this.config = {
...{
selector: '.button--toggle'
},
...options
};

this.handleClick = this.handleClick.bind( this );
this.addEventListeners();
}

/**
* Handle click.
*
* @param {Event} event
*/
handleClick( event ) {
if ( ! event.target.closest( this.config.selector ) ) return;

const btn = event.target.closest( this.config.selector );
const pressed = 'true' === btn.getAttribute( 'aria-pressed' ) || false;
btn.setAttribute( 'aria-pressed', !pressed );
}

/**
* Add event listeners.
*/
addEventListeners() {
document.addEventListener( 'click', this.handleClick );
}
}

export default ToggleButton;
5 changes: 4 additions & 1 deletion src/assets/scripts/Pinecone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ import MenuButton from './MenuButton/index.js';
import NestedCheckbox from './NestedCheckbox/index.js';
import Notification from './Notification/index.js';
import SearchToggle from './SearchToggle/index.js';
import ToggleButton from './ToggleButton/index.js';
import RadioGroup from './RadioGroup/index.js';

export default { Accordion, Card, DeselectAll, Dialog, DisclosureButton, FilterList, Icon, Menu, MenuButton, NestedCheckbox, Notification, SearchToggle, ToggleButton, RadioGroup };

export default { Accordion, Card, DeselectAll, Dialog, DisclosureButton, FilterList, Icon, Menu, MenuButton, NestedCheckbox, Notification, SearchToggle };
11 changes: 11 additions & 0 deletions src/assets/scripts/pinecone.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,14 @@ const searchToggle = document.querySelector( '.search-toggle' );
if ( searchToggle ) {
new Pinecone.SearchToggle( searchToggle, searchToggle.nextElementSibling );
}

new Pinecone.ToggleButton();

const radioGroups = document.querySelectorAll( '.radio-group' );

if ( radioGroups ) {
Array.prototype.forEach.call( radioGroups, radioGroup => {
new Pinecone.RadioGroup( radioGroup );
} );
}

105 changes: 105 additions & 0 deletions src/assets/styles/components/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,111 @@ button:-moz-focusring {
}
}

.button--toggle,
.button--radio {
--color: var(--blue-500);
--background-color: var(--off-white);
--border-color: var(--blue-500);

--active-background-color: var(--blue-500);
--active-color: var(--off-white);
--active-box-shadow: 0 0 0 calc(2 * var(--border-width)) var(--parent-background-color), 0 0 0 calc(4 * var(--border-width)) var(--focus-border-color);

--focus-background-color: var(--off-white);
--focus-border-color: var(--color);
--focus-color: inherit;
--focus-box-shadow: 0 0 0 calc(1 * var(--border-width)) var(--focus-border-color) inset, 0 0 0 calc(2 * var(--border-width)) var(--parent-background-color), 0 0 0 calc(4 * var(--border-width)) var(--focus-border-color);

--hover-color: var(--color);
--hover-background-color: var(--grey-200);

border-radius: rem(5);
font-weight: $font-weight-normal;
margin: rem(8) rem(9);
min-height: rem(32);
padding: 0 rem(10);
width: auto;

&:focus {
border-color: var(--focus-border-color);
}

&[aria-pressed="true"],
&[aria-checked="true"] {
--color: var(--off-white);
--background-color: var(--blue-600);
--border-color: var(--background-color);

--focus-background-color: var(--background-color);
--focus-border-color: var(--background-color);
--focus-color: inherit;
--focus-box-shadow: 0 0 0 calc(2 * var(--border-width)) var(--parent-background-color), 0 0 0 calc(4 * var(--border-width)) var(--focus-border-color);

--hover-background-color: var(--blue-700);
--hover-border-color: var(--blue-700);

&:hover {
border-color: var(--hover-border-color);
}

&:active {
border-color: transparent;
}
}
}

.button--toggle.button--inverse,
.button--radio.button--inverse {
--color: var(--off-white);
--background-color: var(--blue-500);
--border-color: var(--off-white);

--active-background-color: var(--off-white);
--active-color: var(--blue-500);
--active-box-shadow: 0 0 0 calc(2 * var(--border-width)) var(--parent-background-color), 0 0 0 calc(4 * var(--border-width)) var(--focus-border-color);

--focus-background-color: var(--blue-500);
--focus-border-color: var(--color);
--focus-color: inherit;
--focus-box-shadow: 0 0 0 calc(1 * var(--border-width)) var(--focus-border-color) inset, 0 0 0 calc(2 * var(--border-width)) var(--parent-background-color), 0 0 0 calc(4 * var(--border-width)) var(--focus-border-color);

--hover-background-color: var(--blue-600);

&:active {
border-color: transparent;
}

&[aria-pressed="true"],
&[aria-checked="true"] {
--color: var(--blue-700);
--background-color: var(--blue-50);
--border-color: var(--background-color);

--focus-background-color: var(--background-color);
--focus-border-color: var(--background-color);
--focus-color: inherit;
--focus-box-shadow: 0 0 0 calc(2 * var(--border-width)) var(--parent-background-color), 0 0 0 calc(4 * var(--border-width)) var(--focus-border-color);

--hover-background-color: var(--blue-150);
--hover-border-color: var(--blue-150);

&:hover {
border-color: var(--hover-border-color);
}

&:active {
border-color: transparent;
}
}
}

.radio-group {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-left: rem(-9);
}

.button[disabled] {
background-color: var(--disabled-background-color);
color: var(--disabled-color);
Expand Down
4 changes: 4 additions & 0 deletions src/assets/styles/mandelbrot.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ a:hover {
.Pen-previewLink svg {
fill: var(--blue-400);
}

.Prose a:hover {
color: var(--blue-400);
}
7 changes: 7 additions & 0 deletions src/components/atoms/button/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Toggle Button

The Toggle Button is a button with an `aria-pressed` attribute representing a boolean (true or false) value. To use a toggle button, the following JavaScript must be executed on the page where the component is used:

```javascript
new PineCone.ToggleButton();
```
25 changes: 25 additions & 0 deletions src/components/atoms/button/button.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,31 @@ module.exports = {
icon: 'close',
iconPosition: 'end',
}
},
{
name: 'Toggle Button',
label: 'Toggle Button',
context: {
id: false,
label: 'Toggle',
modifiers: ['toggle'],
ariaPressed: true,
pressed: false,
icon: false
}
},
{
name: 'Toggle Button Inverse',
label: 'Toggle Button (Inverse)',
context: {
id: false,
label: 'Toggle',
modifiers: ['toggle', 'inverse'],
ariaPressed: true,
pressed: false,
bodyClass: 'has-dark-mint-500-background-color',
icon: false
}
}
]
};
2 changes: 1 addition & 1 deletion src/components/atoms/button/button.njk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% if not standAlone %}<div class="spacer"></div>{% endif %}
<button {% if id %}id="{{ id }}" {% endif %}type="{{ type }}" class="button{% if modifiers %}{% for modifier in modifiers %} button--{{ modifier }}{% endfor %}{% endif %}{% if className %} {{ className }}{% endif %}">
<button {% if id %}id="{{ id }}" {% endif %}type="{{ type }}" {% if ariaPressed %}aria-pressed="{{ pressed }}" {% endif %}class="button{% if modifiers %}{% for modifier in modifiers %} button--{{ modifier }}{% endfor %}{% endif %}{% if className %} {{ className }}{% endif %}">
{% if icon and iconPosition === 'start' %}{% render '@svg', {svg:icon}, true %}{% endif %}<span class="button__label{{ ' screen-reader-text' if labelVisuallyHidden }}">{{ label | safe }}</span>{% if icon and iconPosition === 'end' %}{% render '@svg', {svg:icon}, true %}{% endif %}
</button>
12 changes: 12 additions & 0 deletions src/components/molecules/radio-group/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Radio Group

The Radio Group displays a group of related buttons using flexbox. The parent element must have a class of `.radio-group` and a role of `radiogroup` and the buttons must have a role of `radio` and be the direct descendants of the parent element.

Toggling the `aria-checked` state of one button in a Radio Group to `true` will set the `aria-checked` state of all the other buttons to `false`.

To instantiate a Radio Group, the following JavaScript must be called:

```javascript
const radioGroup = document.querySelector('.radio-group');
new Pinecone.RadioGroup( radioGroup );
```
Loading