diff --git a/src/components/tile/ha-tile-container.ts b/src/components/tile/ha-tile-container.ts new file mode 100644 index 0000000000..327fac2eb3 --- /dev/null +++ b/src/components/tile/ha-tile-container.ts @@ -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` +
+ +
+
+
+ + +
+ +
+ `; + } + + 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; + } +} diff --git a/src/components/tile/ha-tile-icon.ts b/src/components/tile/ha-tile-icon.ts index 72b8175db4..c68c21a834 100644 --- a/src/components/tile/ha-tile-icon.ts +++ b/src/components/tile/ha-tile-icon.ts @@ -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` -
- -
- - `; + return html``; } + if (this.icon) { + return html``; + } + if (this.iconPath) { + return html``; + } + return nothing; + } + + protected render(): TemplateResult { + const hasImage = Boolean(this.imageUrl); return html` -
- +
+ ${this._renderIcon()}
`; @@ -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; diff --git a/src/panels/lovelace/cards/hui-area-card.ts b/src/panels/lovelace/cards/hui-area-card.ts index 60bd6cf40d..50fbbd270c 100644 --- a/src/panels/lovelace/cards/hui-area-card.ts +++ b/src/panels/lovelace/cards/hui-area-card.ts @@ -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` -
- -
${displayType === "compact" ? nothing : html` @@ -628,30 +610,30 @@ export class HuiAreaCard extends LitElement implements LovelaceCard { ${this._renderAlertSensors()}
`} -
-
- - ${displayType === "compact" - ? this._renderAlertSensorBadge() - : nothing} - ${icon - ? html`` - : html` - - `} - - -
+ + + ${displayType === "compact" + ? this._renderAlertSensorBadge() + : nothing} + + ${features.length > 0 ? html` ` : nothing} -
+ `; } - 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 { diff --git a/src/panels/lovelace/cards/hui-discovered-devices-card.ts b/src/panels/lovelace/cards/hui-discovered-devices-card.ts index fd267e0fca..a54249797f 100644 --- a/src/panels/lovelace/cards/hui-discovered-devices-card.ts +++ b/src/panels/lovelace/cards/hui-discovered-devices-card.ts @@ -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` -
- -
-
-
- - - - -
-
+ + +
`; } - 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 { diff --git a/src/panels/lovelace/cards/hui-home-summary-card.ts b/src/panels/lovelace/cards/hui-home-summary-card.ts index ac07b42129..935d56ff99 100644 --- a/src/panels/lovelace/cards/hui-home-summary-card.ts +++ b/src/panels/lovelace/cards/hui-home-summary-card.ts @@ -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 = { @@ -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` -
- -
-
-
- - - - -
-
+ + +
`; } - 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 { diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 1487310ea6..6b191ab188 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -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` -
- -
-
-
- - - ${renderTileBadge(stateObj, this.hass)} - - - ${name} - ${stateDisplay - ? html`${stateDisplay}` - : nothing} - -
+ + + ${renderTileBadge(stateObj, this.hass)} + + + ${name} + ${stateDisplay + ? html`${stateDisplay}` + : nothing} + ${features.length > 0 ? html` ` : nothing} -
+
`; } - 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 { diff --git a/src/panels/lovelace/cards/tile/tile-card-style.ts b/src/panels/lovelace/cards/tile/tile-card-style.ts new file mode 100644 index 0000000000..093a947f93 --- /dev/null +++ b/src/panels/lovelace/cards/tile/tile-card-style.ts @@ -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); + } +`;