Skip to content

Commit ef4d94b

Browse files
committed
Add shopping-list card
1 parent 6da1eb3 commit ef4d94b

18 files changed

+786
-2
lines changed

.hass_dev/lovelace-mushroom-showcase.yaml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
title: Mushroom Showcase
1+
title: Mushroom Showcase
22
views:
33
- !include views/light-view.yaml
44
- !include views/cover-view.yaml
@@ -14,4 +14,5 @@ views:
1414
- !include views/vacuum-view.yaml
1515
- !include views/lock-view.yaml
1616
- !include views/humidifier-view.yaml
17-
- !include views/select-view.yaml
17+
- !include views/select-view.yaml
18+
- !include views/shopping-list-view.yaml
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
title: Shopping Lists
2+
icon: mdi:cart
3+
cards:
4+
- type: grid
5+
title: Shopping List
6+
cards:
7+
- type: custom:mushroom-shopping-list
8+
name: My Shopping List
9+
icon: mdi:cart
10+
primary_info: name
11+
secondary_info: state
12+
columns: 1
13+
square: false
14+
- type: vertical-stack
15+
title: Layout
16+
cards:
17+
- type: custom:mushroom-shopping-list
18+
name: Horizontal Shopping List
19+
layout: horizontal
20+
primary_info: name
21+
secondary_info: state
22+
- type: grid
23+
columns: 2
24+
square: false
25+
cards:
26+
- type: custom:mushroom-shopping-list
27+
checked_icon: mdi:checkbox-blank-circle
28+
unchecked_icon: mdi:checkbox-blank-circle-outline
29+
- type: custom:mushroom-shopping-list
30+
name: Vertical Shopping List
31+
icon: mdi:arrow-down
32+
layout: vertical
33+
primary_info: name
34+
secondary_info: state

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Different cards are available for differents entities :
8383
- 💧 [Humidifier card](docs/cards/humidifier.md)
8484
- 🌡 [Climate card](docs/cards/climate.md)
8585
- 📑 [Select card](docs/cards/select.md)
86+
- 🛒 [Shopping list card](docs/cards/shopping-list.md)
8687
8788
### Theme customization
8889

docs/cards/shopping-list.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Shopping list card
2+
3+
![Shooping list light](../images/shopping-list-light.png)
4+
![Shopping list dark](../images/shopping-list-dark.png)
5+
6+
## Description
7+
8+
A mushroom card for the [shopping_list](https://www.home-assistant.io/integrations/shopping_list) integration.
9+
10+
## Configuration variables
11+
12+
All options are available in the lovelace editor but you can use `yaml` if you want.
13+
14+
| Name | Type | Default | Description |
15+
| :--------------- | :-------------------------------- | :--------------------------- | :----------------------------------------------- |
16+
| `name` | string | Shopping List | Name of the card. May be displayed as its title. |
17+
| `icon` | string | `mdi:cart` | Icon displayed next to the title. |
18+
| `layout` | `default` `horizontal` `vertical` | `default` | Affects the internal layout of the card. |
19+
| `primary_info` | `name` `state` `none` | `name` | Info to show as title. |
20+
| `secondary_info` | `name` `state` `none` | `state` | Info to show as subtitle. |
21+
| `checked_icon` | string | `mdi:checkbox-marked` | Icon used for completed items. |
22+
| `unchecked_icon` | string | `mdi:checkbox-blank-outline` | Icon used for open items. |

docs/images/shopping-list-dark.png

43.8 KB
Loading

docs/images/shopping-list-light.png

45.2 KB
Loading

src/cards/shopping-list-card/const.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { PREFIX_NAME } from "../../const";
2+
3+
export const SHOPPING_LIST_CARD_NAME = `${PREFIX_NAME}-shopping-list`;
4+
export const SHOPPING_LIST_CARD_EDITOR_NAME = `${SHOPPING_LIST_CARD_NAME}-editor`;
5+
export const SHOPPING_LIST_CARD_ITEM_NAME = `${SHOPPING_LIST_CARD_NAME}-item`;
6+
export const SHOPPING_LIST_CARD_DIVIDER_NAME = `${SHOPPING_LIST_CARD_NAME}-divider`;
7+
export const SHOPPING_LIST_CARD_INPUT_NAME = `${SHOPPING_LIST_CARD_NAME}-input`;
8+
export const SHOPPING_LIST_ENTITY_DOMAINS = [];
9+
export const SHOPPING_LIST_UPDATED_EVENT = "shopping_list_updated";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { assign, string, object, optional } from "superstruct";
2+
import { LovelaceCardConfig } from "../../ha";
3+
import {
4+
AppearanceSharedConfig,
5+
appearanceSharedConfigStruct,
6+
} from "../../shared/config/appearance-config";
7+
import { lovelaceCardConfigStruct } from "../../shared/config/lovelace-card-config";
8+
9+
export type ShoppingListCardConfig = LovelaceCardConfig &
10+
AppearanceSharedConfig & {
11+
name?: string;
12+
icon?: string;
13+
checked_icon?: string;
14+
unchecked_icon?: string;
15+
};
16+
17+
export const shoppingListCardConfigStruct = assign(
18+
lovelaceCardConfigStruct,
19+
appearanceSharedConfigStruct,
20+
object({
21+
name: optional(string()),
22+
icon: optional(string()),
23+
checked_icon: optional(string()),
24+
unchecked_icon: optional(string()),
25+
})
26+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
2+
import { customElement, property } from "lit/decorators.js";
3+
import { classMap } from "lit/directives/class-map.js";
4+
import { fireEvent } from "../../ha";
5+
import setupCustomlocalize from "../../localize";
6+
import "../../shared/badge-icon";
7+
import "../../shared/button";
8+
import "../../shared/card";
9+
import "../../shared/shape-avatar";
10+
import "../../shared/shape-icon";
11+
import "../../shared/state-info";
12+
import "../../shared/state-item";
13+
import { SHOPPING_LIST_CARD_DIVIDER_NAME } from "./const";
14+
15+
@customElement(SHOPPING_LIST_CARD_DIVIDER_NAME)
16+
export class ShoppingListCardDivider extends LitElement {
17+
@property() localize: (key: string) => string = (key) => key;
18+
19+
private _buttonClicked() {
20+
const event = new CustomEvent("clear-completed", { bubbles: true, composed: true });
21+
this.dispatchEvent(event);
22+
}
23+
24+
protected render(): TemplateResult {
25+
return html`
26+
<div class="divider">
27+
<hr class="hr-start" />
28+
<button class="button" @click=${this._buttonClicked}>
29+
<ha-icon class="button-icon" icon="mdi:delete-sweep-outline"></ha-icon>
30+
${this.localize("editor.card.shopping_list.clear_completed")}
31+
</button>
32+
<hr class="hr-end" />
33+
</div>
34+
`;
35+
}
36+
37+
static get styles(): CSSResultGroup {
38+
return [
39+
css`
40+
.divider * {
41+
box-sizing: border-box;
42+
}
43+
44+
.divider {
45+
display: flex;
46+
align-items: center;
47+
margin: 0 calc(var(--spacing) * -1);
48+
}
49+
50+
.hr-start,
51+
.hr-end {
52+
height: 1px;
53+
background: rgba(var(--rgb-primary-text-color), 0.05);
54+
flex-shrink: 0;
55+
border: none;
56+
min-width: calc(var(--spacing) * 1);
57+
}
58+
59+
.hr-start {
60+
flex: 1;
61+
}
62+
63+
.button {
64+
flex-shrink: 0;
65+
box-sizing: border-box;
66+
background: transparent;
67+
border: none;
68+
font-weight: 600;
69+
color: var(--secondary-text-color);
70+
display: flex;
71+
align-items: center;
72+
justify-content: center;
73+
padding: 0 var(--spacing);
74+
cursor: pointer;
75+
}
76+
77+
.button-icon {
78+
--mdc-icon-size: 20px;
79+
margin-inline-end: 0.5rem;
80+
}
81+
`,
82+
];
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { html, TemplateResult } from "lit";
2+
import { customElement, state } from "lit/decorators.js";
3+
import memoizeOne from "memoize-one";
4+
import { assert } from "superstruct";
5+
import { fireEvent, LovelaceCardEditor } from "../../ha";
6+
import setupCustomlocalize from "../../localize";
7+
import { APPEARANCE_FORM_SCHEMA } from "../../shared/config/appearance-config";
8+
import { MushroomBaseElement } from "../../utils/base-element";
9+
import { GENERIC_LABELS } from "../../utils/form/generic-fields";
10+
import { HaFormSchema } from "../../utils/form/ha-form";
11+
import { loadHaComponents } from "../../utils/loader";
12+
import { SHOPPING_LIST_CARD_EDITOR_NAME } from "./const";
13+
import { ShoppingListCardConfig, shoppingListCardConfigStruct } from "./shopping-list-card-config";
14+
15+
export const SHOPPING_LIST_LABELS = ["checked_icon", "unchecked_icon"];
16+
17+
const schema = [
18+
{ name: "name", selector: { text: {} } },
19+
{ name: "icon", selector: { icon: {} } },
20+
{
21+
type: "grid",
22+
name: "",
23+
schema: [
24+
{ name: "layout", selector: { "mush-layout": {} } },
25+
{ name: "primary_info", selector: { "mush-info": {} } },
26+
{ name: "secondary_info", selector: { "mush-info": {} } },
27+
],
28+
},
29+
{
30+
type: "grid",
31+
name: "",
32+
schema: [
33+
{ name: "unchecked_icon", selector: { icon: {} } },
34+
{ name: "checked_icon", selector: { icon: {} } },
35+
],
36+
},
37+
];
38+
39+
@customElement(SHOPPING_LIST_CARD_EDITOR_NAME)
40+
export class ShoppingListCardEditor extends MushroomBaseElement implements LovelaceCardEditor {
41+
@state() private _config?: ShoppingListCardConfig;
42+
43+
connectedCallback() {
44+
super.connectedCallback();
45+
void loadHaComponents();
46+
}
47+
48+
public setConfig(config: ShoppingListCardConfig): void {
49+
assert(config, shoppingListCardConfigStruct);
50+
this._config = config;
51+
}
52+
53+
private _computeLabel = (schema: HaFormSchema) => {
54+
const customLocalize = setupCustomlocalize(this.hass!);
55+
56+
if (GENERIC_LABELS.includes(schema.name)) {
57+
return customLocalize(`editor.card.generic.${schema.name}`);
58+
}
59+
if (SHOPPING_LIST_LABELS.includes(schema.name)) {
60+
return customLocalize(`editor.card.shopping_list.${schema.name}`);
61+
}
62+
return this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`);
63+
};
64+
65+
protected render(): TemplateResult {
66+
if (!this.hass || !this._config) {
67+
return html``;
68+
}
69+
70+
return html`
71+
<ha-form
72+
.hass=${this.hass}
73+
.data=${this._config}
74+
.schema=${schema}
75+
.computeLabel=${this._computeLabel}
76+
@value-changed=${this._valueChanged}
77+
></ha-form>
78+
`;
79+
}
80+
81+
private _valueChanged(ev: CustomEvent): void {
82+
fireEvent(this, "config-changed", { config: ev.detail.value });
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
2+
import { customElement, property } from "lit/decorators.js";
3+
import { classMap } from "lit/directives/class-map.js";
4+
import { fireEvent } from "../../ha";
5+
import "../../shared/badge-icon";
6+
import "../../shared/button";
7+
import "../../shared/card";
8+
import "../../shared/shape-avatar";
9+
import "../../shared/shape-icon";
10+
import "../../shared/state-info";
11+
import "../../shared/state-item";
12+
import { SHOPPING_LIST_CARD_INPUT_NAME } from "./const";
13+
14+
@customElement(SHOPPING_LIST_CARD_INPUT_NAME)
15+
export class ShoppingListItemInput extends LitElement {
16+
@property() value: string = "";
17+
@property() placeholder: string = "";
18+
19+
private _handleInput(e) {
20+
fireEvent(this, "value-changed", { value: e.target.value });
21+
}
22+
23+
protected render(): TemplateResult {
24+
return html`
25+
<input
26+
type="text"
27+
class="input"
28+
@input=${this._handleInput}
29+
.placeholder=${this.placeholder}
30+
.value=${this.value}
31+
/>
32+
`;
33+
}
34+
35+
static get styles(): CSSResultGroup {
36+
return [
37+
css`
38+
.input {
39+
height: 42px;
40+
display: block;
41+
background: rgba(var(--rgb-primary-text-color), 0.05);
42+
transition: background-color 280ms ease-in-out 0s;
43+
border-radius: var(--control-border-radius);
44+
border: none;
45+
box-sizing: border-box;
46+
padding: 0 1rem;
47+
font-weight: 600;
48+
color: var(--primary-text-color);
49+
outline: none !important;
50+
min-width: 0;
51+
width: 100%;
52+
}
53+
54+
.input:placeholder {
55+
font-weight: 600;
56+
color: var(--secondary-text-color);
57+
}
58+
`,
59+
];
60+
}
61+
}

0 commit comments

Comments
 (0)