@seamapi/react 4.1.0 → 4.3.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 (242) hide show
  1. package/README.md +2 -2
  2. package/dist/elements.js +5596 -5352
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +100 -12
  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/icons/AccessCodeKey.js +1 -1
  9. package/lib/icons/AccessCodeKey.js.map +1 -1
  10. package/lib/icons/Add.js +1 -1
  11. package/lib/icons/Add.js.map +1 -1
  12. package/lib/icons/ArrowBack.js +1 -1
  13. package/lib/icons/ArrowBack.js.map +1 -1
  14. package/lib/icons/ArrowRestart.js +1 -1
  15. package/lib/icons/ArrowRestart.js.map +1 -1
  16. package/lib/icons/ArrowRight.js +1 -1
  17. package/lib/icons/ArrowRight.js.map +1 -1
  18. package/lib/icons/BatteryLevelCritical.js +1 -1
  19. package/lib/icons/BatteryLevelCritical.js.map +1 -1
  20. package/lib/icons/BatteryLevelFull.js +1 -1
  21. package/lib/icons/BatteryLevelFull.js.map +1 -1
  22. package/lib/icons/BatteryLevelHigh.js +1 -1
  23. package/lib/icons/BatteryLevelHigh.js.map +1 -1
  24. package/lib/icons/BatteryLevelLow.js +1 -1
  25. package/lib/icons/BatteryLevelLow.js.map +1 -1
  26. package/lib/icons/BatteryLevelWired.js +1 -1
  27. package/lib/icons/BatteryLevelWired.js.map +1 -1
  28. package/lib/icons/Bee.js +1 -1
  29. package/lib/icons/Bee.js.map +1 -1
  30. package/lib/icons/Check.js +1 -1
  31. package/lib/icons/Check.js.map +1 -1
  32. package/lib/icons/CheckBlack.js +1 -1
  33. package/lib/icons/CheckBlack.js.map +1 -1
  34. package/lib/icons/CheckGreen.js +1 -1
  35. package/lib/icons/CheckGreen.js.map +1 -1
  36. package/lib/icons/CheckboxBlank.js +1 -1
  37. package/lib/icons/CheckboxBlank.js.map +1 -1
  38. package/lib/icons/CheckboxFilled.js +1 -1
  39. package/lib/icons/CheckboxFilled.js.map +1 -1
  40. package/lib/icons/ChevronDown.js +1 -1
  41. package/lib/icons/ChevronDown.js.map +1 -1
  42. package/lib/icons/ChevronRight.js +1 -1
  43. package/lib/icons/ChevronRight.js.map +1 -1
  44. package/lib/icons/ChevronWide.js +1 -1
  45. package/lib/icons/ChevronWide.js.map +1 -1
  46. package/lib/icons/ClimateSettingSchedule.js +1 -1
  47. package/lib/icons/ClimateSettingSchedule.js.map +1 -1
  48. package/lib/icons/Clock.js +1 -1
  49. package/lib/icons/Clock.js.map +1 -1
  50. package/lib/icons/Close.js +1 -1
  51. package/lib/icons/Close.js.map +1 -1
  52. package/lib/icons/CloseWhite.js +1 -1
  53. package/lib/icons/CloseWhite.js.map +1 -1
  54. package/lib/icons/Copy.js +1 -1
  55. package/lib/icons/Copy.js.map +1 -1
  56. package/lib/icons/DotsEllipsisMore.js +1 -1
  57. package/lib/icons/DotsEllipsisMore.js.map +1 -1
  58. package/lib/icons/Edit.js +2 -4
  59. package/lib/icons/Edit.js.map +1 -1
  60. package/lib/icons/ExclamationCircle.js +1 -1
  61. package/lib/icons/ExclamationCircle.js.map +1 -1
  62. package/lib/icons/ExclamationCircleOutline.js +1 -1
  63. package/lib/icons/ExclamationCircleOutline.js.map +1 -1
  64. package/lib/icons/Fan.js +1 -1
  65. package/lib/icons/Fan.js.map +1 -1
  66. package/lib/icons/FanOutline.js +1 -1
  67. package/lib/icons/FanOutline.js.map +1 -1
  68. package/lib/icons/Info.js +1 -1
  69. package/lib/icons/Info.js.map +1 -1
  70. package/lib/icons/InfoBlue.js +1 -1
  71. package/lib/icons/InfoBlue.js.map +1 -1
  72. package/lib/icons/InfoDark.js +1 -1
  73. package/lib/icons/InfoDark.js.map +1 -1
  74. package/lib/icons/LockLocked.js +1 -1
  75. package/lib/icons/LockLocked.js.map +1 -1
  76. package/lib/icons/LockUnlocked.js +1 -1
  77. package/lib/icons/LockUnlocked.js.map +1 -1
  78. package/lib/icons/NoiseLevels.js +1 -1
  79. package/lib/icons/NoiseLevels.js.map +1 -1
  80. package/lib/icons/NoiseLevelsRed.js +1 -1
  81. package/lib/icons/NoiseLevelsRed.js.map +1 -1
  82. package/lib/icons/Off.js +1 -1
  83. package/lib/icons/Off.js.map +1 -1
  84. package/lib/icons/OnlineStatusAccountOffline.js +1 -1
  85. package/lib/icons/OnlineStatusAccountOffline.js.map +1 -1
  86. package/lib/icons/OnlineStatusDeviceOffline.js +1 -1
  87. package/lib/icons/OnlineStatusDeviceOffline.js.map +1 -1
  88. package/lib/icons/OnlineStatusOnline.js +1 -1
  89. package/lib/icons/OnlineStatusOnline.js.map +1 -1
  90. package/lib/icons/RadioChecked.js +1 -1
  91. package/lib/icons/RadioChecked.js.map +1 -1
  92. package/lib/icons/RadioUnchecked.js +1 -1
  93. package/lib/icons/RadioUnchecked.js.map +1 -1
  94. package/lib/icons/Seam.js +1 -1
  95. package/lib/icons/Seam.js.map +1 -1
  96. package/lib/icons/Search.js +1 -1
  97. package/lib/icons/Search.js.map +1 -1
  98. package/lib/icons/TemperatureAdd.js +1 -1
  99. package/lib/icons/TemperatureAdd.js.map +1 -1
  100. package/lib/icons/TemperatureSubtract.js +1 -1
  101. package/lib/icons/TemperatureSubtract.js.map +1 -1
  102. package/lib/icons/ThermostatCool.js +1 -1
  103. package/lib/icons/ThermostatCool.js.map +1 -1
  104. package/lib/icons/ThermostatCoolLarge.js +1 -1
  105. package/lib/icons/ThermostatCoolLarge.js.map +1 -1
  106. package/lib/icons/ThermostatHeat.js +1 -1
  107. package/lib/icons/ThermostatHeat.js.map +1 -1
  108. package/lib/icons/ThermostatHeatCool.js +1 -1
  109. package/lib/icons/ThermostatHeatCool.js.map +1 -1
  110. package/lib/icons/ThermostatHeatLarge.js +1 -1
  111. package/lib/icons/ThermostatHeatLarge.js.map +1 -1
  112. package/lib/icons/ThermostatOff.js +1 -1
  113. package/lib/icons/ThermostatOff.js.map +1 -1
  114. package/lib/icons/TriangleWarning.js +1 -1
  115. package/lib/icons/TriangleWarning.js.map +1 -1
  116. package/lib/icons/TriangleWarningOutline.js +1 -1
  117. package/lib/icons/TriangleWarningOutline.js.map +1 -1
  118. package/lib/seam/components/DeviceDetails/DeviceDetails.js +15 -3
  119. package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -1
  120. package/lib/seam/components/DeviceDetails/DeviceInfo.js +1 -1
  121. package/lib/seam/components/DeviceDetails/DeviceInfo.js.map +1 -1
  122. package/lib/seam/components/DeviceDetails/LockDeviceDetails.d.ts +2 -1
  123. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js +3 -2
  124. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js.map +1 -1
  125. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.d.ts +2 -1
  126. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js +3 -2
  127. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js.map +1 -1
  128. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.d.ts +2 -1
  129. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js +2 -2
  130. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js.map +1 -1
  131. package/lib/seam/components/SeamEditableDeviceName/SeamEditableDeviceName.d.ts +8 -0
  132. package/lib/seam/components/SeamEditableDeviceName/SeamEditableDeviceName.js +85 -0
  133. package/lib/seam/components/SeamEditableDeviceName/SeamEditableDeviceName.js.map +1 -0
  134. package/lib/seam/devices/use-update-device-name.d.ts +8 -0
  135. package/lib/seam/devices/use-update-device-name.js +43 -0
  136. package/lib/seam/devices/use-update-device-name.js.map +1 -0
  137. package/lib/ui/Snackbar/Snackbar.js +1 -1
  138. package/lib/ui/Snackbar/Snackbar.js.map +1 -1
  139. package/lib/ui/device/BatteryStatusIndicator.js +4 -4
  140. package/lib/ui/device/BatteryStatusIndicator.js.map +1 -1
  141. package/lib/ui/device/OnlineStatus.js +3 -3
  142. package/lib/ui/device/OnlineStatus.js.map +1 -1
  143. package/lib/ui/thermostat/ClimateModeMenu.js +1 -1
  144. package/lib/ui/thermostat/ClimateModeMenu.js.map +1 -1
  145. package/lib/ui/thermostat/ClimateSettingStatus.js +7 -1
  146. package/lib/ui/thermostat/ClimateSettingStatus.js.map +1 -1
  147. package/lib/ui/thermostat/TemperatureControlGroup.js +1 -1
  148. package/lib/ui/thermostat/TemperatureControlGroup.js.map +1 -1
  149. package/lib/ui/thermostat/ThermostatCard.d.ts +2 -1
  150. package/lib/ui/thermostat/ThermostatCard.js +4 -3
  151. package/lib/ui/thermostat/ThermostatCard.js.map +1 -1
  152. package/lib/version.d.ts +1 -1
  153. package/lib/version.js +1 -1
  154. package/package.json +2 -2
  155. package/src/lib/icons/AccessCodeKey.tsx +3 -2
  156. package/src/lib/icons/Add.tsx +3 -2
  157. package/src/lib/icons/ArrowBack.tsx +3 -2
  158. package/src/lib/icons/ArrowRestart.tsx +3 -2
  159. package/src/lib/icons/ArrowRight.tsx +3 -2
  160. package/src/lib/icons/BatteryLevelCritical.tsx +3 -2
  161. package/src/lib/icons/BatteryLevelFull.tsx +3 -2
  162. package/src/lib/icons/BatteryLevelHigh.tsx +3 -2
  163. package/src/lib/icons/BatteryLevelLow.tsx +3 -2
  164. package/src/lib/icons/BatteryLevelWired.tsx +3 -2
  165. package/src/lib/icons/Bee.tsx +3 -2
  166. package/src/lib/icons/Check.tsx +3 -2
  167. package/src/lib/icons/CheckBlack.tsx +3 -2
  168. package/src/lib/icons/CheckGreen.tsx +3 -2
  169. package/src/lib/icons/CheckboxBlank.tsx +3 -2
  170. package/src/lib/icons/CheckboxFilled.tsx +3 -2
  171. package/src/lib/icons/ChevronDown.tsx +3 -2
  172. package/src/lib/icons/ChevronRight.tsx +3 -2
  173. package/src/lib/icons/ChevronWide.tsx +3 -2
  174. package/src/lib/icons/ClimateSettingSchedule.tsx +3 -2
  175. package/src/lib/icons/Clock.tsx +3 -2
  176. package/src/lib/icons/Close.tsx +3 -2
  177. package/src/lib/icons/CloseWhite.tsx +3 -2
  178. package/src/lib/icons/Copy.tsx +3 -2
  179. package/src/lib/icons/DotsEllipsisMore.tsx +3 -2
  180. package/src/lib/icons/Edit.tsx +9 -21
  181. package/src/lib/icons/ExclamationCircle.tsx +3 -2
  182. package/src/lib/icons/ExclamationCircleOutline.tsx +3 -2
  183. package/src/lib/icons/Fan.tsx +3 -2
  184. package/src/lib/icons/FanOutline.tsx +3 -2
  185. package/src/lib/icons/Info.tsx +3 -2
  186. package/src/lib/icons/InfoBlue.tsx +3 -2
  187. package/src/lib/icons/InfoDark.tsx +3 -2
  188. package/src/lib/icons/LockLocked.tsx +3 -2
  189. package/src/lib/icons/LockUnlocked.tsx +3 -2
  190. package/src/lib/icons/NoiseLevels.tsx +3 -2
  191. package/src/lib/icons/NoiseLevelsRed.tsx +3 -2
  192. package/src/lib/icons/Off.tsx +3 -2
  193. package/src/lib/icons/OnlineStatusAccountOffline.tsx +3 -2
  194. package/src/lib/icons/OnlineStatusDeviceOffline.tsx +3 -2
  195. package/src/lib/icons/OnlineStatusOnline.tsx +3 -2
  196. package/src/lib/icons/RadioChecked.tsx +3 -2
  197. package/src/lib/icons/RadioUnchecked.tsx +3 -2
  198. package/src/lib/icons/Seam.tsx +3 -2
  199. package/src/lib/icons/Search.tsx +3 -2
  200. package/src/lib/icons/TemperatureAdd.tsx +3 -2
  201. package/src/lib/icons/TemperatureSubtract.tsx +3 -2
  202. package/src/lib/icons/ThermostatCool.tsx +3 -2
  203. package/src/lib/icons/ThermostatCoolLarge.tsx +3 -2
  204. package/src/lib/icons/ThermostatHeat.tsx +3 -2
  205. package/src/lib/icons/ThermostatHeatCool.tsx +3 -2
  206. package/src/lib/icons/ThermostatHeatLarge.tsx +3 -2
  207. package/src/lib/icons/ThermostatOff.tsx +3 -2
  208. package/src/lib/icons/TriangleWarning.tsx +3 -2
  209. package/src/lib/icons/TriangleWarningOutline.tsx +3 -2
  210. package/src/lib/seam/components/DeviceDetails/DeviceDetails.tsx +35 -4
  211. package/src/lib/seam/components/DeviceDetails/DeviceInfo.tsx +3 -1
  212. package/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx +9 -1
  213. package/src/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.tsx +8 -1
  214. package/src/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.tsx +3 -1
  215. package/src/lib/seam/components/SeamEditableDeviceName/SeamEditableDeviceName.tsx +208 -0
  216. package/src/lib/seam/devices/use-update-device-name.ts +93 -0
  217. package/src/lib/ui/Snackbar/Snackbar.tsx +4 -1
  218. package/src/lib/ui/device/BatteryStatusIndicator.tsx +4 -4
  219. package/src/lib/ui/device/OnlineStatus.tsx +3 -3
  220. package/src/lib/ui/thermostat/ClimateModeMenu.tsx +1 -1
  221. package/src/lib/ui/thermostat/ClimateSettingStatus.tsx +10 -1
  222. package/src/lib/ui/thermostat/TemperatureControlGroup.tsx +2 -2
  223. package/src/lib/ui/thermostat/ThermostatCard.tsx +11 -6
  224. package/src/lib/version.ts +1 -1
  225. package/src/styles/_access-code-details.scss +2 -2
  226. package/src/styles/_alert.scss +1 -0
  227. package/src/styles/_buttons.scss +6 -8
  228. package/src/styles/_checkbox.scss +1 -0
  229. package/src/styles/_device-details.scss +1 -1
  230. package/src/styles/_icons.scss +2 -0
  231. package/src/styles/_inputs.scss +1 -1
  232. package/src/styles/_layout.scss +2 -0
  233. package/src/styles/_main.scss +2 -0
  234. package/src/styles/_radio-field.scss +1 -0
  235. package/src/styles/_seam-editable-device-name.scss +62 -0
  236. package/src/styles/_seam-table.scss +4 -3
  237. package/src/styles/_snackbar.scss +6 -0
  238. package/src/styles/_supported-device-table-manufacturer-keys.scss +1 -0
  239. package/src/styles/_supported-device-table.scss +1 -0
  240. package/src/styles/_tables.scss +3 -2
  241. package/src/styles/_thermostat.scss +26 -0
  242. package/src/styles/_typography.scss +7 -0
