From a34195da26f4e1951dad3b029420e52ca48ffe5e Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 24 Jul 2024 22:10:09 +0200 Subject: [PATCH] Update custom cards, add custom badges and custom card features (#2258) * Add custom card feature doc * Add layout options in custom card documentation * Update example * Add custom badge documentation * Update docs/frontend/custom-ui/custom-card.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-card.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-card-feature.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-card.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-badge.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-badge.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-badge.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-card-feature.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/frontend/custom-ui/custom-card-feature.md * Apply suggestions from code review --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Bram Kragten --- docs/frontend/custom-ui/custom-badge.md | 131 +++++++++++++ .../frontend/custom-ui/custom-card-feature.md | 116 ++++++++++++ docs/frontend/custom-ui/custom-card.md | 172 ++++++------------ sidebars.js | 4 +- ...shboard-custom-card-feature-screenshot.png | Bin 0 -> 12524 bytes ...shboard-custom-tile-feature-screenshot.png | Bin 5330 -> 0 bytes 6 files changed, 302 insertions(+), 121 deletions(-) create mode 100644 docs/frontend/custom-ui/custom-badge.md create mode 100644 docs/frontend/custom-ui/custom-card-feature.md create mode 100644 static/img/en/frontend/dashboard-custom-card-feature-screenshot.png delete mode 100644 static/img/en/frontend/dashboard-custom-tile-feature-screenshot.png diff --git a/docs/frontend/custom-ui/custom-badge.md b/docs/frontend/custom-ui/custom-badge.md new file mode 100644 index 00000000..d73e12cb --- /dev/null +++ b/docs/frontend/custom-ui/custom-badge.md @@ -0,0 +1,131 @@ +--- +title: "Custom badge" +--- + +[Badges](https://www.home-assistant.io/dashboards/badges/) are small widgets that sit at the top of a view, above all cards. We offer a built-in badge, the [entity badge](https://next.home-assistant.io/dashboards/badges/#entity-badge), but you're not just limited that one. You can build and use your own! + +## Defining your badge + +Defining a badge is done in a very similar way to defining a [custom card](/docs/frontend/custom-ui/custom-card). + +Let's create a basic badge that displays custom text at the top of the screen. +Create a new file in your Home Assistant config dir as `/www/text-badge.js` and put in the following contents: + +```js + +class TextBadge extends HTMLElement { + // Whenever the state changes, a new `hass` object is set. Use this to + // update your content. + set hass(hass) { + this._hass = hass; + this.updateContent(); + } + + // The user supplied configuration. Throw an exception and Home Assistant + // will render an error badge. + setConfig(config) { + if (!config.entity) { + throw new Error("You need to define an entity"); + } + this.config = config; + this.updateContent(); + } + + updateContent() { + if (!this.config || !this._hass) return; + + const entityId = this.config.entity; + const state = this._hass.states[entityId]; + const stateStr = state ? state.state : "unavailable"; + + this.innerHTML = `

${stateStr}

`; + } +} + +customElements.define("text-badge", TextBadge); +``` + +## Referencing your new badge + +In our example badge, we defined a badge with the tag `text-badge` (see last line), so our badge type will be `custom:text-badge`. And because you created the file in your `/www` directory, it will be accessible in your browser via the url `/local/` (if you have recently added the www folder you will need to re-start Home Assistant for files to be picked up). + +Add a resource to your dashboard configuration with URL `/local/text-badge.js` and type `module` ([resource docs](/docs/frontend/custom-ui/registering-resources)). + +You can then use your badge in your dashboard configuration: + +```yaml +# Example dashboard configuration +views: + - name: Example + badges: + - type: "custom:text-badge" + entity: light.bedside_lamp +``` + +## API + +Custom badges are defined as a [custom element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements). It's up to you to decide how to render your DOM inside your element. You can use Polymer, Angular, Preact or any other popular framework (except for React – [more info on React here](https://custom-elements-everywhere.com/#react)). + +Home Assistant will call `setConfig(config)` when the configuration changes (rare). If you throw an exception if the configuration is invalid, Home Assistant will render an error badge to notify the user. + +Home Assistant will set [the `hass` property](/docs/frontend/data/) when the state of Home Assistant changes (frequent). Whenever the state changes, the component will have to update itself to represent the latest state. + +## Graphical badge configuration + +Your badge can define a `getConfigElement` method that returns a custom element for editing the user configuration. Home Assistant will display this element in the badge editor in the dashboard. + +Your badge can also define a `getStubConfig` method that returns a default badge configuration (without the `type:` parameter) in json form for use by the badge type picker in the dashboard. + +Home Assistant will call the `setConfig` method of the config element on setup. +Home Assistant will update the `hass` property of the config element on state changes, and the `lovelace` element, which contains information about the dashboard configuration. + +Changes to the configuration are communicated back to the dashboard by dispatching a `config-changed` event with the new configuration in its detail. + +To have your badge displayed in the badge picker dialog in the dashboard, add an object describing it to the array `window.customBadges`. Required properties of the object are `type` and `name` (see example below). + +```js +import "./text-badge-editor.js"; + +class TextBadge extends HTMLElement { + + ... + + static getConfigElement() { + return document.createElement("text-badge-editor"); + } + + static getStubConfig() { + return { entity: "sun.sun" }; + } +} + +customElements.define("text-badge", TextBadge); +``` + +```js +class TextBadgeEditor extends HTMLElement { + setConfig(config) { + this._config = config; + } + + configChanged(newConfig) { + const event = new Event("config-changed", { + bubbles: true, + composed: true, + }); + event.detail = { config: newConfig }; + this.dispatchEvent(event); + } +} + +customElements.define("text-badge-editor", TextBadgeEditor); +window.customBadges = window.customBadges || []; +window.customBadges.push({ + type: "text-badge", + name: "Text badge", + preview: false, // Optional - defaults to false + description: "A custom badge made by me!", // Optional + documentationURL: + "https://developers.home-assistant.io/docs/frontend/custom-ui/custom-badge", // Adds a help link in the frontend badge editor +}); +``` diff --git a/docs/frontend/custom-ui/custom-card-feature.md b/docs/frontend/custom-ui/custom-card-feature.md new file mode 100644 index 00000000..f294fb5a --- /dev/null +++ b/docs/frontend/custom-ui/custom-card-feature.md @@ -0,0 +1,116 @@ +--- +title: "Custom card feature" +--- + +Some dashboard cards have support for [features](https://www.home-assistant.io/dashboards/features/). These widgets add quick controls to the card. We offer a lot of built-in features, but you're not just limited to the ones that we decided to include in Home Assistant. You can build and use your own in the same way as defining [custom cards](/docs/frontend/custom-ui/custom-card). + +## Defining your card feature + +Below is an example of a custom card feature for [button entity](/docs/core/entity/button/). + +![Screenshot of the custom card feature example](/img/en/frontend/dashboard-custom-card-feature-screenshot.png) + +```js +import { + LitElement, + html, + css, +} from "https://unpkg.com/lit-element@2.0.1/lit-element.js?module"; + +const supportsButtonPressCardFeature = (stateObj) => { + const domain = stateObj.entity_id.split(".")[0]; + return domain === "button"; +}; + +class ButtonPressCardFeature extends LitElement { + static get properties() { + return { + hass: undefined, + config: undefined, + stateObj: undefined, + }; + } + + static getStubConfig() { + return { + type: "custom:button-press-card-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 || + !supportsButtonPressCardFeature(this.stateObj) + ) { + return null; + } + + return html` + + `; + } + + static get styles() { + return css` + .button { + display: block; + height: var(--feature-height, 42px); + width: 100%; + border-radius: var(--feature-border-radius, 12px); + 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-card-feature", ButtonPressCardFeature); + +window.customCardFeatures = window.customCardFeatures || []; +window.customCardFeatures.push({ + type: "button-press-card-feature", + name: "Button press", + supported: supportsButtonPressCardFeature, // Optional + configurable: true, // Optional - defaults to false +}); +``` + +If you want your feature to better integrate with the default design of home assistant, you can use these CSS variables: + +- `--feature-height`: Recommended height (42px). +- `--feature-border-radius`: Recommended border radius (12px). It be can useful to set button or slider border radius. +- `--feature-button-spacing`: Recommended space between buttons (12px). It can be useful if you have multiple buttons in your feature. + +The main difference with custom cards is the graphical configuration option. +To have it displayed in the card editor, you must add an object describing it to the array `window.customCardFeatures`. + +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 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 cards. diff --git a/docs/frontend/custom-ui/custom-card.md b/docs/frontend/custom-ui/custom-card.md index 0f9d2e89..04dd556e 100644 --- a/docs/frontend/custom-ui/custom-card.md +++ b/docs/frontend/custom-ui/custom-card.md @@ -1,5 +1,5 @@ --- -title: "Custom cards" +title: "Custom card" --- [Dashboards](https://www.home-assistant.io/dashboards/) are our approach to defining your user interface for Home Assistant. We offer a lot of built-in cards, but you're not just limited to the ones that we decided to include in Home Assistant. You can build and use your own! @@ -46,10 +46,20 @@ class ContentCardExample extends HTMLElement { } // The height of your card. Home Assistant uses this to automatically - // distribute all cards over the available columns. + // distribute all cards over the available columns in masonry view getCardSize() { return 3; } + + // The rules for your card for sizing your card if the grid in section view + getLayoutOptions() { + return { + grid_rows: 3, + grid_columns: 2, + grid_min_rows: 3, + grid_max_rows: 3, + }; + } } customElements.define("content-card-example", ContentCardExample); @@ -76,11 +86,15 @@ views: Custom cards are defined as a [custom element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements). It's up to you to decide how to render your DOM inside your element. You can use Polymer, Angular, Preact or any other popular framework (except for React – [more info on React here](https://custom-elements-everywhere.com/#react)). +### Configuration + Home Assistant will call `setConfig(config)` when the configuration changes (rare). If you throw an exception if the configuration is invalid, Home Assistant will render an error card to notify the user. Home Assistant will set [the `hass` property](/docs/frontend/data/) when the state of Home Assistant changes (frequent). Whenever the state changes, the component will have to update itself to represent the latest state. -Your card can define a `getCardSize` method that returns the size of your card as a number or a promise that will resolve to a number. A height of 1 is equivalent to 50 pixels. This will help Home Assistant distribute the cards evenly over the columns. A card size of `1` will be assumed if the method is not defined. +### Sizing in masonry view + +Your card can define a `getCardSize` method that returns the size of your card as a number or a promise that will resolve to a number. A height of 1 is equivalent to 50 pixels. This will help Home Assistant distribute the cards evenly over the columns in the [masonry view](https://www.home-assistant.io/dashboards/masonry/). A card size of `1` will be assumed if the method is not defined. 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. @@ -90,7 +104,39 @@ return customElements .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. +### Sizing in sections view + +You card can define a `getLayoutOptions` method that returns the min, max and default number of cells your card will take in the grid if your card is used if the [sections view](https://www.home-assistant.io/dashboards/masonry/) +If you don't define this method, the card will take 4 columns (full width) and will ignore the rows of the grid. + +A cell of the grid is defined with the following dimension: + +- width: between `80px` and `120px` depending on the screen size +- height: `56px` +- gap between cells: `8px` + +The different layout options are: + +- `grid_rows`: Default number of rows the card takes +- `grid_min_rows`: Minimal number of rows the card takes +- `grid_max_rows`: Maximal number of rows the card takes +- `grid_columns`: Default number of columns the card takes +- `grid_min_columns`: Minimal number of columns the card takes +- `grid_max_columns`: Maximal number of columns the card takes + +Example of implementation: + +```js +public getLayoutOptions() { + return { + grid_rows: 2, + grid_columns: 2, + grid_min_rows: 2, + }; +} +``` + +In this example, the card will take 2 x 2 cells by default. The height of the card cannot be smaller than 2 rows. According to the cell dimension, the card will have a height of `120px` (`2` * `56px` + `8px`). ## Advanced example @@ -267,121 +313,7 @@ window.customCards.push({ name: "Content Card", preview: false, // Optional - defaults to false description: "A custom card made by me!", // Optional - documentationURL: "https://developers.home-assistant.io/docs/frontend/custom-ui/custom-card/", // Adds a help link in the frontend card editor + documentationURL: + "https://developers.home-assistant.io/docs/frontend/custom-ui/custom-card", // Adds a help link in the frontend card editor }); ``` - -## 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. diff --git a/sidebars.js b/sidebars.js index 8a41850c..5ced7b39 100644 --- a/sidebars.js +++ b/sidebars.js @@ -40,6 +40,8 @@ module.exports = { label: "Custom UI", items: [ "frontend/custom-ui/custom-card", + "frontend/custom-ui/custom-card-feature", + "frontend/custom-ui/custom-badge", "frontend/custom-ui/custom-strategy", "frontend/custom-ui/custom-view", "frontend/custom-ui/creating-custom-panels", @@ -287,7 +289,7 @@ module.exports = { "asyncio_categorizing_functions", "asyncio_working_with_async", "asyncio_thread_safety", - "asyncio_blocking_operations" + "asyncio_blocking_operations", ], }, ], diff --git a/static/img/en/frontend/dashboard-custom-card-feature-screenshot.png b/static/img/en/frontend/dashboard-custom-card-feature-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a8e7bd681596f0bc99feaf75491f8deb1870f173 GIT binary patch literal 12524 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 NnHX5=H)75u{~sm%ia7uP