mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-09 10:59:50 +00:00
Migrate ha-button-toggle-group to webawesome (#26506)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
@@ -61,7 +61,6 @@
|
|||||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/mwc-base": "0.27.0",
|
"@material/mwc-base": "0.27.0",
|
||||||
"@material/mwc-button": "0.27.0",
|
|
||||||
"@material/mwc-checkbox": "0.27.0",
|
"@material/mwc-checkbox": "0.27.0",
|
||||||
"@material/mwc-dialog": "0.27.0",
|
"@material/mwc-dialog": "0.27.0",
|
||||||
"@material/mwc-drawer": "0.27.0",
|
"@material/mwc-drawer": "0.27.0",
|
||||||
|
|||||||
82
src/components/ha-button-group.ts
Normal file
82
src/components/ha-button-group.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import ButtonGroup from "@awesome.me/webawesome/dist/components/button-group/button-group";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import type { HaButton } from "./ha-button";
|
||||||
|
import { StateSet } from "../resources/polyfills/stateset";
|
||||||
|
|
||||||
|
export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an ha-button element either as the current element or within its descendants.
|
||||||
|
* @param el - The HTML element to search from
|
||||||
|
* @returns The found HaButton element, or null if not found
|
||||||
|
*/
|
||||||
|
function findButton(el: HTMLElement) {
|
||||||
|
const selector = "ha-button";
|
||||||
|
return (el.closest(selector) ?? el.querySelector(selector)) as HaButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @element ha-button-group
|
||||||
|
* @extends {ButtonGroup}
|
||||||
|
* @summary
|
||||||
|
* Group buttons. Extend Webawesome to be able to work with ha-button tags
|
||||||
|
*
|
||||||
|
* @documentation https://webawesome.com/components/button-group
|
||||||
|
*/
|
||||||
|
@customElement("ha-button-group") // @ts-expect-error Intentionally overriding private methods
|
||||||
|
export class HaButtonGroup extends ButtonGroup {
|
||||||
|
attachInternals() {
|
||||||
|
const internals = super.attachInternals();
|
||||||
|
Object.defineProperty(internals, "states", {
|
||||||
|
value: new StateSet(this, internals.states),
|
||||||
|
});
|
||||||
|
return internals;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error updateClassNames is used in super class
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
private override updateClassNames() {
|
||||||
|
const slottedElements = [
|
||||||
|
...this.defaultSlot.assignedElements({ flatten: true }),
|
||||||
|
] as HTMLElement[];
|
||||||
|
this.hasOutlined = false;
|
||||||
|
|
||||||
|
slottedElements.forEach((el) => {
|
||||||
|
const index = slottedElements.indexOf(el);
|
||||||
|
const button = findButton(el);
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
if ((button as HaButton).appearance === "outlined")
|
||||||
|
this.hasOutlined = true;
|
||||||
|
if (this.size) button.setAttribute("size", this.size);
|
||||||
|
button.classList.add("wa-button-group__button");
|
||||||
|
button.classList.toggle(
|
||||||
|
"wa-button-group__horizontal",
|
||||||
|
this.orientation === "horizontal"
|
||||||
|
);
|
||||||
|
button.classList.toggle(
|
||||||
|
"wa-button-group__vertical",
|
||||||
|
this.orientation === "vertical"
|
||||||
|
);
|
||||||
|
button.classList.toggle("wa-button-group__button-first", index === 0);
|
||||||
|
button.classList.toggle(
|
||||||
|
"wa-button-group__button-inner",
|
||||||
|
index > 0 && index < slottedElements.length - 1
|
||||||
|
);
|
||||||
|
button.classList.toggle(
|
||||||
|
"wa-button-group__button-last",
|
||||||
|
index === slottedElements.length - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
// use button-group variant
|
||||||
|
button.setAttribute("variant", this.variant);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-button-group": HaButtonGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,141 +1,72 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
|
||||||
import type { Button } from "@material/mwc-button/mwc-button";
|
|
||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property, queryAll } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { ToggleButton } from "../types";
|
import type { ToggleButton } from "../types";
|
||||||
import "./ha-icon-button";
|
import "./ha-svg-icon";
|
||||||
|
import "./ha-button";
|
||||||
|
import "./ha-button-group";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @element ha-button-toggle-group
|
||||||
|
*
|
||||||
|
* @summary
|
||||||
|
* A button-group with one active selection.
|
||||||
|
*
|
||||||
|
* @attr {ToggleButton[]} buttons - the button config
|
||||||
|
* @attr {string} active - The value of the currently active button.
|
||||||
|
* @attr {("small"|"medium")} size - The size of the buttons in the group.
|
||||||
|
* @attr {("brand"|"neutral"|"success"|"warning"|"danger")} variant - The variant of the buttons in the group.
|
||||||
|
*
|
||||||
|
* @fires value-changed - Dispatched when the active button changes.
|
||||||
|
*/
|
||||||
@customElement("ha-button-toggle-group")
|
@customElement("ha-button-toggle-group")
|
||||||
export class HaButtonToggleGroup extends LitElement {
|
export class HaButtonToggleGroup extends LitElement {
|
||||||
@property({ attribute: false }) public buttons!: ToggleButton[];
|
@property({ attribute: false }) public buttons!: ToggleButton[];
|
||||||
|
|
||||||
@property() public active?: string;
|
@property() public active?: string;
|
||||||
|
|
||||||
@property({ attribute: "full-width", type: Boolean })
|
@property({ reflect: true }) size: "small" | "medium" = "medium";
|
||||||
public fullWidth = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public dense = false;
|
@property() public variant:
|
||||||
|
| "brand"
|
||||||
@queryAll("mwc-button") private _buttons?: Button[];
|
| "neutral"
|
||||||
|
| "success"
|
||||||
|
| "warning"
|
||||||
|
| "danger" = "brand";
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<ha-button-group .variant=${this.variant} .size=${this.size}>
|
||||||
${this.buttons.map((button) =>
|
${this.buttons.map(
|
||||||
button.iconPath
|
(button) =>
|
||||||
? html`<ha-icon-button
|
html`<ha-button
|
||||||
.label=${button.label}
|
class="icon"
|
||||||
.path=${button.iconPath}
|
.value=${button.value}
|
||||||
.value=${button.value}
|
@click=${this._handleClick}
|
||||||
?active=${this.active === button.value}
|
.title=${button.label}
|
||||||
@click=${this._handleClick}
|
.appearance=${this.active === button.value ? "accent" : "filled"}
|
||||||
></ha-icon-button>`
|
>
|
||||||
: html`<mwc-button
|
${button.iconPath
|
||||||
style=${styleMap({
|
? html`<ha-svg-icon
|
||||||
width: this.fullWidth
|
aria-label=${button.label}
|
||||||
? `${100 / this.buttons.length}%`
|
.path=${button.iconPath}
|
||||||
: "initial",
|
></ha-svg-icon>`
|
||||||
})}
|
: button.label}
|
||||||
outlined
|
</ha-button>`
|
||||||
.dense=${this.dense}
|
|
||||||
.value=${button.value}
|
|
||||||
?active=${this.active === button.value}
|
|
||||||
@click=${this._handleClick}
|
|
||||||
>${button.label}</mwc-button
|
|
||||||
>`
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</ha-button-group>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated() {
|
|
||||||
// Work around Safari default margin that is not reset in mwc-button as of aug 2021
|
|
||||||
this._buttons?.forEach(async (button) => {
|
|
||||||
await button.updateComplete;
|
|
||||||
(
|
|
||||||
button.shadowRoot!.querySelector("button") as HTMLButtonElement
|
|
||||||
).style.margin = "0";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleClick(ev): void {
|
private _handleClick(ev): void {
|
||||||
this.active = ev.currentTarget.value;
|
this.active = ev.currentTarget.value;
|
||||||
fireEvent(this, "value-changed", { value: this.active });
|
fireEvent(this, "value-changed", { value: this.active });
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
div {
|
:host {
|
||||||
display: flex;
|
|
||||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
|
||||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
mwc-button {
|
|
||||||
flex: 1;
|
|
||||||
--mdc-shape-small: 0;
|
|
||||||
--mdc-button-outline-width: 1px 0 1px 1px;
|
|
||||||
--mdc-button-outline-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
border: 1px solid var(--primary-color);
|
|
||||||
border-right-width: 0px;
|
|
||||||
}
|
|
||||||
ha-icon-button,
|
|
||||||
mwc-button {
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
ha-icon-button::before,
|
|
||||||
mwc-button::before {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
content: "";
|
|
||||||
transition:
|
|
||||||
opacity 15ms linear,
|
|
||||||
background-color 15ms linear;
|
|
||||||
}
|
|
||||||
ha-icon-button[active]::before,
|
|
||||||
mwc-button[active]::before {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
ha-icon-button[active] {
|
|
||||||
--icon-primary-color: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
mwc-button[active] {
|
|
||||||
--mdc-theme-primary: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
ha-icon-button:first-child,
|
|
||||||
mwc-button:first-child {
|
|
||||||
--mdc-shape-small: 4px 0 0 4px;
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
--mdc-button-outline-width: 1px;
|
|
||||||
}
|
|
||||||
mwc-button:first-child::before {
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
}
|
|
||||||
ha-icon-button:last-child,
|
|
||||||
mwc-button:last-child {
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
border-right-width: 1px;
|
|
||||||
--mdc-shape-small: 0 4px 4px 0;
|
|
||||||
--mdc-button-outline-width: 1px;
|
|
||||||
}
|
|
||||||
mwc-button:last-child::before {
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
}
|
|
||||||
ha-icon-button:only-child,
|
|
||||||
mwc-button:only-child {
|
|
||||||
--mdc-shape-small: 4px;
|
|
||||||
border-right-width: 1px;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
|||||||
* @attr {boolean} loading - shows a loading indicator instead of the buttons label and disable buttons click.
|
* @attr {boolean} loading - shows a loading indicator instead of the buttons label and disable buttons click.
|
||||||
* @attr {boolean} disabled - Disables the button and prevents user interaction.
|
* @attr {boolean} disabled - Disables the button and prevents user interaction.
|
||||||
*/
|
*/
|
||||||
@customElement("ha-button")
|
@customElement("ha-button") // @ts-expect-error Intentionally overriding private methods
|
||||||
export class HaButton extends Button {
|
export class HaButton extends Button {
|
||||||
variant: "brand" | "neutral" | "success" | "warning" | "danger" = "brand";
|
variant: "brand" | "neutral" | "success" | "warning" | "danger" = "brand";
|
||||||
|
|
||||||
@@ -47,6 +47,42 @@ export class HaButton extends Button {
|
|||||||
return internals;
|
return internals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error handleLabelSlotChange is used in super class
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
private override handleLabelSlotChange() {
|
||||||
|
const nodes = this.labelSlot.assignedNodes({ flatten: true });
|
||||||
|
let hasIconLabel = false;
|
||||||
|
let hasIcon = false;
|
||||||
|
let text = "";
|
||||||
|
|
||||||
|
// If there's only an icon and no text, it's an icon button
|
||||||
|
[...nodes].forEach((node) => {
|
||||||
|
if (
|
||||||
|
node.nodeType === Node.ELEMENT_NODE &&
|
||||||
|
(node as HTMLElement).localName === "ha-svg-icon"
|
||||||
|
) {
|
||||||
|
hasIcon = true;
|
||||||
|
if (!hasIconLabel)
|
||||||
|
hasIconLabel = (node as HTMLElement).hasAttribute("aria-label");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate text nodes
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
text += node.textContent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isIconButton = text.trim() === "" && hasIcon;
|
||||||
|
|
||||||
|
if (__DEV__ && this.isIconButton && !hasIconLabel) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
'Icon buttons must have a label for screen readers. Add <ha-svg-icon aria-label="..."> to remove this warning.',
|
||||||
|
this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
Button.styles,
|
Button.styles,
|
||||||
@@ -181,6 +217,11 @@ export class HaButton extends Button {
|
|||||||
color: var(--wa-color-on-normal);
|
color: var(--wa-color-on-normal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
:host([appearance~="filled"]) .button {
|
||||||
|
color: var(--wa-color-on-normal);
|
||||||
|
background-color: var(--wa-color-fill-normal);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
:host([appearance~="filled"])
|
:host([appearance~="filled"])
|
||||||
.button:not(.disabled):not(.loading):active {
|
.button:not(.disabled):not(.loading):active {
|
||||||
background-color: var(--button-color-fill-normal-active);
|
background-color: var(--button-color-fill-normal-active);
|
||||||
|
|||||||
@@ -385,30 +385,22 @@ export class HAFullCalendar extends LitElement {
|
|||||||
if (!this._viewButtons) {
|
if (!this._viewButtons) {
|
||||||
this._viewButtons = [
|
this._viewButtons = [
|
||||||
{
|
{
|
||||||
label: localize(
|
label: localize("ui.components.calendar.views.dayGridMonth"),
|
||||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridMonth"
|
|
||||||
),
|
|
||||||
value: "dayGridMonth",
|
value: "dayGridMonth",
|
||||||
iconPath: mdiViewModule,
|
iconPath: mdiViewModule,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: localize(
|
label: localize("ui.components.calendar.views.dayGridWeek"),
|
||||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridWeek"
|
|
||||||
),
|
|
||||||
value: "dayGridWeek",
|
value: "dayGridWeek",
|
||||||
iconPath: mdiViewWeek,
|
iconPath: mdiViewWeek,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: localize(
|
label: localize("ui.components.calendar.views.dayGridDay"),
|
||||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridDay"
|
|
||||||
),
|
|
||||||
value: "dayGridDay",
|
value: "dayGridDay",
|
||||||
iconPath: mdiViewDay,
|
iconPath: mdiViewDay,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: localize(
|
label: localize("ui.components.calendar.views.listWeek"),
|
||||||
"ui.panel.lovelace.editor.card.calendar.views.listWeek"
|
|
||||||
),
|
|
||||||
value: "listWeek",
|
value: "listWeek",
|
||||||
iconPath: mdiViewAgenda,
|
iconPath: mdiViewAgenda,
|
||||||
},
|
},
|
||||||
@@ -493,10 +485,6 @@ export class HAFullCalendar extends LitElement {
|
|||||||
--mdc-icon-button-size: 32px;
|
--mdc-icon-button-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-button-toggle-group {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-fab {
|
ha-fab {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 16px;
|
bottom: 16px;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { mdiHelpCircle, mdiStarFourPoints } from "@mdi/js";
|
import { mdiHelpCircle, mdiStarFourPoints } from "@mdi/js";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
|||||||
@@ -1168,6 +1168,12 @@
|
|||||||
},
|
},
|
||||||
"summary": "Summary",
|
"summary": "Summary",
|
||||||
"description": "Description"
|
"description": "Description"
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"dayGridMonth": "[%key:ui::panel::lovelace::editor::card::calendar::views::dayGridMonth%]",
|
||||||
|
"dayGridWeek": "[%key:ui::panel::lovelace::editor::card::calendar::views::dayGridWeek%]",
|
||||||
|
"dayGridDay": "[%key:ui::panel::lovelace::editor::card::calendar::views::dayGridDay%]",
|
||||||
|
"listWeek": "[%key:ui::panel::lovelace::editor::card::calendar::views::listWeek%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
|||||||
@@ -2666,7 +2666,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@material/mwc-button@npm:0.27.0, @material/mwc-button@npm:^0.27.0":
|
"@material/mwc-button@npm:^0.27.0":
|
||||||
version: 0.27.0
|
version: 0.27.0
|
||||||
resolution: "@material/mwc-button@npm:0.27.0"
|
resolution: "@material/mwc-button@npm:0.27.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9383,7 +9383,6 @@ __metadata:
|
|||||||
"@material/chips": "npm:=14.0.0-canary.53b3cad2f.0"
|
"@material/chips": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||||
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"
|
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||||
"@material/mwc-base": "npm:0.27.0"
|
"@material/mwc-base": "npm:0.27.0"
|
||||||
"@material/mwc-button": "npm:0.27.0"
|
|
||||||
"@material/mwc-checkbox": "npm:0.27.0"
|
"@material/mwc-checkbox": "npm:0.27.0"
|
||||||
"@material/mwc-dialog": "npm:0.27.0"
|
"@material/mwc-dialog": "npm:0.27.0"
|
||||||
"@material/mwc-drawer": "npm:0.27.0"
|
"@material/mwc-drawer": "npm:0.27.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user