blob: 49d3c7c7715c3cab6d7c05d5044c9e50db1d8873 [file] [log] [blame]
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../gr-icon/gr-icon';
import '@polymer/paper-button/paper-button';
import {spinnerStyles} from '../../../styles/gr-spinner-styles';
import {votingStyles} from '../../../styles/gr-voting-styles';
import {css, html, LitElement, nothing, PropertyValues} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {addShortcut, getEventPath, Key} from '../../../utils/dom-util';
import {getAppContext} from '../../../services/app-context';
import {classMap} from 'lit/directives/class-map.js';
import {Interaction} from '../../../constants/reporting';
declare global {
interface HTMLElementTagNameMap {
'gr-button': GrButton;
}
}
/**
* @attr {Boolean} no-uppercase - text in button is not uppercased
* @attr {Boolean} position-below
* @attr {Boolean} primary - set primary button color
* @attr {Boolean} secondary - set secondary button color
*/
@customElement('gr-button')
export class GrButton extends LitElement {
// Private but used in tests.
readonly reporting = getAppContext().reportingService;
/**
* Should this button be rendered as a vote chip? Then we are applying
* the .voteChip class (see gr-voting-styles) to the paper-button.
*/
@property({type: Boolean, reflect: true})
voteChip = false;
// Note: don't assign a value to this, since constructor is called
// after created, the initial value maybe overridden by this
private initialTabindex?: string;
@property({type: Boolean, attribute: 'down-arrow'})
downArrow = false;
@property({type: Boolean, reflect: true})
link = false;
// If flattened then the button will not be shown as raised.
@property({type: Boolean, reflect: true})
flatten = false;
@property({type: Boolean, reflect: true})
loading = false;
@property({type: Boolean, reflect: true})
disabled: boolean | null = null;
static override get styles() {
return [
votingStyles,
spinnerStyles,
css`
/* general styles for all buttons */
:host {
--background-color: var(
--button-background-color,
var(--default-button-background-color)
);
--text-color: var(
--gr-button-text-color,
var(--default-button-text-color)
);
display: inline-block;
position: relative;
}
:host([hidden]) {
display: none;
}
:host([no-uppercase]) paper-button {
text-transform: none;
}
paper-button {
/* paper-button sets this to anti-aliased, which appears different than
bold font elsewhere on macOS. */
-webkit-font-smoothing: initial;
align-items: center;
background-color: var(--background-color);
color: var(--text-color);
display: flex;
font-family: var(--font-family, inherit);
/** Without this '.keyboard-focus' buttons will get bolded. */
font-weight: var(--font-weight-normal, inherit);
justify-content: center;
margin: var(--margin, 0);
min-width: var(--border, 0);
padding: var(--gr-button-padding, var(--spacing-s) var(--spacing-m));
}
paper-button[elevation='1'] {
box-shadow: var(--elevation-level-1);
}
paper-button[elevation='2'] {
box-shadow: var(--elevation-level-2);
}
paper-button[elevation='3'] {
box-shadow: var(--elevation-level-3);
}
paper-button[elevation='4'] {
box-shadow: var(--elevation-level-4);
}
paper-button[elevation='5'] {
box-shadow: var(--elevation-level-5);
}
paper-button:hover {
background: linear-gradient(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.12)),
var(--background-color);
}
:host([primary]) {
--background-color: var(--primary-button-background-color);
--text-color: var(--primary-button-text-color);
}
:host([link][primary]) {
--text-color: var(--primary-button-background-color);
}
/* Keep below color definition for primary so that this takes precedence
when disabled. */
:host([disabled]),
:host([loading]) {
--background-color: var(--disabled-button-background-color);
--text-color: var(--deemphasized-text-color);
cursor: default;
}
:host([disabled][flatten]) {
--background-color: transparent;
--text-color: var(--disabled-foreground);
}
/* Styles for link buttons specifically */
:host([link]) {
--background-color: transparent;
--margin: 0;
}
:host([link]) paper-button {
padding: var(--gr-button-padding, var(--spacing-s));
}
:host([disabled][link]),
:host([loading][link]) {
--background-color: transparent;
--text-color: var(--disabled-foreground);
cursor: default;
}
gr-icon.downArrow {
color: inherit;
}
.newVoteChip {
border: 1px solid var(--border-color);
box-shadow: none;
box-sizing: border-box;
min-width: 3em;
color: var(--vote-text-color);
}
`,
];
}
override render() {
return html`<paper-button
?raised=${!this.link && !this.flatten}
?disabled=${this.disabled || this.loading}
role="button"
tabindex="-1"
part="paper-button"
class=${classMap({
newVoteChip: this.voteChip,
})}
>
${this.loading ? html`<span class="loadingSpin"></span>` : ''}
<slot></slot>
${this.renderArrowIcon()}
</paper-button>`;
}
renderArrowIcon() {
if (!this.downArrow) return nothing;
return html`<gr-icon icon="arrow_drop_down" class="downArrow"></gr-icon>`;
}
constructor() {
super();
this.initialTabindex = this.getAttribute('tabindex') || '0';
this.addEventListener('click', e => this._handleAction(e));
addShortcut(this, {key: Key.ENTER}, () => this.click());
addShortcut(this, {key: Key.SPACE}, () => this.click());
}
override updated(changedProperties: PropertyValues) {
if (changedProperties.has('disabled')) {
this.setAttribute(
'tabindex',
this.disabled ? '-1' : this.initialTabindex || '0'
);
}
if (changedProperties.has('loading') || changedProperties.has('disabled')) {
this.setAttribute(
'aria-disabled',
this.disabled || this.loading ? 'true' : 'false'
);
}
}
override connectedCallback() {
super.connectedCallback();
if (!this.getAttribute('role')) {
this.setAttribute('role', 'button');
}
if (!this.getAttribute('tabindex')) {
this.setAttribute('tabindex', '0');
}
}
_handleAction(e: MouseEvent) {
if (this.disabled || this.loading) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return;
}
this.reporting.reportInteraction(Interaction.BUTTON_CLICK, {
path: getEventPath(e),
});
}
}