Commit 712e6f2d authored by Lyza Danger Gardner's avatar Lyza Danger Gardner

Add `ActionButton` component

parent 9eb89202
'use strict';
const classnames = require('classnames');
const propTypes = require('prop-types');
const { createElement } = require('preact');
const SvgIcon = require('./svg-icon');
/**
* A button, with (required) text label and an (optional) icon
*/
function ActionButton({
icon = '',
isActive = false,
isPrimary = false,
label,
onClick = () => null,
useCompactStyle = false,
}) {
return (
<button
className={classnames('action-button', {
'action-button--compact': useCompactStyle,
'action-button--primary': isPrimary,
'is-active': isActive,
})}
onClick={onClick}
aria-pressed={isActive}
title={label}
>
{icon && (
<SvgIcon
name={icon}
className={classnames('action-button__icon', {
'action-button__icon--compact': useCompactStyle,
'action-button__icon--primary': isPrimary,
})}
/>
)}
<span className="action-button__label">{label}</span>
</button>
);
}
ActionButton.propTypes = {
/** The name of the SVGIcon to render */
icon: propTypes.string,
/** Is this button currently in an "active" or in an "on" state? */
isActive: propTypes.bool,
/**
* Does this button represent the "primary" action available? If so,
* differentiating styles will be applied.
*/
isPrimary: propTypes.bool,
/** a label used for the `title` and `aria-label` attributes */
label: propTypes.string.isRequired,
/** callback for button clicks */
onClick: propTypes.func,
/** Allows a variant of this type of button that takes up less space */
useCompactStyle: propTypes.bool,
};
module.exports = ActionButton;
'use strict';
const { createElement } = require('preact');
const { mount } = require('enzyme');
const ActionButton = require('../action-button');
const mockImportedComponents = require('./mock-imported-components');
describe('ActionButton', () => {
let fakeOnClick;
function createComponent(props = {}) {
return mount(
<ActionButton
icon="fakeIcon"
label="My Action"
onClick={fakeOnClick}
{...props}
/>
);
}
beforeEach(() => {
fakeOnClick = sinon.stub();
ActionButton.$imports.$mock(mockImportedComponents());
});
afterEach(() => {
ActionButton.$imports.$restore();
});
it('renders `SvgIcon` if icon property set', () => {
const wrapper = createComponent();
assert.equal(wrapper.find('SvgIcon').prop('name'), 'fakeIcon');
});
it('does not render `SvgIcon` if no icon property set', () => {
const wrapper = createComponent({ icon: undefined });
assert.isFalse(wrapper.find('SvgIcon').exists());
});
it('invokes `onClick` callback when pressed', () => {
const wrapper = createComponent();
wrapper.find('button').simulate('click');
assert.calledOnce(fakeOnClick);
});
it('adds compact styles if `useCompactStyle` is `true`', () => {
const wrapper = createComponent({ useCompactStyle: true });
assert.isTrue(wrapper.find('button').hasClass('action-button--compact'));
});
context('in active state (`isActive` is `true`)', () => {
it('adds `is-active` className', () => {
const wrapper = createComponent({ isActive: true });
assert.isTrue(wrapper.find('button').hasClass('is-active'));
});
it('sets `aria-pressed` attribute on button', () => {
const wrapper = createComponent({ isActive: true });
assert.isTrue(wrapper.find('button').prop('aria-pressed'));
});
});
});
......@@ -43,13 +43,36 @@
color: var.$grey-5;
font-weight: 700;
&:hover {
background-color: var.$grey-2;
}
&__label {
padding-left: 0.25em;
padding-right: 0.25em;
}
&__icon {
color: var.$grey-5;
margin: $icon-margin;
&--compact {
margin: 0;
}
&--primary {
color: var.$grey-1;
}
}
&:hover {
background-color: var.$grey-2;
&--primary {
color: var.$grey-1;
background-color: var.$grey-mid;
&:hover {
color: var.$grey-1;
background-color: var.$grey-6;
}
}
}
......
......@@ -2,8 +2,4 @@
.action-button {
@include buttons.action-button;
&__icon--compact {
margin: 0;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment