@seamapi/react 2.8.6 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +2 -2
  2. package/dist/elements.js +6853 -6176
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +31 -2
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.min.css +1 -1
  7. package/dist/index.min.css.map +1 -1
  8. package/lib/debounce.d.ts +6 -0
  9. package/lib/debounce.js +20 -0
  10. package/lib/debounce.js.map +1 -0
  11. package/lib/icons/AccessCodeKey.js +1 -1
  12. package/lib/icons/AccessCodeKey.js.map +1 -1
  13. package/lib/icons/ArrowRestart.js +1 -1
  14. package/lib/icons/ArrowRestart.js.map +1 -1
  15. package/lib/icons/BatteryLevelWired.js +1 -1
  16. package/lib/icons/BatteryLevelWired.js.map +1 -1
  17. package/lib/icons/Bee.js +1 -1
  18. package/lib/icons/Bee.js.map +1 -1
  19. package/lib/icons/CheckGreen.js +1 -1
  20. package/lib/icons/CheckGreen.js.map +1 -1
  21. package/lib/icons/ClimateSettingSchedule.js +1 -1
  22. package/lib/icons/ClimateSettingSchedule.js.map +1 -1
  23. package/lib/icons/Copy.js +1 -1
  24. package/lib/icons/Copy.js.map +1 -1
  25. package/lib/icons/DotsEllipsisMore.js +1 -1
  26. package/lib/icons/DotsEllipsisMore.js.map +1 -1
  27. package/lib/icons/Edit.js +1 -1
  28. package/lib/icons/Edit.js.map +1 -1
  29. package/lib/icons/ExclamationCircle.js +1 -1
  30. package/lib/icons/ExclamationCircle.js.map +1 -1
  31. package/lib/icons/ExclamationCircleOutline.js +1 -1
  32. package/lib/icons/ExclamationCircleOutline.js.map +1 -1
  33. package/lib/icons/Fan.js +1 -1
  34. package/lib/icons/Fan.js.map +1 -1
  35. package/lib/icons/FanOutline.js +1 -1
  36. package/lib/icons/FanOutline.js.map +1 -1
  37. package/lib/icons/Info.js +1 -1
  38. package/lib/icons/Info.js.map +1 -1
  39. package/lib/icons/InfoBlue.js +1 -1
  40. package/lib/icons/InfoBlue.js.map +1 -1
  41. package/lib/icons/InfoDark.js +1 -1
  42. package/lib/icons/InfoDark.js.map +1 -1
  43. package/lib/icons/Off.js +1 -1
  44. package/lib/icons/Off.js.map +1 -1
  45. package/lib/icons/OnlineStatusAccountOffline.js +1 -1
  46. package/lib/icons/OnlineStatusAccountOffline.js.map +1 -1
  47. package/lib/icons/OnlineStatusDeviceOffline.js +1 -1
  48. package/lib/icons/OnlineStatusDeviceOffline.js.map +1 -1
  49. package/lib/icons/OnlineStatusOnline.js +1 -1
  50. package/lib/icons/OnlineStatusOnline.js.map +1 -1
  51. package/lib/icons/Seam.js +1 -1
  52. package/lib/icons/Seam.js.map +1 -1
  53. package/lib/icons/Search.js +1 -1
  54. package/lib/icons/Search.js.map +1 -1
  55. package/lib/icons/ThermostatHeat.js +1 -1
  56. package/lib/icons/ThermostatHeat.js.map +1 -1
  57. package/lib/icons/ThermostatHeatCool.js +1 -1
  58. package/lib/icons/ThermostatHeatCool.js.map +1 -1
  59. package/lib/icons/ThermostatHeatLarge.js +1 -1
  60. package/lib/icons/ThermostatHeatLarge.js.map +1 -1
  61. package/lib/icons/ThermostatOff.js +1 -1
  62. package/lib/icons/ThermostatOff.js.map +1 -1
  63. package/lib/icons/TriangleWarning.js +1 -1
  64. package/lib/icons/TriangleWarning.js.map +1 -1
  65. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js +111 -17
  66. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js.map +1 -1
  67. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js.map +1 -1
  68. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.d.ts +2 -2
  69. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.d.ts +2 -4
  70. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js +0 -6
  71. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js.map +1 -1
  72. package/lib/seam/components/SupportedDeviceTable/use-device-model.d.ts +2 -2
  73. package/lib/seam/components/SupportedDeviceTable/use-device-models.d.ts +2 -2
  74. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js.map +1 -1
  75. package/lib/temperature-bounds.d.ts +2 -1
  76. package/lib/temperature-bounds.js +16 -0
  77. package/lib/temperature-bounds.js.map +1 -1
  78. package/lib/ui/layout/AccordionRow.d.ts +2 -1
  79. package/lib/ui/layout/AccordionRow.js +2 -2
  80. package/lib/ui/layout/AccordionRow.js.map +1 -1
  81. package/lib/ui/thermostat/ClimateModeMenu.d.ts +2 -1
  82. package/lib/ui/thermostat/ClimateModeMenu.js +2 -3
  83. package/lib/ui/thermostat/ClimateModeMenu.js.map +1 -1
  84. package/lib/version.d.ts +1 -1
  85. package/lib/version.js +1 -1
  86. package/package.json +9 -9
  87. package/src/lib/debounce.ts +33 -0
  88. package/src/lib/icons/AccessCodeKey.tsx +1 -1
  89. package/src/lib/icons/ArrowRestart.tsx +1 -1
  90. package/src/lib/icons/BatteryLevelWired.tsx +1 -1
  91. package/src/lib/icons/Bee.tsx +1 -1
  92. package/src/lib/icons/CheckGreen.tsx +1 -1
  93. package/src/lib/icons/ClimateSettingSchedule.tsx +1 -1
  94. package/src/lib/icons/Copy.tsx +1 -1
  95. package/src/lib/icons/DotsEllipsisMore.tsx +1 -1
  96. package/src/lib/icons/Edit.tsx +1 -1
  97. package/src/lib/icons/ExclamationCircle.tsx +1 -1
  98. package/src/lib/icons/ExclamationCircleOutline.tsx +1 -1
  99. package/src/lib/icons/Fan.tsx +1 -1
  100. package/src/lib/icons/FanOutline.tsx +1 -1
  101. package/src/lib/icons/Info.tsx +1 -1
  102. package/src/lib/icons/InfoBlue.tsx +1 -1
  103. package/src/lib/icons/InfoDark.tsx +1 -1
  104. package/src/lib/icons/Off.tsx +1 -1
  105. package/src/lib/icons/OnlineStatusAccountOffline.tsx +1 -1
  106. package/src/lib/icons/OnlineStatusDeviceOffline.tsx +1 -1
  107. package/src/lib/icons/OnlineStatusOnline.tsx +1 -1
  108. package/src/lib/icons/Seam.tsx +1 -1
  109. package/src/lib/icons/Search.tsx +1 -1
  110. package/src/lib/icons/ThermostatHeat.tsx +1 -1
  111. package/src/lib/icons/ThermostatHeatCool.tsx +1 -1
  112. package/src/lib/icons/ThermostatHeatLarge.tsx +1 -1
  113. package/src/lib/icons/ThermostatOff.tsx +1 -1
  114. package/src/lib/icons/TriangleWarning.tsx +1 -1
  115. package/src/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.tsx +232 -35
  116. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.tsx +3 -3
  117. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.tsx +2 -2
  118. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.tsx +4 -15
  119. package/src/lib/seam/components/SupportedDeviceTable/use-device-model.ts +2 -2
  120. package/src/lib/seam/components/SupportedDeviceTable/use-device-models.ts +2 -2
  121. package/src/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.ts +6 -7
  122. package/src/lib/temperature-bounds.ts +23 -1
  123. package/src/lib/ui/layout/AccordionRow.tsx +6 -1
  124. package/src/lib/ui/thermostat/ClimateModeMenu.tsx +3 -3
  125. package/src/lib/version.ts +1 -1
  126. package/src/styles/_layout.scss +13 -2
  127. package/src/styles/_thermostat.scss +26 -0
