mirror of
https://github.com/home-assistant/frontend.git
synced 2026-02-15 07:25:54 +00:00
Create reusable ha tile container component. (#29038)
* Create reusable component for tile based card * Fix icon interaction * Add icon and iconPath props * Migrate discovered devices card * Refactor * Share card style
This commit is contained in:
155
src/components/tile/ha-tile-container.ts
Normal file
155
src/components/tile/ha-tile-container.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import type { ActionHandlerOptions } from "../../data/lovelace/action_handler";
|
||||
import { actionHandler } from "../../panels/lovelace/common/directives/action-handler-directive";
|
||||
import "../ha-ripple";
|
||||
|
||||
@customElement("ha-tile-container")
|
||||
export class HaTileContainer extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public featurePosition: "bottom" | "inline" = "bottom";
|
||||
|
||||
@property({ type: Boolean })
|
||||
public vertical = false;
|
||||
|
||||
@property({ type: Boolean, attribute: false })
|
||||
public interactive = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public actionHandlerOptions?: ActionHandlerOptions;
|
||||
|
||||
private _handleFocus(ev: FocusEvent) {
|
||||
if ((ev.target as HTMLElement).matches(":focus-visible")) {
|
||||
this.setAttribute("focused", "");
|
||||
}
|
||||
}
|
||||
|
||||
private _handleBlur() {
|
||||
this.removeAttribute("focused");
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const containerOrientationClass =
|
||||
this.featurePosition === "inline" ? "horizontal" : "";
|
||||
const contentClasses = { vertical: this.vertical };
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="background"
|
||||
role=${ifDefined(this.interactive ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this.interactive ? "0" : undefined)}
|
||||
aria-labelledby="info"
|
||||
.actionHandler=${actionHandler(this.actionHandlerOptions)}
|
||||
@focus=${this._handleFocus}
|
||||
@blur=${this._handleBlur}
|
||||
>
|
||||
<ha-ripple .disabled=${!this.interactive}></ha-ripple>
|
||||
</div>
|
||||
<div class="container ${containerOrientationClass}">
|
||||
<div class="content ${classMap(contentClasses)}">
|
||||
<slot name="icon"></slot>
|
||||
<slot name="info" id="info"></slot>
|
||||
</div>
|
||||
<slot name="features"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
--ha-ripple-color: var(--tile-color);
|
||||
--ha-ripple-hover-opacity: 0.04;
|
||||
--ha-ripple-pressed-opacity: 0.12;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.container.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.vertical ::slotted([slot="info"]) {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
::slotted([slot="icon"]) {
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
}
|
||||
::slotted([slot="icon"]:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
::slotted([slot="info"]) {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
::slotted([slot="features"]) {
|
||||
padding: 0 var(--ha-space-3) var(--ha-space-3) var(--ha-space-3);
|
||||
}
|
||||
|
||||
.container.horizontal ::slotted([slot="features"]) {
|
||||
width: calc(50% - var(--column-gap, 0px) / 2 - var(--ha-space-3));
|
||||
flex: none;
|
||||
--feature-height: var(--ha-space-9);
|
||||
padding: 0 var(--ha-space-3);
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-tile-container": HaTileContainer;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import type { ActionHandlerOptions } from "../../data/lovelace/action_handler";
|
||||
import { actionHandler } from "../../panels/lovelace/common/directives/action-handler-directive";
|
||||
import "../ha-icon";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
@@ -13,34 +16,53 @@ import "../ha-svg-icon";
|
||||
* A tile icon component, used in tile card in Home Assistant to display an icon or image.
|
||||
*
|
||||
* @slot - Additional content (for example, a badge).
|
||||
* @slot icon - The icon container (usually for icons).
|
||||
* @slot icon - The icon container (usually for custom icons like ha-state-icon).
|
||||
*
|
||||
* @cssprop --ha-tile-icon-border-radius - The border radius of the tile icon. defaults to `var(--ha-border-radius-pill)`.
|
||||
*
|
||||
* @attr {boolean} interactive - Whether the icon is interactive (hover and focus styles).
|
||||
* @attr {string} image-url - The URL of the image to display instead of an icon.
|
||||
*/
|
||||
@customElement("ha-tile-icon")
|
||||
export class HaTileIcon extends LitElement {
|
||||
@property({ type: Boolean, reflect: true })
|
||||
@property({ type: Boolean, reflect: true, attribute: "interactive" })
|
||||
public interactive = false;
|
||||
|
||||
@property({ attribute: "image-url", type: String })
|
||||
public imageUrl?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@property({ type: String })
|
||||
public icon?: string;
|
||||
|
||||
@property({ type: String, attribute: "icon-path" })
|
||||
public iconPath?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public actionHandlerOptions?: ActionHandlerOptions;
|
||||
|
||||
private _renderIcon() {
|
||||
if (this.imageUrl) {
|
||||
return html`
|
||||
<div class="container">
|
||||
<img alt="" src=${this.imageUrl} />
|
||||
</div>
|
||||
<slot></slot>
|
||||
`;
|
||||
return html`<img alt="" src=${this.imageUrl} />`;
|
||||
}
|
||||
if (this.icon) {
|
||||
return html`<ha-icon .icon=${this.icon}></ha-icon>`;
|
||||
}
|
||||
if (this.iconPath) {
|
||||
return html`<ha-svg-icon .path=${this.iconPath}></ha-svg-icon>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const hasImage = Boolean(this.imageUrl);
|
||||
|
||||
return html`
|
||||
<div class="container ${this.interactive ? "background" : ""}">
|
||||
<slot name="icon"></slot>
|
||||
<div
|
||||
class="container ${this.interactive && !hasImage ? "background" : ""}"
|
||||
role=${ifDefined(this.interactive ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this.interactive ? "0" : undefined)}
|
||||
.actionHandler=${actionHandler(this.actionHandlerOptions)}
|
||||
>
|
||||
<slot name="icon">${this._renderIcon()}</slot>
|
||||
</div>
|
||||
<slot></slot>
|
||||
`;
|
||||
@@ -60,6 +82,11 @@ export class HaTileIcon extends LitElement {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
transition: transform 180ms ease-in-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
:host([interactive]) {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
pointer-events: auto;
|
||||
}
|
||||
:host([interactive]:active) {
|
||||
transform: scale(1.2);
|
||||
@@ -78,9 +105,16 @@ export class HaTileIcon extends LitElement {
|
||||
overflow: hidden;
|
||||
transition: box-shadow 180ms ease-in-out;
|
||||
}
|
||||
:host([interactive]:focus-visible) .container {
|
||||
.container:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--tile-icon-color);
|
||||
}
|
||||
.container:focus {
|
||||
outline: none;
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.container.background::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -94,7 +128,9 @@ export class HaTileIcon extends LitElement {
|
||||
opacity 180ms ease-in-out;
|
||||
opacity: var(--tile-icon-opacity);
|
||||
}
|
||||
.container ::slotted([slot="icon"]) {
|
||||
.container ::slotted([slot="icon"]),
|
||||
.container ha-icon,
|
||||
.container ha-svg-icon {
|
||||
display: flex;
|
||||
color: var(--tile-icon-color);
|
||||
transition: color 180ms ease-in-out;
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
@@ -30,21 +28,20 @@ import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-domain-icon";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/tile/ha-tile-badge";
|
||||
import "../../../components/tile/ha-tile-container";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../card-features/hui-card-features";
|
||||
import type { LovelaceCardFeatureContext } from "../card-features/types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import type {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceGridOptions,
|
||||
} from "../types";
|
||||
import { tileCardStyle } from "./tile/tile-card-style";
|
||||
import type { AreaCardConfig } from "./types";
|
||||
|
||||
export const DEFAULT_ASPECT_RATIO = "16:9";
|
||||
@@ -548,9 +545,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
const icon = area.icon;
|
||||
const icon = area.icon || undefined;
|
||||
|
||||
const name = this._config.name || computeAreaName(area);
|
||||
|
||||
@@ -560,9 +555,6 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
const featurePosition = this._featurePosition(this._config);
|
||||
const features = this._displayedFeatures(this._config);
|
||||
|
||||
const containerOrientationClass =
|
||||
featurePosition === "inline" ? "horizontal" : "";
|
||||
|
||||
const displayType = this._config.display_type || "picture";
|
||||
|
||||
const cameraEntityId =
|
||||
@@ -582,16 +574,6 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
|
||||
return html`
|
||||
<ha-card style=${styleMap(style)}>
|
||||
<div
|
||||
class="background"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler()}
|
||||
role=${ifDefined(this._hasCardAction ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this._hasCardAction ? "0" : undefined)}
|
||||
aria-labelledby="info"
|
||||
>
|
||||
<ha-ripple .disabled=${!this._hasCardAction}></ha-ripple>
|
||||
</div>
|
||||
${displayType === "compact"
|
||||
? nothing
|
||||
: html`
|
||||
@@ -628,30 +610,30 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
${this._renderAlertSensors()}
|
||||
</div>
|
||||
`}
|
||||
<div class="container ${containerOrientationClass}">
|
||||
<div class="content ${classMap(contentClasses)}">
|
||||
<ha-tile-icon>
|
||||
${displayType === "compact"
|
||||
? this._renderAlertSensorBadge()
|
||||
: nothing}
|
||||
${icon
|
||||
? html`<ha-icon slot="icon" .icon=${icon}></ha-icon>`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info
|
||||
id="info"
|
||||
.primary=${primary}
|
||||
.secondary=${secondary}
|
||||
></ha-tile-info>
|
||||
</div>
|
||||
<ha-tile-container
|
||||
.featurePosition=${featurePosition}
|
||||
.vertical=${Boolean(this._config.vertical)}
|
||||
.interactive=${Boolean(this._hasCardAction)}
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-tile-icon
|
||||
slot="icon"
|
||||
.icon=${icon}
|
||||
.iconPath=${icon ? undefined : mdiTextureBox}
|
||||
>
|
||||
${displayType === "compact"
|
||||
? this._renderAlertSensorBadge()
|
||||
: nothing}
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info
|
||||
slot="info"
|
||||
.primary=${primary}
|
||||
.secondary=${secondary}
|
||||
></ha-tile-info>
|
||||
${features.length > 0
|
||||
? html`
|
||||
<hui-card-features
|
||||
slot="features"
|
||||
.hass=${this.hass}
|
||||
.context=${this._featureContext}
|
||||
.color=${this._config.color}
|
||||
@@ -660,181 +642,100 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
></hui-card-features>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-tile-container>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--tile-color: var(--state-icon-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
ha-card:has(.background:focus-visible) {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--tile-color);
|
||||
border-color: var(--tile-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
ha-card {
|
||||
--ha-ripple-color: var(--tile-color);
|
||||
--ha-ripple-hover-opacity: 0.04;
|
||||
--ha-ripple-pressed-opacity: 0.12;
|
||||
height: 100%;
|
||||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
border-end-end-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.picture {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
}
|
||||
.picture hui-image {
|
||||
height: 100%;
|
||||
}
|
||||
.picture .icon-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
--mdc-icon-size: var(--ha-space-12);
|
||||
color: var(--tile-color);
|
||||
}
|
||||
.picture .icon-container::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--tile-color);
|
||||
opacity: 0.12;
|
||||
}
|
||||
.container {
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.header + .container {
|
||||
height: auto;
|
||||
flex: none;
|
||||
}
|
||||
.container.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.vertical ha-tile-info {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
}
|
||||
ha-tile-badge {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
inset-inline-end: 3px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
ha-tile-info {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
hui-card-features {
|
||||
--feature-color: var(--tile-color);
|
||||
padding: 0 var(--ha-space-3) var(--ha-space-3) var(--ha-space-3);
|
||||
}
|
||||
.container.horizontal hui-card-features {
|
||||
width: calc(50% - var(--column-gap, 0px) / 2 - var(--ha-space-3));
|
||||
flex: none;
|
||||
--feature-height: var(--ha-space-9);
|
||||
padding: 0 var(--ha-space-3);
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
.alert-badge {
|
||||
--tile-badge-background-color: var(--orange-color);
|
||||
}
|
||||
.alerts {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--ha-space-2);
|
||||
padding: var(--ha-space-2);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.alert {
|
||||
background-color: var(--orange-color);
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
width: var(--ha-space-6);
|
||||
height: var(--ha-space-6);
|
||||
padding: 2px;
|
||||
box-sizing: border-box;
|
||||
--mdc-icon-size: var(--ha-space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
}
|
||||
`;
|
||||
static styles = [
|
||||
tileCardStyle,
|
||||
css`
|
||||
:host {
|
||||
--tile-color: var(--state-icon-color);
|
||||
}
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.header {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
border-end-end-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.picture {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
}
|
||||
.picture hui-image {
|
||||
height: 100%;
|
||||
}
|
||||
.picture .icon-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
--mdc-icon-size: var(--ha-space-12);
|
||||
color: var(--tile-color);
|
||||
}
|
||||
.picture .icon-container::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--tile-color);
|
||||
opacity: 0.12;
|
||||
}
|
||||
.header + ha-tile-container {
|
||||
height: auto;
|
||||
flex: none;
|
||||
}
|
||||
ha-tile-badge {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
inset-inline-end: 3px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
hui-card-features {
|
||||
--feature-color: var(--tile-color);
|
||||
}
|
||||
.alert-badge {
|
||||
--tile-badge-background-color: var(--orange-color);
|
||||
}
|
||||
.alerts {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--ha-space-2);
|
||||
padding: var(--ha-space-2);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.alert {
|
||||
background-color: var(--orange-color);
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
width: var(--ha-space-6);
|
||||
height: var(--ha-space-6);
|
||||
padding: 2px;
|
||||
box-sizing: border-box;
|
||||
--mdc-icon-size: var(--ha-space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { mdiDevices } from "@mdi/js";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/tile/ha-tile-container";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
import {
|
||||
@@ -20,14 +17,12 @@ import type { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import type { LovelaceCard, LovelaceGridOptions } from "../types";
|
||||
import { tileCardStyle } from "./tile/tile-card-style";
|
||||
import type { DiscoveredDevicesCardConfig } from "./types";
|
||||
|
||||
const ICON = mdiDevices;
|
||||
|
||||
@customElement("hui-discovered-devices-card")
|
||||
export class HuiDiscoveredDevicesCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
@@ -159,121 +154,36 @@ export class HuiDiscoveredDevicesCard
|
||||
})
|
||||
: this.hass.localize("ui.card.discovered-devices.no_devices");
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<div
|
||||
class="background"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
<ha-tile-container
|
||||
.vertical=${Boolean(this._config.vertical)}
|
||||
.interactive=${this._hasCardAction}
|
||||
.actionHandlerOptions=${{
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
role=${ifDefined(this._hasCardAction ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this._hasCardAction ? "0" : undefined)}
|
||||
aria-labelledby="info"
|
||||
}}
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-ripple .disabled=${!this._hasCardAction}></ha-ripple>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="content ${classMap(contentClasses)}">
|
||||
<ha-tile-icon>
|
||||
<ha-svg-icon slot="icon" .path=${ICON}></ha-svg-icon>
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info
|
||||
id="info"
|
||||
.primary=${label}
|
||||
.secondary=${secondary}
|
||||
></ha-tile-info>
|
||||
</div>
|
||||
</div>
|
||||
<ha-tile-icon slot="icon" .iconPath=${mdiDevices}></ha-tile-icon>
|
||||
<ha-tile-info
|
||||
slot="info"
|
||||
.primary=${label}
|
||||
.secondary=${secondary}
|
||||
></ha-tile-info>
|
||||
</ha-tile-container>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--tile-color: var(--primary-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
ha-card:has(.background:focus-visible) {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--tile-color);
|
||||
border-color: var(--tile-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
ha-card {
|
||||
--ha-ripple-color: var(--tile-color);
|
||||
--ha-ripple-hover-opacity: 0.04;
|
||||
--ha-ripple-pressed-opacity: 0.12;
|
||||
height: 100%;
|
||||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.vertical ha-tile-info {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
}
|
||||
ha-tile-info {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
static styles = [
|
||||
tileCardStyle,
|
||||
css`
|
||||
:host {
|
||||
--tile-color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -2,8 +2,6 @@ import { endOfDay, startOfDay } from "date-fns";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { calcDate } from "../../../common/datetime/calc_date";
|
||||
@@ -14,8 +12,7 @@ import {
|
||||
} from "../../../common/entity/entity_filter";
|
||||
import { formatNumber } from "../../../common/number/format_number";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/tile/ha-tile-container";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
import type { EnergyData } from "../../../data/energy";
|
||||
@@ -28,7 +25,6 @@ import {
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import {
|
||||
@@ -38,6 +34,7 @@ import {
|
||||
type HomeSummary,
|
||||
} from "../strategies/home/helpers/home-summaries";
|
||||
import type { LovelaceCard, LovelaceGridOptions } from "../types";
|
||||
import { tileCardStyle } from "./tile/tile-card-style";
|
||||
import type { HomeSummaryCard } from "./types";
|
||||
|
||||
const COLORS: Record<HomeSummary, string> = {
|
||||
@@ -269,8 +266,6 @@ export class HuiHomeSummaryCard
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
const color = computeCssColor(COLORS[this._config.summary]);
|
||||
|
||||
const style = {
|
||||
@@ -284,125 +279,34 @@ export class HuiHomeSummaryCard
|
||||
|
||||
return html`
|
||||
<ha-card style=${styleMap(style)}>
|
||||
<div
|
||||
class="background"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
<ha-tile-container
|
||||
.vertical=${Boolean(this._config.vertical)}
|
||||
.interactive=${this._hasCardAction}
|
||||
.actionHandlerOptions=${{
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
role=${ifDefined(this._hasCardAction ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this._hasCardAction ? "0" : undefined)}
|
||||
aria-labelledby="info"
|
||||
}}
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-ripple .disabled=${!this._hasCardAction}></ha-ripple>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="content ${classMap(contentClasses)}">
|
||||
<ha-tile-icon>
|
||||
<ha-icon slot="icon" .icon=${icon}></ha-icon>
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info
|
||||
id="info"
|
||||
.primary=${label}
|
||||
.secondary=${secondary}
|
||||
></ha-tile-info>
|
||||
</div>
|
||||
</div>
|
||||
<ha-tile-icon slot="icon" .icon=${icon}></ha-tile-icon>
|
||||
<ha-tile-info
|
||||
slot="info"
|
||||
.primary=${label}
|
||||
.secondary=${secondary}
|
||||
></ha-tile-info>
|
||||
</ha-tile-container>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--tile-color: var(--state-inactive-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
ha-card:has(.background:focus-visible) {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--tile-color);
|
||||
border-color: var(--tile-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
ha-card {
|
||||
--ha-ripple-color: var(--tile-color);
|
||||
--ha-ripple-hover-opacity: 0.04;
|
||||
--ha-ripple-pressed-opacity: 0.12;
|
||||
height: 100%;
|
||||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-card.active {
|
||||
--tile-color: var(--state-icon-color);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.container.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.vertical ha-tile-info {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
}
|
||||
|
||||
ha-tile-info {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
static styles = [
|
||||
tileCardStyle,
|
||||
css`
|
||||
:host {
|
||||
--tile-color: var(--state-inactive-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -12,9 +12,8 @@ import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-state-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/tile/ha-tile-container";
|
||||
import "../../../components/tile/ha-tile-badge";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
@@ -24,12 +23,12 @@ import "../../../state-display/state-display";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../card-features/hui-card-features";
|
||||
import type { LovelaceCardFeatureContext } from "../card-features/types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { tileCardStyle } from "./tile/tile-card-style";
|
||||
import type {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
@@ -253,8 +252,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
const name = computeLovelaceEntityName(
|
||||
this.hass,
|
||||
stateObj,
|
||||
@@ -287,58 +284,49 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
const featurePosition = this._featurePosition(this._config);
|
||||
const features = this._displayedFeatures(this._config);
|
||||
|
||||
const containerOrientationClass =
|
||||
featurePosition === "inline" ? "horizontal" : "";
|
||||
|
||||
return html`
|
||||
<ha-card style=${styleMap(style)} class=${classMap({ active })}>
|
||||
<div
|
||||
class="background"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
<ha-tile-container
|
||||
.featurePosition=${featurePosition}
|
||||
.vertical=${Boolean(this._config.vertical)}
|
||||
.interactive=${this._hasCardAction}
|
||||
.actionHandlerOptions=${{
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
role=${ifDefined(this._hasCardAction ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this._hasCardAction ? "0" : undefined)}
|
||||
aria-labelledby="info"
|
||||
}}
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-ripple .disabled=${!this._hasCardAction}></ha-ripple>
|
||||
</div>
|
||||
<div class="container ${containerOrientationClass}">
|
||||
<div class="content ${classMap(contentClasses)}">
|
||||
<ha-tile-icon
|
||||
role=${ifDefined(this._hasIconAction ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this._hasIconAction ? "0" : undefined)}
|
||||
@action=${this._handleIconAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config!.icon_hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.icon_double_tap_action),
|
||||
})}
|
||||
.interactive=${this._hasIconAction}
|
||||
.imageUrl=${imageUrl}
|
||||
data-domain=${ifDefined(domain)}
|
||||
data-state=${ifDefined(stateObj?.state)}
|
||||
class=${classMap({ image: Boolean(imageUrl) })}
|
||||
>
|
||||
<ha-state-icon
|
||||
slot="icon"
|
||||
.icon=${this._config.icon}
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></ha-state-icon>
|
||||
${renderTileBadge(stateObj, this.hass)}
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info id="info">
|
||||
<span slot="primary" class="primary">${name}</span>
|
||||
${stateDisplay
|
||||
? html`<span slot="secondary">${stateDisplay}</span>`
|
||||
: nothing}
|
||||
</ha-tile-info>
|
||||
</div>
|
||||
<ha-tile-icon
|
||||
slot="icon"
|
||||
@action=${this._handleIconAction}
|
||||
.actionHandlerOptions=${{
|
||||
hasHold: hasAction(this._config!.icon_hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.icon_double_tap_action),
|
||||
}}
|
||||
.interactive=${this._hasIconAction}
|
||||
.imageUrl=${imageUrl}
|
||||
data-domain=${ifDefined(domain)}
|
||||
data-state=${ifDefined(stateObj?.state)}
|
||||
class=${classMap({ image: Boolean(imageUrl) })}
|
||||
>
|
||||
<ha-state-icon
|
||||
slot="icon"
|
||||
.icon=${this._config.icon}
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></ha-state-icon>
|
||||
${renderTileBadge(stateObj, this.hass)}
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info slot="info">
|
||||
<span slot="primary" class="primary">${name}</span>
|
||||
${stateDisplay
|
||||
? html`<span slot="secondary">${stateDisplay}</span>`
|
||||
: nothing}
|
||||
</ha-tile-info>
|
||||
${features.length > 0
|
||||
? html`
|
||||
<hui-card-features
|
||||
slot="features"
|
||||
.hass=${this.hass}
|
||||
.context=${this._featureContext}
|
||||
.color=${this._config.color}
|
||||
@@ -346,148 +334,63 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
></hui-card-features>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-tile-container>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--tile-color: var(--state-inactive-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
ha-card:has(.background:focus-visible) {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--tile-color);
|
||||
border-color: var(--tile-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
ha-card {
|
||||
--ha-ripple-color: var(--tile-color);
|
||||
--ha-ripple-hover-opacity: 0.04;
|
||||
--ha-ripple-pressed-opacity: 0.12;
|
||||
height: 100%;
|
||||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-card.active {
|
||||
--tile-color: var(--state-icon-color);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.container.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.vertical ha-tile-info {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
}
|
||||
ha-tile-badge {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
inset-inline-end: 3px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
ha-tile-info {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
hui-card-features {
|
||||
--feature-color: var(--tile-color);
|
||||
padding: 0 var(--ha-space-3) var(--ha-space-3) var(--ha-space-3);
|
||||
}
|
||||
.container.horizontal hui-card-features {
|
||||
width: calc(50% - var(--column-gap, 0px) / 2 - var(--ha-space-3));
|
||||
flex: none;
|
||||
--feature-height: var(--ha-space-9);
|
||||
padding: 0 var(--ha-space-3);
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
ha-tile-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||
ha-tile-icon[data-domain="alarm_control_panel"][data-state="arming"],
|
||||
ha-tile-icon[data-domain="alarm_control_panel"][data-state="triggered"],
|
||||
ha-tile-icon[data-domain="lock"][data-state="jammed"] {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
/* Make sure we display the whole image */
|
||||
ha-tile-icon.image[data-domain="update"] {
|
||||
--tile-icon-border-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
/* Make sure we display the almost the whole image but it often use text */
|
||||
ha-tile-icon.image[data-domain="media_player"] {
|
||||
--tile-icon-border-radius: min(
|
||||
var(--ha-tile-icon-border-radius, var(--ha-border-radius-sm)),
|
||||
var(--ha-border-radius-sm)
|
||||
);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
static styles = [
|
||||
tileCardStyle,
|
||||
css`
|
||||
:host {
|
||||
--tile-color: var(--state-inactive-color);
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
ha-card.active {
|
||||
--tile-color: var(--state-icon-color);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
ha-tile-badge {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
inset-inline-end: 3px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
}
|
||||
`;
|
||||
hui-card-features {
|
||||
--feature-color: var(--tile-color);
|
||||
}
|
||||
|
||||
ha-tile-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||
ha-tile-icon[data-domain="alarm_control_panel"][data-state="arming"],
|
||||
ha-tile-icon[data-domain="alarm_control_panel"][data-state="triggered"],
|
||||
ha-tile-icon[data-domain="lock"][data-state="jammed"] {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
/* Make sure we display the whole image */
|
||||
ha-tile-icon.image[data-domain="update"] {
|
||||
--tile-icon-border-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
/* Make sure we display the almost the whole image but it often use text */
|
||||
ha-tile-icon.image[data-domain="media_player"] {
|
||||
--tile-icon-border-radius: min(
|
||||
var(--ha-tile-icon-border-radius, var(--ha-border-radius-sm)),
|
||||
var(--ha-border-radius-sm)
|
||||
);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
19
src/panels/lovelace/cards/tile/tile-card-style.ts
Normal file
19
src/panels/lovelace/cards/tile/tile-card-style.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { css } from "lit";
|
||||
|
||||
export const tileCardStyle = css`
|
||||
ha-card:has(ha-tile-container[focused]) {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--tile-color);
|
||||
border-color: var(--tile-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
ha-card {
|
||||
height: 100%;
|
||||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
}
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user