diff --git a/README.md b/README.md
index 423ba58..5850b2e 100644
--- a/README.md
+++ b/README.md
@@ -6,22 +6,24 @@ A custom Lovelace card for Home Assistant that combines a button entity (for tog
- **Button Toggle**: Click the card to send a toggle command to your button entity
- **Status Display**: Shows the current state from your binary sensor entity
-- **Custom Icons**: Support for custom icons with separate icons for on/off states
-- **Visual Feedback**: Status indicator dot and icon color changes based on state
-- **Card Editor**: Full UI editor support in Lovelace
+- **Custom Icons**: Icon picker with Material Design Icons support
+- **Icon Size Control**: Adjustable icon size (16px to 500px)
+- **Visual Feedback**: Icon color changes based on state (yellow when on, gray when off)
+- **Show/Hide Options**: Toggle visibility of name and status text
+- **Card Editor**: Full visual editor support in Lovelace with filtered entity pickers
## Installation
### Manual Installation
-1. Copy `dist/light-button-card.js` to your `www/community/light-button-card/` directory in Home Assistant
+1. Copy `light-button-card.js` to your `www/light-button-card/` directory in Home Assistant
2. Add the resource to your Lovelace configuration:
-
-```yaml
-resources:
- - url: /local/light-button-card/light-button-card.js
- type: module
-```
+ - Go to **Settings** → **Dashboards** → **Resources**
+ - Click **"+ ADD RESOURCE"**
+ - Enter the URL: `/local/light-button-card/light-button-card.js`
+ - Set Type to: **JavaScript Module**
+ - Click **CREATE**
+3. Refresh your browser (hard refresh: Ctrl+Shift+R)
### HACS Installation (if published)
@@ -45,15 +47,26 @@ binary_sensor_entity: binary_sensor.plc_light_1_status
```yaml
type: custom:light-button-card
-button_entity: button.plc_light_1 # Required: Button entity to toggle
+button_entity: button.plc_light_1 # Required: Button entity to toggle
binary_sensor_entity: binary_sensor.plc_light_1_status # Required: Binary sensor for status
-name: Living Room Light # Optional: Display name
-icon: mdi:lightbulb # Optional: Default icon (MDI format)
-icon_on: mdi:lightbulb # Optional: Icon when state is "on"
-icon_off: mdi:lightbulb-outline # Optional: Icon when state is "off"
-show_name: true # Optional: Show/hide name (default: true)
+name: Living Room Light # Optional: Display name
+icon: mdi:lightbulb # Optional: Icon (MDI format)
+icon_size: 64 # Optional: Icon size in pixels (16-500, default: 64)
+show_name: true # Optional: Show/hide name (default: true)
+show_status: true # Optional: Show/hide status text (default: true)
```
+## Visual Editor
+
+The card includes a full visual editor with:
+- **Button Entity Picker**: Filtered to show only button entities
+- **Binary Sensor Entity Picker**: Filtered to show only binary sensor entities
+- **Icon Picker**: Dropdown with Material Design Icons
+- **Icon Size Field**: Number input for icon size (16-500px)
+- **Name Field**: Text input for card name
+- **Show Name Switch**: Toggle name visibility
+- **Show Status Switch**: Toggle status text visibility
+
## Icon Configuration
Icons use Material Design Icons (MDI) format. You can use any icon from [Material Design Icons](https://materialdesignicons.com/).
@@ -64,6 +77,7 @@ Examples:
- `mdi:lamp` - Lamp
- `mdi:ceiling-light` - Ceiling light
- `mdi:wall-sconce` - Wall sconce
+- `mdi:light-recessed` - Recessed light
## Usage Example
@@ -72,9 +86,10 @@ type: custom:light-button-card
button_entity: button.plc_kitchen_light
binary_sensor_entity: binary_sensor.plc_kitchen_light_status
name: Kitchen Light
-icon_on: mdi:lightbulb
-icon_off: mdi:lightbulb-outline
+icon: mdi:lightbulb
+icon_size: 90
show_name: true
+show_status: true
```
## How It Works
@@ -84,25 +99,19 @@ show_name: true
2. **Binary Sensor Entity**: The card continuously monitors the binary sensor entity to display the current state (on/off) of the PLC output.
3. **Visual Feedback**:
- - The icon changes color and style based on the state
- - A status indicator dot shows green when on, gray when off
- - The state text displays the current state
+ - The icon changes color based on the state (yellow when on, gray when off)
+ - Icon size is customizable from 16px to 500px
+ - Name and status text can be shown or hidden
+ - Hover effects provide visual feedback
-## Development
+## File Structure
-### Building
-
-```bash
-npm install
-npm run build
```
-
-The compiled file will be in `dist/light-button-card.js`.
-
-### Development Mode
-
-```bash
-npm run watch
+HomeAssistant-Light-Buttons/
+├── light-button-card.js # Main card file (ready to use)
+├── example-configuration.yaml # Example YAML configuration
+├── hacs.json # HACS metadata
+└── README.md # This file
```
## Requirements
@@ -111,7 +120,24 @@ npm run watch
- Button entity (for toggling)
- Binary sensor entity (for status)
+## Troubleshooting
+
+### Card not appearing
+1. Clear browser cache (Ctrl+Shift+R)
+2. Reload Lovelace resources (Settings → Dashboards → Resources → Reload resources)
+3. Verify the resource URL is `/local/light-button-card/light-button-card.js`
+4. Check browser console (F12) for errors
+
+### Icon size not changing
+1. Hard refresh browser (Ctrl+Shift+R)
+2. Enter the size value directly in the field
+3. Check that the value is between 16 and 500
+
+### Can only type one character in editor
+1. Clear browser cache completely
+2. Hard refresh (Ctrl+Shift+R)
+3. The editor should maintain focus while typing
+
## License
MIT
-
diff --git a/example-configuration.yaml b/example-configuration.yaml
index 29e2d69..0aa9ac4 100644
--- a/example-configuration.yaml
+++ b/example-configuration.yaml
@@ -5,16 +5,17 @@ type: custom:light-button-card
button_entity: button.plc_light_1
binary_sensor_entity: binary_sensor.plc_light_1_status
name: Living Room Light
-icon_on: mdi:lightbulb
-icon_off: mdi:lightbulb-outline
+icon: mdi:lightbulb
+icon_size: 64
show_name: true
+show_status: true
-# Another example with different icon
+# Another example with different settings
# type: custom:light-button-card
# button_entity: button.plc_light_2
# binary_sensor_entity: binary_sensor.plc_light_2_status
# name: Kitchen Light
-# icon_on: mdi:ceiling-light
-# icon_off: mdi:ceiling-light-outline
+# icon: mdi:ceiling-light
+# icon_size: 90
# show_name: true
-
+# show_status: false
diff --git a/light-button-card.js b/light-button-card.js
new file mode 100644
index 0000000..a556a76
--- /dev/null
+++ b/light-button-card.js
@@ -0,0 +1,458 @@
+// Light Button Card for Home Assistant
+// Based on: https://developers.home-assistant.io/docs/frontend/custom-ui/custom-card/
+
+class LightButtonCard extends HTMLElement {
+ set hass(hass) {
+ this._hass = hass;
+ this._updateContent();
+ }
+
+ setConfig(config) {
+ if (!config.button_entity || !config.binary_sensor_entity) {
+ throw new Error('button_entity and binary_sensor_entity are required');
+ }
+ this._config = {
+ icon: 'mdi:lightbulb',
+ name: '',
+ show_name: true,
+ show_status: true,
+ icon_size: 64,
+ ...config,
+ };
+ this._updateContent();
+ }
+
+ _updateContent() {
+ if (!this._config || !this._hass) {
+ return;
+ }
+
+ if (!this.content) {
+ this.innerHTML = `
+
+
+
+ `;
+ this.content = this.querySelector('.button-container');
+ this.icon = this.querySelector('ha-icon');
+ this.nameDiv = this.querySelector('.name');
+ this.stateText = this.querySelector('.state-text');
+
+ // Add click handler
+ this.querySelector('ha-card').addEventListener('click', () => this._handleClick());
+
+ // Add styles
+ if (!this.styleSheet) {
+ const style = document.createElement('style');
+ style.textContent = `
+ ha-card {
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ cursor: pointer;
+ transition: transform 0.2s, box-shadow 0.2s;
+ }
+ ha-card:hover {
+ transform: scale(1.02);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+ }
+ ha-card:active {
+ transform: scale(0.98);
+ }
+ .button-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ }
+ .icon-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: auto;
+ }
+ ha-icon {
+ color: var(--primary-color);
+ transition: color 0.3s, transform 0.2s;
+ display: inline-flex !important;
+ align-items: center;
+ justify-content: center;
+ --mdc-icon-size: var(--icon-size, 64px) !important;
+ }
+ ha-icon svg,
+ ha-icon ha-svg-icon,
+ ha-icon ha-svg-icon svg {
+ width: 100% !important;
+ height: 100% !important;
+ max-width: none !important;
+ max-height: none !important;
+ }
+ .icon-container.on ha-icon {
+ color: var(--paper-item-icon-active-color, #fdd835);
+ filter: drop-shadow(0 0 8px rgba(253, 216, 53, 0.6));
+ }
+ .icon-container.off ha-icon {
+ color: var(--disabled-text-color, #9e9e9e);
+ }
+ .name {
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--primary-text-color);
+ text-align: center;
+ }
+ .state-text {
+ font-size: 12px;
+ color: var(--secondary-text-color);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
+ `;
+ this.appendChild(style);
+ this.styleSheet = style;
+ }
+ }
+
+ const state = this._getState();
+ const isOn = this._isOn();
+ const icon = this._getIcon();
+ const iconSize = this._config.icon_size || 64;
+
+ // Update icon
+ this.icon.setAttribute('icon', icon);
+ this.icon.style.setProperty('--icon-size', `${iconSize}px`);
+ this.icon.style.width = `${iconSize}px`;
+ this.icon.style.height = `${iconSize}px`;
+ this.icon.style.minWidth = `${iconSize}px`;
+ this.icon.style.minHeight = `${iconSize}px`;
+ this.icon.style.maxWidth = `${iconSize}px`;
+ this.icon.style.maxHeight = `${iconSize}px`;
+ this.icon.style.setProperty('--mdc-icon-size', `${iconSize}px`);
+
+ // Ensure internal SVG scales - check multiple possible structures
+ const svgIcon = this.icon.querySelector('ha-svg-icon');
+ if (svgIcon) {
+ svgIcon.style.width = `${iconSize}px`;
+ svgIcon.style.height = `${iconSize}px`;
+ svgIcon.style.setProperty('--mdc-icon-size', `${iconSize}px`);
+ const svg = svgIcon.querySelector('svg');
+ if (svg) {
+ svg.style.width = `${iconSize}px`;
+ svg.style.height = `${iconSize}px`;
+ svg.setAttribute('width', `${iconSize}`);
+ svg.setAttribute('height', `${iconSize}`);
+ }
+ }
+ const svg = this.icon.querySelector('svg');
+ if (svg) {
+ svg.style.width = `${iconSize}px`;
+ svg.style.height = `${iconSize}px`;
+ svg.setAttribute('width', `${iconSize}`);
+ svg.setAttribute('height', `${iconSize}`);
+ }
+
+ // Update icon container class
+ const iconContainer = this.querySelector('.icon-container');
+ if (isOn) {
+ iconContainer.classList.add('on');
+ iconContainer.classList.remove('off');
+ } else {
+ iconContainer.classList.add('off');
+ iconContainer.classList.remove('on');
+ }
+
+ // Update name
+ if (this._config.show_name && this._config.name) {
+ this.nameDiv.textContent = this._config.name;
+ this.nameDiv.style.display = 'block';
+ } else {
+ this.nameDiv.style.display = 'none';
+ }
+
+ // Update state text
+ if (this._config.show_status) {
+ this.stateText.textContent = state;
+ this.stateText.style.display = 'block';
+ } else {
+ this.stateText.style.display = 'none';
+ }
+ }
+
+ _getState() {
+ if (!this._hass || !this._config.binary_sensor_entity) {
+ return 'unknown';
+ }
+ const stateObj = this._hass.states[this._config.binary_sensor_entity];
+ if (!stateObj) {
+ return 'unknown';
+ }
+ return stateObj.state;
+ }
+
+ _isOn() {
+ const state = this._getState();
+ return state === 'on' || state === 'true' || state === '1';
+ }
+
+ _getIcon() {
+ return this._config.icon || 'mdi:lightbulb';
+ }
+
+ async _handleClick() {
+ if (!this._hass || !this._config.button_entity) {
+ return;
+ }
+
+ const buttonEntity = this._hass.states[this._config.button_entity];
+ if (!buttonEntity) {
+ console.error(`Button entity ${this._config.button_entity} not found`);
+ return;
+ }
+
+ // Call the button press service
+ await this._hass.callService('button', 'press', {
+ entity_id: this._config.button_entity,
+ });
+ }
+
+ getCardSize() {
+ return 3;
+ }
+
+ static getStubConfig() {
+ return {
+ button_entity: 'button.example',
+ binary_sensor_entity: 'binary_sensor.example',
+ icon: 'mdi:lightbulb',
+ name: 'Light',
+ show_name: true,
+ show_status: true,
+ icon_size: 64,
+ };
+ }
+}
+
+customElements.define('light-button-card', LightButtonCard);
+
+// Card editor
+class LightButtonCardEditor extends HTMLElement {
+ set hass(hass) {
+ this._hass = hass;
+ if (this.content && this._elements) {
+ // Update hass on existing elements
+ this._elements.buttonPicker.hass = hass;
+ this._elements.sensorPicker.hass = hass;
+ } else if (this.content) {
+ this._updateEditor();
+ }
+ }
+
+ setConfig(config) {
+ this._config = { ...config };
+ if (!this.content) {
+ this._updateEditor();
+ } else {
+ // Update values without rebuilding
+ this._updateEditorValues();
+ }
+ }
+
+ _updateEditorValues() {
+ if (!this._elements) return;
+
+ if (this._elements.buttonPicker) {
+ this._elements.buttonPicker.value = this._config.button_entity || '';
+ }
+ if (this._elements.sensorPicker) {
+ this._elements.sensorPicker.value = this._config.binary_sensor_entity || '';
+ }
+ if (this._elements.iconPicker) {
+ this._elements.iconPicker.value = this._config.icon || 'mdi:lightbulb';
+ }
+ if (this._elements.iconSizeField) {
+ this._elements.iconSizeField.value = this._config.icon_size || 64;
+ }
+ if (this._elements.nameField) {
+ this._elements.nameField.value = this._config.name || '';
+ }
+ if (this._elements.showNameSwitch) {
+ this._elements.showNameSwitch.checked = this._config.show_name !== false;
+ }
+ if (this._elements.showStatusSwitch) {
+ this._elements.showStatusSwitch.checked = this._config.show_status !== false;
+ }
+ }
+
+ _updateEditor() {
+ if (!this._hass) {
+ return;
+ }
+
+ if (!this.content) {
+ this.innerHTML = `
+
+ `;
+ this.content = this.querySelector('.card-config');
+
+ // Add styles
+ const style = document.createElement('style');
+ style.textContent = `
+ .card-config {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+ `;
+ this.appendChild(style);
+ }
+
+ // Clear and rebuild editor only if elements don't exist
+ if (!this._elements) {
+ this.content.innerHTML = '';
+ this._elements = {};
+
+ // Button Entity Picker
+ const buttonPicker = document.createElement('ha-entity-picker');
+ buttonPicker.label = 'Button Entity';
+ buttonPicker.hass = this._hass;
+ buttonPicker.value = this._config.button_entity || '';
+ buttonPicker.configValue = 'button_entity';
+ buttonPicker.required = true;
+ buttonPicker.includeDomains = ['button'];
+ buttonPicker.addEventListener('value-changed', (ev) => {
+ this._config.button_entity = ev.detail.value;
+ this._fireConfigChanged();
+ });
+ this.content.appendChild(buttonPicker);
+ this._elements.buttonPicker = buttonPicker;
+
+ // Binary Sensor Entity Picker
+ const sensorPicker = document.createElement('ha-entity-picker');
+ sensorPicker.label = 'Binary Sensor Entity';
+ sensorPicker.hass = this._hass;
+ sensorPicker.value = this._config.binary_sensor_entity || '';
+ sensorPicker.configValue = 'binary_sensor_entity';
+ sensorPicker.required = true;
+ sensorPicker.includeDomains = ['binary_sensor'];
+ sensorPicker.addEventListener('value-changed', (ev) => {
+ this._config.binary_sensor_entity = ev.detail.value;
+ this._fireConfigChanged();
+ });
+ this.content.appendChild(sensorPicker);
+ this._elements.sensorPicker = sensorPicker;
+
+ // Icon Picker
+ const iconPicker = document.createElement('ha-icon-picker');
+ iconPicker.label = 'Icon';
+ iconPicker.value = this._config.icon || 'mdi:lightbulb';
+ iconPicker.configValue = 'icon';
+ iconPicker.addEventListener('value-changed', (ev) => {
+ this._config.icon = ev.detail.value;
+ this._fireConfigChanged();
+ });
+ this.content.appendChild(iconPicker);
+ this._elements.iconPicker = iconPicker;
+
+ // Icon Size field
+ const iconSizeField = document.createElement('ha-textfield');
+ iconSizeField.label = 'Icon Size (pixels)';
+ iconSizeField.type = 'number';
+ iconSizeField.value = this._config.icon_size || 64;
+ iconSizeField.configValue = 'icon_size';
+ iconSizeField.min = 16;
+ iconSizeField.max = 500;
+ iconSizeField.step = 1;
+ iconSizeField.addEventListener('input', (ev) => {
+ const value = parseInt(ev.target.value);
+ if (!isNaN(value) && value >= 16) {
+ this._config.icon_size = value;
+ this._fireConfigChanged();
+ }
+ });
+ this.content.appendChild(iconSizeField);
+ this._elements.iconSizeField = iconSizeField;
+
+ // Name field
+ const nameField = document.createElement('ha-textfield');
+ nameField.label = 'Name (optional)';
+ nameField.value = this._config.name || '';
+ nameField.configValue = 'name';
+ nameField.addEventListener('input', (ev) => {
+ this._config.name = ev.target.value;
+ this._fireConfigChanged();
+ });
+ this.content.appendChild(nameField);
+ this._elements.nameField = nameField;
+
+ // Show Name switch
+ const showNameContainer = document.createElement('div');
+ showNameContainer.style.display = 'flex';
+ showNameContainer.style.alignItems = 'center';
+ showNameContainer.style.justifyContent = 'space-between';
+ const showNameLabel = document.createElement('div');
+ showNameLabel.textContent = 'Show Name';
+ showNameLabel.style.flex = '1';
+ const showNameSwitch = document.createElement('ha-switch');
+ showNameSwitch.checked = this._config.show_name !== false;
+ showNameSwitch.configValue = 'show_name';
+ showNameSwitch.addEventListener('change', (ev) => {
+ this._config.show_name = ev.target.checked;
+ this._fireConfigChanged();
+ });
+ showNameContainer.appendChild(showNameLabel);
+ showNameContainer.appendChild(showNameSwitch);
+ this.content.appendChild(showNameContainer);
+ this._elements.showNameSwitch = showNameSwitch;
+
+ // Show Status switch
+ const showStatusContainer = document.createElement('div');
+ showStatusContainer.style.display = 'flex';
+ showStatusContainer.style.alignItems = 'center';
+ showStatusContainer.style.justifyContent = 'space-between';
+ const showStatusLabel = document.createElement('div');
+ showStatusLabel.textContent = 'Show Status';
+ showStatusLabel.style.flex = '1';
+ const showStatusSwitch = document.createElement('ha-switch');
+ showStatusSwitch.checked = this._config.show_status !== false;
+ showStatusSwitch.configValue = 'show_status';
+ showStatusSwitch.addEventListener('change', (ev) => {
+ this._config.show_status = ev.target.checked;
+ this._fireConfigChanged();
+ });
+ showStatusContainer.appendChild(showStatusLabel);
+ showStatusContainer.appendChild(showStatusSwitch);
+ this.content.appendChild(showStatusContainer);
+ this._elements.showStatusSwitch = showStatusSwitch;
+ }
+ }
+
+ _fireConfigChanged() {
+ const event = new CustomEvent('config-changed', {
+ bubbles: true,
+ composed: true,
+ detail: { config: { ...this._config } }
+ });
+ this.dispatchEvent(event);
+ }
+}
+
+customElements.define('light-button-card-editor', LightButtonCardEditor);
+
+// Register card for card picker
+LightButtonCard.getConfigElement = () => {
+ return document.createElement('light-button-card-editor');
+};
+
+window.customCards = window.customCards || [];
+window.customCards.push({
+ type: 'light-button-card',
+ name: 'Light Button Card',
+ description: 'A card with button toggle and binary sensor status',
+});
diff --git a/package.json b/package.json
deleted file mode 100644
index 78f26e5..0000000
--- a/package.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "homeassistant-light-buttons",
- "version": "1.0.0",
- "description": "Custom Lovelace card for Home Assistant with button toggle and binary sensor status",
- "main": "dist/light-button-card.js",
- "scripts": {
- "build": "webpack --mode production",
- "watch": "webpack --mode development --watch"
- },
- "keywords": [
- "home-assistant",
- "lovelace",
- "custom-card"
- ],
- "author": "",
- "license": "MIT",
- "devDependencies": {
- "@types/node": "^20.0.0",
- "ts-loader": "^9.5.0",
- "typescript": "^5.0.0",
- "webpack": "^5.88.0",
- "webpack-cli": "^5.1.0"
- },
- "dependencies": {
- "lit": "^2.8.0"
- },
- "peerDependencies": {
- "home-assistant-js-websocket": "^8.0.0"
- }
-}
-
diff --git a/src/light-button-card.ts b/src/light-button-card.ts
deleted file mode 100644
index 87bbfae..0000000
--- a/src/light-button-card.ts
+++ /dev/null
@@ -1,356 +0,0 @@
-import { LitElement, html, css, TemplateResult } from 'lit';
-import { customElement, property, state } from 'lit/decorators.js';
-
-// Type definitions for Home Assistant
-interface HomeAssistant {
- states: { [key: string]: HassEntity };
- callService(domain: string, service: string, data?: any): Promise;
-}
-
-interface HassEntity {
- entity_id: string;
- state: string;
- attributes: { [key: string]: any };
-}
-
-interface LovelaceCardEditor {
- setConfig(config: any): void;
-}
-
-interface LightButtonCardConfig {
- type: string;
- button_entity: string;
- binary_sensor_entity: string;
- icon?: string;
- icon_on?: string;
- icon_off?: string;
- name?: string;
- show_name?: boolean;
-}
-
-// Helper function to fire events
-function fireEvent(node: HTMLElement, type: string, detail?: any): void {
- const event = new CustomEvent(type, {
- detail,
- bubbles: true,
- composed: true,
- });
- node.dispatchEvent(event);
-}
-
-@customElement('light-button-card')
-export class LightButtonCard extends LitElement {
- @property({ attribute: false }) public hass!: HomeAssistant;
- @state() private _config!: LightButtonCardConfig;
-
- static getStubConfig(): Partial {
- return {
- button_entity: 'button.example',
- binary_sensor_entity: 'binary_sensor.example',
- icon: 'mdi:lightbulb',
- name: 'Light',
- show_name: true,
- };
- }
-
- public setConfig(config: LightButtonCardConfig): void {
- if (!config.button_entity || !config.binary_sensor_entity) {
- throw new Error('button_entity and binary_sensor_entity are required');
- }
- this._config = {
- icon: 'mdi:lightbulb',
- icon_on: 'mdi:lightbulb',
- icon_off: 'mdi:lightbulb-outline',
- name: '',
- show_name: true,
- ...config,
- };
- }
-
- static get styles() {
- return css`
- ha-card {
- padding: 16px;
- display: flex;
- flex-direction: column;
- align-items: center;
- cursor: pointer;
- transition: transform 0.2s, box-shadow 0.2s;
- }
-
- ha-card:hover {
- transform: scale(1.02);
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- }
-
- ha-card:active {
- transform: scale(0.98);
- }
-
- .button-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8px;
- }
-
- .icon-container {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 64px;
- height: 64px;
- }
-
- ha-icon {
- width: 64px;
- height: 64px;
- color: var(--primary-color);
- transition: color 0.3s, transform 0.2s;
- }
-
- .icon-container.on ha-icon {
- color: var(--paper-item-icon-active-color, #fdd835);
- filter: drop-shadow(0 0 8px rgba(253, 216, 53, 0.6));
- }
-
- .icon-container.off ha-icon {
- color: var(--disabled-text-color, #9e9e9e);
- }
-
- .status-indicator {
- width: 12px;
- height: 12px;
- border-radius: 50%;
- position: absolute;
- top: 0;
- right: 0;
- background-color: var(--disabled-text-color, #9e9e9e);
- border: 2px solid var(--card-background-color, #ffffff);
- transition: background-color 0.3s;
- }
-
- .status-indicator.on {
- background-color: #4caf50;
- box-shadow: 0 0 8px rgba(76, 175, 80, 0.6);
- }
-
- .name {
- font-size: 14px;
- font-weight: 500;
- color: var(--primary-text-color);
- text-align: center;
- }
-
- .state-text {
- font-size: 12px;
- color: var(--secondary-text-color);
- text-transform: uppercase;
- letter-spacing: 0.5px;
- }
- `;
- }
-
- private _getState(): string {
- if (!this.hass || !this._config.binary_sensor_entity) {
- return 'unknown';
- }
- const stateObj = this.hass.states[this._config.binary_sensor_entity];
- if (!stateObj) {
- return 'unknown';
- }
- return stateObj.state;
- }
-
- private _isOn(): boolean {
- const state = this._getState();
- return state === 'on' || state === 'true' || state === '1';
- }
-
- private _getIcon(): string {
- if (this._isOn()) {
- return this._config.icon_on || this._config.icon || 'mdi:lightbulb';
- }
- return this._config.icon_off || this._config.icon || 'mdi:lightbulb-outline';
- }
-
- private async _handleClick(): Promise {
- if (!this.hass || !this._config.button_entity) {
- return;
- }
-
- const buttonEntity = this.hass.states[this._config.button_entity];
- if (!buttonEntity) {
- console.error(`Button entity ${this._config.button_entity} not found`);
- return;
- }
-
- // Call the button press service
- await this.hass.callService('button', 'press', {
- entity_id: this._config.button_entity,
- });
-
- // Fire event for card updates
- fireEvent(this, 'hass-more-info', { entityId: this._config.button_entity });
- }
-
- protected render(): TemplateResult {
- if (!this._config || !this.hass) {
- return html``;
- }
-
- const isOn = this._isOn();
- const state = this._getState();
- const icon = this._getIcon();
-
- return html`
-
-
-
- `;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'light-button-card': LightButtonCard;
- }
-}
-
-// Card editor for Lovelace UI
-@customElement('light-button-card-editor')
-export class LightButtonCardEditor extends LitElement implements LovelaceCardEditor {
- @property({ attribute: false }) public hass?: HomeAssistant;
- @property({ attribute: false }) private _config?: LightButtonCardConfig;
-
- public setConfig(config: LightButtonCardConfig): void {
- this._config = config;
- }
-
- static get styles() {
- return css`
- .card-config {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- `;
- }
-
- protected render(): TemplateResult {
- if (!this.hass || !this._config) {
- return html``;
- }
-
- return html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Show Name
-
-
- `;
- }
-
- private _valueChanged(ev: Event): void {
- if (!this._config || !this.hass) {
- return;
- }
-
- const target = ev.target as any;
- const configValue = target.configValue;
-
- if (configValue === 'show_name') {
- this._config = {
- ...this._config,
- [configValue]: target.checked,
- };
- } else {
- this._config = {
- ...this._config,
- [configValue]: target.value || target.checked,
- };
- }
-
- fireEvent(this, 'config-changed', { config: this._config });
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'light-button-card-editor': LightButtonCardEditor;
- }
-}
-
-// Register the card editor
-if (customElements.get('light-button-card-editor') === undefined) {
- customElements.define('light-button-card-editor', LightButtonCardEditor);
-}
-
-// Card configuration helper for Lovelace
-(window as any).customCards = (window as any).customCards || [];
-(window as any).customCards.push({
- type: 'light-button-card',
- name: 'Light Button Card',
- description: 'A card with button toggle and binary sensor status',
-});
-
diff --git a/tsconfig.json b/tsconfig.json
deleted file mode 100644
index 5d2fe81..0000000
--- a/tsconfig.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2020",
- "module": "ES2020",
- "lib": ["ES2020", "DOM"],
- "moduleResolution": "node",
- "strict": true,
- "esModuleInterop": true,
- "skipLibCheck": true,
- "forceConsistentCasingInFileNames": true,
- "declaration": true,
- "outDir": "./dist",
- "rootDir": "./src"
- },
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist"]
-}
-
diff --git a/webpack.config.js b/webpack.config.js
deleted file mode 100644
index 269a06e..0000000
--- a/webpack.config.js
+++ /dev/null
@@ -1,32 +0,0 @@
-const path = require('path');
-
-module.exports = {
- entry: './src/light-button-card.ts',
- output: {
- filename: 'light-button-card.js',
- path: path.resolve(__dirname, 'dist'),
- library: {
- type: 'module',
- },
- },
- experiments: {
- outputModule: true,
- },
- module: {
- rules: [
- {
- test: /\.ts$/,
- use: 'ts-loader',
- exclude: /node_modules/,
- },
- ],
- },
- resolve: {
- extensions: ['.ts', '.js'],
- },
- mode: 'production',
- optimization: {
- minimize: false,
- },
-};
-