@@ -1,23 +1,33 @@
1
1
  import classNames from 'classnames'
2
- import { useState } from 'react'
3
- import type { ThermostatDevice } from 'seamapi'
2
+ import { useEffect, useState } from 'react'
3
+ import type { HvacModeSetting, ThermostatDevice } from 'seamapi'
4
4
 
5
+ import { debounce } from 'lib/debounce.js'
5
6
  import { BeeIcon } from 'lib/icons/Bee.js'
7
+ import { CheckBlackIcon } from 'lib/icons/CheckBlack.js'
6
8
  import { ChevronWideIcon } from 'lib/icons/ChevronWide.js'
7
9
  import { NestedClimateSettingScheduleTable } from 'lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.js'
8
10
  import type { CommonProps } from 'lib/seam/components/common-props.js'
9
11
  import { useConnectedAccount } from 'lib/seam/connected-accounts/use-connected-account.js'
10
12
  import { useClimateSettingSchedules } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedules.js'
13
+ import { useCoolThermostat } from 'lib/seam/thermostats/use-cool-thermostat.js'
14
+ import { useHeatCoolThermostat } from 'lib/seam/thermostats/use-heat-cool-thermostat.js'
15
+ import { useHeatThermostat } from 'lib/seam/thermostats/use-heat-thermostat.js'
16
+ import { useSetThermostatOff } from 'lib/seam/thermostats/use-set-thermostat-off.js'
11
17
  import { useUpdateFanMode } from 'lib/seam/thermostats/use-update-fan-mode.js'
