Convert tile button into more generic button (#15485)

* Add button group component

* Add focus style

* Use new component in tile card
This commit is contained in:
Paul Bottein 2023-02-20 16:25:53 +01:00 committed by GitHub
parent d5fb924cb4
commit cabbbcf9f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 308 additions and 83 deletions

View File

@ -0,0 +1,3 @@
---
title: Bar Button
---

View File

@ -0,0 +1,189 @@
import {
mdiFanSpeed1,
mdiFanSpeed2,
mdiFanSpeed3,
mdiLightbulb,
} from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { repeat } from "lit/directives/repeat";
import "../../../../src/components/ha-bar-button";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-bar-button-group";
type Button = {
label: string;
icon?: string;
class?: string;
disabled?: boolean;
};
const buttons: Button[] = [
{
label: "Button",
},
{
label: "Button and custom style",
class: "custom",
},
{
label: "Disabled Button",
disabled: true,
},
];
type ButtonGroup = {
vertical?: boolean;
class?: string;
};
const buttonGroups: ButtonGroup[] = [
{},
{
class: "custom-group",
},
];
@customElement("demo-components-ha-bar-button")
export class DemoHaBarButton extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card>
${repeat(
buttons,
(btn) => html`
<div class="card-content">
<pre>Config: ${JSON.stringify(btn)}</pre>
<ha-bar-button
class=${ifDefined(btn.class)}
label=${ifDefined(btn.label)}
disabled=${ifDefined(btn.disabled)}
>
<ha-svg-icon .path=${btn.icon || mdiLightbulb}></ha-svg-icon>
</ha-bar-button>
</div>
`
)}
</ha-card>
<ha-card>
${repeat(
buttonGroups,
(group) => html`
<div class="card-content">
<pre>Config: ${JSON.stringify(group)}</pre>
<ha-bar-button-group class=${ifDefined(group.class)}>
<ha-bar-button>
<ha-svg-icon
.path=${mdiFanSpeed1}
label="Speed 1"
></ha-svg-icon>
</ha-bar-button>
<ha-bar-button>
<ha-svg-icon
.path=${mdiFanSpeed2}
label="Speed 2"
></ha-svg-icon>
</ha-bar-button>
<ha-bar-button>
<ha-svg-icon
.path=${mdiFanSpeed3}
label="Speed 3"
></ha-svg-icon>
</ha-bar-button>
</ha-bar-button-group>
</div>
`
)}
</ha-card>
<ha-card>
<div class="card-content">
<p class="title"><b>Vertical</b></p>
<div class="vertical-buttons">
${repeat(
buttonGroups,
(group) => html`
<ha-bar-button-group vertical class=${ifDefined(group.class)}>
<ha-bar-button>
<ha-svg-icon
.path=${mdiFanSpeed1}
label="Speed 1"
></ha-svg-icon>
</ha-bar-button>
<ha-bar-button>
<ha-svg-icon
.path=${mdiFanSpeed2}
label="Speed 2"
></ha-svg-icon>
</ha-bar-button>
<ha-bar-button>
<ha-svg-icon
.path=${mdiFanSpeed3}
label="Speed 3"
></ha-svg-icon>
</ha-bar-button>
</ha-bar-button-group>
`
)}
</div>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
pre {
margin-top: 0;
margin-bottom: 8px;
}
p {
margin: 0;
}
label {
font-weight: 600;
}
.custom {
--button-bar-icon-color: var(--primary-color);
--button-bar-background-color: var(--primary-color);
--button-bar-background-opacity: 0.2;
--button-bar-border-radius: 18px;
height: 100px;
width: 100px;
}
.custom-group {
--button-bar-group-thickness: 100px;
--button-bar-group-border-radius: 18px;
--button-bar-group-spacing: 20px;
}
.custom-group ha-bar-button {
--button-bar-border-radius: 18px;
--mdc-icon-size: 32px;
}
.vertical-buttons {
height: 300px;
display: flex;
flex-direction: row;
justify-content: space-between;
}
p.title {
margin-bottom: 12px;
}
.vertical-switches > *:not(:last-child) {
margin-right: 4px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-bar-button": DemoHaBarButton;
}
}

View File

