Move badge styling into ha-badge component to reuse it (#21864)

* Move badge styling into ha-badge component to reuse it

* Fix error badge

* Update src/components/ha-badge.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Paul Bottein 2024-09-04 10:48:55 +02:00 committed by GitHub
parent b69f0964c9
commit 7aa7019386
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 204 additions and 170 deletions

155
src/components/ha-badge.ts Normal file
View File

@ -0,0 +1,155 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import "./ha-ripple";
type BadgeType = "badge" | "button";
@customElement("ha-badge")
export class HaBadge extends LitElement {
@property() public type: BadgeType = "badge";
@property() public label?: string;
@property({ type: Boolean, attribute: "icon-only" }) iconOnly = false;
protected render() {
const label = this.label;
return html`
<div
class="badge ${classMap({
"icon-only": this.iconOnly,
})}"
role=${ifDefined(this.type === "button" ? "button" : undefined)}
tabindex=${ifDefined(this.type === "button" ? "0" : undefined)}
>
<ha-ripple .disabled=${this.type !== "button"}></ha-ripple>
<slot name="icon"></slot>
${this.iconOnly
? nothing
: html`<span class="info">
${label ? html`<span class="label">${label}</span>` : nothing}
<span class="content"><slot></slot></span>
</span>`}
</div>
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
--badge-color: var(--secondary-text-color);
-webkit-tap-highlight-color: transparent;
}
.badge {
position: relative;
--ha-ripple-color: var(--badge-color);
--ha-ripple-hover-opacity: 0.04;
--ha-ripple-pressed-opacity: 0.12;
transition:
box-shadow 180ms ease-in-out,
border-color 180ms ease-in-out;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
height: var(--ha-badge-size, 36px);
min-width: var(--ha-badge-size, 36px);
padding: 0px 12px;
box-sizing: border-box;
width: auto;
border-radius: var(
--ha-badge-border-radius,
calc(var(--ha-badge-size, 36px) / 2)
);
background: var(
--ha-card-background,
var(--card-background-color, white)
);
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
backdrop-filter: var(--ha-card-backdrop-filter, none);
border-width: var(--ha-card-border-width, 1px);
box-shadow: var(--ha-card-box-shadow, none);
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
}
.badge:focus-visible {
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
--shadow-focus: 0 0 0 1px var(--badge-color);
border-color: var(--badge-color);
box-shadow: var(--shadow-default), var(--shadow-focus);
}
[role="button"] {
cursor: pointer;
}
[role="button"]:focus {
outline: none;
}
.info {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-inline-start: initial;
text-align: center;
font-family: Roboto;
}
.label {
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 10px;
letter-spacing: 0.1px;
color: var(--secondary-text-color);
}
.content {
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px;
letter-spacing: 0.1px;
color: var(--primary-text-color);
}
::slotted([slot="icon"]) {
--mdc-icon-size: 18px;
color: var(--badge-color);
line-height: 0;
margin-left: -4px;
margin-right: 0;
margin-inline-start: -4px;
margin-inline-end: 0;
}
::slotted(img[slot="icon"]) {
width: 30px;
height: 30px;
border-radius: 50%;
object-fit: cover;
overflow: hidden;
margin-left: -10px;
margin-right: 0;
margin-inline-start: -10px;
margin-inline-end: 0;
}
.badge.icon-only {
padding: 0;
}
.badge.icon-only ::slotted([slot="icon"]) {
margin-left: 0;
margin-right: 0;
margin-inline-start: 0;
margin-inline-end: 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-badge": HaBadge;
}
}

View File

@ -3,7 +3,6 @@ import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color"; import { computeCssColor } from "../../../common/color/compute-color";
@ -12,6 +11,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { stateActive } from "../../../common/entity/state_active"; import { stateActive } from "../../../common/entity/state_active";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-badge";
import "../../../components/ha-ripple"; import "../../../components/ha-ripple";
import "../../../components/ha-state-icon"; import "../../../components/ha-state-icon";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
@ -160,15 +160,14 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
if (!stateObj) { if (!stateObj) {
return html` return html`
<div class="badge error"> <ha-badge .label=${entityId} class="error">
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon> <ha-svg-icon
<span class="info"> slot="icon"
<span class="label">${entityId}</span> .hass=${this.hass}
<span class="content"> .path=${mdiAlertCircle}
${this.hass.localize("ui.badge.entity.not_found")} ></ha-svg-icon>
</span> ${this.hass.localize("ui.badge.entity.not_found")}
</span> </ha-badge>
</div>
`; `;
} }
@ -204,42 +203,32 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
const content = showState ? stateDisplay : showName ? name : undefined; const content = showState ? stateDisplay : showName ? name : undefined;
return html` return html`
<div <ha-badge
style=${styleMap(style)} .type=${this.hasAction ? "button" : "badge"}
class="badge ${classMap({
active,
"no-info": !showState && !showName,
"no-icon": !showIcon,
})}"
@action=${this._handleAction} @action=${this._handleAction}
.actionHandler=${actionHandler({ .actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
role=${ifDefined(this.hasAction ? "button" : undefined)} .label=${label}
tabindex=${ifDefined(this.hasAction ? "0" : undefined)} .iconOnly=${!content}
style=${styleMap(style)}
class=${classMap({ active })}
> >
<ha-ripple .disabled=${!this.hasAction}></ha-ripple>
${showIcon ${showIcon
? imageUrl ? imageUrl
? html`<img src=${imageUrl} aria-hidden />` ? html`<img slot="icon" src=${imageUrl} aria-hidden />`
: html` : html`
<ha-state-icon <ha-state-icon
slot="icon"
.hass=${this.hass} .hass=${this.hass}
.stateObj=${stateObj} .stateObj=${stateObj}
.icon=${this._config.icon} .icon=${this._config.icon}
></ha-state-icon> ></ha-state-icon>
` `
: nothing} : nothing}
${content ${content}
? html` </ha-badge>
<span class="info">
${label ? html`<span class="label">${name}</span>` : nothing}
<span class="content">${content}</span>
</span>
`
: nothing}
</div>
`; `;
} }
@ -249,119 +238,15 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host { ha-badge {
--badge-color: var(--state-inactive-color); --badge-color: var(--state-inactive-color);
-webkit-tap-highlight-color: transparent;
} }
.badge.error { ha-badge.error {
--badge-color: var(--red-color); --badge-color: var(--red-color);
} }
.badge { ha-badge.active {
position: relative;
--ha-ripple-color: var(--badge-color);
--ha-ripple-hover-opacity: 0.04;
--ha-ripple-pressed-opacity: 0.12;
transition:
box-shadow 180ms ease-in-out,
border-color 180ms ease-in-out;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
height: var(--ha-badge-size, 36px);
min-width: var(--ha-badge-size, 36px);
padding: 0px 8px;
box-sizing: border-box;
width: auto;
border-radius: var(
--ha-badge-border-radius,
calc(var(--ha-badge-size, 36px) / 2)
);
background: var(
--ha-card-background,
var(--card-background-color, white)
);
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
backdrop-filter: var(--ha-card-backdrop-filter, none);
border-width: var(--ha-card-border-width, 1px);
box-shadow: var(--ha-card-box-shadow, none);
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
--mdc-icon-size: 18px;
text-align: center;
font-family: Roboto;
}
.badge:focus-visible {
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
--shadow-focus: 0 0 0 1px var(--badge-color);
border-color: var(--badge-color);
box-shadow: var(--shadow-default), var(--shadow-focus);
}
button,
[role="button"] {
cursor: pointer;
}
button:focus,
[role="button"]:focus {
outline: none;
}
.badge.active {
--badge-color: var(--primary-color); --badge-color: var(--primary-color);
} }
.info {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-right: 4px;
padding-inline-end: 4px;
padding-inline-start: initial;
}
.label {
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 10px;
letter-spacing: 0.1px;
color: var(--secondary-text-color);
}
.content {
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px;
letter-spacing: 0.1px;
color: var(--primary-text-color);
}
ha-state-icon,
ha-svg-icon {
color: var(--badge-color);
line-height: 0;
}
img {
width: 30px;
height: 30px;
border-radius: 50%;
object-fit: cover;
overflow: hidden;
}
.badge.no-info {
padding: 0;
}
.badge:not(.no-icon):not(.no-info) img {
margin-left: -6px;
margin-inline-start: -6px;
margin-inline-end: initial;
}
.badge.no-icon .info {
padding-right: 4px;
padding-left: 4px;
padding-inline-end: 4px;
padding-inline-start: 4px;
}
`; `;
} }
} }

View File

@ -2,12 +2,11 @@ import { mdiAlertCircle } from "@mdi/js";
import { dump } from "js-yaml"; import { dump } from "js-yaml";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import "../../../components/ha-label-badge"; import "../../../components/ha-badge";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { showAlertDialog } from "../custom-card-helpers"; import { showAlertDialog } from "../custom-card-helpers";
import { LovelaceBadge } from "../types"; import { LovelaceBadge } from "../types";
import { HuiEntityBadge } from "./hui-entity-badge";
import { ErrorBadgeConfig } from "./types"; import { ErrorBadgeConfig } from "./types";
export const createErrorBadgeElement = (config) => { export const createErrorBadgeElement = (config) => {
@ -55,41 +54,36 @@ export class HuiErrorBadge extends LitElement implements LovelaceBadge {
} }
return html` return html`
<button class="badge error" @click=${this._viewDetail}> <ha-badge
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon> class="error"
<ha-ripple></ha-ripple> @click=${this._viewDetail}
<span class="content"> type="button"
<span class="name">Error</span> label="Error"
<span class="state">${this._config.error}</span> >
</span> <ha-svg-icon slot="icon" .path=${mdiAlertCircle}></ha-svg-icon>
</button> <div class="content">${this._config.error}</div>
</ha-badge>
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return css`
HuiEntityBadge.styles, ha-badge {
css` --badge-color: var(--error-color);
.badge.error { --ha-card-border-color: var(--error-color);
--badge-color: var(--error-color); }
border-color: var(--badge-color); .content {
} max-width: 100px;
ha-svg-icon { overflow: hidden;
color: var(--badge-color); text-overflow: ellipsis;
} white-space: nowrap;
.state { }
max-width: 100px; pre {
overflow: hidden; font-family: var(--code-font-family, monospace);
text-overflow: ellipsis; white-space: break-spaces;
white-space: nowrap; user-select: text;
} }
pre { `;
font-family: var(--code-font-family, monospace);
white-space: break-spaces;
user-select: text;
}
`,
];
} }
} }