mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Theme color fixes (#14672)
* Split off and unavailable colors * off and unvailable in history panel * Refactor tile card color code * Use new color in state badge * Use new colors in button and entity card * Rename off to inactive * Add inactive color to color picker * Use amber instead of blue
This commit is contained in:
parent
3c8c1260b1
commit
23573d8c26
@ -4,6 +4,7 @@ export const THEME_COLORS = new Set([
|
||||
"primary",
|
||||
"accent",
|
||||
"disabled",
|
||||
"inactive",
|
||||
"red",
|
||||
"pink",
|
||||
"purple",
|
||||
|
@ -3,12 +3,12 @@ import { batteryStateColor } from "./battery_color";
|
||||
|
||||
export const sensorColor = (
|
||||
stateObj: HassEntity,
|
||||
compareState: string
|
||||
state: string
|
||||
): string | undefined => {
|
||||
const deviceClass = stateObj?.attributes.device_class;
|
||||
|
||||
if (deviceClass === "battery") {
|
||||
return batteryStateColor(compareState);
|
||||
return batteryStateColor(state);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -1,5 +1,6 @@
|
||||
/** Return an color representing a state. */
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import { UpdateEntity, updateIsInstalling } from "../../data/update";
|
||||
import { alarmControlPanelColor } from "./color/alarm_control_panel_color";
|
||||
import { binarySensorColor } from "./color/binary_sensor_color";
|
||||
@ -30,18 +31,23 @@ const STATIC_COLORED_DOMAIN = new Set([
|
||||
"vacuum",
|
||||
]);
|
||||
|
||||
export const stateColorCss = (stateObj?: HassEntity, state?: string) => {
|
||||
if (!stateObj || !stateActive(stateObj, state)) {
|
||||
return `var(--rgb-disabled-color)`;
|
||||
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
if (compareState === UNAVAILABLE) {
|
||||
return `var(--rgb-state-unavailable-color)`;
|
||||
}
|
||||
|
||||
const color = stateColor(stateObj, state);
|
||||
|
||||
if (color) {
|
||||
return `var(--rgb-state-${color}-color)`;
|
||||
if (!stateActive(stateObj, state)) {
|
||||
return `var(--rgb-state-inactive-color)`;
|
||||
}
|
||||
|
||||
return `var(--rgb-state-default-color)`;
|
||||
const domainColor = stateColor(stateObj, state);
|
||||
|
||||
if (domainColor) {
|
||||
return `var(--rgb-state-${domainColor}-color)`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const stateColor = (stateObj: HassEntity, state?: string) => {
|
||||
|
@ -21,6 +21,6 @@ export const iconColorCSS = css`
|
||||
|
||||
/* Color the icon if unavailable */
|
||||
ha-state-icon[data-state="unavailable"] {
|
||||
color: var(--state-unavailable-color);
|
||||
color: rgb(var(--rgb-state-unavailable-color));
|
||||
}
|
||||
`;
|
||||
|
@ -5,6 +5,7 @@ import { labBrighten } from "../../../common/color/lab";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
||||
media_player: {
|
||||
@ -49,11 +50,18 @@ function computeTimelineStateColor(
|
||||
computedStyles: CSSStyleDeclaration,
|
||||
stateObj?: HassEntity
|
||||
): string | undefined {
|
||||
if (!stateObj || !stateActive(stateObj, state)) {
|
||||
const rgb = cssToRgb("--rgb-disabled-color", computedStyles);
|
||||
if (!stateObj || state === UNAVAILABLE) {
|
||||
const rgb = cssToRgb("--rgb-state-unavailable-color", computedStyles);
|
||||
if (!rgb) return undefined;
|
||||
return rgb2hex(rgb);
|
||||
}
|
||||
|
||||
if (!stateActive(stateObj, state)) {
|
||||
const rgb = cssToRgb("--rgb-state-inactive-color", computedStyles);
|
||||
if (!rgb) return undefined;
|
||||
return rgb2hex(rgb);
|
||||
}
|
||||
|
||||
const color = stateColor(stateObj, state);
|
||||
|
||||
if (!color) return undefined;
|
||||
|
@ -14,7 +14,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { stateActive } from "../../common/entity/state_active";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@ -107,24 +107,27 @@ export class StateBadge extends LitElement {
|
||||
}
|
||||
hostStyle.backgroundImage = `url(${imageUrl})`;
|
||||
this._showIcon = false;
|
||||
} else if (stateActive(stateObj) && this._stateColor) {
|
||||
const iconColor = stateColor(stateObj);
|
||||
if (stateObj.attributes.rgb_color) {
|
||||
iconStyle.color = `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
||||
} else if (iconColor) {
|
||||
iconStyle.color = `rgb(var(--rgb-state-${iconColor}-color))`;
|
||||
} else if (this._stateColor) {
|
||||
const color = stateColorCss(stateObj);
|
||||
if (color) {
|
||||
iconStyle.color = `rgb(${color})`;
|
||||
}
|
||||
if (stateObj.attributes.brightness) {
|
||||
const brightness = stateObj.attributes.brightness;
|
||||
if (typeof brightness !== "number") {
|
||||
const errorMessage = `Type error: state-badge expected number, but type of ${
|
||||
stateObj.entity_id
|
||||
}.attributes.brightness is ${typeof brightness} (${brightness})`;
|
||||
// eslint-disable-next-line
|
||||
console.warn(errorMessage);
|
||||
if (stateActive(stateObj)) {
|
||||
if (stateObj.attributes.rgb_color) {
|
||||
iconStyle.color = `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
||||
}
|
||||
if (stateObj.attributes.brightness) {
|
||||
const brightness = stateObj.attributes.brightness;
|
||||
if (typeof brightness !== "number") {
|
||||
const errorMessage = `Type error: state-badge expected number, but type of ${
|
||||
stateObj.entity_id
|
||||
}.attributes.brightness is ${typeof brightness} (${brightness})`;
|
||||
// eslint-disable-next-line
|
||||
console.warn(errorMessage);
|
||||
}
|
||||
// lowest brightness will be around 50% (that's pretty dark)
|
||||
iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
|
||||
}
|
||||
// lowest brightness will be around 50% (that's pretty dark)
|
||||
iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
|
||||
}
|
||||
}
|
||||
} else if (this.overrideImage) {
|
||||
|
@ -26,7 +26,7 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
||||
import "../../../components/ha-card";
|
||||
@ -79,6 +79,15 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@state() private _shouldRenderRipple = false;
|
||||
|
||||
private getStateColor(stateObj: HassEntity, config: ButtonCardConfig) {
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
return (
|
||||
config &&
|
||||
(config.state_color ||
|
||||
(domain === "light" && config.state_color !== false))
|
||||
);
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return (
|
||||
(this._config?.show_icon ? 4 : 0) + (this._config?.show_name ? 1 : 0)
|
||||
@ -146,13 +155,9 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
const name = this._config.show_name
|
||||
? this._config.name || (stateObj ? computeStateName(stateObj) : "")
|
||||
: "";
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
|
||||
const active =
|
||||
(this._config.state_color ||
|
||||
(domain === "light" && this._config.state_color !== false)) &&
|
||||
stateObj &&
|
||||
stateActive(stateObj);
|
||||
const colored = stateObj && this.getStateColor(stateObj, this._config);
|
||||
const active = stateObj && colored && stateActive(stateObj);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
@ -187,9 +192,8 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
.icon=${this._config.icon}
|
||||
.state=${stateObj}
|
||||
style=${styleMap({
|
||||
color: stateObj && active ? this._computeColor(stateObj) : "",
|
||||
filter:
|
||||
stateObj && active ? this._computeBrightness(stateObj) : "",
|
||||
color: colored ? this._computeColor(stateObj) : "",
|
||||
filter: colored ? this._computeBrightness(stateObj) : "",
|
||||
height: this._config.icon_height
|
||||
? this._config.icon_height
|
||||
: "",
|
||||
@ -305,7 +309,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _computeBrightness(stateObj: HassEntity | LightEntity): string {
|
||||
if (!stateObj.attributes.brightness) {
|
||||
if (!stateObj.attributes.brightness && stateActive(stateObj)) {
|
||||
const brightness = stateObj.attributes.brightness;
|
||||
return `brightness(${(brightness + 245) / 5}%)`;
|
||||
}
|
||||
@ -313,12 +317,12 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _computeColor(stateObj: HassEntity | LightEntity): string {
|
||||
if (stateObj.attributes.rgb_color) {
|
||||
if (stateObj.attributes.rgb_color && stateActive(stateObj)) {
|
||||
return `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
||||
}
|
||||
const iconColor = stateColor(stateObj);
|
||||
const iconColor = stateColorCss(stateObj);
|
||||
if (iconColor) {
|
||||
return `rgb(var(--rgb-state-${iconColor}-color))`;
|
||||
return `rgb(${iconColor})`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import {
|
||||
formatNumber,
|
||||
@ -76,6 +76,15 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
|
||||
private _footerElement?: HuiErrorCard | LovelaceHeaderFooter;
|
||||
|
||||
private getStateColor(stateObj: HassEntity, config: EntityCardConfig) {
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
return (
|
||||
config &&
|
||||
(config.state_color ||
|
||||
(domain === "light" && config.state_color !== false))
|
||||
);
|
||||
}
|
||||
|
||||
public setConfig(config: EntityCardConfig): void {
|
||||
if (!config.entity) {
|
||||
throw new Error("Entity must be specified");
|
||||
@ -124,10 +133,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const name = this._config.name || computeStateName(stateObj);
|
||||
|
||||
const active =
|
||||
(this._config.state_color ||
|
||||
(domain === "light" && this._config.state_color !== false)) &&
|
||||
stateActive(stateObj);
|
||||
const colored = stateObj && this.getStateColor(stateObj, this._config);
|
||||
const active = stateObj && colored && stateActive(stateObj);
|
||||
|
||||
return html`
|
||||
<ha-card @click=${this._handleClick} tabindex="0">
|
||||
@ -141,7 +148,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
data-domain=${ifDefined(domain)}
|
||||
data-state=${stateObj.state}
|
||||
style=${styleMap({
|
||||
color: active ? this._computeColor(stateObj) : "",
|
||||
color: colored ? this._computeColor(stateObj) : undefined,
|
||||
height: this._config.icon_height
|
||||
? this._config.icon_height
|
||||
: "",
|
||||
@ -187,19 +194,9 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _computeColor(stateObj: HassEntity | LightEntity): string {
|
||||
const domain = computeStateDomain(stateObj);
|
||||
if (
|
||||
!(
|
||||
this._config?.state_color ||
|
||||
(domain === "light" && this._config?.state_color !== false)
|
||||
) ||
|
||||
!stateActive(stateObj)
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
const iconColor = stateColor(stateObj);
|
||||
const iconColor = stateColorCss(stateObj);
|
||||
if (iconColor) {
|
||||
return `rgb(var(--rgb-state-${iconColor}-color))`;
|
||||
return `rgb(${iconColor})`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import { hsv2rgb, rgb2hsv } from "../../../common/color/convert-color";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateIconPath } from "../../../common/entity/state_icon_path";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
@ -129,8 +128,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _computeStateColor = memoize((entity: HassEntity, color?: string) => {
|
||||
if (UNAVAILABLE_STATES.includes(entity.state)) {
|
||||
return undefined;
|
||||
// Use custom color
|
||||
if (color) {
|
||||
return computeRgbColor(color);
|
||||
}
|
||||
|
||||
// Use default color for person/device_tracker because color is on the badge
|
||||
@ -141,16 +141,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
return "var(--rgb-state-default-color)";
|
||||
}
|
||||
|
||||
if (!stateActive(entity)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (color) {
|
||||
return computeRgbColor(color);
|
||||
}
|
||||
|
||||
let stateColor = stateColorCss(entity);
|
||||
|
||||
// Use light color if the light support rgb
|
||||
if (
|
||||
computeDomain(entity.entity_id) === "light" &&
|
||||
entity.attributes.rgb_color
|
||||
@ -166,10 +157,11 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
hsvColor[1] = 0.4;
|
||||
}
|
||||
}
|
||||
stateColor = hsv2rgb(hsvColor).join(",");
|
||||
return hsv2rgb(hsvColor).join(",");
|
||||
}
|
||||
|
||||
return stateColor;
|
||||
// Fallback to state color
|
||||
return stateColorCss(entity) ?? "var(--rgb-state-default-color)";
|
||||
});
|
||||
|
||||
private _computeStateDisplay(stateObj: HassEntity): TemplateResult | string {
|
||||
@ -360,7 +352,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
--tile-color: var(--rgb-disabled-color);
|
||||
--tile-color: var(--rgb-state-default-color);
|
||||
--tile-tap-padding: 6px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
@ -368,7 +360,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
height: 100%;
|
||||
}
|
||||
ha-card.disabled {
|
||||
background: rgba(var(--rgb-disabled-color), 0.1);
|
||||
background: rgba(var(--rgb-state-unavailable-color), 0.1);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
|
@ -107,6 +107,7 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-primary-color: 3, 169, 244;
|
||||
--rgb-accent-color: 255, 152, 0;
|
||||
--rgb-disabled-color: 189, 189, 189;
|
||||
--rgb-inactive-color: 114, 114, 114;
|
||||
--rgb-primary-text-color: 33, 33, 33;
|
||||
--rgb-secondary-text-color: 114, 114, 114;
|
||||
--rgb-text-primary-color: 255, 255, 255;
|
||||
@ -134,7 +135,11 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-white-color: 255, 255, 255;
|
||||
|
||||
/* rgb state color */
|
||||
--rgb-state-default-color: 68, 115, 158;
|
||||
--rgb-state-default-color: var(--rgb-dark-primary-color, 68, 115, 158);
|
||||
--rgb-state-unavailable-color: var(--rgb-disabled-color);
|
||||
--rgb-state-inactive-color: var(--rgb-inactive-color);
|
||||
|
||||
/* rgb state color */
|
||||
--rgb-state-alarm-armed-color: var(--rgb-red-color);
|
||||
--rgb-state-alarm-arming-color: var(--rgb-orange-color);
|
||||
--rgb-state-alarm-pending-color: var(--rgb-orange-color);
|
||||
@ -142,16 +147,16 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-state-alert-color: var(--rgb-red-color);
|
||||
--rgb-state-automation-color: var(--rgb-amber-color);
|
||||
--rgb-state-binary-sensor-alerting-color: var(--rgb-red-color);
|
||||
--rgb-state-binary-sensor-color: var(--rgb-blue-color);
|
||||
--rgb-state-calendar-color: var(--rgb-blue-color);
|
||||
--rgb-state-camera-color: var(--rgb-blue-color);
|
||||
--rgb-state-binary-sensor-color: var(--rgb-amber-color);
|
||||
--rgb-state-calendar-color: var(--rgb-amber-color);
|
||||
--rgb-state-camera-color: var(--rgb-amber-color);
|
||||
--rgb-state-climate-auto-color: var(--rgb-green-color);
|
||||
--rgb-state-climate-cool-color: var(--rgb-blue-color);
|
||||
--rgb-state-climate-dry-color: var(--rgb-orange-color);
|
||||
--rgb-state-climate-fan-only-color: var(--rgb-cyan-color);
|
||||
--rgb-state-climate-heat-color: var(--rgb-deep-orange-color);
|
||||
--rgb-state-climate-heat-cool-color: var(--rgb-amber-color);
|
||||
--rgb-state-climate-idle-color: var(--rgb-disabled-color);
|
||||
--rgb-state-climate-idle-color: var(--rgb-off-color);
|
||||
--rgb-state-cover-color: var(--rgb-purple-color);
|
||||
--rgb-state-fan-color: var(--rgb-cyan-color);
|
||||
--rgb-state-group-color: var(--rgb-amber-color);
|
||||
@ -164,12 +169,12 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-state-media-player-color: var(--rgb-indigo-color);
|
||||
--rgb-state-person-home-color: var(--rgb-green-color);
|
||||
--rgb-state-person-zone-color: var(--rgb-blue-color);
|
||||
--rgb-state-remote-color: var(--rgb-blue-color);
|
||||
--rgb-state-remote-color: var(--rgb-amber-color);
|
||||
--rgb-state-script-color: var(--rgb-amber-color);
|
||||
--rgb-state-sensor-battery-high-color: var(--rgb-green-color);
|
||||
--rgb-state-sensor-battery-low-color: var(--rgb-red-color);
|
||||
--rgb-state-sensor-battery-medium-color: var(--rgb-orange-color);
|
||||
--rgb-state-sensor-battery-unknown-color: var(--rgb-disabled-color);
|
||||
--rgb-state-sensor-battery-unknown-color: var(--rgb-off-color);
|
||||
--rgb-state-siren-color: var(--rgb-red-color);
|
||||
--rgb-state-sun-day-color: var(--rgb-amber-color);
|
||||
--rgb-state-sun-night-color: var(--rgb-deep-purple-color);
|
||||
|
@ -48,7 +48,8 @@ export const darkStyles = {
|
||||
"energy-grid-return-color": "#a280db",
|
||||
"map-filter":
|
||||
"invert(.9) hue-rotate(170deg) brightness(1.5) contrast(1.2) saturate(.3)",
|
||||
"rgb-disabled-color": "111, 111, 111",
|
||||
"rgb-disabled-color": "70, 70, 70",
|
||||
"rgb-inactive-color": "141, 141, 141",
|
||||
};
|
||||
|
||||
export const derivedStyles = {
|
||||
|
@ -4349,6 +4349,7 @@
|
||||
"primary": "Primary",
|
||||
"accent": "Accent",
|
||||
"disabled": "Disabled",
|
||||
"inactive": "Inactive",
|
||||
"red": "Red",
|
||||
"pink": "Pink",
|
||||
"purple": "Purple",
|
||||
|
Loading…
x
Reference in New Issue
Block a user