Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/elements/chops/chops-choice-buttons/chops-choice-buttons.js b/static_src/elements/chops/chops-choice-buttons/chops-choice-buttons.js
new file mode 100644
index 0000000..e300588
--- /dev/null
+++ b/static_src/elements/chops/chops-choice-buttons/chops-choice-buttons.js
@@ -0,0 +1,133 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {LitElement, html, css} from 'lit-element';
+import 'elements/chops/chops-button/chops-button.js';
+
+/**
+ * @typedef {Object} ChoiceOption
+ * @property {string=} value a unique string identifier for this option.
+ * @property {string=} text the text displayed to the user for this option.
+ * @property {string=} url the url this option navigates to.
+ */
+
+/**
+ * Shared component for rendering a set of choice chips.
+ * @extends {LitElement}
+ */
+export class ChopsChoiceButtons extends LitElement {
+ /** @override */
+ render() {
+ return html`
+ ${(this.options).map((option) => this._renderOption(option))}
+ `;
+ }
+
+ /**
+ * Rendering helper for rendering a single option.
+ * @param {ChoiceOption} option
+ * @return {TemplateResult}
+ */
+ _renderOption(option) {
+ const isSelected = this.value === option.value;
+ if (option.url) {
+ return html`
+ <a
+ ?selected=${isSelected}
+ aria-current=${isSelected ? 'true' : 'false'}
+ href=${option.url}
+ >${option.text}</a>
+ `;
+ }
+ return html`
+ <button
+ ?selected=${isSelected}
+ aria-current=${isSelected ? 'true' : 'false'}
+ @click=${this._setValue}
+ value=${option.value}
+ >${option.text}</button>
+ `;
+ }
+
+ /** @override */
+ static get properties() {
+ return {
+ /**
+ * Array of options where each option is an Object with keys:
+ * {value, text, url}
+ */
+ options: {type: Array},
+ /**
+ * Which button is currently selected.
+ */
+ value: {type: String},
+ };
+ };
+
+ /** @override */
+ constructor() {
+ super();
+
+ /**
+ * @type {Array<ChoiceOption>}
+ */
+ this.options = [];
+ this.value = '';
+ };
+
+ /** @override */
+ static get styles() {
+ return css`
+ :host {
+ display: grid;
+ grid-auto-flow: column;
+ grid-template-columns: auto;
+ }
+ button, a {
+ display: block;
+ cursor: pointer;
+ border: 0;
+ color: var(--chops-gray-700);
+ font-weight: var(--chops-link-font-weight);
+ font-size: var(--chops-normal-font-size);
+ margin: 0.1em 4px;
+ padding: 4px 10px;
+ line-height: 1.4;
+ background: var(--chops-choice-bg);
+ text-decoration: none;
+ border-radius: 16px;
+ }
+ button[selected], a[selected] {
+ background: var(--chops-active-choice-bg);
+ color: var(--chops-link-color);
+ font-weight: var(--chops-link-font-weight);
+ border-radius: 16px;
+ }
+ `;
+ };
+
+ /**
+ * Public method for allowing parents to change the value of this component.
+ * @param {string} newValue
+ * @fires CustomEvent#change
+ */
+ setValue(newValue) {
+ if (newValue !== this.value) {
+ this.value = newValue;
+ this.dispatchEvent(new CustomEvent('change'));
+ }
+ }
+
+ /**
+ * Private setter for updating the value of the component based on an internal
+ * click event.
+ * @param {MouseEvent} e
+ * @private
+ */
+ _setValue(e) {
+ this.setValue(e.target.getAttribute('value'));
+ }
+};
+
+customElements.define('chops-choice-buttons', ChopsChoiceButtons);
diff --git a/static_src/elements/chops/chops-choice-buttons/chops-choice-buttons.test.js b/static_src/elements/chops/chops-choice-buttons/chops-choice-buttons.test.js
new file mode 100644
index 0000000..e529735
--- /dev/null
+++ b/static_src/elements/chops/chops-choice-buttons/chops-choice-buttons.test.js
@@ -0,0 +1,99 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import {assert} from 'chai';
+import sinon from 'sinon';
+import {ChopsChoiceButtons} from './chops-choice-buttons';
+
+let element;
+
+describe('chops-choice-buttons', () => {
+ beforeEach(() => {
+ element = document.createElement('chops-choice-buttons');
+ document.body.appendChild(element);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(element);
+ });
+
+ it('initializes', () => {
+ assert.instanceOf(element, ChopsChoiceButtons);
+ });
+
+ it('clicking option fires change event', async () => {
+ element.options = [{value: 'test', text: 'click me'}];
+ element.value = '';
+
+ await element.updateComplete;
+
+ const changeStub = sinon.stub();
+ element.addEventListener('change', changeStub);
+
+ const option = element.shadowRoot.querySelector('button');
+ option.click();
+
+ sinon.assert.calledOnce(changeStub);
+ });
+
+ it('clicking selected value does not fire change event', async () => {
+ element.options = [{value: 'test', text: 'click me'}];
+ element.value = 'test';
+
+ await element.updateComplete;
+
+ const changeStub = sinon.stub();
+ element.addEventListener('change', changeStub);
+
+ const option = element.shadowRoot.querySelector('button');
+ option.click();
+
+ sinon.assert.notCalled(changeStub);
+ });
+
+ it('selected value highlighted and has aria-current="true"', async () => {
+ element.options = [
+ {value: 'test', text: 'test'},
+ {value: 'selected', text: 'highlighted!'},
+ ];
+ element.value = 'selected';
+
+ await element.updateComplete;
+
+ const options = element.shadowRoot.querySelectorAll('button');
+
+ assert.isFalse(options[0].hasAttribute('selected'));
+ assert.isTrue(options[1].hasAttribute('selected'));
+
+ assert.equal(options[0].getAttribute('aria-current'), 'false');
+ assert.equal(options[1].getAttribute('aria-current'), 'true');
+ });
+
+ it('renders <a> tags when url set', async () => {
+ element.options = [
+ {value: 'test', text: 'test', url: 'http://google.com/'},
+ ];
+
+ await element.updateComplete;
+
+ const options = element.shadowRoot.querySelectorAll('a');
+
+ assert.equal(options[0].textContent.trim(), 'test');
+ assert.equal(options[0].href, 'http://google.com/');
+ });
+
+ it('selected value highlighted for <a> tags', async () => {
+ element.options = [
+ {value: 'test', text: 'test', url: 'http://google.com/'},
+ {value: 'selected', text: 'highlighted!', url: 'http://localhost/'},
+ ];
+ element.value = 'selected';
+
+ await element.updateComplete;
+
+ const options = element.shadowRoot.querySelectorAll('a');
+
+ assert.isFalse(options[0].hasAttribute('selected'));
+ assert.isTrue(options[1].hasAttribute('selected'));
+ });
+});