diff --git a/docs/badges/template.md b/docs/badges/template.md new file mode 100644 index 000000000..6a39ce8c0 --- /dev/null +++ b/docs/badges/template.md @@ -0,0 +1,47 @@ +# Template badge + +![Template light](../images/template-badge-light.png) +![Template dark](../images/template-badge-dark.png) + +## Description + +A template badge allows you to build a custom badge. You can use `entity` as a variable for the entity set on the badge e.g. `{{ states(entity) }}`. + +> [!WARNING] +> Home Assistant **2024.8** is required to use custom badges. + +## Configuration variables + +All the options are available in the lovelace editor but you can use `yaml` if you want. + +| Name | Type | Default | Description | +| :------------------ | :-------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------- | +| `entity` | string | Optional | Entity for template and actions | +| `icon` | string | Optional | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/) \*. | +| `color` | string | Optional | Color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `label` | string | Optional | Label to render. Only displayed if content is not empty. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `content` | string | Optional | Content to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `picture` | string | Optional | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `tap_action` | action | `none` | Home assistant action to perform on tap | +| `hold_action` | action | `none` | Home assistant action to perform on hold | +| `double_tap_action` | action | `none` | Home assistant action to perform on double_tap | +| `entity_id` | `string` `list` | Optional | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. | + +#### Notes + +\* You can render weather svg icons using [weather state](https://developers.home-assistant.io/docs/core/entity/weather/#recommended-values-for-state-and-condition) as icon : + +- weather-clear-night +- weather-cloudy +- weather-fog +- weather-lightning +- weather-lightning-rainy +- weather-partlycloudy +- weather-pouring +- weather-rainy +- weather-hail +- weather-snowy +- weather-snowy-rainy +- weather-sunny +- weather-windy +- weather-windy-variant diff --git a/docs/cards/template.md b/docs/cards/template.md index aa4958f25..0b8d81017 100644 --- a/docs/cards/template.md +++ b/docs/cards/template.md @@ -11,38 +11,39 @@ A template card allows you to build a custom card. You can use `entity` as a var All the options are available in the lovelace editor but you can use `yaml` if you want. -| Name | Type | Default | Description | -| :-------------------- | :-------------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------- | -| `icon` | string | Optional | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/) \*. | -| `icon_color` | string | Optional | Icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `primary` | string | Optional | Primary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `secondary` | string | Optional | Secondary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `badge_icon` | string | Optional | Badge icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `badge_color` | string | Optional | Badge icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `picture` | string | Optional | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `multiline_secondary` | boolean | `false` | Enables support for multiline text for the secondary info. | -| `layout` | string | Optional | Layout of the card. Vertical, horizontal and default layout are supported | -| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout | -| `tap_action` | action | `none` | Home assistant action to perform on tap | -| `hold_action` | action | `none` | Home assistant action to perform on hold | -| `entity_id` | `string` `list` | Optional | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. | -| `double_tap_action` | action | `more-info` | Home assistant action to perform on double_tap | +| Name | Type | Default | Description | +| :-------------------- | :-------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------- | +| `entity` | string | Optional | Entity for template and actions | +| `icon` | string | Optional | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/) \*. | +| `icon_color` | string | Optional | Icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `primary` | string | Optional | Primary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `secondary` | string | Optional | Secondary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `badge_icon` | string | Optional | Badge icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `badge_color` | string | Optional | Badge icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `picture` | string | Optional | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `multiline_secondary` | boolean | `false` | Enables support for multiline text for the secondary info. | +| `layout` | string | Optional | Layout of the card. Vertical, horizontal and default layout are supported | +| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout | +| `tap_action` | action | `none` | Home assistant action to perform on tap | +| `hold_action` | action | `none` | Home assistant action to perform on hold | +| `double_tap_action` | action | `none` | Home assistant action to perform on double_tap | +| `entity_id` | `string` `list` | Optional | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. | #### Notes \* You can render weather svg icons using [weather state](https://developers.home-assistant.io/docs/core/entity/weather/#recommended-values-for-state-and-condition) as icon : -- weather-clear-night -- weather-cloudy -- weather-fog -- weather-lightning -- weather-lightning-rainy -- weather-partlycloudy -- weather-pouring -- weather-rainy -- weather-hail -- weather-snowy -- weather-snowy-rainy -- weather-sunny -- weather-windy -- weather-windy-variant +- weather-clear-night +- weather-cloudy +- weather-fog +- weather-lightning +- weather-lightning-rainy +- weather-partlycloudy +- weather-pouring +- weather-rainy +- weather-hail +- weather-snowy +- weather-snowy-rainy +- weather-sunny +- weather-windy +- weather-windy-variant diff --git a/docs/images/template-badge-dark.png b/docs/images/template-badge-dark.png new file mode 100644 index 000000000..8fbff658a Binary files /dev/null and b/docs/images/template-badge-dark.png differ diff --git a/docs/images/template-badge-light.png b/docs/images/template-badge-light.png new file mode 100644 index 000000000..e09492813 Binary files /dev/null and b/docs/images/template-badge-light.png differ diff --git a/src/badges/template/const.ts b/src/badges/template/const.ts new file mode 100644 index 000000000..35065cc11 --- /dev/null +++ b/src/badges/template/const.ts @@ -0,0 +1,4 @@ +import { PREFIX_NAME } from "../../const"; + +export const TEMPLATE_BADGE_NAME = `${PREFIX_NAME}-template-badge`; +export const TEMPLATE_BADGE_EDITOR_NAME = `${TEMPLATE_BADGE_NAME}-editor`; diff --git a/src/badges/template/template-badge-config.ts b/src/badges/template/template-badge-config.ts new file mode 100644 index 000000000..86e5498ff --- /dev/null +++ b/src/badges/template/template-badge-config.ts @@ -0,0 +1,32 @@ +import { array, assign, object, optional, string, union } from "superstruct"; +import { LovelaceBadgeConfig } from "../../ha"; +import { + ActionsSharedConfig, + actionsSharedConfigStruct, +} from "../../shared/config/actions-config"; +import { lovelaceBadgeConfigStruct } from "../../shared/config/lovelace-badge-config"; + +export type TemplateBadgeConfig = LovelaceBadgeConfig & + ActionsSharedConfig & { + entity?: string; + icon?: string; + color?: string; + label?: string; + content?: string; + picture?: string; + entity_id?: string | string[]; + }; + +export const templateBadgeConfigStruct = assign( + lovelaceBadgeConfigStruct, + actionsSharedConfigStruct, + object({ + entity: optional(string()), + icon: optional(string()), + color: optional(string()), + label: optional(string()), + content: optional(string()), + picture: optional(string()), + entity_id: optional(union([string(), array(string())])), + }) +); diff --git a/src/badges/template/template-badge-editor.ts b/src/badges/template/template-badge-editor.ts new file mode 100644 index 000000000..4e77ab6a7 --- /dev/null +++ b/src/badges/template/template-badge-editor.ts @@ -0,0 +1,99 @@ +import { html, nothing } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import { assert } from "superstruct"; +import { LovelaceBadgeEditor, fireEvent } from "../../ha"; +import setupCustomlocalize from "../../localize"; +import { computeActionsFormSchema } from "../../shared/config/actions-config"; +import { MushroomBaseElement } from "../../utils/base-element"; +import { GENERIC_LABELS } from "../../utils/form/generic-fields"; +import { HaFormSchema } from "../../utils/form/ha-form"; +import { loadHaComponents } from "../../utils/loader"; +import { TEMPLATE_BADGE_EDITOR_NAME } from "./const"; +import { + TemplateBadgeConfig, + templateBadgeConfigStruct, +} from "./template-badge-config"; + +export const TEMPLATE_LABELS = ["content", "label", "picture"]; + +const SCHEMA: HaFormSchema[] = [ + { name: "entity", selector: { entity: {} } }, + { + name: "icon", + selector: { template: {} }, + }, + { + name: "color", + selector: { template: {} }, + }, + { + name: "label", + selector: { template: {} }, + }, + { + name: "content", + selector: { template: {} }, + }, + { + name: "picture", + selector: { template: {} }, + }, + ...computeActionsFormSchema(), +]; + +@customElement(TEMPLATE_BADGE_EDITOR_NAME) +export class TemplateBadgeEditor + extends MushroomBaseElement + implements LovelaceBadgeEditor +{ + @state() private _config?: TemplateBadgeConfig; + + connectedCallback() { + super.connectedCallback(); + void loadHaComponents(); + } + + public setConfig(config: TemplateBadgeConfig): void { + assert(config, templateBadgeConfigStruct); + this._config = config; + } + + private _computeLabel = (schema: HaFormSchema) => { + const customLocalize = setupCustomlocalize(this.hass!); + + if (schema.name === "entity") { + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )} (${customLocalize("editor.card.template.entity_extra")})`; + } + if (GENERIC_LABELS.includes(schema.name)) { + return customLocalize(`editor.card.generic.${schema.name}`); + } + if (TEMPLATE_LABELS.includes(schema.name)) { + return customLocalize(`editor.card.template.${schema.name}`); + } + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + }; + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } +} diff --git a/src/badges/template/template-badge.ts b/src/badges/template/template-badge.ts new file mode 100644 index 000000000..c3eaa4721 --- /dev/null +++ b/src/badges/template/template-badge.ts @@ -0,0 +1,386 @@ +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { styleMap } from "lit/directives/style-map.js"; +import { + actionHandler, + ActionHandlerEvent, + handleAction, + hasAction, + HomeAssistant, + LovelaceBadge, + LovelaceBadgeEditor, + RenderTemplateResult, + subscribeRenderTemplate, +} from "../../ha"; +import { computeCssColor } from "../../ha/common/color/compute-color"; +import { registerCustomBadge } from "../../utils/custom-badges"; +import { TEMPLATE_BADGE_EDITOR_NAME, TEMPLATE_BADGE_NAME } from "./const"; +import { TemplateBadgeConfig } from "./template-badge-config"; +import { getWeatherSvgIcon } from "../../utils/icons/weather-icon"; +import { weatherSVGStyles } from "../../utils/weather"; + +registerCustomBadge({ + type: TEMPLATE_BADGE_NAME, + name: "Mushroom Template", + description: "Build your own badge using templates", +}); + +const TEMPLATE_KEYS = ["icon", "color", "label", "content", "picture"] as const; +type TemplateKey = (typeof TEMPLATE_KEYS)[number]; + +@customElement(TEMPLATE_BADGE_NAME) +export class HuiEntityBadge extends LitElement implements LovelaceBadge { + public static async getConfigElement(): Promise { + await import("./template-badge-editor"); + return document.createElement( + TEMPLATE_BADGE_EDITOR_NAME + ) as LovelaceBadgeEditor; + } + + public static async getStubConfig( + _hass: HomeAssistant + ): Promise { + return { + type: `custom:${TEMPLATE_BADGE_NAME}`, + content: "Hello", + icon: "mdi:mushroom", + color: "red", + }; + } + + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() protected _config?: TemplateBadgeConfig; + + @state() private _templateResults: Partial< + Record + > = {}; + + @state() private _unsubRenderTemplates: Map< + TemplateKey, + Promise + > = new Map(); + + public connectedCallback() { + super.connectedCallback(); + this._tryConnect(); + } + + public disconnectedCallback() { + this._tryDisconnect(); + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (!this._config || !this.hass) { + return; + } + + this._tryConnect(); + } + + private async _tryConnect(): Promise { + TEMPLATE_KEYS.forEach((key) => { + this._tryConnectKey(key); + }); + } + + private async _tryConnectKey(key: TemplateKey): Promise { + if ( + this._unsubRenderTemplates.get(key) !== undefined || + !this.hass || + !this._config || + !this.isTemplate(key) + ) { + return; + } + + try { + const sub = subscribeRenderTemplate( + this.hass.connection, + (result) => { + this._templateResults = { + ...this._templateResults, + [key]: result, + }; + }, + { + template: this._config[key] ?? "", + entity_ids: this._config.entity_id, + variables: { + config: this._config, + user: this.hass.user!.name, + entity: this._config.entity, + }, + strict: true, + } + ); + this._unsubRenderTemplates.set(key, sub); + await sub; + } catch (_err) { + const result = { + result: this._config[key] ?? "", + listeners: { + all: false, + domains: [], + entities: [], + time: false, + }, + }; + this._templateResults = { + ...this._templateResults, + [key]: result, + }; + this._unsubRenderTemplates.delete(key); + } + } + private async _tryDisconnect(): Promise { + TEMPLATE_KEYS.forEach((key) => { + this._tryDisconnectKey(key); + }); + } + + private async _tryDisconnectKey(key: TemplateKey): Promise { + const unsubRenderTemplate = this._unsubRenderTemplates.get(key); + if (!unsubRenderTemplate) { + return; + } + + try { + const unsub = await unsubRenderTemplate; + unsub(); + this._unsubRenderTemplates.delete(key); + } catch (err: any) { + if (err.code === "not_found" || err.code === "template_error") { + // If we get here, the connection was probably already closed. Ignore. + } else { + throw err; + } + } + } + + setConfig(config: TemplateBadgeConfig): void { + TEMPLATE_KEYS.forEach((key) => { + if ( + this._config?.[key] !== config[key] || + this._config?.entity != config.entity + ) { + this._tryDisconnectKey(key); + } + }); + this._config = { + tap_action: { + action: "none", + }, + ...config, + }; + } + + get hasAction() { + return ( + !this._config?.tap_action || + hasAction(this._config?.tap_action) || + hasAction(this._config?.hold_action) || + hasAction(this._config?.double_tap_action) + ); + } + + protected render() { + if (!this._config || !this.hass) { + return nothing; + } + + const icon = this.getValue("icon"); + const color = this.getValue("color"); + const content = this.getValue("content"); + const label = this.getValue("label"); + const picture = this.getValue("picture"); + + const hasContent = !!content; + const hasIcon = !!icon || !!picture; + + const style = {}; + if (color) { + style["--badge-color"] = computeCssColor(color); + } + + const weatherSvg = getWeatherSvgIcon(icon); + + return html` +
+ + ${picture + ? html`` + : weatherSvg + ? weatherSvg + : icon + ? html` + + ` + : nothing} + ${content + ? html` + + ${label ? html`${label}` : nothing} + ${content} + + ` + : nothing} +
+ `; + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + public isTemplate(key: TemplateKey) { + const value = this._config?.[key]; + return value?.includes("{"); + } + + private getValue(key: TemplateKey) { + return this.isTemplate(key) + ? this._templateResults[key]?.result?.toString() + : this._config?.[key]; + } + + static get styles(): CSSResultGroup { + return css` + :host { + -webkit-tap-highlight-color: transparent; + } + .badge { + position: relative; + --ha-ripple-color: var(--badge-color); + --ha-ripple-hover-opacity: 0.04; + --ha-ripple-pressed-opacity: 0.12; + transition: + box-shadow 180ms ease-in-out, + border-color 180ms ease-in-out; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + height: 36px; + min-width: 36px; + padding: 0px 8px; + box-sizing: border-box; + width: auto; + border-radius: 18px; + background-color: var(--card-background-color, white); + border-width: var(--ha-card-border-width, 1px); + border-style: solid; + border-color: var( + --ha-card-border-color, + var(--divider-color, #e0e0e0) + ); + --mdc-icon-size: 18px; + text-align: center; + font-family: Roboto; + } + .badge:focus-visible { + --shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent); + --shadow-focus: 0 0 0 1px var(--badge-color); + border-color: var(--badge-color); + box-shadow: var(--shadow-default), var(--shadow-focus); + } + button, + [role="button"] { + cursor: pointer; + } + button:focus, + [role="button"]:focus { + outline: none; + } + .content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding-right: 4px; + padding-inline-end: 4px; + padding-inline-start: initial; + } + .name { + font-size: 10px; + font-style: normal; + font-weight: 500; + line-height: 10px; + letter-spacing: 0.1px; + color: var(--secondary-text-color); + } + .state { + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.1px; + color: var(--primary-text-color); + } + svg { + width: var(--mdc-icon-size); + height: var(--mdc-icon-size); + display: flex; + } + ha-state-icon { + color: var(--badge-color); + line-height: 0; + } + img { + width: 30px; + height: 30px; + border-radius: 50%; + object-fit: cover; + overflow: hidden; + } + .badge.icon-only { + padding: 0; + } + .badge:not(.icon-only) img { + margin-left: -6px; + margin-inline-start: -6px; + margin-inline-end: initial; + } + .badge.content-only .content { + padding-right: 4px; + padding-left: 4px; + padding-inline-end: 4px; + padding-inline-start: 4px; + } + ${weatherSVGStyles} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-entity-badge": HuiEntityBadge; + } +} diff --git a/src/cards/template-card/template-card.ts b/src/cards/template-card/template-card.ts index 721058297..d67841ac7 100644 --- a/src/cards/template-card/template-card.ts +++ b/src/cards/template-card/template-card.ts @@ -38,8 +38,8 @@ import { TemplateCardConfig } from "./template-card-config"; registerCustomCard({ type: TEMPLATE_CARD_NAME, - name: "Mushroom Template Card", - description: "Card for custom rendering with templates", + name: "Mushroom Template", + description: "Build your own mushroom card using templates", }); const TEMPLATE_KEYS = [ diff --git a/src/ha/common/color/compute-color.ts b/src/ha/common/color/compute-color.ts new file mode 100644 index 000000000..bd28631c1 --- /dev/null +++ b/src/ha/common/color/compute-color.ts @@ -0,0 +1,35 @@ +export const THEME_COLORS = new Set([ + "primary", + "accent", + "disabled", + "red", + "pink", + "purple", + "deep-purple", + "indigo", + "blue", + "light-blue", + "cyan", + "teal", + "green", + "light-green", + "lime", + "yellow", + "amber", + "orange", + "deep-orange", + "brown", + "light-grey", + "grey", + "dark-grey", + "blue-grey", + "black", + "white", +]); + +export function computeCssColor(color: string): string { + if (THEME_COLORS.has(color)) { + return `var(--${color}-color)`; + } + return color; +} diff --git a/src/ha/panels/lovelace/types.ts b/src/ha/panels/lovelace/types.ts index 9a1e2eda3..2f7d32c77 100644 --- a/src/ha/panels/lovelace/types.ts +++ b/src/ha/panels/lovelace/types.ts @@ -1,4 +1,8 @@ -import { LovelaceCardConfig, LovelaceConfig } from "../../data/lovelace"; +import { + LovelaceBadgeConfig, + LovelaceCardConfig, + LovelaceConfig, +} from "../../data/lovelace"; import { FrontendLocaleData } from "../../data/translation"; import { Constructor, HomeAssistant } from "../../types"; @@ -24,6 +28,11 @@ export interface Lovelace { deleteConfig: () => Promise; } +export interface LovelaceBadge extends HTMLElement { + hass?: HomeAssistant; + setConfig(config: LovelaceBadgeConfig): void; +} + export interface LovelaceCard extends HTMLElement { hass?: HomeAssistant; isPanel?: boolean; @@ -45,6 +54,10 @@ export interface LovelaceCardEditor extends LovelaceGenericElementEditor { setConfig(config: LovelaceCardConfig): void; } +export interface LovelaceBadgeEditor extends LovelaceGenericElementEditor { + setConfig(config: LovelaceBadgeConfig): void; +} + export interface LovelaceGenericElementEditor extends HTMLElement { hass?: HomeAssistant; lovelace?: LovelaceConfig; diff --git a/src/mushroom.ts b/src/mushroom.ts index 1c4be62b4..a1b7e446b 100644 --- a/src/mushroom.ts +++ b/src/mushroom.ts @@ -23,6 +23,8 @@ import "./cards/title-card/title-card"; import "./cards/update-card/update-card"; import "./cards/vacuum-card/vacuum-card"; +import "./badges/template/template-badge"; + console.info( `%c🍄 Mushroom 🍄 - ${version}`, "color: #ef5350; font-weight: 700;" diff --git a/src/shared/config/lovelace-badge-config.ts b/src/shared/config/lovelace-badge-config.ts new file mode 100644 index 000000000..956a0e4db --- /dev/null +++ b/src/shared/config/lovelace-badge-config.ts @@ -0,0 +1,6 @@ +import { any, object, string } from "superstruct"; + +export const lovelaceBadgeConfigStruct = object({ + type: string(), + visibility: any(), +}); diff --git a/src/translations/en.json b/src/translations/en.json index 44e6ccd69..b6ef26b48 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -43,6 +43,7 @@ }, "card": { "generic": { + "color": "Color", "icon_color": "Icon color", "layout": "Layout", "fill_container": "Fill container", @@ -75,6 +76,7 @@ "secondary": "Secondary information", "multiline_secondary": "Multiline secondary?", "entity_extra": "Used in templates and actions", + "label": "Label", "content": "Content", "badge_icon": "Badge icon", "badge_color": "Badge color", diff --git a/src/utils/custom-badges.ts b/src/utils/custom-badges.ts new file mode 100644 index 000000000..0595d166b --- /dev/null +++ b/src/utils/custom-badges.ts @@ -0,0 +1,20 @@ +import { repository } from "../../package.json"; + +interface RegisterBadgeParams { + type: string; + name: string; + description: string; +} +export function registerCustomBadge(params: RegisterBadgeParams) { + const windowWithBadges = window as unknown as Window & { + customBadges: unknown[]; + }; + windowWithBadges.customBadges = windowWithBadges.customBadges || []; + + const badgePage = params.type.replace("-badge", "").replace("mushroom-", ""); + windowWithBadges.customBadges.push({ + ...params, + preview: true, + documentationURL: `${repository.url}/blob/main/docs/badges/${badgePage}.md`, + }); +} diff --git a/src/utils/form/generic-fields.ts b/src/utils/form/generic-fields.ts index 9367bcdd0..3ea2273d4 100644 --- a/src/utils/form/generic-fields.ts +++ b/src/utils/form/generic-fields.ts @@ -1,4 +1,5 @@ export const GENERIC_LABELS = [ + "color", "icon_color", "layout", "fill_container",