@@ -6,6 +6,7 @@ import { LockDeviceDetails } from 'lib/seam/components/DeviceDetails/LockDeviceD
6
6
  import { NoiseSensorDeviceDetails } from 'lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js'
7
7
  import { ThermostatDeviceDetails } from 'lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js'
8
8
  import { useDevice } from 'lib/seam/devices/use-device.js'
9
+ import { useUpdateDeviceName } from 'lib/seam/devices/use-update-device-name.js'
9
10
  import { isLockDevice } from 'lib/seam/locks/lock-device.js'
10
11
  import { isNoiseSensorDevice } from 'lib/seam/noise-sensors/noise-sensor-device.js'
11
12
  import { isThermostatDevice } from 'lib/seam/thermostats/thermostat-device.js'
@@ -16,7 +17,6 @@ export interface DeviceDetailsProps extends CommonProps {
16
17
  }
17
18
 
18
19
  export const NestedDeviceDetails = withRequiredCommonProps(DeviceDetails)
19
-
20
20
  export interface NestedSpecificDeviceDetailsProps
21
21
  extends Required<Omit<CommonProps, 'onBack' | 'className'>> {
22
22
  onBack: (() => void) | undefined
@@ -42,6 +42,19 @@ export function DeviceDetails({
42
42
  device_id: deviceId,
43
43
  })
44
44
 
45
+ const { mutate: setDeviceName } = useUpdateDeviceName({
46
+ device_id: deviceId,
47
+ })
48
+
49
+ const updateDeviceName = (newName: string): void => {
50
+ if (device != null) {
51
+ setDeviceName({
52
+ device_id: device.device_id,
53
+ name: newName,
54
+ })
55
+ }
56
+ }
57
+
45
58
  if (device == null) {
46
59
  return null
47
60
  }
@@ -60,15 +73,33 @@ export function DeviceDetails({
60
73
  }
61
74
 
62
75
  if (isLockDevice(device)) {
63
- return <LockDeviceDetails device={device} {...props} />
76
+ return (
77
+ <LockDeviceDetails
78
+ device={device}
79
+ onEditName={updateDeviceName}
80
+ {...props}
81
+ />
82
+ )
64
83
  }
65
84
 
66
85
  if (isThermostatDevice(device)) {
67
- return <ThermostatDeviceDetails device={device} {...props} />
86
+ return (
87
+ <ThermostatDeviceDetails
88
+ device={device}
89
+ onEditName={updateDeviceName}
90
+ {...props}
91
+ />
92
+ )
68
93
  }
69
94
 
70
95
  if (isNoiseSensorDevice(device)) {
71
- return <NoiseSensorDeviceDetails device={device} {...props} />
96
+ return (
97
+ <NoiseSensorDeviceDetails
98
+ device={device}
99
+ onEditName={updateDeviceName}
100
+ {...props}
101
+ />
102
+ )
72
103
  }
73
104
 
74
105
  return null
@@ -32,7 +32,9 @@ export function DeviceInfo({
32
32
  <DetailRow label={t.manufacturer}>
33
33
  <div className='seam-detail-row-hstack'>
34
34
  {device.properties.model.manufacturer_display_name}
35
- {device.properties.manufacturer === 'ecobee' && <BeeIcon />}
35
+ {device.properties.manufacturer === 'ecobee' && (
36
+ <BeeIcon style={{ fontSize: '33px' }} />
37
+ )}
36
38
  </div>
37
39
  </DetailRow>
38
40
  {!disableConnectedAccountInformation && (
@@ -6,6 +6,7 @@ import { NestedAccessCodeTable } from 'lib/seam/components/AccessCodeTable/Acces
6
6
  import type { NestedSpecificDeviceDetailsProps } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
7
7
  import { DeviceInfo } from 'lib/seam/components/DeviceDetails/DeviceInfo.js'
8
8
  import { DeviceModel } from 'lib/seam/components/DeviceDetails/DeviceModel.js'
9
+ import { SeamEditableDeviceName } from 'lib/seam/components/SeamEditableDeviceName/SeamEditableDeviceName.js'
9
10
  import { deviceErrorFilter, deviceWarningFilter } from 'lib/seam/filters.js'
10
11
  import type { LockDevice } from 'lib/seam/locks/lock-device.js'
11
12
  import { useToggleLock } from 'lib/seam/locks/use-toggle-lock.js'
@@ -19,6 +20,7 @@ import { useToggle } from 'lib/ui/use-toggle.js'
19
20
 
20
21
  interface LockDeviceDetailsProps extends NestedSpecificDeviceDetailsProps {
21
22
  device: LockDevice
23
+ onEditName?: (newName: string) => void | Promise<void>
22
24
  }
23
25
 
24
26
  export function LockDeviceDetails({
@@ -33,6 +35,7 @@ export function LockDeviceDetails({
33
35
  disableConnectedAccountInformation,
34
36
  onBack,
35
37
  className,
38
+ onEditName,
36
39
  }: LockDeviceDetailsProps): JSX.Element | null {
37
40
  const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
38
41
  const toggleLock = useToggleLock()
@@ -95,7 +98,12 @@ export function LockDeviceDetails({
95
98
  </div>
96
99
  <div className='seam-info'>
97
100
  <span className='seam-label'>{t.device}</span>
98
- <h4 className='seam-device-name'>{device.properties.name}</h4>
101
+ <SeamEditableDeviceName
102
+ tagName='h4'
103
+ value={device.properties.name}
104
+ className='seam-device-name'
105
+ onEdit={onEditName}
106
+ />
99
107
  <div className='seam-properties'>
100
108
  <span className='seam-label'>{t.status}:</span>{' '}
101
109
  <OnlineStatus device={device} />
@@ -4,6 +4,7 @@ import { useState } from 'react'
4
4
  import type { NestedSpecificDeviceDetailsProps } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
5
5
  import { DeviceInfo } from 'lib/seam/components/DeviceDetails/DeviceInfo.js'
6
6
  import { DeviceModel } from 'lib/seam/components/DeviceDetails/DeviceModel.js'
7
+ import { SeamEditableDeviceName } from 'lib/seam/components/SeamEditableDeviceName/SeamEditableDeviceName.js'
7
8
  import type { NoiseSensorDevice } from 'lib/seam/noise-sensors/noise-sensor-device.js'
8
9
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
9
10
  import { NoiseLevelStatus } from 'lib/ui/device/NoiseLevelStatus.js'
@@ -18,6 +19,7 @@ type TabType = 'details' | 'activity'
18
19
  interface NoiseSensorDeviceDetailsProps
19
20
  extends NestedSpecificDeviceDetailsProps {
20
21
  device: NoiseSensorDevice
22
+ onEditName?: (newName: string) => void | Promise<void>
21
23
  }
22
24
 
23
25
  export function NoiseSensorDeviceDetails({
@@ -26,6 +28,7 @@ export function NoiseSensorDeviceDetails({
26
28
  disableResourceIds,
27
29
  onBack,
28
30
  className,
31
+ onEditName,
29
32
  }: NoiseSensorDeviceDetailsProps): JSX.Element | null {
30
33
  const [tab, setTab] = useState<TabType>('details')
31
34
 
@@ -45,7 +48,11 @@ export function NoiseSensorDeviceDetails({
45
48
  </div>
46
49
  <div className='seam-info'>
47
50
  <span className='seam-label'>{t.noiseSensor}</span>
48
- <h4 className='seam-device-name'>{device.properties.name}</h4>
51
+ <SeamEditableDeviceName
52
+ onEdit={onEditName}
53
+ tagName='h4'
54
+ value={device.properties.name}
55
+ />
49
56
  <div className='seam-properties'>
50
57
  <span className='seam-label'>{t.status}:</span>{' '}
51
58
  <OnlineStatus device={device} />
@@ -29,6 +29,7 @@ import { ThermostatCard } from 'lib/ui/thermostat/ThermostatCard.js'
29
29
  interface ThermostatDeviceDetailsProps
30
30
  extends NestedSpecificDeviceDetailsProps {
31
31
  device: ThermostatDevice
32
+ onEditName?: (newName: string) => void | Promise<void>
32
33
  }
33
34
 
34
35
  export function ThermostatDeviceDetails({
@@ -37,6 +38,7 @@ export function ThermostatDeviceDetails({
37
38
  disableConnectedAccountInformation,
38
39
  onBack,
39
40
  className,
41
+ onEditName,
40
42
  }: ThermostatDeviceDetailsProps): JSX.Element | null {
41
43
  if (device == null) {
42
44
  return null
@@ -47,7 +49,7 @@ export function ThermostatDeviceDetails({
47
49
  <ContentHeader title={t.thermostat} onBack={onBack} />
48
50
 
49
51
  <div className='seam-body'>
50
- <ThermostatCard device={device} />
52
+ <ThermostatCard device={device} onEditName={onEditName} />
51
53
 
52
54
  <div className='seam-thermostat-device-details'>
53
55
  <DetailSectionGroup>
@@ -0,0 +1,208 @@
1
+ import classNames from 'classnames'
2
+ import {
3
+ type ChangeEvent,
4
+ type HTMLAttributes,
5
+ type KeyboardEvent,
6
+ type PropsWithChildren,
7
+ useCallback,
8
+ useState,
9
+ } from 'react'
10
+
11
+ import { CheckIcon } from 'lib/icons/Check.js'
12
+ import { CloseIcon } from 'lib/icons/Close.js'
13
+ import { EditIcon } from 'lib/icons/Edit.js'
14
+
15
+ export type SeamDeviceNameProps = {
16
+ onEdit?: (newName: string) => void
17
+ editable?: boolean
18
+ tagName?: string
19
+ value: string
20
+ } & HTMLAttributes<HTMLElement>
21
+
22
+ function IconButton(
23
+ props: PropsWithChildren<HTMLAttributes<HTMLButtonElement>>
24
+ ): JSX.Element {
25
+ return (
26
+ <button
27
+ {...props}
28
+ className={classNames(
29
+ 'seam-editable-device-name-icon-button',
30
+ props.className
31
+ )}
32
+ >
33
+ {props.children}
34
+ </button>
35
+ )
36
+ }
37
+
38
+ const fixName = (name: string): string => {
39
+ return name.replace(/\s+/g, ' ').trim()
40
+ }
41
+
42
+ type Result = { type: 'success' } | { type: 'error'; message: string }
43
+
44
+ const isValidName = (name: string): Result => {
45
+ if (name.length < 2) {
46
+ return {
47
+ type: 'error',
48
+ message: 'Name must be at least 2 characters long',
49
+ }
50
+ }
51
+
52
+ if (name.length > 64) {
53
+ return {
54
+ type: 'error',
55
+ message: 'Name must be at most 64 characters long',
56
+ }
57
+ }
58
+
59
+ return {
60
+ type: 'success',
61
+ } as const
62
+ }
63
+
64
+ export function SeamEditableDeviceName({
65
+ onEdit,
66
+ editable = true,
67
+ tagName,
68
+ value,
69
+ ...props
70
+ }: SeamDeviceNameProps): JSX.Element {
71
+ const [editing, setEditing] = useState(false)
72
+ const [errorText, setErrorText] = useState<null | string>(null)
73
+ const [currentValue, setCurrentValue] = useState(value)
74
+ const Tag = (tagName ?? 'span') as 'div'
75
+
76
+ const handleCheck = useCallback(() => {
77
+ const fixedName = fixName(currentValue)
78
+ const valid = isValidName(fixedName)
79
+
80
+ if (valid.type === 'error') {
81
+ setErrorText(valid.message)
82
+ return
83
+ }
84
+
85
+ setEditing(false)
86
+ setCurrentValue(fixedName)
87
+ onEdit?.(fixedName)
88
+ }, [currentValue, onEdit])
89
+
90
+ const handleChange = useCallback(
91
+ (event: ChangeEvent<HTMLInputElement>): void => {
92
+ setCurrentValue(event.target.value)
93
+ setErrorText(null)
94
+ },
95
+ []
96
+ )
97
+
98
+ const handleCancel = useCallback(() => {
99
+ setEditing(false)
100
+ setCurrentValue(value)
101
+ setErrorText(null)
102
+ }, [value])
103
+
104
+ const handleInputKeydown = useCallback(
105
+ (e: KeyboardEvent<HTMLInputElement>): void => {
106
+ if (e.repeat) return
107
+
108
+ if (e.key === 'Enter') {
109
+ handleCheck()
110
+ } else if (e.key === 'Escape') {
111
+ handleCancel()
112
+ }
113
+ },
114
+ [handleCheck, handleCancel]
115
+ )
116
+
117
+ return (
118
+ <Tag
119
+ {...props}
120
+ className={classNames('seam-editable-device-name', props.className)}
121
+ >
122
+ <NameView
123
+ editing={editing}
124
+ value={currentValue}
125
+ onChange={handleChange}
126
+ onKeyDown={handleInputKeydown}
127
+ errorText={errorText}
128
+ />
129
+
130
+ {editable && (
131
+ <span className='seam-editable-device-name-icon-wrapper'>
132
+ <ActionButtons
133
+ editing={editing}
134
+ onEdit={() => {
135
+ setEditing(true)
136
+ }}
137
+ onCancel={handleCancel}
138
+ onCheck={handleCheck}
139
+ />
140
+ </span>
141
+ )}
142
+ </Tag>
143
+ )
144
+ }
145
+
146
+ interface NameViewProps {
147
+ editing: boolean
148
+ value: string
149
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void
150
+ onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void
151
+ errorText?: string | null
152
+ }
153
+
154
+ function NameView(props: NameViewProps): JSX.Element {
155
+ if (!props.editing) {
156
+ return <span>{props.value}</span>
157
+ }
158
+
159
+ return (
160
+ <span className='seam-editable-device-name-input-wrapper'>
161
+ <input
162
+ type='text'
163
+ defaultValue={props.value}
164
+ onChange={props.onChange}
165
+ onKeyDown={props.onKeyDown}
166
+ ref={(el) => {
167
+ setTimeout(() => {
168
+ el?.focus()
169
+ }, 0)
170
+ }}
171
+ />
172
+
173
+ {props.errorText != null && (
174
+ <span className='seam-editable-device-name-input-error'>
175
+ {props.errorText}
176
+ </span>
177
+ )}
178
+ </span>
179
+ )
180
+ }
181
+
182
+ interface ActionButtonsProps {
183
+ onEdit: () => void
184
+ onCancel: () => void
185
+ onCheck: () => void
186
+ editing: boolean
187
+ }
188
+
189
+ function ActionButtons(props: ActionButtonsProps): JSX.Element {
190
+ if (props.editing) {
191
+ return (
192
+ <>
193
+ <IconButton onClick={props.onCheck}>
194
+ <CheckIcon width='1em' height='1em' viewBox='0 0 24 24' />
195
+ </IconButton>
196
+ <IconButton onClick={props.onCancel}>
197
+ <CloseIcon width='1em' height='1em' viewBox='0 0 24 24' />
198
+ </IconButton>
199
+ </>
200
+ )
201
+ }
202
+
203
+ return (
204
+ <IconButton onClick={props.onEdit}>
205
+ <EditIcon width='1em' height='1em' viewBox='0 0 24 24' />
206
+ </IconButton>
207
+ )
208
+ }
@@ -0,0 +1,93 @@
1
+ import type {
2
+ DevicesGetParams,
3
+ DevicesUpdateBody,
4
+ SeamHttpApiError,
5
+ } from '@seamapi/http/connect'
6
+ import type { Device } from '@seamapi/types/connect'
7
+ import {
8
+ useMutation,
9
+ type UseMutationResult,
10
+ useQueryClient,
11
+ } from '@tanstack/react-query'
12
+
13
+ import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js'
14
+
15
+ export type UseUpdateDeviceNameParams = never
16
+
17
+ export type UseUpdateDeviceNameData = undefined
18
+
19
+ export type UseUpdateDeviceNameMutationVariables = Pick<
20
+ DevicesUpdateBody,
21
+ 'device_id' | 'name'
22
+ >
23
+
24
+ type MutationError = SeamHttpApiError
25
+
26
+ export function useUpdateDeviceName(
27
+ params: DevicesGetParams
28
+ ): UseMutationResult<
29
+ UseUpdateDeviceNameData,
30
+ MutationError,
31
+ UseUpdateDeviceNameMutationVariables
32
+ > {
33
+ const { client } = useSeamClient()
34
+ const queryClient = useQueryClient()
35
+
36
+ return useMutation<
37
+ UseUpdateDeviceNameData,
38
+ MutationError,
39
+ UseUpdateDeviceNameMutationVariables
40
+ >({
41
+ mutationFn: async (variables) => {
42
+ if (client === null) throw new NullSeamClientError()
43
+ await client.devices.update(variables)
44
+ },
45
+ onSuccess: (_data, variables) => {
46
+ queryClient.setQueryData<Device | null>(
47
+ ['devices', 'get', params],
48
+ (device) => {
49
+ if (device == null) {
50
+ return
51
+ }
52
+
53
+ return getUpdatedDevice(
54
+ device,
55
+ variables.name ?? device.properties.name
56
+ )
57
+ }
58
+ )
59
+
60
+ queryClient.setQueryData<Device[]>(
61
+ ['devices', 'list', { device_id: variables.device_id }],
62
+ (devices): Device[] => {
63
+ if (devices == null) {
64
+ return []
65
+ }
66
+
67
+ return devices.map((device) => {
68
+ if (device.device_id === variables.device_id) {
69
+ return getUpdatedDevice(
70
+ device,
71
+ variables.name ?? device.properties.name
72
+ )
73
+ }
74
+
75
+ return device
76
+ })
77
+ }
78
+ )
79
+ },
80
+ })
81
+ }
82
+
83
+ const getUpdatedDevice = (device: Device, name: string): Device => {
84
+ const { properties } = device
85
+
86
+ return {
87
+ ...device,
88
+ properties: {
89
+ ...properties,
90
+ name,
91
+ },
92
+ }
93
+ }
@@ -67,7 +67,10 @@ export function Snackbar({
67
67
  'seam-snackbar-visible': automaticVisibility ? !hidden : visible,
68
68
  })}
69
69
  >
70
- <SnackbarIcon variant={variant} />
70
+ <span className='seam-snackbar-icon-wrap'>
71
+ <SnackbarIcon variant={variant} />
72
+ </span>
73
+
71
74
  <div className='seam-snackbar-message-wrap'>
72
75
  <p className='seam-snackbar-message'>{message}</p>
73
76
  </div>
@@ -34,7 +34,7 @@ function Content(props: {
34
34
  if (status === 'full') {
35
35
  return (
36
36
  <>
37
- <BatteryLevelFullIcon />
37
+ <BatteryLevelFullIcon style={{ fontSize: '24px' }} />
38
38
  <span className='seam-status-text'>
39
39
  {t.full}
40
40
  <Percentage level={level} />
@@ -46,7 +46,7 @@ function Content(props: {
46
46
  if (status === 'good') {
47
47
  return (
48
48
  <>
49
- <BatteryLevelHighIcon />
49
+ <BatteryLevelHighIcon style={{ fontSize: '24px' }} />
50
50
  <span className='seam-status-text'>
51
51
  {t.high}
52
52
  <Percentage level={level} />
@@ -58,7 +58,7 @@ function Content(props: {
58
58
  if (status === 'low') {
59
59
  return (
60
60
  <>
61
- <BatteryLevelLowIcon />
61
+ <BatteryLevelLowIcon style={{ fontSize: '24px' }} />
62
62
  <span className='seam-status-text'>
63
63
  {t.low}
64
64
  <Percentage level={level} />
@@ -70,7 +70,7 @@ function Content(props: {
70
70
  if (status === 'critical') {
71
71
  return (
72
72
  <>
73
- <BatteryLevelCriticalIcon />
73
+ <BatteryLevelCriticalIcon style={{ fontSize: '24px' }} />
74
74
  <span className='seam-text-danger'>
75
75
  {t.critical}
76
76
  <Percentage level={level} />
@@ -25,7 +25,7 @@ export function OnlineStatus(props: OnlineStatusProps): JSX.Element {
25
25
  function AccountOfflineContent(): JSX.Element {
26
26
  return (
27
27
  <>
28
- <OnlineStatusAccountOfflineIcon />
28
+ <OnlineStatusAccountOfflineIcon className='seam-font-24' />
29
29
  <span className='seam-text-danger'>{t.accountOffline}</span>
30
30
  </>
31
31
  )
@@ -36,7 +36,7 @@ function AccountOnlineContent(props: { isOnline: boolean }): JSX.Element {
36
36
  if (isOnline) {
37
37
  return (
38
38
  <>
39
- <OnlineStatusOnlineIcon />
39
+ <OnlineStatusOnlineIcon className='seam-font-24' />
40
40
  <span className='seam-status-text'>{t.online}</span>
41
41
  </>
42
42
  )
@@ -44,7 +44,7 @@ function AccountOnlineContent(props: { isOnline: boolean }): JSX.Element {
44
44
 
45
45
  return (
46
46
  <>
47
- <OnlineStatusDeviceOfflineIcon />
47
+ <OnlineStatusDeviceOfflineIcon className='seam-font-24' />
48
48
  <span className='seam-text-danger'>{t.deviceOffline}</span>
49
49
  </>
50
50
  )
@@ -25,7 +25,7 @@ export function ClimateModeMenu({
25
25
  <div className='seam-climate-mode-menu-button-icon'>
26
26
  <ModeIcon mode={mode} />
27
27
  </div>
28
- <ChevronDownIcon />
28
+ <ChevronDownIcon className='seam-climate-mode-menu-button-chevron' />
29
29
  </button>
30
30
  )}
31
31
  verticalOffset={-180}
@@ -1,3 +1,5 @@
1
+ import classNames from 'classnames'
2
+
1
3
  import { ThermostatCoolIcon } from 'lib/icons/ThermostatCool.js'
2
4
  import { ThermostatHeatIcon } from 'lib/icons/ThermostatHeat.js'
3
5
  import { ThermostatHeatCoolIcon } from 'lib/icons/ThermostatHeatCool.js'
@@ -46,7 +48,14 @@ function ClimateSettingIcon(props: {
46
48
  const { mode } = props
47
49
 
48
50
  return (
49
- <div className='seam-climate-setting-status-icon'>
51
+ <div
52
+ className={classNames('seam-climate-setting-status-icon', {
53
+ 'seam-climate-setting-status-icon-cool': mode === 'cool',
54
+ 'seam-climate-setting-status-icon-heat': mode === 'heat',
55
+ 'seam-climate-setting-status-icon-heat-cool': mode === 'heat_cool',
56
+ 'seam-climate-setting-status-icon-off': mode === 'off',
57
+ })}
58
+ >
50
59
  {mode === 'cool' && <ThermostatCoolIcon />}
51
60
  {mode === 'heat' && <ThermostatHeatIcon />}
52
61
  {mode === 'heat_cool' && <ThermostatHeatCoolIcon />}
@@ -138,7 +138,7 @@ export function TemperatureControlGroup({
138
138
  <div className='seam-temperature-control-group'>
139
139
  {showHeat && (
140
140
  <div className='seam-temperature-control-group-block'>
141
- <ThermostatHeatLargeIcon />
141
+ <ThermostatHeatLargeIcon className='seam-temperature-control-group-block-thermostat-icon' />
142
142
  <TemperatureControl
143
143
  variant='heat'
144
144
  value={heatValue}
@@ -151,7 +151,7 @@ export function TemperatureControlGroup({
151
151
 
152
152
  {showCool && (
153
153
  <div className='seam-temperature-control-group-block'>
154
- <ThermostatCoolLargeIcon />
154
+ <ThermostatCoolLargeIcon className='seam-temperature-control-group-block-thermostat-icon' />
155
155
  <TemperatureControl
156
156
  variant='cool'
157
157
  value={coolValue}
@@ -3,6 +3,7 @@ import { useState } from 'react'
3
3
 
4
4
  import { FanIcon } from 'lib/icons/Fan.js'
5
5
  import { OffIcon } from 'lib/icons/Off.js'
6
+ import { SeamEditableDeviceName } from 'lib/seam/components/SeamEditableDeviceName/SeamEditableDeviceName.js'
6
7
  import type { ThermostatDevice } from 'lib/seam/thermostats/thermostat-device.js'
7
8
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
8
9
  import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
@@ -10,17 +11,18 @@ import { Temperature } from 'lib/ui/thermostat/Temperature.js'
10
11
 
11
12
  interface ThermostatCardProps {
12
13
  device: ThermostatDevice
14
+ onEditName?: (newName: string) => void
13
15
  }
14
16
 
15
- export function ThermostatCard({ device }: ThermostatCardProps): JSX.Element {
17
+ export function ThermostatCard(props: ThermostatCardProps): JSX.Element {
16
18
  return (
17
19
  <div className='seam-thermostat-card'>
18
- <Content device={device} />
20
+ <Content device={props.device} onEditName={props.onEditName} />
19
21
  </div>
20
22
  )
21
23
  }
22
24
 
23
- function Content(props: { device: ThermostatDevice }): JSX.Element | null {
25
+ function Content(props: ThermostatCardProps): JSX.Element | null {
24
26
  const { device } = props
25
27
 
26
28
  const [temperatureUnit, setTemperatureUnit] = useState<
@@ -50,9 +52,12 @@ function Content(props: { device: ThermostatDevice }): JSX.Element | null {
50
52
  </div>
51
53
  <div className='seam-thermostat-card-details'>
52
54
  <div className='seam-thermostat-heading-wrap'>
53
- <h4 className='seam-thermostat-card-heading'>
54
- {device.properties.name}
55
- </h4>
55
+ <SeamEditableDeviceName
56
+ value={device.properties.name}
57
+ tagName='h4'
58
+ className='seam-thermostat-card-heading'
59
+ onEdit={props.onEditName}
60
+ />
56
61
  <button
57
62
  onClick={toggleTemperatureUnit}
58
63
  className='seam-thermostat-temperature-toggle'
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '4.1.0'
1
+ const seamapiReactVersion = '4.3.0'
2
2
 
3
3
  export default seamapiReactVersion
@@ -59,7 +59,7 @@
59
59
  }
60
60
 
61
61
  svg {
62
- scale: 0.8333;
62
+ font-size: 20px;
63
63
  }
64
64
  }
65
65
  }
@@ -141,7 +141,7 @@
141
141
  }
142
142
 
143
143
  svg {
144
- scale: 0.8333;
144
+ font-size: 20px;
145
145
  }
146
146
  }
147
147
  }