@ -0,0 +1,63 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-bar-button-group")
export class HaBarButtonGroup extends LitElement {
@property({ type: Boolean, reflect: true })
public vertical = false;
protected render(): TemplateResult {
return html`
<div class="container">
<slot></slot>
</div>
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
--button-bar-group-spacing: 12px;
--button-bar-group-thickness: 40px;
height: var(--button-bar-group-thickness);
width: auto;
display: block;
}
.container {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
::slotted(*) {
flex: 1;
height: 100%;
width: 100%;
}
::slotted(*:not(:last-child)) {
margin-right: var(--button-bar-group-spacing);
margin-inline-end: var(--button-bar-group-spacing);
margin-inline-start: initial;
direction: var(--direction);
}
:host([vertical]) {
width: var(--button-bar-group-thickness);
height: auto;
}
:host([vertical]) .container {
flex-direction: column;
}
:host([vertical]) ::slotted(ha-bar-button:not(:last-child)) {
margin-right: initial;
margin-inline-end: initial;
margin-bottom: var(--button-bar-group-spacing);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-bar-button-group": HaBarButtonGroup;
}
}

View File

@ -9,11 +9,9 @@ import {
state,
} from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import "../ha-icon";
import "../ha-svg-icon";
@customElement("ha-tile-button")
export class HaTileButton extends LitElement {
@customElement("ha-bar-button")
export class HaBarButton extends LitElement {
@property({ type: Boolean, reflect: true }) disabled = false;
@property() public label?: string;
@ -28,7 +26,7 @@ export class HaTileButton extends LitElement {
type="button"
class="button"
aria-label=${ifDefined(this.label)}
.title=${this.label}
title=${ifDefined(this.label)}
.disabled=${Boolean(this.disabled)}
@focus=${this.handleRippleFocus}
@blur=${this.handleRippleBlur}
@ -81,9 +79,11 @@ export class HaTileButton extends LitElement {
static get styles(): CSSResultGroup {
return css`
:host {
--tile-button-icon-color: var(--primary-text-color);
--tile-button-background-color: var(--disabled-color);
--tile-button-background-opacity: 0.2;
display: block;
--button-bar-icon-color: var(--primary-text-color);
--button-bar-background-color: var(--disabled-color);
--button-bar-background-opacity: 0.2;
--button-bar-border-radius: 10px;
width: 40px;
height: 40px;
-webkit-tap-highlight-color: transparent;
@ -97,7 +97,7 @@ export class HaTileButton extends LitElement {
justify-content: center;
width: 100%;
height: 100%;
border-radius: 10px;
border-radius: var(--button-bar-border-radius);
border: none;
margin: 0;
padding: 0;
@ -106,7 +106,8 @@ export class HaTileButton extends LitElement {
outline: none;
overflow: hidden;
background: none;
--mdc-ripple-color: var(--tile-button-background-color);
z-index: 1;
--mdc-ripple-color: var(--button-bar-background-color);
}
.button::before {
content: "";
@ -115,22 +116,21 @@ export class HaTileButton extends LitElement {
left: 0;
height: 100%;
width: 100%;
background-color: var(--tile-button-background-color);
background-color: var(--button-bar-background-color);
transition: background-color 180ms ease-in-out,
opacity 180ms ease-in-out;
opacity: var(--tile-button-background-opacity);
opacity: var(--button-bar-background-opacity);
}
.button ::slotted(*) {
--mdc-icon-size: 20px;
transition: color 180ms ease-in-out;
color: var(--tile-button-icon-color);
color: var(--button-bar-icon-color);
pointer-events: none;
}
.button:disabled {
cursor: not-allowed;
--tile-button-background-color: var(--disabled-color);
--tile-button-icon-color: var(--disabled-text-color);
--tile-button-background-opacity: 0.2;
--button-bar-background-color: var(--disabled-color);
--button-bar-icon-color: var(--disabled-text-color);
--button-bar-background-opacity: 0.2;
}
`;
}
@ -138,6 +138,6 @@ export class HaTileButton extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"ha-tile-button": HaTileButton;
"ha-bar-button": HaBarButton;
}
}

View File

@ -7,7 +7,8 @@ import {
computeOpenIcon,
} from "../../../common/entity/cover_icon";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/tile/ha-tile-button";
import "../../../components/ha-bar-button";
import "../../../components/ha-bar-button-group";
import {
canClose,
canOpen,
@ -69,10 +70,10 @@ class HuiCoverOpenCloseTileFeature
}
return html`
<div class="container">
<ha-bar-button-group>
${supportsFeature(this.stateObj, CoverEntityFeature.OPEN)
? html`
<ha-tile-button
<ha-bar-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.open_cover"
)}
@ -82,12 +83,12 @@ class HuiCoverOpenCloseTileFeature
<ha-svg-icon
.path=${computeOpenIcon(this.stateObj)}
></ha-svg-icon>
</ha-tile-button>
</ha-bar-button>
`
: null}
${supportsFeature(this.stateObj, CoverEntityFeature.STOP)
? html`
<ha-tile-button
<ha-bar-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.stop_cover"
)}
@ -95,12 +96,12 @@ class HuiCoverOpenCloseTileFeature
.disabled=${!canStop(this.stateObj)}
>
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
</ha-tile-button>
</ha-bar-button>
`
: null}
${supportsFeature(this.stateObj, CoverEntityFeature.CLOSE)
? html`
<ha-tile-button
<ha-bar-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.close_cover"
)}
@ -110,29 +111,18 @@ class HuiCoverOpenCloseTileFeature
<ha-svg-icon
.path=${computeCloseIcon(this.stateObj)}
></ha-svg-icon>
</ha-tile-button>
</ha-bar-button>
`
: undefined}
</div>
</ha-bar-button-group>
`;
}
static get styles() {
return css`
.container {
display: flex;
flex-direction: row;
padding: 0 12px 12px 12px;
width: auto;
}
ha-tile-button {
flex: 1;
}
ha-tile-button:not(:last-child) {
margin-right: 12px;
margin-inline-end: 12px;
margin-inline-start: initial;
direction: var(--direction);
ha-bar-button-group {
margin: 0 12px 12px 12px;
--button-bar-group-spacing: 12px;
}
`;
}

View File

@ -3,7 +3,8 @@ import { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/tile/ha-tile-button";
import "../../../components/ha-bar-button";
import "../../../components/ha-bar-button-group";
import {
canCloseTilt,
canOpenTilt,
@ -65,10 +66,10 @@ class HuiCoverTiltTileFeature
}
return html`
<div class="container">
<ha-bar-button-group>
${supportsFeature(this.stateObj, CoverEntityFeature.OPEN_TILT)
? html`
<ha-tile-button
<ha-bar-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.open_tilt_cover"
)}
@ -76,12 +77,12 @@ class HuiCoverTiltTileFeature
.disabled=${!canOpenTilt(this.stateObj)}
>
<ha-svg-icon .path=${mdiArrowTopRight}></ha-svg-icon>
</ha-tile-button>
</ha-bar-button>
`
: null}
${supportsFeature(this.stateObj, CoverEntityFeature.STOP_TILT)
? html`
<ha-tile-button
<ha-bar-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.stop_cover"
)}
@ -89,12 +90,12 @@ class HuiCoverTiltTileFeature
.disabled=${!canStopTilt(this.stateObj)}
>
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
</ha-tile-button>
</ha-bar-button>
`
: null}
${supportsFeature(this.stateObj, CoverEntityFeature.CLOSE_TILT)
? html`
<ha-tile-button
<ha-bar-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.close_tilt_cover"
)}
@ -102,29 +103,18 @@ class HuiCoverTiltTileFeature
.disabled=${!canCloseTilt(this.stateObj)}
>
<ha-svg-icon .path=${mdiArrowBottomLeft}></ha-svg-icon>
</ha-tile-button>
</ha-bar-button>
`
: undefined}
</div>
</ha-bar-button-group>
`;
}
static get styles() {
return css`
.container {
display: flex;
flex-direction: row;
padding: 0 12px 12px 12px;
width: auto;
}
ha-tile-button {
flex: 1;
}
ha-tile-button:not(:last-child) {
margin-right: 12px;
margin-inline-end: 12px;
margin-inline-start: initial;
direction: var(--direction);
ha-bar-button-group {
margin: 0 12px 12px 12px;
--button-bar-group-spacing: 12px;
}
`;
}

View File

@ -11,7 +11,8 @@ import { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/tile/ha-tile-button";
import "../../../components/ha-bar-button";
import "../../../components/ha-bar-button-group";
import { UNAVAILABLE } from "../../../data/entity";
import {
canReturnHome,
@ -167,7 +168,7 @@ class HuiVacuumCommandTileFeature
const stateObj = this.stateObj as VacuumEntity;
return html`
<div class="container">
<ha-bar-button-group>
${VACUUM_COMMANDS.filter(
(command) =>
supportsVacuumCommand(stateObj, command) &&
@ -175,7 +176,7 @@ class HuiVacuumCommandTileFeature
).map((command) => {
const button = VACUUM_COMMANDS_BUTTONS[command](stateObj);
return html`
<ha-tile-button
<ha-bar-button
.entry=${button}
.label=${this.hass!.localize(
// @ts-ignore
@ -185,29 +186,18 @@ class HuiVacuumCommandTileFeature
.disabled=${button.disabled || stateObj.state === UNAVAILABLE}
>
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
</ha-tile-button>
</ha-bar-button>
`;
})}
</div>
</ha-bar-button-group>
`;
}
static get styles() {
return css`
.container {
display: flex;
flex-direction: row;
padding: 0 12px 12px 12px;
width: auto;
}
ha-tile-button {
flex: 1;
}
ha-tile-button:not(:last-child) {
margin-right: 12px;
margin-inline-end: 12px;
margin-inline-start: initial;
direction: var(--direction);
ha-bar-button-group {
margin: 0 12px 12px 12px;
--button-bar-group-spacing: 12px;
}
`;
}