From 838db3fad4e1918cc7af033c5d51d2660d1ccee8 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 15 Feb 2023 18:58:22 +0100 Subject: [PATCH] Add custom tile features documentation (#1680) * Add custom tile features documentation * Feedbacks --- docs/frontend/custom-ui/custom-card.md | 172 +++++++++++++++--- ...shboard-custom-tile-feature-screenshot.png | Bin 0 -> 7723 bytes 2 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 static/img/en/frontend/dashboard-custom-tile-feature-screenshot.png diff --git a/docs/frontend/custom-ui/custom-card.md b/docs/frontend/custom-ui/custom-card.md index 129184b7..49967107 100644 --- a/docs/frontend/custom-ui/custom-card.md +++ b/docs/frontend/custom-ui/custom-card.md @@ -22,12 +22,12 @@ class ContentCardExample extends HTMLElement {
`; - this.content = this.querySelector('div'); + this.content = this.querySelector("div"); } const entityId = this.config.entity; const state = hass.states[entityId]; - const stateStr = state ? state.state : 'unavailable'; + const stateStr = state ? state.state : "unavailable"; this.content.innerHTML = ` The state of ${entityId} is ${stateStr}! @@ -40,7 +40,7 @@ class ContentCardExample extends HTMLElement { // will render an error card. setConfig(config) { if (!config.entity) { - throw new Error('You need to define an entity'); + throw new Error("You need to define an entity"); } this.config = config; } @@ -52,7 +52,7 @@ class ContentCardExample extends HTMLElement { } } -customElements.define('content-card-example', ContentCardExample); +customElements.define("content-card-example", ContentCardExample); ``` ## Referencing your new card @@ -66,10 +66,10 @@ You can then use your card in your dashboard configuration: ```yaml # Example dashboard configuration views: -- name: Example - cards: - - type: "custom:content-card-example" - entity: input_boolean.switch_tv + - name: Example + cards: + - type: "custom:content-card-example" + entity: input_boolean.switch_tv ``` ## API @@ -85,9 +85,9 @@ Your card can define a `getCardSize` method that returns the size of your card a Since some elements can be lazy loaded, if you want to get the card size of another element, you should first check it is defined. ```js - return customElements - .whenDefined(element.localName) - .then(() => element.getCardSize()); +return customElements + .whenDefined(element.localName) + .then(() => element.getCardSize()); ``` Your card can define a `getConfigElement` method that returns a custom element for editing the user configuration. Home Assistant will display this element in the card editor in the dashboard. @@ -106,7 +106,7 @@ import "https://unpkg.com/wired-toggle@0.8.0/wired-toggle.js?module"; import { LitElement, html, - css + css, } from "https://unpkg.com/lit-element@2.0.1/lit-element.js?module"; function loadCSS(url) { @@ -123,14 +123,14 @@ class WiredToggleCard extends LitElement { static get properties() { return { hass: {}, - config: {} + config: {}, }; } render() { return html` - ${this.config.entities.map(ent => { + ${this.config.entities.map((ent) => { const stateObj = this.hass.states[ent]; return stateObj ? html` @@ -138,13 +138,11 @@ class WiredToggleCard extends LitElement { ${stateObj.attributes.friendly_name} ` - : html` -
Entity ${ent} not found.
- `; + : html`
Entity ${ent} not found.
`; })}
`; @@ -165,7 +163,7 @@ class WiredToggleCard extends LitElement { _toggle(state) { this.hass.callService("homeassistant", "toggle", { - entity_id: state.entity_id + entity_id: state.entity_id, }); } @@ -208,13 +206,13 @@ And for your configuration: ```yaml # Example dashboard configuration views: -- name: Example - cards: - - type: "custom:wired-toggle-card" - entities: - - input_boolean.switch_ac_kitchen - - input_boolean.switch_ac_livingroom - - input_boolean.switch_tv + - name: Example + cards: + - type: "custom:wired-toggle-card" + entities: + - input_boolean.switch_ac_kitchen + - input_boolean.switch_ac_livingroom + - input_boolean.switch_tv ``` ## Graphical card configuration @@ -248,7 +246,6 @@ customElements.define('content-card-example', ContentCardExample); ```js class ContentCardEditor extends LitElement { - setConfig(config) { this._config = config; } @@ -256,9 +253,9 @@ class ContentCardEditor extends LitElement { configChanged(newConfig) { const event = new Event("config-changed", { bubbles: true, - composed: true + composed: true, }); - event.detail = {config: newConfig}; + event.detail = { config: newConfig }; this.dispatchEvent(event); } } @@ -269,10 +266,125 @@ window.customCards.push({ type: "content-card-example", name: "Content Card", preview: false, // Optional - defaults to false - description: "A custom card made by me!" // Optional + description: "A custom card made by me!", // Optional }); ``` +## Tile features + +The tile card has support for "features" to add quick actions to control the entity. We offer some built-in features, but you can build and use your own using similar way than defining custom cards. + +Below is an example of a custom tile feature for [button entity](/docs/core/entity/button/). + +![Screenshot of the custom tile feature example](/img/en/frontend/dashboard-custom-tile-feature-screenshot.png) + +```js +import { + LitElement, + html, + css, +} from "https://unpkg.com/lit-element@2.0.1/lit-element.js?module"; + +const supportsButtonPressTileFeature = (stateObj) => { + const domain = stateObj.entity_id.split(".")[0]; + return domain === "button"; +}; + +class ButtonPressTileFeature extends LitElement { + static get properties() { + return { + hass: undefined, + config: undefined, + stateObj: undefined, + }; + } + + static getStubConfig() { + return { + type: "custom:button-press-tile-feature", + label: "Press", + }; + } + + setConfig(config) { + if (!config) { + throw new Error("Invalid configuration"); + } + this.config = config; + } + + _press(ev) { + ev.stopPropagation(); + this.hass.callService("button", "press", { + entity_id: this.stateObj.entity_id, + }); + } + + render() { + if ( + !this.config || + !this.hass || + !this.stateObj || + !supportsButtonPressTileFeature(this.stateObj) + ) { + return null; + } + + return html` +
+ +
+ `; + } + + static get styles() { + return css` + .container { + display: flex; + flex-direction: row; + padding: 0 12px 12px 12px; + width: auto; + } + .button { + display: block; + width: 100%; + height: 40px; + border-radius: 6px; + border: none; + background-color: #eeeeee; + cursor: pointer; + transition: background-color 180ms ease-in-out; + } + .button:hover { + background-color: #dddddd; + } + .button:focus { + background-color: #cdcdcd; + } + `; + } +} + +customElements.define("button-press-tile-feature", ButtonPressTileFeature); + +window.customTileFeatures = window.customTileFeatures || []; +window.customTileFeatures.push({ + type: "button-press-tile-feature", + name: "Button press", + supported: supportsButtonPressTileFeature, // Optional + configurable: true, // Optional - defaults to false +}); +``` + +The only difference with custom cards is the graphical configuration option. +To have it displayed in the tile card editor, you must add an object describing it to the array `window.customTileFeatures`. + +Required properties of the object are `type` and `name`. It is recommended to define the `supported` option with a function so the editor can only propose the feature if it is compatible with the selected entity in the tile card. Set `configurable` to `true` if your entity has additional configuration (e.g. `label` option in the example above) so the editor. + +Also, the static functions `getConfigElement` and `getStubConfig` work the same as with normal custom maps. + ## Advanced Resources Community Maintained Boilerplate Card - Advanced Template (Typescript, Rollup, Linting, etc.) diff --git a/static/img/en/frontend/dashboard-custom-tile-feature-screenshot.png b/static/img/en/frontend/dashboard-custom-tile-feature-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..9a55d9923a20f917c869b85e70abe5fd25955133 GIT binary patch literal 7723 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 literal 0 HcmV?d00001