Compare commits

...

7 Commits

Author SHA1 Message Date
Simon Lamon
b70d1492b3 Remove duplicated example 2025-11-03 18:45:59 +00:00
Simon Lamon
fba38cc6ec Merge branch 'dev' into dropdown 2025-11-03 19:32:12 +01:00
Simon Lamon
a8b0291a9a Documentation 2025-11-03 18:27:47 +00:00
Simon Lamon
a240bd2634 Update src/resources/theme/color/wa.globals.ts
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-11-03 18:56:17 +01:00
Simon Lamon
9d4bf30753 Merge branch 'dev' into dropdown 2025-10-11 11:55:22 +02:00
Simon Lamon
10b99433ea Shadow and fixes 2025-10-11 09:52:22 +00:00
Simon Lamon
f0d4c9cb72 Introduce ha-dropdown 2025-10-08 17:24:10 +00:00
8 changed files with 300 additions and 17 deletions

View File

@@ -0,0 +1,55 @@
---
title: Dropdown
---
# Dropdown `<ha-dropdown>`
## Implementation
A compact, accessible dropdown menu for choosing actions or settings. `ha-dropdown` supports composed menu items (`<ha-dropdown-item>`) for icons, submenus, checkboxes, disabled entries, and destructive variants. Use composition with `slot="trigger"` to control the trigger button and use `<ha-dropdown-item>` for rich item content.
### Example usage (composition)
```html
<ha-dropdown open>
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
<ha-dropdown-item>
<ha-svg-icon .path="${mdiContentCut}" slot="icon"></ha-svg-icon>
Cut
</ha-dropdown-item>
<ha-dropdown-item>
<ha-svg-icon .path="${mdiContentCopy}" slot="icon"></ha-svg-icon>
Copy
</ha-dropdown-item>
<ha-dropdown-item disabled>
<ha-svg-icon .path="${mdiContentPaste}" slot="icon"></ha-svg-icon>
Paste
</ha-dropdown-item>
<ha-dropdown-item>
Show images
<ha-dropdown-item slot="submenu" value="show-all-images"
>Show all images</ha-dropdown-item
>
<ha-dropdown-item slot="submenu" value="show-thumbnails"
>Show thumbnails</ha-dropdown-item
>
</ha-dropdown-item>
<ha-dropdown-item type="checkbox" checked>Emoji shortcuts</ha-dropdown-item>
<ha-dropdown-item type="checkbox" checked>Word wrap</ha-dropdown-item>
<ha-dropdown-item variant="danger">
<ha-svg-icon .path="${mdiDelete}" slot="icon"></ha-svg-icon>
Delete
</ha-dropdown-item>
</ha-dropdown>
```
### API
This component is based on the webawesome dropdown component.
Check the [webawesome documentation](https://webawesome.com/docs/components/dropdown/) for more details.

View File

@@ -0,0 +1,133 @@
import {
mdiContentCopy,
mdiContentCut,
mdiContentPaste,
mdiDelete,
} from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-button";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-dropdown-item";
import "@home-assistant/webawesome/dist/components/icon/icon";
import "@home-assistant/webawesome/dist/components/button/button";
import "@home-assistant/webawesome/dist/components/dropdown/dropdown";
import "../../../../src/components/ha-dropdown";
import "@home-assistant/webawesome/dist/components/popup/popup";
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
import "../../../../src/components/ha-icon-button";
@customElement("demo-components-ha-dropdown")
export class DemoHaDropdown extends LitElement {
protected render(): TemplateResult {
return html`
${["light", "dark"].map(
(mode) => html`
<div class=${mode}>
<ha-card header="ha-button in ${mode}">
<div class="card-content">
<ha-dropdown open>
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
<ha-dropdown-item>
<ha-svg-icon
.path=${mdiContentCut}
slot="icon"
></ha-svg-icon>
Cut
</ha-dropdown-item>
<ha-dropdown-item>
<ha-svg-icon
.path=${mdiContentCopy}
slot="icon"
></ha-svg-icon>
Copy
</ha-dropdown-item>
<ha-dropdown-item disabled>
<ha-svg-icon
.path=${mdiContentPaste}
slot="icon"
></ha-svg-icon>
Paste
</ha-dropdown-item>
<ha-dropdown-item>
Show images
<ha-dropdown-item slot="submenu" value="show-all-images"
>Show All Images</ha-dropdown-item
>
<ha-dropdown-item slot="submenu" value="show-thumbnails"
>Show Thumbnails</ha-dropdown-item
>
</ha-dropdown-item>
<ha-dropdown-item type="checkbox" checked
>Emoji Shortcuts</ha-dropdown-item
>
<ha-dropdown-item type="checkbox" checked
>Word Wrap</ha-dropdown-item
>
<ha-dropdown-item variant="danger">
<ha-svg-icon .path=${mdiDelete} slot="icon"></ha-svg-icon>
Delete
</ha-dropdown-item>
</ha-dropdown>
</div>
</ha-card>
</div>
`
)}
`;
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
applyThemesOnElement(
this.shadowRoot!.querySelector(".dark"),
{
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
},
undefined,
undefined,
true
);
}
static styles = css`
:host {
display: flex;
justify-content: center;
}
.dark,
.light {
display: block;
background-color: var(--primary-background-color);
padding: 0 50px;
}
.button {
padding: unset;
}
ha-card {
margin: 24px auto;
}
.card-content {
display: flex;
flex-direction: column;
gap: 24px;
}
.card-content div {
display: flex;
gap: 8px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-dropdown": DemoHaDropdown;
}
}

View File

@@ -0,0 +1,33 @@
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
import { css, type CSSResultGroup } from "lit";
import { customElement } from "lit/decorators";
/**
* Home Assistant dropdown item component
*
* @element ha-dropdown-item
* @extends {DropdownItem}
*
* @summary
* A stylable dropdown item component supporting Home Assistant theming, variants, and appearances based on webawesome dropdown item.
*
*/
@customElement("ha-dropdown-item")
export class HaDropdownItem extends DropdownItem {
static get styles(): CSSResultGroup {
return [
DropdownItem.styles,
css`
:host {
min-height: 40px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-dropdown-item": HaDropdownItem;
}
}

View File

@@ -0,0 +1,45 @@
import Dropdown from "@home-assistant/webawesome/dist/components/dropdown/dropdown";
import { css, type CSSResultGroup } from "lit";
import { customElement, property } from "lit/decorators";
/**
* Home Assistant dropdown component
*
* @element ha-dropdown
* @extends {Dropdown}
*
* @summary
* A stylable dropdown component supporting Home Assistant theming, variants, and appearances based on webawesome dropdown.
*
*/
@customElement("ha-dropdown")
export class HaDropdown extends Dropdown {
@property({ attribute: false }) dropdownTag = "ha-dropdown";
@property({ attribute: false }) dropdownItemTag = "ha-dropdown-item";
static get styles(): CSSResultGroup {
return [
Dropdown.styles,
css`
:host {
--wa-color-surface-border: var(--ha-color-border-normal);
--wa-color-surface-raised: var(
--card-background-color,
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
);
}
#menu {
--wa-shadow-m: 0px 4px 8px 0px var(--ha-color-shadow);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-dropdown": HaDropdown;
}
}

View File

@@ -29,6 +29,8 @@ import {
} from "../../dialogs/generic/show-dialog-box"; } from "../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../resources/styles"; import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../../components/ha-dropdown-item";
import "../../components/ha-dropdown";
// Client ID used by iOS app // Client ID used by iOS app
const iOSclientId = "https://home-assistant.io/iOS"; const iOSclientId = "https://home-assistant.io/iOS";
@@ -146,19 +148,18 @@ class HaRefreshTokens extends LitElement {
)} )}
</div> </div>
<div> <div>
<ha-md-button-menu positioning="popover"> <ha-dropdown>
<ha-icon-button <ha-icon-button
slot="trigger" slot="trigger"
.label=${this.hass.localize("ui.common.menu")} .label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical} .path=${mdiDotsVertical}
></ha-icon-button> ></ha-icon-button>
<ha-md-menu-item <ha-dropdown-item
graphic="icon"
@click=${this._toggleTokenExpiration} @click=${this._toggleTokenExpiration}
.token=${token} .token=${token}
> >
<ha-svg-icon <ha-svg-icon
slot="start" slot="icon"
.path=${token.expire_at .path=${token.expire_at
? mdiClockRemoveOutline ? mdiClockRemoveOutline
: mdiClockCheckOutline} : mdiClockCheckOutline}
@@ -170,24 +171,20 @@ class HaRefreshTokens extends LitElement {
: this.hass.localize( : this.hass.localize(
"ui.panel.profile.refresh_tokens.enable_token_expiration" "ui.panel.profile.refresh_tokens.enable_token_expiration"
)} )}
</ha-md-menu-item> </ha-dropdown-item>
<ha-md-menu-item <ha-dropdown-item
graphic="icon" variant="danger"
class="warning"
.disabled=${token.is_current} .disabled=${token.is_current}
@click=${this._deleteToken} @click=${this._deleteToken}
.token=${token} .token=${token}
> >
<ha-svg-icon <ha-svg-icon
class="warning" slot="icon"
slot="start"
.path=${mdiDelete} .path=${mdiDelete}
></ha-svg-icon> ></ha-svg-icon>
<div slot="headline"> ${this.hass.localize("ui.common.delete")}
${this.hass.localize("ui.common.delete")} </ha-dropdown-item>
</div> </ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
</div> </div>
</ha-settings-row> </ha-settings-row>
` `

View File

@@ -155,6 +155,10 @@ export const semanticColorStyles = css`
/* Surfaces */ /* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-95); --ha-color-surface-default: var(--ha-color-neutral-95);
--ha-color-on-surface-default: var(--ha-color-neutral-05);
/* shadow */
--ha-color-shadow: rgba(0, 0, 0, 0.2);
} }
`; `;
@@ -286,5 +290,9 @@ export const darkSemanticColorStyles = css`
/* Surfaces */ /* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-10); --ha-color-surface-default: var(--ha-color-neutral-10);
--ha-color-on-surface-default: var(--ha-color-neutral-95);
/* shadow */
--ha-color-shadow: rgba(255, 255, 255, 0.2);
} }
`; `;

View File

@@ -52,7 +52,9 @@ export const waColorStyles = css`
--wa-color-danger-on-normal: var(--ha-color-on-danger-normal); --wa-color-danger-on-normal: var(--ha-color-on-danger-normal);
--wa-color-danger-on-quiet: var(--ha-color-on-danger-quiet); --wa-color-danger-on-quiet: var(--ha-color-on-danger-quiet);
--wa-color-surface-default: var(--card-background-color); --wa-color-text-normal: var(--ha-color-text-primary);
--wa-color-surface-default: var(--card-background-color);
--wa-color-surface-raised: var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff));
--wa-panel-border-radius: var(--ha-border-radius-3xl); --wa-panel-border-radius: var(--ha-border-radius-3xl);
--wa-panel-border-style: solid; --wa-panel-border-style: solid;
--wa-panel-border-width: 1px; --wa-panel-border-width: 1px;

View File

@@ -16,7 +16,17 @@ export const waMainStyles = css`
--wa-font-weight-action: var(--ha-font-weight-medium); --wa-font-weight-action: var(--ha-font-weight-medium);
--wa-transition-fast: 75ms; --wa-transition-fast: 75ms;
--wa-transition-easing: ease; --wa-transition-easing: ease;
--wa-border-width-l: var(--ha-border-radius-lg);
--wa-border-style: solid;
--wa-border-width-s: var(--ha-border-width-sm);
--wa-border-width-m: var(--ha-border-width-md);
--wa-border-width-l: var(--ha-border-width-lg);
--wa-border-radius-s: var(--ha-border-radius-sm);
--wa-border-radius-m: var(--ha-border-radius-md);
--wa-border-radius-l: var(--ha-border-radius-lg);
--wa-line-height-condensed: 1.25;
--wa-space-xl: 32px; --wa-space-xl: 32px;
} }