Commit 1f4f0a34 authored by Esmael El-Moslimany's avatar Esmael El-Moslimany Committed by Commit Bot

WebUI: create paper-button replacement, cr-button

As part of the MD refresh, the WebUI button style changed dramatically.
We opted to reuse the paper-button and overriding the styling. The
button logic is fairly straightforward. That said, paper-button use
5 Polymer behaviors.

IronA11yKeysBehavior: a different way to handle keybindings.
IronButtonState: supports toggling and whether focus was from a
    keyboard.
IronControlState: handles disabling a control with the |disabled|
    property.
PaperButtonBehaviorImpl: supports button elevation based on the button
    state (focused, pressed, disabled and normal).
PaperRippleBehavior: lazily creates paper-ripple and forwards noink
    property to paper-ripple.

cr-button applies the MD refresh style directly and implements
only the functionality that is required. It makes use of the
PaperRippleBehavior and cr.ui.FocusOutlineManager (used in place of
IronButtonState which also supports toggling).

Bug: 967793
Change-Id: I0417974fcc69467be249920410b5ae77bdc529db
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1632894Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Commit-Queue: Esmael El-Moslimany <aee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665213}
parent 96d7c14f
// 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.
suite('cr-button', function() {
let button;
setup(() => {
PolymerTest.clearBody();
button = document.createElement('cr-button');
document.body.appendChild(button);
});
/** @param {string} key */
function press(key) {
button.dispatchEvent(new KeyboardEvent('keydown', {key: key}));
button.dispatchEvent(new KeyboardEvent('keyup', {key: key}));
}
test('label is displayed', async () => {
const widthWithoutLabel = button.offsetWidth;
document.body.innerHTML = '<cr-button>Long Label</cr-button>';
button = document.body.querySelector('cr-button');
assertTrue(widthWithoutLabel < button.offsetWidth);
});
test('tabindex and aria-disabled', () => {
assertFalse(button.disabled);
assertFalse(button.hasAttribute('disabled'));
assertEquals('0', button.getAttribute('tabindex'));
assertEquals('false', button.getAttribute('aria-disabled'));
button.disabled = true;
assertTrue(button.hasAttribute('disabled'));
assertEquals('-1', button.getAttribute('tabindex'));
assertEquals('true', button.getAttribute('aria-disabled'));
});
test('enter/space/click events and programmatic click() calls', async () => {
let clickCount = 0;
const clickHandler = () => {
clickCount++;
};
button.addEventListener('click', clickHandler);
const checkClicks = expectedCount => {
clickCount = 0;
press('Enter');
press(' ');
button.dispatchEvent(new MouseEvent('click'));
button.click();
assertEquals(expectedCount, clickCount);
};
checkClicks(4);
button.disabled = true;
checkClicks(0);
button.disabled = false;
checkClicks(4);
button.removeEventListener('click', clickHandler);
});
test('when tabindex is -1, it stays -1', async () => {
document.body.innerHTML = '<cr-button tabindex="-1"></cr-button>';
button = document.body.querySelector('cr-button');
assertEquals('-1', button.getAttribute('tabindex'));
});
test('hidden', () => {
assertFalse(button.hidden);
assertFalse(button.hasAttribute('hidden'));
assertNotEquals('none', getComputedStyle(button).display);
button.hidden = true;
assertTrue(button.hasAttribute('hidden'));
assertEquals('none', getComputedStyle(button).display);
button.hidden = false;
assertFalse(button.hasAttribute('hidden'));
assertNotEquals('none', getComputedStyle(button).display);
});
});
......@@ -474,6 +474,28 @@ TEST_F('CrElementsRadioGroupTest', 'All', function() {
mocha.run();
});
/**
* @constructor
* @extends {CrElementsBrowserTest}
*/
function CrElementsButtonTest() {}
CrElementsButtonTest.prototype = {
__proto__: CrElementsBrowserTest.prototype,
/** @override */
browsePreload: 'chrome://resources/cr_elements/cr_button/cr_button.html',
/** @override */
extraLibraries: CrElementsBrowserTest.prototype.extraLibraries.concat([
'cr_button_tests.js',
]),
};
TEST_F('CrElementsButtonTest', 'All', function() {
mocha.run();
});
/**
* @constructor
* @extends {CrElementsBrowserTest}
......
......@@ -10,6 +10,7 @@ group("closure_compile") {
"chromeos/cr_picture:closure_compile",
"chromeos/network:closure_compile",
"cr_action_menu:closure_compile",
"cr_button:closure_compile",
"cr_checkbox:closure_compile",
"cr_dialog:closure_compile",
"cr_drawer:closure_compile",
......
# 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("//third_party/closure_compiler/compile_js.gni")
js_type_check("closure_compile") {
deps = [
":cr_button",
]
}
js_library("cr_button") {
deps = [
"//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-ripple-behavior-extracted",
"//ui/webui/resources/js/cr/ui:focus_outline_manager",
]
}
<link rel="import" href="../../html/polymer.html">
<link rel="import" href="chrome://resources/html/cr/ui/focus_outline_manager.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-ripple-behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
<link rel="import" href="../hidden_style_css.html">
<link rel="import" href="../shared_vars_css.html">
<dom-module id="cr-button">
<template>
<style include="cr-hidden-style">
:host {
--active-shadow-rgb: var(--google-grey-800-rgb);
--active-shadow-action-rgb: var(--google-blue-500-rgb);
--bg-action: var(--google-blue-600);
--border-color: var(--google-grey-refresh-300);
--disabled-bg-action: var(--google-grey-refresh-100);
--disabled-bg: white;
--disabled-border-color: var(--google-grey-refresh-100);
--focus-shadow-color: rgba(var(--google-blue-600-rgb), .4);
--hover-bg-action: rgba(var(--google-blue-600-rgb), .9);
--hover-bg-color: rgba(var(--google-blue-500-rgb), .04);
--ink-color-action: white;
/* Blue-ish color used either as a background or as a text color,
* depending on the type of button. */
--ink-color: var(--google-blue-600);
--ripple-opacity-action: .32;
--ripple-opacity: .1;
--text-color-action: white;
--text-color: var(--google-blue-600);
}
:host-context([dark]) {
/* Only in dark. */
--active-bg: black linear-gradient(rgba(255, 255, 255, .06),
rgba(255, 255, 255, .06));
--active-shadow-rgb: 0, 0, 0;
--active-shadow-action-rgb: var(--google-blue-refresh-500-rgb);
--bg-action: var(--google-blue-refresh-300);
--border-color: var(--google-grey-refresh-700);
--disabled-bg-action: var(--google-grey-800);
/* TODO(dbeam): get --disabled-bg from Namrata. */
--disabled-bg: transparent;
--disabled-border-color: var(--google-grey-800);
--focus-shadow-color: rgba(var(--google-blue-refresh-300-rgb), .5);
--hover-bg-action: var(--bg-action)
linear-gradient(rgba(0, 0, 0, .08), rgba(0, 0, 0, .08));
--hover-bg-color: rgba(var(--google-blue-refresh-300-rgb), .08);
--ink-color-action: black;
--ink-color: var(--google-blue-refresh-300);
--ripple-opacity-action: .16;
--ripple-opacity: .16;
--text-color-action: var(--google-grey-900);
--text-color: var(--google-blue-refresh-300);
}
:host {
--paper-ripple-opacity: var(--ripple-opacity);
-webkit-tap-highlight-color: transparent;
align-items: center;
border: 1px solid var(--border-color);
border-radius: 4px;
box-sizing: border-box;
color: var(--text-color);
cursor: pointer;
display: inline-flex;
font-weight: 500;
height: var(--cr-button-height);
min-width: 5.14em;
outline-width: 0;
padding: 8px 16px;
position: relative;
user-select: none;
white-space: nowrap;
}
:host-context(.focus-outline-visible):host(:focus) {
box-shadow: 0 0 0 2px var(--focus-shadow-color);
}
:host(:active) {
background: var(--active-bg);
box-shadow:
0 1px 2px 0 rgba(var(--active-shadow-rgb), .3),
0 3px 6px 2px rgba(var(--active-shadow-rgb), .15);
}
:host(:hover) {
background-color: var(--hover-bg-color);
}
:host-context(html:not([dark])):host(:hover) {
border-color: var(--google-blue-refresh-100);
}
:host(.action-button) {
--ink-color: var(--ink-color-action);
--paper-ripple-opacity: var(--ripple-opacity-action);
background-color: var(--bg-action);
border: none;
color: var(--text-color-action);
}
:host(.action-button:active) {
box-shadow:
0 1px 2px 0 rgba(var(--active-shadow-action-rgb), .3),
0 3px 6px 2px rgba(var(--active-shadow-action-rgb), .15);
}
:host(.action-button:hover) {
background: var(--hover-bg-action);
}
:host-context(html:not([dark])):host(.action-button:hover) {
box-shadow: 0 1px 2px 0 rgba(var(--google-blue-500-rgb), .3),
0 1px 3px 1px rgba(var(--google-blue-500-rgb), .15);
}
:host([disabled]) {
background-color: var(--disabled-bg);
border-color: var(--disabled-border-color);
color: var(--google-grey-600);
cursor: auto;
pointer-events: none;
}
:host(.action-button[disabled]) {
background-color: var(--disabled-bg-action);
border-color: transparent;
}
/* cancel-button is meant to be used within a cr-dialog */
:host(.cancel-button) {
margin-inline-end: 8px;
}
:host(.action-button),
:host(.cancel-button) {
line-height: 154%;
}
paper-ripple {
color: var(--ink-color);
}
</style>
<slot></slot>
</template>
<script src="cr_button.js"></script>
</dom-module>
// 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.
/**
* @fileoverview 'cr-button' is a button which displays slotted elements. It can
* be interacted with like a normal button using click as well as space and
* enter to effectively click the button and fire a 'click' event.
*/
Polymer({
is: 'cr-button',
behaviors: [
Polymer.PaperRippleBehavior,
],
properties: {
disabled: {
type: Boolean,
value: false,
reflectToAttribute: true,
observer: 'disabledChanged_',
},
},
hostAttributes: {
'aria-disabled': 'false',
role: 'button',
tabindex: 0,
},
listeners: {
click: 'onClick_',
keydown: 'onKeyDown_',
keyup: 'onKeyUp_',
pointerdown: 'onPointerDown_',
},
/** @override */
ready: function() {
cr.ui.FocusOutlineManager.forDocument(document);
},
/**
* @param {boolean} newValue
* @param {boolean} oldValue
* @private
*/
disabledChanged_: function(newValue, oldValue) {
if (!newValue && oldValue == undefined) {
return;
}
if (this.disabled) {
this.blur();
}
this.setAttribute('aria-disabled', Boolean(this.disabled));
this.setAttribute('tabindex', this.disabled ? -1 : 0);
},
/**
* @param {!Event} e
* @private
*/
onClick_: function(e) {
if (this.disabled) {
e.stopImmediatePropagation();
}
},
/**
* @param {!KeyboardEvent} e
* @private
*/
onKeyDown_: function(e) {
if (e.key != ' ' && e.key != 'Enter') {
return;
}
e.preventDefault();
e.stopPropagation();
if (e.repeat) {
return;
}
if (e.key == 'Enter') {
this.click();
} else {
this.getRipple().uiDownAction();
}
},
/**
* @param {!KeyboardEvent} e
* @private
*/
onKeyUp_: function(e) {
if (e.key == ' ' || e.key == 'Enter') {
e.preventDefault();
e.stopPropagation();
}
if (e.key == ' ') {
this.click();
this.getRipple().uiUpAction();
}
},
/** @private */
onPointerDown_: function() {
this.ensureRipple();
},
});
......@@ -7,7 +7,8 @@
<style>
/* Included here so we don't have to include "iron-positioning" in every
* stylesheet. See crbug.com/498405. */
[hidden] {
[hidden],
:host([hidden]) {
display: none !important;
}
</style>
......
......@@ -9,6 +9,14 @@
file="cr_elements/cr_action_menu/cr_action_menu.js"
type="chrome_html"
compress="gzip" />
<structure name="IDR_CR_ELEMENTS_CR_BUTTON_HTML"
file="cr_elements/cr_button/cr_button.html"
type="chrome_html"
compress="gzip" />
<structure name="IDR_CR_ELEMENTS_CR_BUTTON_JS"
file="cr_elements/cr_button/cr_button.js"
type="chrome_html"
compress="gzip" />
<structure name="IDR_CR_ELEMENTS_CR_CHECKBOX_HTML"
file="cr_elements/cr_checkbox/cr_checkbox.html"
type="chrome_html"
......
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