12
18
  import { useUpdateThermostat } from 'lib/seam/thermostats/use-update-thermostat.js'
19
+ import { getSupportedThermostatModes } from 'lib/temperature-bounds.js'
20
+ import { AccordionRow } from 'lib/ui/layout/AccordionRow.js'
13
21
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
14
22
  import { DetailRow } from 'lib/ui/layout/DetailRow.js'
15
23
  import { DetailSection } from 'lib/ui/layout/DetailSection.js'
16
24
  import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
17
25
  import { Snackbar } from 'lib/ui/Snackbar/Snackbar.js'
18
26
  import { Switch } from 'lib/ui/Switch/Switch.js'
27
+ import { ClimateModeMenu } from 'lib/ui/thermostat/ClimateModeMenu.js'
19
28
  import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
20
29
  import { FanModeMenu } from 'lib/ui/thermostat/FanModeMenu.js'
30
+ import { TemperatureControlGroup } from 'lib/ui/thermostat/TemperatureControlGroup.js'
21
31
  import { ThermostatCard } from 'lib/ui/thermostat/ThermostatCard.js'
22
32
 
23
33
  interface ThermostatDeviceDetailsProps extends CommonProps {
@@ -105,12 +115,7 @@ export function ThermostatDeviceDetails({
105
115
  label={t.currentSettings}
106
116
  tooltipContent={t.currentSettingsTooltip}
107
117
  >
108
- <DetailRow label={t.climate}>
109
- <ClimateSettingStatus
110
- climateSetting={device.properties.current_climate_setting}
111
- temperatureUnit='fahrenheit'
112
- />
113
- </DetailRow>
118
+ <ClimateSettingRow device={device} />
114
119
  <FanModeRow device={device} />
115
120
  </DetailSection>
116
121
 
@@ -166,22 +171,25 @@ function ManualOverrideRow({
166
171
 
167
172
  return (
168
173
  <>
169
- <DetailRow label={t.allowManualOverride}>
170
- <Switch
171
- checked={
172
- device.properties.default_climate_setting
173
- ?.manual_override_allowed ?? true
174
- }
175
- onChange={(checked) => {
176
- mutate({
177
- device_id: device.device_id,
178
- default_climate_setting: {
179
- manual_override_allowed: checked,
180
- },
181
- })
182
- }}
183
- />
184
- </DetailRow>
174
+ <div className='seam-detail-row-wrap'>
175
+ <DetailRow label={t.allowManualOverride}>
176
+ <Switch
177
+ checked={
178
+ device.properties.default_climate_setting
179
+ ?.manual_override_allowed ?? true
180
+ }
181
+ onChange={(checked) => {
182
+ mutate({
183
+ device_id: device.device_id,
184
+ default_climate_setting: {
185
+ manual_override_allowed: checked,
186
+ },
187
+ })
188
+ }}
189
+ />
190
+ </DetailRow>
191
+ </div>
192
+
185
193
  <Snackbar
186
194
  message={t.manualOverrideSuccess}
187
195
  variant='success'
@@ -204,17 +212,19 @@ function FanModeRow({ device }: { device: ThermostatDevice }): JSX.Element {
204
212
 
205
213
  return (
206
214
  <>
207
- <DetailRow label={t.fanMode}>
208
- <FanModeMenu
209
- mode={device.properties.fan_mode_setting}
210
- onChange={(fanMode) => {
211
- mutate({
212
- device_id: device.device_id,
213
- fan_mode_setting: fanMode,
214
- })
215
- }}
216
- />
217
- </DetailRow>
215
+ <div className='seam-detail-row-wrap'>
216
+ <DetailRow label={t.fanMode}>
217
+ <FanModeMenu
218
+ mode={device.properties.fan_mode_setting}
219
+ onChange={(fanMode) => {
220
+ mutate({
221
+ device_id: device.device_id,
222
+ fan_mode_setting: fanMode,
223
+ })
224
+ }}
225
+ />
226
+ </DetailRow>
227
+ </div>
218
228
 
219
229
  <Snackbar
220
230
  message={t.fanModeSuccess}
@@ -234,6 +244,191 @@ function FanModeRow({ device }: { device: ThermostatDevice }): JSX.Element {
234
244
  )
235
245
  }
236
246
 
247
+ function ClimateSettingRow({
248
+ device,
249
+ }: {
250
+ device: ThermostatDevice
251
+ }): JSX.Element {
252
+ const deviceHeatValue =
253
+ device.properties.current_climate_setting.heating_set_point_fahrenheit
254
+ const deviceCoolValue =
255
+ device.properties.current_climate_setting.cooling_set_point_fahrenheit
256
+
257
+ const supportedModes = getSupportedThermostatModes(device)
258
+
259
+ const [showSuccess, setShowSuccess] = useState(false)
260
+ const [mode, setMode] = useState<HvacModeSetting>(
261
+ (supportedModes.includes('heat_cool') ? 'heat_cool' : supportedModes[0]) ??
262
+ 'off'
263
+ )
264
+
265
+ const [heatValue, setHeatValue] = useState(
266
+ device.properties.current_climate_setting.heating_set_point_fahrenheit ?? 0
267
+ )
268
+
269
+ const [coolValue, setCoolValue] = useState(
270
+ device.properties.current_climate_setting.cooling_set_point_fahrenheit ?? 0
271
+ )
272
+
273
+ const {
274
+ mutate: heatCoolThermostat,
275
+ isSuccess: isHeatCoolSuccess,
276
+ isError: isHeatCoolError,
277
+ } = useHeatCoolThermostat()
278
+
279
+ const {
280
+ mutate: heatThermostat,
281
+ isSuccess: isHeatSuccess,
282
+ isError: isHeatError,
283
+ } = useHeatThermostat()
284
+
285
+ const {
286
+ mutate: coolThermostat,
287
+ isSuccess: isCoolSuccess,
288
+ isError: isCoolError,
289
+ } = useCoolThermostat()
290
+
291
+ const {
292
+ mutate: setThermostatOff,
293
+ isSuccess: isSetOffSuccess,
294
+ isError: isSetOffError,
295
+ } = useSetThermostatOff()
296
+
297
+ useEffect(() => {
298
+ const handler = debounce(() => {
299
+ switch (mode) {
300
+ case 'heat_cool':
301
+ heatCoolThermostat({
302
+ device_id: device.device_id,
303
+ heating_set_point_fahrenheit: heatValue,
304
+ cooling_set_point_fahrenheit: coolValue,
305
+ })
306
+ break
307
+ case 'heat':
308
+ heatThermostat({
309
+ device_id: device.device_id,
310
+ heating_set_point_fahrenheit: heatValue,
311
+ })
312
+ break
313
+ case 'cool':
314
+ coolThermostat({
315
+ device_id: device.device_id,
316
+ cooling_set_point_fahrenheit: coolValue,
317
+ })
318
+ break
319
+ case 'off':
320
+ setThermostatOff({
321
+ device_id: device.device_id,
322
+ })
323
+ break
324
+ }
325
+ }, 2000)
326
+
327
+ if (
328
+ heatValue !== deviceHeatValue ||
329
+ coolValue !== deviceCoolValue ||
330
+ mode === 'off'
331
+ ) {
332
+ handler()
333
+ }
334
+
335
+ return () => {
336
+ handler.cancel()
337
+ }
338
+ }, [
339
+ heatValue,
340
+ coolValue,
341
+ mode,
342
+ deviceHeatValue,
343
+ deviceCoolValue,
344
+ device,
345
+ heatThermostat,
346
+ coolThermostat,
347
+ heatCoolThermostat,
348
+ setThermostatOff,
349
+ ])
350
+
351
+ useEffect(() => {
352
+ if (
353
+ isHeatCoolSuccess ||
354
+ isHeatSuccess ||
355
+ isCoolSuccess ||
356
+ isSetOffSuccess
357
+ ) {
358
+ setShowSuccess(true)
359
+
360
+ const timeout = globalThis.setTimeout(() => {
361
+ setShowSuccess(false)
362
+ }, 3000)
363
+
364
+ return () => {
365
+ globalThis.clearTimeout(timeout)
366
+ }
367
+ }
368
+
369
+ return () => {}
370
+ }, [isHeatCoolSuccess, isHeatSuccess, isCoolSuccess, isSetOffSuccess])
371
+
372
+ return (
373
+ <>
374
+ <AccordionRow
375
+ label={t.climate}
376
+ leftContent={
377
+ <div
378
+ className={classNames('seam-thermostat-mutation-status', {
379
+ 'is-visible': showSuccess,
380
+ })}
381
+ >
382
+ <div className='seam-thermostat-mutation-status-icon'>
383
+ <CheckBlackIcon />
384
+ </div>
385
+ <div className='seam-thermostat-mutation-status-label'>
386
+ {t.saved}
387
+ </div>
388
+ </div>
389
+ }
390
+ rightCollapsedContent={
391
+ <ClimateSettingStatus
392
+ climateSetting={device.properties.current_climate_setting}
393
+ temperatureUnit='fahrenheit'
394
+ />
395
+ }
396
+ >
397
+ <div className='seam-detail-row-end-alignment'>
398
+ {mode !== 'off' && (
399
+ <TemperatureControlGroup
400
+ mode={mode}
401
+ heatValue={heatValue}
402
+ coolValue={coolValue}
403
+ onHeatValueChange={setHeatValue}
404
+ onCoolValueChange={setCoolValue}
405
+ delta={
406
+ Number(
407
+ 'min_heating_cooling_delta_fahrenheit' in device.properties &&
408
+ device.properties.min_heating_cooling_delta_fahrenheit
409
+ ) ?? 0
410
+ }
411
+ />
412
+ )}
413
+
414
+ <ClimateModeMenu
415
+ mode={mode}
416
+ onChange={setMode}
417
+ supportedModes={supportedModes}
418
+ />
419
+ </div>
420
+ </AccordionRow>
421
+
422
+ <Snackbar
423
+ message={t.climateSettingError}
424
+ variant='error'
425
+ visible={isHeatCoolError || isHeatError || isCoolError || isSetOffError}
426
+ automaticVisibility
427
+ />
428
+ </>
429
+ )
430
+ }
431
+
237
432
  const t = {
238
433
  thermostat: 'Thermostat',
239
434
  climateSchedule: 'scheduled climate',
@@ -261,4 +456,6 @@ const t = {
261
456
  fanModeError: 'Error updating fan mode. Please try again.',
262
457
  manualOverrideSuccess: 'Successfully updated manual override!',
263
458
  manualOverrideError: 'Error updating manual override. Please try again.',
459
+ climateSettingError: 'Error updating climate setting. Please try again.',
460
+ saved: 'Saved',
264
461
  }
@@ -1,4 +1,4 @@
1
- import type { DeviceModelV1 } from '@seamapi/types/devicedb'
1
+ import type { DeviceModel } from '@seamapi/types/devicedb'
2
2
  import { useMemo } from 'react'
3
3
 
4
4
  import { SupportedDeviceManufacturerSection } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.js'
@@ -141,8 +141,8 @@ function EmptyResult({
141
141
 
142
142
  const groupDeviceModelsByManufacturer = (
143
143
  deviceModels: UseDeviceModelsData
144
- ): Record<string, DeviceModelV1[]> => {
145
- const result: Record<string, DeviceModelV1[]> = {}
144
+ ): Record<string, DeviceModel[]> => {
145
+ const result: Record<string, DeviceModel[]> = {}
146
146
 
147
147
  for (const model of deviceModels) {
148
148
  const { manufacturer } = model
@@ -1,5 +1,5 @@
1
1
  import type {
2
- DeviceModelV1,
2
+ DeviceModel,
3
3
  ManufacturerAnnotation,
4
4
  } from '@seamapi/types/devicedb'
5
5
  import classNames from 'classnames'
@@ -20,7 +20,7 @@ const maxDevicesBeforeCollapsing = 3
20
20
 
21
21
  interface SupportedDeviceManufacturerSectionProps {
22
22
  manufacturerId: string
23
- deviceModels: DeviceModelV1[]
23
+ deviceModels: DeviceModel[]
24
24
  }
25
25
 
26
26
  export function SupportedDeviceManufacturerSection({
@@ -1,11 +1,10 @@
1
- import type { DeviceModelV1 } from '@seamapi/types/devicedb'
1
+ import type { DeviceModel } from '@seamapi/types/devicedb'
2
2
  import classNames from 'classnames'
3
- import type { DeviceModel } from 'seamapi'
4
3
 
5
4
  import { DotDivider } from 'lib/ui/layout/DotDivider.js'
6
5
 
7
6
  interface SupportedDeviceRowProps {
8
- deviceModel: DeviceModelV1
7
+ deviceModel: DeviceModel
9
8
  }
10
9
 
11
10
  export function SupportedDeviceRow({
@@ -75,7 +74,7 @@ export function StatusColumn({
75
74
  }
76
75
 
77
76
  const supportLevelColors: Record<
78
- DeviceModelV1['manufacturer']['integration'],
77
+ DeviceModel['manufacturer']['integration'],
79
78
  'green' | 'blue' | 'unknown'
80
79
  > = {
81
80
  stable: 'green',
@@ -85,20 +84,10 @@ const supportLevelColors: Record<
85
84
  inquire: 'unknown',
86
85
  }
87
86
 
88
- const status: Record<DeviceModelV1['manufacturer']['integration'], string> = {
87
+ const status: Record<DeviceModel['manufacturer']['integration'], string> = {
89
88
  stable: 'LIVE',
90
89
  beta: 'BETA',
91
90
  unsupported: 'Inquire',
92
91
  planned: 'Inquire',
93
92
  inquire: 'Inquire',
94
93
  }
95
-
96
- export const connectionTypeNames: Record<
97
- DeviceModel['connection_type'],
98
- string
99
- > = {
100
- wifi: 'Wifi',
101
- zwave: 'Z-Wave',
102
- zigbee: 'Zigbee',
103
- unknown: 'Unknown',
104
- }
@@ -1,5 +1,5 @@
1
1
  import type {
2
- DeviceModelV1,
2
+ DeviceModel,
3
3
  RouteRequestParams,
4
4
  RouteResponse,
5
5
  } from '@seamapi/types/devicedb'
@@ -10,7 +10,7 @@ import { useSeamClient } from 'lib/seam/use-seam-client.js'
10
10
  import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
11
11
 
12
12
  export type UseDeviceModelParams = DeviceModelsGetParams | string
13
- export type UseDeviceModelData = DeviceModelV1 | null
13
+ export type UseDeviceModelData = DeviceModel | null
14
14
 
15
15
  export function useDeviceModel(
16
16
  params?: UseDeviceModelParams
@@ -1,5 +1,5 @@
1
1
  import type {
2
- DeviceModelV1,
2
+ DeviceModel,
3
3
  RouteRequestParams,
4
4
  RouteResponse,
5
5
  } from '@seamapi/types/devicedb'
@@ -10,7 +10,7 @@ import { useSeamClient } from 'lib/seam/use-seam-client.js'
10
10
  import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
11
11
 
12
12
  export type UseDeviceModelsParams = DeviceModelsListParams
13
- export type UseDeviceModelsData = DeviceModelV1[]
13
+ export type UseDeviceModelsData = DeviceModel[]
14
14
 
15
15
  export function useDeviceModels(
16
16
  params?: UseDeviceModelsParams
@@ -77,13 +77,12 @@ export const useFilteredDeviceModels = ({
77
77
  // UPSTREAM: API does not parse zero-length arrays correctly.
78
78
  includeIf?.length === 0
79
79
  ? []
80
- : deviceModels?.filter(
81
- (deviceModel) =>
82
- manufacturers?.some(
83
- (manufacturer) =>
84
- deviceModel.manufacturer.manufacturer_id ===
85
- manufacturer.manufacturer_id
86
- )
80
+ : deviceModels?.filter((deviceModel) =>
81
+ manufacturers?.some(
82
+ (manufacturer) =>
83
+ deviceModel.manufacturer.manufacturer_id ===
84
+ manufacturer.manufacturer_id
85
+ )
87
86
  ),
88
87
  }
89
88
  }
@@ -1,4 +1,4 @@
1
- import type { HvacModeSetting } from 'seamapi'
1
+ import type { HvacModeSetting, ThermostatDevice } from 'seamapi'
2
2
 
3
3
  export interface ControlBounds {
4
4
  mode: Exclude<HvacModeSetting, 'off'>
@@ -51,3 +51,25 @@ export const getTemperatureBounds = (
51
51
  heat: getHeatBounds(controlBounds),
52
52
  cool: getCoolBounds(controlBounds),
53
53
  })
54
+
55
+ export const getSupportedThermostatModes = (
56
+ device: ThermostatDevice
57
+ ): HvacModeSetting[] => {
58
+ const allModes: HvacModeSetting[] = ['heat', 'cool', 'heat_cool', 'off']
59
+
60
+ return allModes.filter((mode) => {
61
+ switch (mode) {
62
+ case 'cool':
63
+ return device.properties.is_cooling_available
64
+ case 'heat':
65
+ return device.properties.is_heating_available
66
+ case 'heat_cool':
67
+ return (
68
+ device.properties.is_heating_available &&
69
+ device.properties.is_cooling_available
70
+ )
71
+ default:
72
+ return true
73
+ }
74
+ })
75
+ }
@@ -5,11 +5,13 @@ import { useToggle } from 'lib/ui/use-toggle.js'
5
5
 
6
6
  interface AccordionRowProps extends PropsWithChildren {
7
7
  label: string
8
+ leftContent?: JSX.Element
8
9
  rightCollapsedContent?: JSX.Element
9
10
  }
10
11
 
11
12
  export function AccordionRow({
12
13
  label,
14
+ leftContent,
13
15
  rightCollapsedContent,
14
16
  children,
15
17
  }: AccordionRowProps): JSX.Element {
@@ -18,7 +20,10 @@ export function AccordionRow({
18
20
  return (
19
21
  <div className='seam-accordion-row' aria-expanded={isExpanded}>
20
22
  <button className='seam-accordion-row-trigger' onClick={toggle}>
21
- <p className='seam-row-label'>{label}</p>
23
+ <div className='seam-row-inner-wrap'>
24
+ <p className='seam-row-label'>{label}</p>
25
+ <div className='seam-row-trigger-left-content'>{leftContent}</div>
26
+ </div>
22
27
  <div className='seam-row-inner-wrap'>
23
28
  <div className='seam-row-trigger-right-content'>
24
29
  {rightCollapsedContent}
@@ -8,16 +8,16 @@ import { ThermostatHeatCoolIcon } from 'lib/icons/ThermostatHeatCool.js'
8
8
  import { Menu } from 'lib/ui/Menu/Menu.js'
9
9
  import { ThermoModeMenuOption } from 'lib/ui/thermostat/ThermoModeMenuOption.js'
10
10
 
11
- const modes: HvacModeSetting[] = ['heat', 'cool', 'heat_cool', 'off']
12
-
13
11
  interface ClimateModeMenuProps {
14
12
  mode: HvacModeSetting
15
13
  onChange: (mode: HvacModeSetting) => void
14
+ supportedModes?: HvacModeSetting[]
16
15
  }
17
16
 
18
17
  export function ClimateModeMenu({
19
18
  mode,
20
19
  onChange,
20
+ supportedModes = ['heat', 'cool', 'heat_cool', 'off'],
21
21
  }: ClimateModeMenuProps): JSX.Element {
22
22
  return (
23
23
  <Menu
@@ -35,7 +35,7 @@ export function ClimateModeMenu({
35
35
  className: 'seam-thermo-mode-menu',
36
36
  }}
37
37
  >
38
- {modes.map((m) => (
38
+ {supportedModes.map((m) => (
39
39
  <ThermoModeMenuOption
40
40
  key={m}
41
41
  label={t[m]}
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '2.8.6'
1
+ const seamapiReactVersion = '2.9.0'
2
2
 
3
3
  export default seamapiReactVersion
@@ -126,6 +126,16 @@
126
126
  gap: 8px;
127
127
  }
128
128
 
129
+ .seam-detail-row-end-alignment {
130
+ width: 100%;
131
+ display: flex;
132
+ justify-content: flex-end;
133
+ align-items: flex-start;
134
+ flex-direction: row;
135
+ gap: 24px;
136
+ padding-top: 12px;
137
+ }
138
+
129
139
  .seam-detail-row-rotated-icon {
130
140
  transform: rotate(-90deg);
131
141
  }
@@ -168,7 +178,7 @@
168
178
 
169
179
  &[aria-expanded='true'] {
170
180
  .seam-accordion-row-content {
171
- max-height: 64px;
181
+ max-height: 128px;
172
182
  opacity: 1;
173
183
  }
174
184
 
@@ -183,7 +193,8 @@
183
193
  }
184
194
 
185
195
  .seam-accordion-row-inner-content {
186
- height: 64px;
196
+ width: 100%;
197
+ height: 128px;
187
198
  padding: 0 16px;
188
199
  overflow: hidden;
189
200
  }
@@ -703,6 +703,31 @@
703
703
  }
704
704
  }
705
705
 
706
+ @mixin status {
707
+ .seam-thermostat-mutation-status {
708
+ display: flex;
709
+ justify-content: center;
710
+ align-items: center;
711
+ flex-direction: row;
712
+ gap: 4px;
713
+ opacity: 0;
714
+ transition: 0.2s ease-in-out;
715
+
716
+ &.is-visible {
717
+ opacity: 1;
718
+ }
719
+ }
720
+
721
+ .seam-thermostat-mutation-status-icon {
722
+ opacity: 0.25;
723
+ }
724
+
725
+ .seam-thermostat-mutation-status-label {
726
+ font-size: 14px;
727
+ color: colors.$text-gray-2-5;
728
+ }
729
+ }
730
+
706
731
  @mixin all {
707
732
  @include climate-setting-control-group;
708
733
  @include temperature-control-group;
@@ -712,4 +737,5 @@
712
737
  @include mode-menu-common;
713
738
  @include fan-mode-menu;
714
739
  @include climate-mode-menu;
740
+ @include status;
715
741
  }