diff --git a/CHANGELOG.md b/CHANGELOG.md index befe2621..b68e4274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ +# 3.1.4 +## @hakit/components +- NEW - FamilyCard - a card that allows multiple person entities to render in a single card, this will also allow the user to long press and show the location of the person. Huge thanks to @jensea for introducing this feature! +- The same "location / map" functionality is also available when long pressing a row in the EntitiesCard if the entity entered is a "person" entity. +- BUGFIX - types for GarbageCollectionCard subtypes were previously not exported, now exported under GarbageCollectionCardTypes +- BUGFIX - automatic titles in modal have been fixed to convert to title case as well as allowing an override in the "modalProps" attribute for all cards. + +## @hakit/core +- updating provider to allow global style overrides for new cards (FamilyCard, PersonCard) + # 3.1.3 ## @hakit/components - No changes here - aligning semantic versions diff --git a/README.md b/README.md index c3bb81a6..4a8d0720 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,9 @@ There's extensive [documentation](https://shannonhochkins.github.io/ha-component ### Home Assistant Addon There's a [Home Assistant Addon](ADDON.md) available which will serve your dashboard to a new sidebar link in home assistant making it easier to access your custom dashboard. -### WIP - Next Release - -- Theming - allow individual components to display different theme control, either by a hue shift or global change ### Supported Cards +- [NEW FamilyCard](https://shannonhochkins.github.io/ha-component-kit/?path=/docs/components-cards-familycard--docs) - [AreaCard](https://shannonhochkins.github.io/ha-component-kit/?path=/docs/components-cards-areacard--docs) - [ButtonCard](https://shannonhochkins.github.io/ha-component-kit/?path=/docs/components-cards-buttoncard--docs) - [ClimateCard](https://shannonhochkins.github.io/ha-component-kit/?path=/docs/components-cards-climatecard--docs) diff --git a/hass-connect-fake/assets/john_doe_ai_generated.png b/hass-connect-fake/assets/john_doe_ai_generated.png new file mode 100644 index 00000000..81f415c1 Binary files /dev/null and b/hass-connect-fake/assets/john_doe_ai_generated.png differ diff --git a/hass-connect-fake/mocks/createPerson.ts b/hass-connect-fake/mocks/createPerson.ts new file mode 100644 index 00000000..26240502 --- /dev/null +++ b/hass-connect-fake/mocks/createPerson.ts @@ -0,0 +1,33 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { createEntity } from "./createEntity"; + +const now = new Date(); + +const defaults = { + entity_id: "person.john_doe", + state: "home", + attributes: { + editable: true, + id: "", + latitude: 43.72279692728114, + longitude: 10.388657661382009, + gps_accuracy: 35, + source: "device_tracker.john_doe_iphone", + user_id: "", + device_trackers: ["device_tracker.john_doe_iphone"], + entity_picture: "", + friendly_name: "John", + icon: "mdi:account", + }, + context: { + id: "", + parent_id: null, + user_id: null, + }, + last_changed: now.toISOString(), + last_updated: now.toISOString(), +} satisfies HassEntity; + +export const createPerson = (entity_id: string, overrides: Partial = {}) => { + return createEntity(entity_id, defaults, overrides); +}; diff --git a/hass-connect-fake/mocks/mockEntities.ts b/hass-connect-fake/mocks/mockEntities.ts index 719c6cf7..3f644416 100644 --- a/hass-connect-fake/mocks/mockEntities.ts +++ b/hass-connect-fake/mocks/mockEntities.ts @@ -1,46 +1,47 @@ import type { HassEntities } from "home-assistant-js-websocket"; +import { createAutomation } from "./createAutomation"; +import { createBinarySensor } from "./createBinarySensor"; +import { createCalendar } from "./createCalendar"; +import { createCamera } from "./createCamera"; +import { createClimate } from "./createClimate"; +import { createCover } from "./createCover"; import { createLight } from "./createLight"; +import { createMediaPlayer } from "./createMediaPlayer"; +import { createPerson } from "./createPerson"; +import { createScene } from "./createScene"; +import { createSensor } from "./createSensor"; import { createSwitch } from "./createSwitch"; -import { createMediaPlayer } from './createMediaPlayer'; -import { createSensor } from './createSensor'; -import { createScene } from './createScene'; -import { createWeather } from './createWeather'; -import { createClimate } from './createClimate'; -import { createVacuum } from './createVacuum'; -import { createAutomation } from './createAutomation'; -import { createCalendar } from './createCalendar'; -import { createCamera } from './createCamera'; -import { createBinarySensor } from './createBinarySensor'; -import { createCover} from './createCover'; +import { createVacuum } from "./createVacuum"; +import { createWeather } from "./createWeather"; export const entities: HassEntities = { - ...createCover('cover.cover_with_tilt'), - ...createCover('cover.cover_position_only', { + ...createCover("cover.cover_with_tilt"), + ...createCover("cover.cover_position_only", { attributes: { supported_features: 15, - } + }, }), - ...createCover('cover.cover_no_position', { + ...createCover("cover.cover_no_position", { attributes: { supported_features: 9, - } + }, }), - - ...createCamera('camera.demo_camera'), - ...createLight('light.unavailable', { - "state": "unavailable", + + ...createCamera("camera.demo_camera"), + ...createLight("light.unavailable", { + state: "unavailable", attributes: { - friendly_name: 'Unavailable light demo', - } + friendly_name: "Unavailable light demo", + }, }), - ...createSwitch('switch.record', { + ...createSwitch("switch.record", { attributes: { icon: "mdi:record-rec", - friendly_name: "Backyard Record" + friendly_name: "Backyard Record", }, - state: 'on' + state: "on", }), - ...createBinarySensor('binary_sensor.vehicle'), + ...createBinarySensor("binary_sensor.vehicle"), ...createLight("light.fake_light_1", { attributes: { friendly_name: "Dining room light", @@ -63,19 +64,19 @@ export const entities: HassEntities = { rgb_color: [64, 255, 112], }, }), - ...createSwitch('switch.fake_switch'), - ...createSwitch('switch.unavailable', { + ...createSwitch("switch.fake_switch"), + ...createSwitch("switch.unavailable", { state: "unavailable", attributes: { friendly_name: "Unavailable switch demo", }, }), - ...createSwitch('switch.coffee_machine', { + ...createSwitch("switch.coffee_machine", { attributes: { friendly_name: "Coffee Machine", - } + }, }), - ...createMediaPlayer('media_player.groups', { + ...createMediaPlayer("media_player.groups", { state: "playing", attributes: { volume_level: 0.54, @@ -87,13 +88,14 @@ export const entities: HassEntities = { media_title: "My Way", media_artist: "Calvin Harris", app_name: "YouTube Music", - entity_picture: "https://lh3.googleusercontent.com/sLxiYvk82ZBXIYW-g_qh4BDjkApX4gdRvGxeinQIC0HBwte4AKOzS3u2mDPaYjPBw6dD_Of-r0x10egf=w544-h544-l90-rj", + entity_picture: + "https://lh3.googleusercontent.com/sLxiYvk82ZBXIYW-g_qh4BDjkApX4gdRvGxeinQIC0HBwte4AKOzS3u2mDPaYjPBw6dD_Of-r0x10egf=w544-h544-l90-rj", friendly_name: "Bedroom Speaker", supported_features: 4127295, - } + }, }), - ...createMediaPlayer('media_player.fake_tv'), - ...createMediaPlayer('media_player.fake_speaker', { + ...createMediaPlayer("media_player.fake_tv"), + ...createMediaPlayer("media_player.fake_speaker", { state: "playing", attributes: { volume_level: 0.54, @@ -105,12 +107,13 @@ export const entities: HassEntities = { media_title: "My Way", media_artist: "Calvin Harris", app_name: "YouTube Music", - entity_picture: "https://lh3.googleusercontent.com/sLxiYvk82ZBXIYW-g_qh4BDjkApX4gdRvGxeinQIC0HBwte4AKOzS3u2mDPaYjPBw6dD_Of-r0x10egf=w544-h544-l90-rj", + entity_picture: + "https://lh3.googleusercontent.com/sLxiYvk82ZBXIYW-g_qh4BDjkApX4gdRvGxeinQIC0HBwte4AKOzS3u2mDPaYjPBw6dD_Of-r0x10egf=w544-h544-l90-rj", friendly_name: "Bedroom Speaker", - supported_features: 152511 - } + supported_features: 152511, + }, }), - ...createMediaPlayer('media_player.fake_speaker_2', { + ...createMediaPlayer("media_player.fake_speaker_2", { state: "playing", attributes: { volume_level: 0.54, @@ -122,617 +125,623 @@ export const entities: HassEntities = { media_title: "My Way", media_artist: "Calvin Harris", app_name: "YouTube Music", - entity_picture: "https://lh3.googleusercontent.com/sLxiYvk82ZBXIYW-g_qh4BDjkApX4gdRvGxeinQIC0HBwte4AKOzS3u2mDPaYjPBw6dD_Of-r0x10egf=w544-h544-l90-rj", + entity_picture: + "https://lh3.googleusercontent.com/sLxiYvk82ZBXIYW-g_qh4BDjkApX4gdRvGxeinQIC0HBwte4AKOzS3u2mDPaYjPBw6dD_Of-r0x10egf=w544-h544-l90-rj", friendly_name: "Bedroom Speaker", - supported_features: 152511 - } + supported_features: 152511, + }, }), - ...createScene('scene.good_morning'), - ...createSensor('sensor.time', { + ...createScene("scene.good_morning"), + ...createSensor("sensor.time", { attributes: { icon: "mdi:clock", friendly_name: "Time", }, }), - ...createSensor('sensor.date', { + ...createSensor("sensor.date", { state: "2023-07-19", attributes: { icon: "mdi:calendar", friendly_name: "Date", }, }), - ...createSensor('sensor.air_conditioner_inside_temperature', { + ...createSensor("sensor.air_conditioner_inside_temperature", { state: "21", attributes: { unit_of_measurement: "°C", friendly_name: "Air Conditioner Inside Temperature", }, }), - ...createSensor('sensor.openweathermap_uv_index', { + ...createSensor("sensor.openweathermap_uv_index", { entity_id: "sensor.openweathermap_uv_index", state: "6.6", attributes: { - state_class: "measurement", - unit_of_measurement: "UV index", - attribution: "Data provided by OpenWeatherMap", - icon: "mdi:sun-wireless-outline", - friendly_name: "UV Index" + state_class: "measurement", + unit_of_measurement: "UV index", + attribution: "Data provided by OpenWeatherMap", + icon: "mdi:sun-wireless-outline", + friendly_name: "UV Index", }, }), - ...createSensor('sensor.openweathermap_wind_speed', { - entity_id: "sensor.openweathermap_wind_speed", - state: "15.91", - attributes: { - state_class: "measurement", - unit_of_measurement: "km/h", - attribution: "Data provided by OpenWeatherMap", - device_class: "wind_speed", - friendly_name: "Wind speed" - }, + ...createSensor("sensor.openweathermap_wind_speed", { + entity_id: "sensor.openweathermap_wind_speed", + state: "15.91", + attributes: { + state_class: "measurement", + unit_of_measurement: "km/h", + attribution: "Data provided by OpenWeatherMap", + device_class: "wind_speed", + friendly_name: "Wind speed", + }, }), - ...createSensor('sensor.openweathermap_humidity', { + ...createSensor("sensor.openweathermap_humidity", { entity_id: "sensor.openweathermap_humidity", state: "53", attributes: { - state_class: "measurement", - unit_of_measurement: "%", - attribution: "Data provided by OpenWeatherMap", - device_class: "humidity", - friendly_name: "Humidity" + state_class: "measurement", + unit_of_measurement: "%", + attribution: "Data provided by OpenWeatherMap", + device_class: "humidity", + friendly_name: "Humidity", }, }), - ...createSensor('sensor.openweathermap_pressure', { + ...createSensor("sensor.openweathermap_pressure", { entity_id: "sensor.openweathermap_pressure", state: "1025", attributes: { - state_class: "measurement", - unit_of_measurement: "hPa", - attribution: "Data provided by OpenWeatherMap", - device_class: "pressure", - friendly_name: "Pressure" + state_class: "measurement", + unit_of_measurement: "hPa", + attribution: "Data provided by OpenWeatherMap", + device_class: "pressure", + friendly_name: "Pressure", }, }), - ...createSensor('sensor.curtain', { - state: '12', + ...createSensor("sensor.curtain", { + state: "12", attributes: { - device_class: 'battery', - icon: 'mdi:curtains', - friendly_name: 'Office curtain sensor', - unit_of_measurement: '%' - } + device_class: "battery", + icon: "mdi:curtains", + friendly_name: "Office curtain sensor", + unit_of_measurement: "%", + }, }), - ...createSensor('sensor.remote', { - state: '20', + ...createSensor("sensor.remote", { + state: "20", attributes: { - icon: 'mdi:remote', - friendly_name: 'Remote battery', - device_class: 'battery', - unit_of_measurement: '%' - } + icon: "mdi:remote", + friendly_name: "Remote battery", + device_class: "battery", + unit_of_measurement: "%", + }, }), - ...createSensor('sensor.motion_sensor', { - state: '3', + ...createSensor("sensor.motion_sensor", { + state: "3", attributes: { - device_class: 'battery', - friendly_name: 'Motion Sensor', + device_class: "battery", + friendly_name: "Motion Sensor", icon: "mdi:proximity-sensor-off", - unit_of_measurement: '%' - } + unit_of_measurement: "%", + }, }), ...createCalendar("calendar.google_calendar"), ...createCalendar("calendar.another_google_calendar"), ...createWeather("weather.entity"), - ...createWeather('weather.openweathermap', { - "entity_id": "weather.openweathermap", - "state": "sunny", - "attributes": { - "temperature": 20.1, - "apparent_temperature": 19.5, - "temperature_unit": "°C", - "humidity": 53, - "cloud_coverage": 10, - "pressure": 1025, - "pressure_unit": "hPa", - "wind_bearing": 84, - "wind_gust_speed": 13.25, - "wind_speed": 15.91, - "wind_speed_unit": "km/h", - "visibility_unit": "km", - "precipitation_unit": "mm", - "forecast": [ - { - "condition": "partlycloudy", - "precipitation_probability": 0, - "datetime": "2023-09-24T06:00:00+00:00", - "wind_bearing": 70, - "cloud_coverage": 11, - "temperature": 19.1, - "pressure": 1025, - "wind_speed": 16.06, - "precipitation": 0, - "humidity": 55 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-24T09:00:00+00:00", - "wind_bearing": 54, - "cloud_coverage": 4, - "temperature": 17.1, - "pressure": 1025, - "wind_speed": 18.11, - "precipitation": 0, - "humidity": 63 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-24T12:00:00+00:00", - "wind_bearing": 25, - "cloud_coverage": 0, - "temperature": 14.2, - "pressure": 1026, - "wind_speed": 13.75, - "precipitation": 0, - "humidity": 76 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-24T15:00:00+00:00", - "wind_bearing": 320, - "cloud_coverage": 0, - "temperature": 11.9, - "pressure": 1024, - "wind_speed": 7.74, - "precipitation": 0, - "humidity": 86 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-24T18:00:00+00:00", - "wind_bearing": 320, - "cloud_coverage": 1, - "temperature": 11.4, - "pressure": 1024, - "wind_speed": 5.11, - "precipitation": 0, - "humidity": 88 - }, - { - "condition": "sunny", - "precipitation_probability": 0, - "datetime": "2023-09-24T21:00:00+00:00", - "wind_bearing": 326, - "cloud_coverage": 2, - "temperature": 12.8, - "pressure": 1025, - "wind_speed": 7.38, - "precipitation": 0, - "humidity": 82 - }, - { - "condition": "sunny", - "precipitation_probability": 0, - "datetime": "2023-09-25T00:00:00+00:00", - "wind_bearing": 49, - "cloud_coverage": 1, - "temperature": 18.5, - "pressure": 1023, - "wind_speed": 11.59, - "precipitation": 0, - "humidity": 57 - }, - { - "condition": "sunny", - "precipitation_probability": 0, - "datetime": "2023-09-25T03:00:00+00:00", - "wind_bearing": 72, - "cloud_coverage": 0, - "temperature": 20, - "pressure": 1020, - "wind_speed": 22.28, - "precipitation": 0, - "humidity": 57 - }, - { - "condition": "sunny", - "precipitation_probability": 0, - "datetime": "2023-09-25T06:00:00+00:00", - "wind_bearing": 59, - "cloud_coverage": 0, - "temperature": 19, - "pressure": 1018, - "wind_speed": 23.58, - "precipitation": 0, - "humidity": 68 - }, - { - "condition": "partlycloudy", - "precipitation_probability": 0, - "datetime": "2023-09-25T09:00:00+00:00", - "wind_bearing": 44, - "cloud_coverage": 24, - "temperature": 17.2, - "pressure": 1019, - "wind_speed": 18.72, - "precipitation": 0, - "humidity": 82 - }, - { - "condition": "cloudy", - "precipitation_probability": 0, - "datetime": "2023-09-25T12:00:00+00:00", - "wind_bearing": 8, - "cloud_coverage": 60, - "temperature": 16.1, - "pressure": 1020, - "wind_speed": 9.07, - "precipitation": 0, - "humidity": 78 - }, - { - "condition": "partlycloudy", - "precipitation_probability": 0, - "datetime": "2023-09-25T15:00:00+00:00", - "wind_bearing": 217, - "cloud_coverage": 36, - "temperature": 14.4, - "pressure": 1019, - "wind_speed": 11.56, - "precipitation": 0, - "humidity": 84 - }, - { - "condition": "partlycloudy", - "precipitation_probability": 0, - "datetime": "2023-09-25T18:00:00+00:00", - "wind_bearing": 247, - "cloud_coverage": 21, - "temperature": 13.3, - "pressure": 1019, - "wind_speed": 11.56, - "precipitation": 0, - "humidity": 86 - }, - { - "condition": "cloudy", - "precipitation_probability": 0, - "datetime": "2023-09-25T21:00:00+00:00", - "wind_bearing": 248, - "cloud_coverage": 52, - "temperature": 15.2, - "pressure": 1021, - "wind_speed": 12.17, - "precipitation": 0, - "humidity": 83 - }, - { - "condition": "cloudy", - "precipitation_probability": 0, - "datetime": "2023-09-26T00:00:00+00:00", - "wind_bearing": 172, - "cloud_coverage": 64, - "temperature": 20.1, - "pressure": 1021, - "wind_speed": 15.98, - "precipitation": 0, - "humidity": 69 - }, - { - "condition": "rainy", - "precipitation_probability": 20, - "datetime": "2023-09-26T03:00:00+00:00", - "wind_bearing": 138, - "cloud_coverage": 87, - "temperature": 20.2, - "pressure": 1019, - "wind_speed": 18.83, - "precipitation": 0.23, - "humidity": 71 - }, - { - "condition": "rainy", - "precipitation_probability": 33, - "datetime": "2023-09-26T06:00:00+00:00", - "wind_bearing": 144, - "cloud_coverage": 93, - "temperature": 18.3, - "pressure": 1019, - "wind_speed": 9.9, - "precipitation": 0.45, - "humidity": 84 - }, - { - "condition": "rainy", - "precipitation_probability": 51, - "datetime": "2023-09-26T09:00:00+00:00", - "wind_bearing": 114, - "cloud_coverage": 95, - "temperature": 16.8, - "pressure": 1020, - "wind_speed": 8.1, - "precipitation": 1.26, - "humidity": 91 - }, - { - "condition": "cloudy", - "precipitation_probability": 47, - "datetime": "2023-09-26T12:00:00+00:00", - "wind_bearing": 317, - "cloud_coverage": 63, - "temperature": 16.3, - "pressure": 1020, - "wind_speed": 3.02, - "precipitation": 0, - "humidity": 91 - }, - { - "condition": "partlycloudy", - "precipitation_probability": 0, - "datetime": "2023-09-26T15:00:00+00:00", - "wind_bearing": 253, - "cloud_coverage": 44, - "temperature": 15.3, - "pressure": 1019, - "wind_speed": 4.86, - "precipitation": 0, - "humidity": 93 - }, - { - "condition": "partlycloudy", - "precipitation_probability": 0, - "datetime": "2023-09-26T18:00:00+00:00", - "wind_bearing": 253, - "cloud_coverage": 38, - "temperature": 14.4, - "pressure": 1018, - "wind_speed": 6.08, - "precipitation": 0, - "humidity": 96 - }, - { - "condition": "cloudy", - "precipitation_probability": 0, - "datetime": "2023-09-26T21:00:00+00:00", - "wind_bearing": 282, - "cloud_coverage": 70, - "temperature": 15.6, - "pressure": 1019, - "wind_speed": 2.3, - "precipitation": 0, - "humidity": 93 - }, - { - "condition": "rainy", - "precipitation_probability": 24, - "datetime": "2023-09-27T00:00:00+00:00", - "wind_bearing": 165, - "cloud_coverage": 85, - "temperature": 17.5, - "pressure": 1020, - "wind_speed": 3.89, - "precipitation": 0.23, - "humidity": 87 - }, - { - "condition": "rainy", - "precipitation_probability": 20, - "datetime": "2023-09-27T03:00:00+00:00", - "wind_bearing": 115, - "cloud_coverage": 100, - "temperature": 18.1, - "pressure": 1018, - "wind_speed": 15.08, - "precipitation": 0.23, - "humidity": 84 - }, - { - "condition": "cloudy", - "precipitation_probability": 0, - "datetime": "2023-09-27T06:00:00+00:00", - "wind_bearing": 126, - "cloud_coverage": 100, - "temperature": 18.1, - "pressure": 1017, - "wind_speed": 14.8, - "precipitation": 0, - "humidity": 82 - }, - { - "condition": "cloudy", - "precipitation_probability": 18, - "datetime": "2023-09-27T09:00:00+00:00", - "wind_bearing": 62, - "cloud_coverage": 100, - "temperature": 17.1, - "pressure": 1019, - "wind_speed": 11.12, - "precipitation": 0, - "humidity": 91 - }, - { - "condition": "rainy", - "precipitation_probability": 49, - "datetime": "2023-09-27T12:00:00+00:00", - "wind_bearing": 192, - "cloud_coverage": 96, - "temperature": 16.6, - "pressure": 1021, - "wind_speed": 2.56, - "precipitation": 0.86, - "humidity": 93 - }, - { - "condition": "rainy", - "precipitation_probability": 87, - "datetime": "2023-09-27T15:00:00+00:00", - "wind_bearing": 177, - "cloud_coverage": 100, - "temperature": 16.8, - "pressure": 1021, - "wind_speed": 4.18, - "precipitation": 1.99, - "humidity": 91 - }, - { - "condition": "rainy", - "precipitation_probability": 96, - "datetime": "2023-09-27T18:00:00+00:00", - "wind_bearing": 209, - "cloud_coverage": 100, - "temperature": 15.9, - "pressure": 1022, - "wind_speed": 10.8, - "precipitation": 1, - "humidity": 92 - }, - { - "condition": "rainy", - "precipitation_probability": 39, - "datetime": "2023-09-27T21:00:00+00:00", - "wind_bearing": 204, - "cloud_coverage": 84, - "temperature": 16.5, - "pressure": 1025, - "wind_speed": 12.74, - "precipitation": 0.19, - "humidity": 89 - }, - { - "condition": "rainy", - "precipitation_probability": 71, - "datetime": "2023-09-28T00:00:00+00:00", - "wind_bearing": 139, - "cloud_coverage": 55, - "temperature": 18.1, - "pressure": 1026, - "wind_speed": 17.03, - "precipitation": 0.82, - "humidity": 81 - }, - { - "condition": "rainy", - "precipitation_probability": 64, - "datetime": "2023-09-28T03:00:00+00:00", - "wind_bearing": 129, - "cloud_coverage": 52, - "temperature": 18.8, - "pressure": 1025, - "wind_speed": 17.21, - "precipitation": 0.56, - "humidity": 67 - }, - { - "condition": "partlycloudy", - "precipitation_probability": 43, - "datetime": "2023-09-28T06:00:00+00:00", - "wind_bearing": 115, - "cloud_coverage": 36, - "temperature": 18.1, - "pressure": 1025, - "wind_speed": 12.71, - "precipitation": 0, - "humidity": 67 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-28T09:00:00+00:00", - "wind_bearing": 75, - "cloud_coverage": 3, - "temperature": 16.1, - "pressure": 1026, - "wind_speed": 9.22, - "precipitation": 0, - "humidity": 80 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-28T12:00:00+00:00", - "wind_bearing": 18, - "cloud_coverage": 2, - "temperature": 15.3, - "pressure": 1027, - "wind_speed": 7.99, - "precipitation": 0, - "humidity": 84 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-28T15:00:00+00:00", - "wind_bearing": 299, - "cloud_coverage": 1, - "temperature": 13.5, - "pressure": 1026, - "wind_speed": 7.56, - "precipitation": 0, - "humidity": 90 - }, - { - "condition": "clear-night", - "precipitation_probability": 0, - "datetime": "2023-09-28T18:00:00+00:00", - "wind_bearing": 311, - "cloud_coverage": 1, - "temperature": 13, - "pressure": 1025, - "wind_speed": 6.52, - "precipitation": 0, - "humidity": 89 - }, - { - "condition": "sunny", - "precipitation_probability": 0, - "datetime": "2023-09-28T21:00:00+00:00", - "wind_bearing": 326, - "cloud_coverage": 3, - "temperature": 14.7, - "pressure": 1025, - "wind_speed": 8.64, - "precipitation": 0, - "humidity": 84 - }, - { - "condition": "sunny", - "precipitation_probability": 0, - "datetime": "2023-09-29T00:00:00+00:00", - "wind_bearing": 10, - "cloud_coverage": 2, - "temperature": 22.3, - "pressure": 1022, - "wind_speed": 11.95, - "precipitation": 0, - "humidity": 53 - }, - { - "condition": "sunny", - "precipitation_probability": 0, - "datetime": "2023-09-29T03:00:00+00:00", - "wind_bearing": 39, - "cloud_coverage": 0, - "temperature": 26, - "pressure": 1018, - "wind_speed": 11.05, - "precipitation": 0, - "humidity": 42 - } - ], - "attribution": "Data provided by OpenWeatherMap", - "friendly_name": "OpenWeatherMap", - "supported_features": 2 + ...createWeather("weather.openweathermap", { + entity_id: "weather.openweathermap", + state: "sunny", + attributes: { + temperature: 20.1, + apparent_temperature: 19.5, + temperature_unit: "°C", + humidity: 53, + cloud_coverage: 10, + pressure: 1025, + pressure_unit: "hPa", + wind_bearing: 84, + wind_gust_speed: 13.25, + wind_speed: 15.91, + wind_speed_unit: "km/h", + visibility_unit: "km", + precipitation_unit: "mm", + forecast: [ + { + condition: "partlycloudy", + precipitation_probability: 0, + datetime: "2023-09-24T06:00:00+00:00", + wind_bearing: 70, + cloud_coverage: 11, + temperature: 19.1, + pressure: 1025, + wind_speed: 16.06, + precipitation: 0, + humidity: 55, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-24T09:00:00+00:00", + wind_bearing: 54, + cloud_coverage: 4, + temperature: 17.1, + pressure: 1025, + wind_speed: 18.11, + precipitation: 0, + humidity: 63, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-24T12:00:00+00:00", + wind_bearing: 25, + cloud_coverage: 0, + temperature: 14.2, + pressure: 1026, + wind_speed: 13.75, + precipitation: 0, + humidity: 76, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-24T15:00:00+00:00", + wind_bearing: 320, + cloud_coverage: 0, + temperature: 11.9, + pressure: 1024, + wind_speed: 7.74, + precipitation: 0, + humidity: 86, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-24T18:00:00+00:00", + wind_bearing: 320, + cloud_coverage: 1, + temperature: 11.4, + pressure: 1024, + wind_speed: 5.11, + precipitation: 0, + humidity: 88, + }, + { + condition: "sunny", + precipitation_probability: 0, + datetime: "2023-09-24T21:00:00+00:00", + wind_bearing: 326, + cloud_coverage: 2, + temperature: 12.8, + pressure: 1025, + wind_speed: 7.38, + precipitation: 0, + humidity: 82, + }, + { + condition: "sunny", + precipitation_probability: 0, + datetime: "2023-09-25T00:00:00+00:00", + wind_bearing: 49, + cloud_coverage: 1, + temperature: 18.5, + pressure: 1023, + wind_speed: 11.59, + precipitation: 0, + humidity: 57, + }, + { + condition: "sunny", + precipitation_probability: 0, + datetime: "2023-09-25T03:00:00+00:00", + wind_bearing: 72, + cloud_coverage: 0, + temperature: 20, + pressure: 1020, + wind_speed: 22.28, + precipitation: 0, + humidity: 57, + }, + { + condition: "sunny", + precipitation_probability: 0, + datetime: "2023-09-25T06:00:00+00:00", + wind_bearing: 59, + cloud_coverage: 0, + temperature: 19, + pressure: 1018, + wind_speed: 23.58, + precipitation: 0, + humidity: 68, + }, + { + condition: "partlycloudy", + precipitation_probability: 0, + datetime: "2023-09-25T09:00:00+00:00", + wind_bearing: 44, + cloud_coverage: 24, + temperature: 17.2, + pressure: 1019, + wind_speed: 18.72, + precipitation: 0, + humidity: 82, + }, + { + condition: "cloudy", + precipitation_probability: 0, + datetime: "2023-09-25T12:00:00+00:00", + wind_bearing: 8, + cloud_coverage: 60, + temperature: 16.1, + pressure: 1020, + wind_speed: 9.07, + precipitation: 0, + humidity: 78, + }, + { + condition: "partlycloudy", + precipitation_probability: 0, + datetime: "2023-09-25T15:00:00+00:00", + wind_bearing: 217, + cloud_coverage: 36, + temperature: 14.4, + pressure: 1019, + wind_speed: 11.56, + precipitation: 0, + humidity: 84, + }, + { + condition: "partlycloudy", + precipitation_probability: 0, + datetime: "2023-09-25T18:00:00+00:00", + wind_bearing: 247, + cloud_coverage: 21, + temperature: 13.3, + pressure: 1019, + wind_speed: 11.56, + precipitation: 0, + humidity: 86, + }, + { + condition: "cloudy", + precipitation_probability: 0, + datetime: "2023-09-25T21:00:00+00:00", + wind_bearing: 248, + cloud_coverage: 52, + temperature: 15.2, + pressure: 1021, + wind_speed: 12.17, + precipitation: 0, + humidity: 83, + }, + { + condition: "cloudy", + precipitation_probability: 0, + datetime: "2023-09-26T00:00:00+00:00", + wind_bearing: 172, + cloud_coverage: 64, + temperature: 20.1, + pressure: 1021, + wind_speed: 15.98, + precipitation: 0, + humidity: 69, + }, + { + condition: "rainy", + precipitation_probability: 20, + datetime: "2023-09-26T03:00:00+00:00", + wind_bearing: 138, + cloud_coverage: 87, + temperature: 20.2, + pressure: 1019, + wind_speed: 18.83, + precipitation: 0.23, + humidity: 71, + }, + { + condition: "rainy", + precipitation_probability: 33, + datetime: "2023-09-26T06:00:00+00:00", + wind_bearing: 144, + cloud_coverage: 93, + temperature: 18.3, + pressure: 1019, + wind_speed: 9.9, + precipitation: 0.45, + humidity: 84, + }, + { + condition: "rainy", + precipitation_probability: 51, + datetime: "2023-09-26T09:00:00+00:00", + wind_bearing: 114, + cloud_coverage: 95, + temperature: 16.8, + pressure: 1020, + wind_speed: 8.1, + precipitation: 1.26, + humidity: 91, + }, + { + condition: "cloudy", + precipitation_probability: 47, + datetime: "2023-09-26T12:00:00+00:00", + wind_bearing: 317, + cloud_coverage: 63, + temperature: 16.3, + pressure: 1020, + wind_speed: 3.02, + precipitation: 0, + humidity: 91, + }, + { + condition: "partlycloudy", + precipitation_probability: 0, + datetime: "2023-09-26T15:00:00+00:00", + wind_bearing: 253, + cloud_coverage: 44, + temperature: 15.3, + pressure: 1019, + wind_speed: 4.86, + precipitation: 0, + humidity: 93, + }, + { + condition: "partlycloudy", + precipitation_probability: 0, + datetime: "2023-09-26T18:00:00+00:00", + wind_bearing: 253, + cloud_coverage: 38, + temperature: 14.4, + pressure: 1018, + wind_speed: 6.08, + precipitation: 0, + humidity: 96, + }, + { + condition: "cloudy", + precipitation_probability: 0, + datetime: "2023-09-26T21:00:00+00:00", + wind_bearing: 282, + cloud_coverage: 70, + temperature: 15.6, + pressure: 1019, + wind_speed: 2.3, + precipitation: 0, + humidity: 93, + }, + { + condition: "rainy", + precipitation_probability: 24, + datetime: "2023-09-27T00:00:00+00:00", + wind_bearing: 165, + cloud_coverage: 85, + temperature: 17.5, + pressure: 1020, + wind_speed: 3.89, + precipitation: 0.23, + humidity: 87, + }, + { + condition: "rainy", + precipitation_probability: 20, + datetime: "2023-09-27T03:00:00+00:00", + wind_bearing: 115, + cloud_coverage: 100, + temperature: 18.1, + pressure: 1018, + wind_speed: 15.08, + precipitation: 0.23, + humidity: 84, + }, + { + condition: "cloudy", + precipitation_probability: 0, + datetime: "2023-09-27T06:00:00+00:00", + wind_bearing: 126, + cloud_coverage: 100, + temperature: 18.1, + pressure: 1017, + wind_speed: 14.8, + precipitation: 0, + humidity: 82, + }, + { + condition: "cloudy", + precipitation_probability: 18, + datetime: "2023-09-27T09:00:00+00:00", + wind_bearing: 62, + cloud_coverage: 100, + temperature: 17.1, + pressure: 1019, + wind_speed: 11.12, + precipitation: 0, + humidity: 91, + }, + { + condition: "rainy", + precipitation_probability: 49, + datetime: "2023-09-27T12:00:00+00:00", + wind_bearing: 192, + cloud_coverage: 96, + temperature: 16.6, + pressure: 1021, + wind_speed: 2.56, + precipitation: 0.86, + humidity: 93, + }, + { + condition: "rainy", + precipitation_probability: 87, + datetime: "2023-09-27T15:00:00+00:00", + wind_bearing: 177, + cloud_coverage: 100, + temperature: 16.8, + pressure: 1021, + wind_speed: 4.18, + precipitation: 1.99, + humidity: 91, + }, + { + condition: "rainy", + precipitation_probability: 96, + datetime: "2023-09-27T18:00:00+00:00", + wind_bearing: 209, + cloud_coverage: 100, + temperature: 15.9, + pressure: 1022, + wind_speed: 10.8, + precipitation: 1, + humidity: 92, + }, + { + condition: "rainy", + precipitation_probability: 39, + datetime: "2023-09-27T21:00:00+00:00", + wind_bearing: 204, + cloud_coverage: 84, + temperature: 16.5, + pressure: 1025, + wind_speed: 12.74, + precipitation: 0.19, + humidity: 89, + }, + { + condition: "rainy", + precipitation_probability: 71, + datetime: "2023-09-28T00:00:00+00:00", + wind_bearing: 139, + cloud_coverage: 55, + temperature: 18.1, + pressure: 1026, + wind_speed: 17.03, + precipitation: 0.82, + humidity: 81, + }, + { + condition: "rainy", + precipitation_probability: 64, + datetime: "2023-09-28T03:00:00+00:00", + wind_bearing: 129, + cloud_coverage: 52, + temperature: 18.8, + pressure: 1025, + wind_speed: 17.21, + precipitation: 0.56, + humidity: 67, + }, + { + condition: "partlycloudy", + precipitation_probability: 43, + datetime: "2023-09-28T06:00:00+00:00", + wind_bearing: 115, + cloud_coverage: 36, + temperature: 18.1, + pressure: 1025, + wind_speed: 12.71, + precipitation: 0, + humidity: 67, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-28T09:00:00+00:00", + wind_bearing: 75, + cloud_coverage: 3, + temperature: 16.1, + pressure: 1026, + wind_speed: 9.22, + precipitation: 0, + humidity: 80, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-28T12:00:00+00:00", + wind_bearing: 18, + cloud_coverage: 2, + temperature: 15.3, + pressure: 1027, + wind_speed: 7.99, + precipitation: 0, + humidity: 84, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-28T15:00:00+00:00", + wind_bearing: 299, + cloud_coverage: 1, + temperature: 13.5, + pressure: 1026, + wind_speed: 7.56, + precipitation: 0, + humidity: 90, + }, + { + condition: "clear-night", + precipitation_probability: 0, + datetime: "2023-09-28T18:00:00+00:00", + wind_bearing: 311, + cloud_coverage: 1, + temperature: 13, + pressure: 1025, + wind_speed: 6.52, + precipitation: 0, + humidity: 89, + }, + { + condition: "sunny", + precipitation_probability: 0, + datetime: "2023-09-28T21:00:00+00:00", + wind_bearing: 326, + cloud_coverage: 3, + temperature: 14.7, + pressure: 1025, + wind_speed: 8.64, + precipitation: 0, + humidity: 84, + }, + { + condition: "sunny", + precipitation_probability: 0, + datetime: "2023-09-29T00:00:00+00:00", + wind_bearing: 10, + cloud_coverage: 2, + temperature: 22.3, + pressure: 1022, + wind_speed: 11.95, + precipitation: 0, + humidity: 53, + }, + { + condition: "sunny", + precipitation_probability: 0, + datetime: "2023-09-29T03:00:00+00:00", + wind_bearing: 39, + cloud_coverage: 0, + temperature: 26, + pressure: 1018, + wind_speed: 11.05, + precipitation: 0, + humidity: 42, + }, + ], + attribution: "Data provided by OpenWeatherMap", + friendly_name: "OpenWeatherMap", + supported_features: 2, }, }), ...createClimate("climate.air_conditioner"), ...createClimate("climate.unavailable", { - state: 'unavailable', + state: "unavailable", attributes: { - hvac_action: 'unavailable', - } + hvac_action: "unavailable", + }, }), ...createVacuum("vacuum.robot_vacuum"), ...createAutomation("automation.dim_lights"), + ...createPerson("person.john_doe", { attributes: { entity_picture: "/hass-connect-fake/assets/john_doe_ai_generated.png" } }), + ...createPerson("person.jane_doe", { + state: "not_home", + attributes: { friendly_name: "Jane", latitude: 48.857543231604986, longitude: 2.274926660937714 }, + }), } as const; diff --git a/package-lock.json b/package-lock.json index 74a56d6d..fb8b5fbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5679,6 +5679,17 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "peer": true, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@rollup/plugin-alias": { "version": "5.0.0", "dev": true, @@ -8678,6 +8689,12 @@ "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==", "dev": true }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -8803,6 +8820,15 @@ "@types/node": "*" } }, + "node_modules/@types/leaflet": { + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz", + "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/lodash": { "version": "4.14.197", "dev": true, @@ -17864,6 +17890,12 @@ "node": ">=14.0.0" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true + }, "node_modules/less": { "version": "4.2.0", "dev": true, @@ -19989,6 +20021,20 @@ "dev": true, "license": "MIT" }, + "node_modules/react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "peer": true, + "dependencies": { + "@react-leaflet/core": "^2.1.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "dev": true, @@ -24674,10 +24720,11 @@ }, "packages/components": { "name": "@hakit/components", - "version": "3.1.1", - "license": "ISC", + "version": "3.1.3", + "license": "SEE LICENSE IN LICENSE.md", "devDependencies": { "@emotion/babel-plugin": "^11.x", + "@types/leaflet": "^1.9.8", "vite-plugin-svgr": "^3.2.0" }, "engines": { @@ -24691,16 +24738,18 @@ "@emotion/react": ">=11.x", "@emotion/styled": ">=11.x", "@fullcalendar/react": ">=6.x.x", - "@hakit/core": "^3.1.1", + "@hakit/core": "^3.1.3", "@use-gesture/react": ">=10.x", "autolinker": ">=4.x", "framer-motion": ">=10.x", "fullcalendar": ">=6.x.x", "hls.js": ">=1.x.x", + "leaflet": ">=1.x.x", "lodash": ">=4.x", "react": ">=16.x", "react-dom": ">=16.x", "react-error-boundary": "^4.x", + "react-leaflet": ">=4.x.x", "react-resize-detector": ">=9.x.x", "react-use": ">=17.x", "use-debounce": ">=9.x", @@ -24709,8 +24758,8 @@ }, "packages/core": { "name": "@hakit/core", - "version": "3.1.1", - "license": "ISC", + "version": "3.1.3", + "license": "SEE LICENSE IN LICENSE.md", "bin": { "hakit-sync-types": "dist/sync/cli/cli.js" }, @@ -27045,6 +27094,7 @@ "version": "file:packages/components", "requires": { "@emotion/babel-plugin": "^11.x", + "@types/leaflet": "^1.9.8", "vite-plugin-svgr": "^3.2.0" } }, @@ -28435,6 +28485,13 @@ "@babel/runtime": "^7.13.10" } }, + "@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "peer": true, + "requires": {} + }, "@rollup/plugin-alias": { "version": "5.0.0", "dev": true, @@ -30404,6 +30461,12 @@ "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==", "dev": true }, + "@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true + }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -30510,6 +30573,15 @@ "@types/node": "*" } }, + "@types/leaflet": { + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz", + "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/lodash": { "version": "4.14.197", "dev": true @@ -36576,6 +36648,12 @@ "dotenv-expand": "^10.0.0" } }, + "leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true + }, "less": { "version": "4.2.0", "dev": true, @@ -37986,6 +38064,15 @@ "version": "18.2.0", "dev": true }, + "react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "peer": true, + "requires": { + "@react-leaflet/core": "^2.1.0" + } + }, "react-refresh": { "version": "0.14.0", "dev": true diff --git a/package.json b/package.json index 15cb4fd6..8fc5c502 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "build:create": "npm run build --workspace=create-hakit", "build:components": "npm run build --workspace=@hakit/components", "build:storybook": "BUILD_STORYBOOK=true storybook build", - "watch:build:components": "npm run watch:build --workspace=@hakit/components", "watch:build:core": "npm run watch:build --workspace=@hakit/core", "watch:build:sync-script": "npm run watch:build:sync-script --workspace=@hakit/core", diff --git a/packages/components/package.json b/packages/components/package.json index f28da5dc..3b90cd6c 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,7 +1,7 @@ { "name": "@hakit/components", "type": "module", - "version": "3.1.3", + "version": "3.1.4", "private": false, "keywords": [ "react", @@ -68,16 +68,18 @@ "@emotion/react": ">=11.x", "@emotion/styled": ">=11.x", "@fullcalendar/react": ">=6.x.x", - "@hakit/core": "^3.1.3", + "@hakit/core": "^3.1.4", "@use-gesture/react": ">=10.x", "autolinker": ">=4.x", "framer-motion": ">=10.x", "fullcalendar": ">=6.x.x", "hls.js": ">=1.x.x", + "leaflet": ">=1.x.x", "lodash": ">=4.x", "react": ">=16.x", "react-dom": ">=16.x", "react-error-boundary": "^4.x", + "react-leaflet": ">=4.x.x", "react-resize-detector": ">=9.x.x", "react-use": ">=17.x", "use-debounce": ">=9.x", @@ -85,6 +87,7 @@ }, "devDependencies": { "@emotion/babel-plugin": "^11.x", + "@types/leaflet": "^1.9.8", "vite-plugin-svgr": "^3.2.0" } } diff --git a/packages/components/src/Cards/FamilyCard/FamilyCard.stories.tsx b/packages/components/src/Cards/FamilyCard/FamilyCard.stories.tsx new file mode 100644 index 00000000..800e2cae --- /dev/null +++ b/packages/components/src/Cards/FamilyCard/FamilyCard.stories.tsx @@ -0,0 +1,39 @@ +import { Column, FamilyCard, PersonCard, ThemeProvider } from "@components"; +import { HassConnect } from "@hass-connect-fake"; +import type { Args, Meta, StoryObj } from "@storybook/react"; + +function Render(args?: Args) { + return ( + + + + + + + + + + + + + + + + + + ); +} + +export default { + title: "COMPONENTS/Cards/FamilyCard", + component: FamilyCard, + tags: ["autodocs"], + parameters: { + fullWidth: true, + }, +} satisfies Meta; +export type FamilyStory = StoryObj; +export const Example: FamilyStory = { + render: Render, + args: {}, +}; diff --git a/packages/components/src/Cards/FamilyCard/PersonCard.tsx b/packages/components/src/Cards/FamilyCard/PersonCard.tsx new file mode 100644 index 00000000..e876b81c --- /dev/null +++ b/packages/components/src/Cards/FamilyCard/PersonCard.tsx @@ -0,0 +1,186 @@ +import { CardBase, CardBaseProps, fallback } from "@components"; +import styled from "@emotion/styled"; +import { EntityName, FilterByDomain, useEntity, useHass, useIcon } from "@hakit/core"; +import { useMemo } from "react"; +import { ErrorBoundary } from "react-error-boundary"; + +const PersonBaseCard = styled(CardBase)` + background-color: var(--ha-S500); + &:not(.disabled):hover, + &:not(:disabled):hover { + background-color: var(--ha-S600); + } +`; + +const PersonCardContent = styled.div` + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 8px; + border-radius: 0.5rem; + padding: 0.5rem; + .state-text { + font-size: 0.8rem; + color: var(--ha-S500-contrast); + } +`; + +const UserAvatarDiv = styled.div<{ width: string; height: string; withBorder: boolean }>` + position: relative; + .avatar-image, + .avatar-icon { + border-radius: 100%; + width: ${({ width }) => width}; + height: ${({ height }) => height}; + border: ${({ withBorder }) => (withBorder ? "2px solid var(--ha-50)" : "none")}; + box-shadow: ${({ withBorder }) => (withBorder ? "0 2px 2px rgba(0, 0, 0, 0.2)" : "none")}; + background: var(--ha-S900); + } + .avatar-icon { + display: flex; + align-items: center; + justify-content: center; + } +`; + +const UserAvatarStateIcon = styled.div` + position: absolute; + background-color: var(--ha-50); + border-radius: 100%; + top: calc(50% + 8px); + right: calc(0% - 11px); + display: flex; + justify-content: center; + align-items: center; + height: 28px; + width: 28px; + border: 3px solid var(--ha-S500); +`; + +const NameAndState = styled.div` + flex-grow: 1; + font-size: 0.8rem; + color: var(--ha-S50-contrast); + font-weight: 500; + text-align: center; + span { + width: 100%; + font-size: 0.7rem; + font-weight: 400; + display: block; + margin-top: 0.2rem; + color: var(--ha-S500-contrast); + } +`; + +export type PersonEntity = FilterByDomain; +type PersonStateMap = { + [key: string]: + | { + text: string; + icon: string; + } + | undefined; +}; + +type OmitProperties = "as" | "active" | "disabled" | "children" | "entity" | "title" | "onClick" | "serviceData" | "service" | "ref"; + +export interface PersonCardProps extends Omit, OmitProperties> { + /** person entity in the form of "person.{string}" */ + entity: PersonEntity; + /** optional person.state to value object map, i.e: + * {home: {text: "Home", icon: "mdi:home"}, not_home: { text: "Away", icon: "mdi:walk" }, zone_id: { text: "At work", icon: "mdi:briefcase" }, etc.} + * + * Defaults to: + * { home: { text: "Home", icon: "mdi:home" }, not_home: { text: "Away", icon: "mdi:walk" } } + */ + personStateMap?: PersonStateMap; +} + +export type UserAvatarProps = { + entity: PersonEntity; + iconSize?: { width: string; height: string }; + avatarSize?: { width: string; height: string }; + withBorder?: boolean; + stateIcon?: string; +}; + +export const UserAvatar = ({ + entity, + iconSize = { width: "2.5rem", height: "2.5rem" }, + avatarSize = { width: "3.5rem", height: "3.5rem" }, + withBorder = false, + stateIcon, +}: UserAvatarProps) => { + const person = useEntity(entity); + const { joinHassUrl } = useHass(); + + const userImage = useMemo(() => { + const url = person.attributes.entity_picture ? person.attributes.entity_picture : null; + return url && url.startsWith("/") ? joinHassUrl(url) : url; + }, [person.attributes.entity_picture, joinHassUrl]); + + const userIcon = useIcon(person.attributes.icon ?? "mdi:account", iconSize); + const renderedStateIcon = useIcon(stateIcon ?? "", { width: "16px", height: "16px", color: "var(--ha-S900)" }); + + return userImage ? ( + + + {stateIcon && renderedStateIcon && {renderedStateIcon}} + + ) : ( + +
{userIcon}
+ {stateIcon && renderedStateIcon && {renderedStateIcon}} +
+ ); +}; + +function _PersonCard({ + entity, + personStateMap = { home: { text: "Home", icon: "mdi:home" }, not_home: { text: "Away", icon: "mdi:walk" } }, + cssStyles, + modalProps, + className, + ...rest +}: PersonCardProps): JSX.Element { + const { useStore } = useHass(); + const globalComponentStyle = useStore((state) => state.globalComponentStyles); + + const person = useEntity(entity); + const stateText = personStateMap[person.state]?.text ?? person.state; + const stateIcon = personStateMap[person.state]?.icon; + return ( + + + + + {person.attributes.friendly_name} + {stateText} + + + + ); +} + +/** The PersonCard component is an easy way to represent the state of a person. Can be added as children to the FamilyCard component to quickly give an overview of the whole family. */ +export function PersonCard(props: PersonCardProps) { + return ( + + <_PersonCard disableColumns {...props} /> + + ); +} diff --git a/packages/components/src/Cards/FamilyCard/index.tsx b/packages/components/src/Cards/FamilyCard/index.tsx new file mode 100644 index 00000000..9ca7b8fc --- /dev/null +++ b/packages/components/src/Cards/FamilyCard/index.tsx @@ -0,0 +1,160 @@ +import { + AvailableQueries, + CardBase, + getColumnSizeCSS, + mq, + CardBaseProps, + Column, + PersonCard, + PersonCardProps, + Row, + fallback, +} from "@components"; +import styled from "@emotion/styled"; +import { EntityName, FilterByDomain, useHass } from "@hakit/core"; +import { Children, ReactElement, cloneElement, isValidElement } from "react"; +import { ErrorBoundary } from "react-error-boundary"; + +const FamilyBaseCard = styled(CardBase)` + cursor: default; +`; + +const FamilyCardContent = styled.div` + padding: 1rem; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: space-between; + .person-card { + &.entity-count-2-plus { + width: ${getColumnSizeCSS(6)}; + ${mq( + ["xxs", "xs"], + ` + width: ${getColumnSizeCSS(12)}; + `, + )} + ${mq( + ["md", "lg", "xlg"], + ` + width: ${getColumnSizeCSS(4)}; + `, + )} + } + + &.entity-count-1 { + width: ${getColumnSizeCSS(12)}; + } + + &.entity-count-2 { + width: ${getColumnSizeCSS(6)}; + ${mq( + ["xxs", "xs"], + ` + width: ${getColumnSizeCSS(12)}; + `, + )} + } + } +`; + +const Title = styled.div` + font-size: 1rem; + color: var(--ha-S50-contrast); +`; + +type OmitProperties = + | "as" + | "active" + | "disabled" + | "children" + | "entity" + | "title" + | "onClick" + | "modalProps" + | "serviceData" + | "service" + | "disableRipples" + | "disableScale" + | "disableActiveState" + | "rippleProps" + | "borderRadius" + | "disableActiveState" + | "onlyFunctionality" + | "ref"; + +export interface FamilyCardProps extends Omit>, OmitProperties> { + /** the children for the FamilyCard, it accepts Person components */ + children: ReactElement | ReactElement[]; + /** optional title of the card */ + title?: string; +} + +function _FamilyCard({ title, cssStyles, children, className, ...rest }: FamilyCardProps): JSX.Element { + const { useStore } = useHass(); + const globalComponentStyle = useStore((state) => state.globalComponentStyles); + const len = Children.count(children); + const count = len > 2 ? "2-plus" : len === 1 ? "1" : "2"; + const childrenWithKeys = Children.map(children, (child, index) => { + if (isValidElement(child)) { + return cloneElement(child, { + key: child.key || index, + className: `entity-count-${count}`, + }); + } + return child; + }); + + return ( + + + {title && ( + + {title} + + )} + + {childrenWithKeys} + + + + ); +} + +/** The FamilyCard component is an easy way to represent the state of the persons of your family within a simple layout, add each person to a PersonCard and place them as children within this card and you're good to go! */ +export function FamilyCard(props: FamilyCardProps) { + const defaultColumns: AvailableQueries = { + xxs: 12, + xs: 6, + sm: 6, + md: 4, + lg: 4, + xlg: 3, + }; + return ( + + <_FamilyCard {...defaultColumns} {...props} /> + + ); +} diff --git a/packages/components/src/Cards/GarbageCollectionCard/index.tsx b/packages/components/src/Cards/GarbageCollectionCard/index.tsx index e53811b5..42cccc35 100644 --- a/packages/components/src/Cards/GarbageCollectionCard/index.tsx +++ b/packages/components/src/Cards/GarbageCollectionCard/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-namespace */ import styled from "@emotion/styled"; import React, { useMemo, useEffect, useCallback, useRef, useState, CSSProperties, Key } from "react"; import { useEntity, useHass } from "@hakit/core"; @@ -113,39 +114,46 @@ function VerticalText({ height, text }: VerticalTextProps) { ); } -interface SvgProperties { - color: CSSProperties["color"]; - /** If the name is provided, this color will apply to the text of the bin @default rgba(0,0,0,0.6) */ - textColor?: CSSProperties["color"]; - /** the color of the icon if provided to any of the bins @default rgba(0,0,0,0.6) */ - iconColor?: CSSProperties["color"]; - /** the size of the rendered default svg @default 45 */ - size?: number; -} -interface BinProperties extends SvgProperties { - /** the name of the bin @default "''" */ - name?: string; - /** the icon name from iconify to display over the bin, if provided as well as name, the display may look odd */ - icon?: string; - /** if provided, the default garbage bin will not be rendered and you can render your own */ - render?: (bin: BinProperties, key: Key) => React.ReactElement; -} - -type WeekConfig = Array | null; -type Day = "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday"; +export module GarbageCollectionCardTypes { + export type Day = "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday"; + export type Week = Array | null; + export interface SvgProperties { + color: CSSProperties["color"]; + /** If the name is provided, this color will apply to the text of the bin @default rgba(0,0,0,0.6) */ + textColor?: CSSProperties["color"]; + /** the color of the icon if provided to any of the bins @default rgba(0,0,0,0.6) */ + iconColor?: CSSProperties["color"]; + /** the size of the rendered default svg @default 45 */ + size?: number; + } + export interface BinProperties extends SvgProperties { + /** the name of the bin @default "''" */ + name?: string; + /** the icon name from iconify to display over the bin, if provided as well as name, the display may look odd */ + icon?: string; + /** if provided, the default garbage bin will not be rendered and you can render your own */ + render?: (bin: BinProperties, key: Key) => React.ReactElement; + } -interface Schedule { - /** optional title to appear in each schedule */ - title?: React.ReactNode; - /** on what day does your garbage get collected */ - day: Day; - /** as there's (usually) 4 weeks in a month, provide a config for each week, if you only have your garbage collected once a month, on a tuesday, specify null for the weeks that aren't relevant */ - weeks: [WeekConfig, WeekConfig, WeekConfig, WeekConfig]; - /** how often is your garbage collected */ - frequency: "weekly" | "fortnightly" | "monthly"; - /** hide the next collection time @default false */ - hideNextCollection?: boolean; + export interface Schedule { + /** optional title to appear in each schedule */ + title?: React.ReactNode; + /** on what day does your garbage get collected */ + day: GarbageCollectionCardTypes.Day; + /** as there's (usually) 4 weeks in a month, provide a config for each week, if you only have your garbage collected once a month, on a tuesday, specify null for the weeks that aren't relevant */ + weeks: [ + GarbageCollectionCardTypes.Week, + GarbageCollectionCardTypes.Week, + GarbageCollectionCardTypes.Week, + GarbageCollectionCardTypes.Week, + ]; + /** how often is your garbage collected */ + frequency: "weekly" | "fortnightly" | "monthly"; + /** hide the next collection time @default false */ + hideNextCollection?: boolean; + } } + type OmitProperties = | "onClick" | "as" @@ -169,9 +177,9 @@ export interface GarbageCollectionCardProps extends Omit void; } @@ -190,7 +198,7 @@ function _GarbageCollectionCard({ const dateSensor = useEntity("sensor.date", { returnNullIfNotFound: true, }); - const defaultSVGProperties = useMemo>( + const defaultSVGProperties = useMemo>( () => ({ size: 45, color: "#484848", @@ -209,7 +217,7 @@ function _GarbageCollectionCard({ const dayNames = useMemo(() => ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], []); const renderBin = useCallback( - (bin: CSSProperties["color"] | BinProperties | undefined, key: Key) => { + (bin: CSSProperties["color"] | GarbageCollectionCardTypes.BinProperties | undefined, key: Key) => { if (typeof bin === "object" && typeof bin.render === "function") return bin.render(bin, key); const size = typeof bin === "string" ? defaultSVGProperties.size : bin?.size ?? defaultSVGProperties.size; const color = typeof bin === "string" ? bin : bin?.color ?? defaultSVGProperties.color; @@ -247,7 +255,7 @@ function _GarbageCollectionCard({ return days === 0 ? "Today" : days === 1 ? "Tomorrow" : `in ${days} days`; }, []); - const findNextNonNullWeek = useCallback((weeks: WeekConfig[], startWeek: number): number => { + const findNextNonNullWeek = useCallback((weeks: GarbageCollectionCardTypes.Week[], startWeek: number): number => { for (let i = 0; i < weeks.length; i++) { const index = (startWeek + i) % weeks.length; if (weeks[index] !== null) { diff --git a/packages/components/src/Shared/Entity/Person/PersonControls/index.tsx b/packages/components/src/Shared/Entity/Person/PersonControls/index.tsx new file mode 100644 index 00000000..63cfe63e --- /dev/null +++ b/packages/components/src/Shared/Entity/Person/PersonControls/index.tsx @@ -0,0 +1,102 @@ +import { UserAvatar, fallback } from "@components"; +import styled from "@emotion/styled"; +import type { EntityName, FilterByDomain } from "@hakit/core"; +import { useEntity } from "@hakit/core"; +import L, { DivIcon } from "leaflet"; +import "leaflet/dist/leaflet.css"; +import { useEffect } from "react"; +import { createPortal } from "react-dom"; +import { ErrorBoundary } from "react-error-boundary"; +import { MapContainer, Marker, MarkerProps, TileLayer } from "react-leaflet"; + +const StyledMapContainer = styled(MapContainer)` + .leaflet-layer, + .leaflet-control-zoom-in, + .leaflet-control-zoom-out, + .leaflet-control-attribution { + filter: brightness(0.6) invert(1) contrast(4) hue-rotate(300deg) saturate(0.3) brightness(1) grayscale(40%); + } + .leaflet-div-icon { + background: none; + border: none; + } +`; + +type ReactProps = { children: JSX.Element }; + +type ContainerProps = { + tagName: string; + className?: string; + container?: HTMLElement; +}; + +type DivIconMarkerProps = ReactProps & { marker: MarkerProps } & { + container: ContainerProps; +}; +export const MapMarker = ({ children, marker, container }: DivIconMarkerProps) => { + const { tagName, className } = container; + const element = L.DomUtil.create(tagName, className); + const divIcon = new DivIcon({ html: element }); + const portal = createPortal(children, element); + const { position, eventHandlers } = marker; + + useEffect(() => { + return () => { + L.DomUtil.remove(element); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Cleanup runs on unmount + + return ( + <> + {portal} + + + ); +}; + +export interface PersonControlsProps { + entity: FilterByDomain; + mapHeight: number; +} + +function _PersonControls({ entity: _entity, mapHeight }: PersonControlsProps) { + const entity = useEntity(_entity); + const position = new L.LatLng(entity.attributes.latitude, entity.attributes.longitude); + + return ( + + + + + + + ); +} + +/** + * The PersonControls component renders a map with a user avatar marker of the persons location. + * */ +export function PersonControls(props: PersonControlsProps) { + return ( + + <_PersonControls {...props} /> + + ); +} diff --git a/packages/components/src/Shared/Modal/ModalByEntityDomain/Person/index.tsx b/packages/components/src/Shared/Modal/ModalByEntityDomain/Person/index.tsx new file mode 100644 index 00000000..64aaede1 --- /dev/null +++ b/packages/components/src/Shared/Modal/ModalByEntityDomain/Person/index.tsx @@ -0,0 +1,14 @@ +import type { PersonControlsProps } from "@components"; +import type { EntityName, FilterByDomain } from "@hakit/core"; +import { lazy } from "react"; + +export interface ModalPersonControlsProps extends PersonControlsProps { + entity: FilterByDomain; + mapHeight: number; +} + +const PersonControls = lazy(() => import("@components").then((module) => ({ default: module.PersonControls }))); + +export function ModalPersonControls({ entity, mapHeight, ...props }: ModalPersonControlsProps) { + return ; +} diff --git a/packages/components/src/Shared/Modal/ModalByEntityDomain/index.tsx b/packages/components/src/Shared/Modal/ModalByEntityDomain/index.tsx index 7cdc8ea9..d31d1645 100644 --- a/packages/components/src/Shared/Modal/ModalByEntityDomain/index.tsx +++ b/packages/components/src/Shared/Modal/ModalByEntityDomain/index.tsx @@ -1,37 +1,40 @@ -import { computeDomain } from "@utils/computeDomain"; -import { useMemo, useState, useEffect, useRef, useCallback } from "react"; -import { - useHass, - useEntity, - type EntityRegistryEntry, - type AllDomains, - type EntityName, - type FilterByDomain, - type ExtractDomain, -} from "@hakit/core"; -import type { ModalProps } from ".."; import { - Modal, + Column, EntityAttributes, - ModalLightControls, - ModalClimateControls, - ModalSwitchControls, + FabCard, + LogBookRenderer, + Modal, ModalCameraControls, + ModalClimateControls, ModalCoverControls, - ModalWeatherControls, + ModalLightControls, ModalMediaPlayerControls, - FabCard, - LogBookRenderer, - Column, - type ModalWeatherControlsProps, - type ModalClimateControlsProps, - type ModalLightControlsProps, - type ModalSwitchControlsProps, + ModalPersonControls, + ModalSwitchControls, + ModalWeatherControls, type ModalCameraControlsProps, + type ModalClimateControlsProps, type ModalCoverControlsProps, + type ModalLightControlsProps, type ModalMediaPlayerControlsProps, + type ModalPersonControlsProps, + type ModalSwitchControlsProps, + type ModalWeatherControlsProps, } from "@components"; import styled from "@emotion/styled"; +import { + useEntity, + useHass, + type AllDomains, + type EntityName, + type EntityRegistryEntry, + type ExtractDomain, + type FilterByDomain, +} from "@hakit/core"; +import { computeDomain } from "@utils/computeDomain"; +import { lowerCase, startCase } from "lodash"; +import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import type { ModalProps } from ".."; const Separator = styled.div` height: 30px; @@ -70,6 +73,7 @@ interface ModalPropsByDomain { camera: ModalCameraControlsProps; cover: ModalCoverControlsProps; media_player: ModalMediaPlayerControlsProps; + person: ModalPersonControlsProps; } export type ModalPropsHelper = D extends keyof ModalPropsByDomain @@ -82,6 +86,7 @@ export type ModalByEntityDomainProps = ModalPropsHelper; export function ModalByEntityDomain({ @@ -124,7 +129,7 @@ export function ModalByEntityDomain({ }, [device, joinHassUrl]); const [modalProps, childProps] = useMemo(() => { - const { open, id, title, description, onClose, backdropProps, ...childProps } = rest; + const { open, id, title, description, onClose, backdropProps, stateTitle, ...childProps } = rest; return [ { open, @@ -133,6 +138,7 @@ export function ModalByEntityDomain({ description, onClose, backdropProps, + stateTitle, }, childProps, ]; @@ -165,6 +171,16 @@ export function ModalByEntityDomain({ return } onStateChange={onStateChange} {...childProps} />; case "weather": return } {...childProps} />; + case "person": + return ( + Loading map...}> + } + mapHeight={modalProps.open ? 300 : 0} + {...childProps} + /> + + ); case "media_player": { return ( // @ts-expect-error - child prop types are correct, it does have groupEntities but ts doesn't think it does, will fix later, parent intellisense is correct @@ -180,12 +196,12 @@ export function ModalByEntityDomain({ default: return null; } - }, [entity, childProps, onStateChange, domain]); + }, [entity, childProps, onStateChange, domain, modalProps.open]); const stateRef = useRef(null); const titleValue = useMemo(() => { - return `${_entity.state}${_entity.attributes.unit_of_measurement ?? ""}`; - }, [_entity]); + return modalProps.stateTitle ?? startCase(lowerCase(`${_entity.state}${_entity.attributes.unit_of_measurement ?? ""}`)); + }, [_entity, modalProps.stateTitle]); return ( { .join("\n"); }; -const getColumnSizeCSS = (column: GridSpan): string => { +export const getColumnSizeCSS = (column: GridSpan): string => { // Calculate the base width for each column. return `calc( (100% - 11 * var(--gap, 0px)) * ${column} / 12 + (${column} - 1) * var(--gap, 0px) diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 88775b9a..01355fa3 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,6 +1,15 @@ import "./.d.ts"; /// -export { mq, getBreakpoints, type AvailableQueries, type GridSpan, type BreakPoint, type BreakPoints } from "./ThemeProvider/breakpoints"; +export { + getBreakpoints, + mq, + getColumnSizeCSS, + generateColumnBreakpoints, + type AvailableQueries, + type BreakPoint, + type BreakPoints, + type GridSpan, +} from "./ThemeProvider/breakpoints"; // media query helpers export { useBreakpoint } from "./hooks/useBreakpoint"; // the base card component @@ -34,12 +43,14 @@ export { getAdditionalWeatherInformation } from "./Cards/WeatherCard/helpers"; // WeatherCardDetail export { WeatherCardDetail, type WeatherCardDetailProps } from "./Cards/WeatherCard/WeatherCardDetail"; // GarbageCollectionCard -export { GarbageCollectionCard, type GarbageCollectionCardProps } from "./Cards/GarbageCollectionCard"; +export { GarbageCollectionCard, type GarbageCollectionCardProps, type GarbageCollectionCardTypes } from "./Cards/GarbageCollectionCard"; // TimeCard export { TimeCard, type TimeCardProps } from "./Cards/TimeCard"; // AreaCard export { AreaCard, type AreaCardProps } from "./Cards/AreaCard"; +// ts-ignore import { AreaCard as ActualAreaCard, type AreaCardProps as ActualAreaCardProps } from "./Cards/AreaCard"; + /** * @deprecated RoomCard has been renamed to AreaCard. Please use {@link AreaCard} instead. */ @@ -52,6 +63,10 @@ export type RoomCardProps = ActualAreaCardProps; export { PictureCard, type PictureCardProps } from "./Cards/PictureCard"; // FabCard export { FabCard, type FabCardProps } from "./Cards/FabCard"; +// FamilyCard +export { FamilyCard, type FamilyCardProps } from "./Cards/FamilyCard"; +// PersonCard +export { PersonCard, UserAvatar, type PersonCardProps, type UserAvatarProps } from "./Cards/FamilyCard/PersonCard"; // SidebarCard export { SidebarCard, type SidebarCardProps } from "./Cards/SidebarCard"; // ClimateControls @@ -67,6 +82,8 @@ export { CoverControls, type CoverControlsProps } from "./Shared/Entity/Cover/Co export { SwitchControls, type SwitchControlsProps } from "./Shared/Entity/Switch/SwitchControls"; // MediaPlayerControls export { MediaPlayerControls, type MediaPlayerControlsProps } from "./Shared/Entity/MediaPlayer/MediaPlayerControls"; +// PersonControls +export { PersonControls, type PersonControlsProps } from "./Shared/Entity/Person/PersonControls"; // ClimateCard export { ClimateCard, type ClimateCardProps } from "./Cards/ClimateCard"; // EntitiesCard @@ -76,8 +93,8 @@ export { EntitiesCardRow, type EntitiesCardRowProps } from "./Cards/EntitiesCard // MediaPlayerCard export { MediaPlayerCard, type MediaPlayerCardProps } from "./Cards/MediaPlayerCard"; // MediaPlayerShared -export { VolumeControls, type VolumeControlsProps } from "./Cards/MediaPlayerCard/VolumeControls"; export { PlaybackControls, type PlaybackControlsProps } from "./Cards/MediaPlayerCard/PlaybackControls"; +export { VolumeControls, type VolumeControlsProps } from "./Cards/MediaPlayerCard/VolumeControls"; // CalendarCard export { CalendarCard, type CalendarCardProps } from "./Cards/CalendarCard"; // ButtonBar @@ -90,19 +107,20 @@ export { ButtonGroup, type ButtonGroupProps } from "./Shared/Entity/Miscellaneou export { ButtonGroupButton, type ButtonGroupButtonProps } from "./Shared/Entity/Miscellaneous/ButtonGroup/ButtonGroupButton.tsx"; // CameraCard export { CameraCard, type CameraCardProps } from "./Cards/CameraCard"; -export { CameraStream, type CameraStreamProps } from "./Cards/CameraCard/stream"; export type { VideoState } from "./Cards/CameraCard/players"; +export { CameraStream, type CameraStreamProps } from "./Cards/CameraCard/stream"; // Modal -export { Modal, type ModalProps } from "./Shared/Modal"; export { LogBookRenderer, type LogBookRendererProps } from "./Shared/Entity/Miscellaneous/LogBookRenderer"; -export { ModalLightControls, type ModalLightControlsProps } from "./Shared/Modal/ModalByEntityDomain/Light"; -export { ModalClimateControls, type ModalClimateControlsProps } from "./Shared/Modal/ModalByEntityDomain/Climate"; -export { ModalSwitchControls, type ModalSwitchControlsProps } from "./Shared/Modal/ModalByEntityDomain/Switch"; +export { Modal, type ModalProps } from "./Shared/Modal"; +export { ModalByEntityDomain, type ModalByEntityDomainProps, type ModalPropsHelper } from "./Shared/Modal/ModalByEntityDomain"; export { ModalCameraControls, type ModalCameraControlsProps } from "./Shared/Modal/ModalByEntityDomain/Camera"; +export { ModalClimateControls, type ModalClimateControlsProps } from "./Shared/Modal/ModalByEntityDomain/Climate"; export { ModalCoverControls, type ModalCoverControlsProps } from "./Shared/Modal/ModalByEntityDomain/Cover"; -export { ModalWeatherControls, type ModalWeatherControlsProps } from "./Shared/Modal/ModalByEntityDomain/Weather"; +export { ModalLightControls, type ModalLightControlsProps } from "./Shared/Modal/ModalByEntityDomain/Light"; export { ModalMediaPlayerControls, type ModalMediaPlayerControlsProps } from "./Shared/Modal/ModalByEntityDomain/MediaPlayer"; -export { ModalByEntityDomain, type ModalByEntityDomainProps, type ModalPropsHelper } from "./Shared/Modal/ModalByEntityDomain"; +export { ModalPersonControls, type ModalPersonControlsProps } from "./Shared/Modal/ModalByEntityDomain/Person"; +export { ModalSwitchControls, type ModalSwitchControlsProps } from "./Shared/Modal/ModalByEntityDomain/Switch"; +export { ModalWeatherControls, type ModalWeatherControlsProps } from "./Shared/Modal/ModalByEntityDomain/Weather"; // ControlSlider export { ControlSlider, type ControlSliderProps } from "./Shared/ControlSlider"; // ControlToggle @@ -114,7 +132,7 @@ export { Menu, type MenuProps } from "./Shared/Menu"; // ColorTempPicker export { ColorTempPicker, type ColorTempPickerProps } from "./Shared/Entity/Light/ColorTempPicker"; // ColorPicker -export { ColorPicker, type ColorPickerProps, type ColorPickerOutputColors } from "./Shared/Entity/Light/ColorPicker"; +export { ColorPicker, type ColorPickerOutputColors, type ColorPickerProps } from "./Shared/Entity/Light/ColorPicker"; // EntityAttributes export { EntityAttributes, type EntityAttributesProps } from "./Shared/Entity/Miscellaneous/EntityAttributes"; // ImagePreloader @@ -124,5 +142,5 @@ export { Alert, type AlertProps } from "./Shared/Alert"; // ThemeProvider export { ThemeProvider, type ThemeProviderProps } from "./ThemeProvider"; -export { theme } from "./ThemeProvider/theme"; export * from "./ThemeProvider/constants"; +export { theme } from "./ThemeProvider/theme"; diff --git a/packages/core/package.json b/packages/core/package.json index 962d7597..c719ec8d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@hakit/core", - "version": "3.1.3", + "version": "3.1.4", "private": false, "type": "module", "keywords": [ diff --git a/packages/core/src/HassConnect/Provider.tsx b/packages/core/src/HassConnect/Provider.tsx index 69b761d1..ac1f6315 100644 --- a/packages/core/src/HassConnect/Provider.tsx +++ b/packages/core/src/HassConnect/Provider.tsx @@ -65,7 +65,9 @@ export type SupportedComponentOverrides = | "timeCard" | "triggerCard" | "weatherCard" - | "menu"; + | "menu" + | "personCard" + | "familyCard"; export interface Store { entities: HassEntities; setEntities: (entities: HassEntities) => void; diff --git a/stories/1.demo.stories.tsx b/stories/1.demo.stories.tsx index ac63d157..e3b263b0 100644 --- a/stories/1.demo.stories.tsx +++ b/stories/1.demo.stories.tsx @@ -24,6 +24,8 @@ import { ButtonBarButton, EntitiesCardRow, ButtonGroup, + FamilyCard, + PersonCard, ButtonGroupButton, } from '@components'; import office from './office.jpg'; @@ -57,7 +59,7 @@ function Template() { - + - + - - + + Custom Content @@ -183,7 +185,7 @@ function Template() { - + @@ -203,7 +205,7 @@ function Template() { - + @@ -218,12 +220,12 @@ function Template() { - + - + @@ -238,6 +240,10 @@ function Template() { + + + +