@seamapi/react 1.6.0 → 1.8.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 (212) hide show
  1. package/README.md +18 -5
  2. package/dist/elements.js +25043 -0
  3. package/dist/elements.js.map +1 -0
  4. package/hooks.d.ts +1 -0
  5. package/hooks.js +2 -0
  6. package/hooks.js.map +1 -0
  7. package/index.css +6 -0
  8. package/index.css.map +1 -1
  9. package/index.min.css +1 -1
  10. package/index.min.css.map +1 -1
  11. package/lib/{sort-by.d.ts → dates.d.ts} +1 -1
  12. package/lib/{sort-by.js → dates.js} +2 -2
  13. package/lib/dates.js.map +1 -0
  14. package/lib/index.d.ts +2 -2
  15. package/lib/index.js +2 -2
  16. package/lib/index.js.map +1 -1
  17. package/lib/{SeamProvider.d.ts → seam/SeamProvider.d.ts} +9 -6
  18. package/lib/{SeamProvider.js → seam/SeamProvider.js} +29 -30
  19. package/lib/seam/SeamProvider.js.map +1 -0
  20. package/lib/seam/access-codes/use-access-codes.js +2 -2
  21. package/lib/seam/access-codes/use-access-codes.js.map +1 -1
  22. package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDetails.d.ts +2 -1
  23. package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDetails.js +9 -8
  24. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -0
  25. package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDevice.js +7 -7
  26. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js.map +1 -0
  27. package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +7 -0
  28. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +77 -0
  29. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -0
  30. package/lib/{ui → seam/components}/AccessCodeTable/CodeDetails.js +3 -3
  31. package/lib/seam/components/AccessCodeTable/CodeDetails.js.map +1 -0
  32. package/lib/seam/components/DeviceDetails/DeviceDetails.d.ts +7 -0
  33. package/lib/seam/components/DeviceDetails/DeviceDetails.js +80 -0
  34. package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -0
  35. package/lib/seam/components/DeviceDetails/DeviceModel.js.map +1 -0
  36. package/lib/{ui → seam/components}/DeviceTable/DeviceHealthBar.d.ts +3 -2
  37. package/lib/{ui → seam/components}/DeviceTable/DeviceHealthBar.js +4 -4
  38. package/lib/seam/components/DeviceTable/DeviceHealthBar.js.map +1 -0
  39. package/lib/{ui → seam/components}/DeviceTable/DeviceRow.d.ts +1 -1
  40. package/lib/{ui → seam/components}/DeviceTable/DeviceRow.js +9 -9
  41. package/lib/seam/components/DeviceTable/DeviceRow.js.map +1 -0
  42. package/lib/seam/components/DeviceTable/DeviceTable.d.ts +9 -0
  43. package/lib/seam/components/DeviceTable/DeviceTable.js +66 -0
  44. package/lib/seam/components/DeviceTable/DeviceTable.js.map +1 -0
  45. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.d.ts +20 -0
  46. package/lib/{ui → seam/components}/SupportedDeviceTable/FilterCategoryMenu.js +8 -5
  47. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js.map +1 -0
  48. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.d.ts +9 -0
  49. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js +39 -0
  50. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js.map +1 -0
  51. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.d.ts +9 -0
  52. package/lib/{ui → seam/components}/SupportedDeviceTable/SupportedDeviceFilterArea.js +33 -28
  53. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js.map +1 -0
  54. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.d.ts +2 -0
  55. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js +12 -0
  56. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js.map +1 -0
  57. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.d.ts +6 -0
  58. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js +19 -0
  59. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js.map +1 -0
  60. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.d.ts +6 -0
  61. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js +17 -0
  62. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js.map +1 -0
  63. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.d.ts +7 -0
  64. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js +18 -0
  65. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js.map +1 -0
  66. package/lib/seam/components/index.js.map +1 -0
  67. package/lib/seam/device-models/use-device-models.d.ts +5 -0
  68. package/lib/seam/device-models/use-device-models.js +17 -0
  69. package/lib/seam/device-models/use-device-models.js.map +1 -0
  70. package/lib/seam/devices/use-devices.js +3 -3
  71. package/lib/seam/devices/use-devices.js.map +1 -1
  72. package/lib/seam/devices/use-toggle-lock.js +1 -1
  73. package/lib/seam/devices/use-toggle-lock.js.map +1 -1
  74. package/lib/seam/index.d.ts +1 -0
  75. package/lib/seam/index.js +1 -0
  76. package/lib/seam/index.js.map +1 -1
  77. package/lib/seam/use-seam-client.js +1 -1
  78. package/lib/seam/use-seam-client.js.map +1 -1
  79. package/lib/seam/use-seam-font.d.ts +1 -1
  80. package/lib/seam/use-seam-font.js +1 -1
  81. package/lib/seam/use-seam-font.js.map +1 -1
  82. package/lib/seam/use-seam-styles.js.map +1 -1
  83. package/lib/{capitalize.js → strings.js} +1 -1
  84. package/lib/strings.js.map +1 -0
  85. package/lib/ui/Menu/Menu.d.ts +1 -1
  86. package/lib/ui/Menu/Menu.js.map +1 -1
  87. package/lib/ui/Menu/MenuItem.d.ts +1 -1
  88. package/lib/ui/Menu/MoreActionsMenu.d.ts +1 -1
  89. package/lib/ui/Table/TableFilterBar/TableFilterBar.d.ts +2 -1
  90. package/lib/ui/Table/TableFilterBar/TableFilterItem.d.ts +2 -1
  91. package/lib/ui/TextField/SearchTextField.js.map +1 -1
  92. package/lib/{copy-to-clipboard.js → ui/clipboard.js} +1 -1
  93. package/lib/ui/clipboard.js.map +1 -0
  94. package/lib/ui/use-interval.js.map +1 -0
  95. package/lib/{use-is-date-in-past.js → ui/use-is-date-in-past.js} +1 -1
  96. package/lib/ui/use-is-date-in-past.js.map +1 -0
  97. package/lib/ui/use-toggle.js.map +1 -0
  98. package/lib/version.d.ts +2 -2
  99. package/lib/version.js +3 -1
  100. package/lib/version.js.map +1 -1
  101. package/package.json +21 -8
  102. package/src/elements.ts +27 -0
  103. package/src/hooks.ts +1 -0
  104. package/src/lib/{sort-by.ts → dates.ts} +1 -1
  105. package/src/lib/element.tsx +86 -0
  106. package/src/lib/index.ts +2 -2
  107. package/src/lib/{SeamProvider.tsx → seam/SeamProvider.tsx} +53 -46
  108. package/src/lib/seam/access-codes/use-access-codes.ts +2 -2
  109. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +12 -0
  110. package/src/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDetails.tsx +9 -6
  111. package/src/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDevice.tsx +2 -2
  112. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +12 -0
  113. package/src/lib/{ui → seam/components}/AccessCodeTable/AccessCodeTable.tsx +30 -11
  114. package/src/lib/{ui → seam/components}/AccessCodeTable/CodeDetails.tsx +2 -3
  115. package/src/lib/seam/components/DeviceDetails/DeviceDetails.element.ts +12 -0
  116. package/src/lib/{ui → seam/components}/DeviceDetails/DeviceDetails.tsx +22 -12
  117. package/src/lib/{ui → seam/components}/DeviceTable/DeviceHealthBar.tsx +1 -1
  118. package/src/lib/{ui → seam/components}/DeviceTable/DeviceRow.tsx +1 -1
  119. package/src/lib/seam/components/DeviceTable/DeviceTable.element.ts +11 -0
  120. package/src/lib/{ui → seam/components}/DeviceTable/DeviceTable.tsx +15 -12
  121. package/src/lib/{ui → seam/components}/SupportedDeviceTable/FilterCategoryMenu.tsx +26 -13
  122. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.tsx +120 -0
  123. package/src/lib/{ui → seam/components}/SupportedDeviceTable/SupportedDeviceFilterArea.tsx +43 -39
  124. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.tsx +22 -0
  125. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.tsx +48 -0
  126. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.element.ts +11 -0
  127. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.tsx +48 -0
  128. package/src/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.ts +35 -0
  129. package/src/lib/seam/components/elements.ts +5 -0
  130. package/src/lib/seam/device-models/use-device-models.ts +34 -0
  131. package/src/lib/seam/devices/use-devices.ts +3 -3
  132. package/src/lib/seam/devices/use-toggle-lock.ts +1 -2
  133. package/src/lib/seam/index.ts +1 -0
  134. package/src/lib/seam/use-seam-client.ts +1 -1
  135. package/src/lib/seam/use-seam-font.ts +5 -1
  136. package/src/lib/seam/use-seam-styles.ts +1 -1
  137. package/src/lib/ui/Menu/Menu.tsx +4 -4
  138. package/src/lib/ui/Menu/MenuItem.tsx +1 -1
  139. package/src/lib/ui/Menu/MoreActionsMenu.tsx +1 -1
  140. package/src/lib/ui/Table/TableFilterBar/TableFilterBar.tsx +2 -2
  141. package/src/lib/ui/Table/TableFilterBar/TableFilterItem.tsx +1 -1
  142. package/src/lib/ui/TextField/SearchTextField.tsx +3 -3
  143. package/src/lib/ui/clipboard.ts +3 -0
  144. package/src/lib/{use-interval.ts → ui/use-interval.ts} +1 -1
  145. package/src/lib/{use-is-date-in-past.ts → ui/use-is-date-in-past.ts} +1 -1
  146. package/src/lib/version.ts +3 -1
  147. package/src/styles/_access-code-table.scss +7 -0
  148. package/lib/SeamProvider.js.map +0 -1
  149. package/lib/capitalize.js.map +0 -1
  150. package/lib/copy-to-clipboard.js.map +0 -1
  151. package/lib/sort-by.js.map +0 -1
  152. package/lib/ui/AccessCodeDetails/AccessCodeDetails.js.map +0 -1
  153. package/lib/ui/AccessCodeDetails/AccessCodeDevice.js.map +0 -1
  154. package/lib/ui/AccessCodeTable/AccessCodeTable.d.ts +0 -6
  155. package/lib/ui/AccessCodeTable/AccessCodeTable.js +0 -69
  156. package/lib/ui/AccessCodeTable/AccessCodeTable.js.map +0 -1
  157. package/lib/ui/AccessCodeTable/CodeDetails.js.map +0 -1
  158. package/lib/ui/DeviceDetails/DeviceDetails.d.ts +0 -6
  159. package/lib/ui/DeviceDetails/DeviceDetails.js +0 -80
  160. package/lib/ui/DeviceDetails/DeviceDetails.js.map +0 -1
  161. package/lib/ui/DeviceDetails/DeviceModel.js.map +0 -1
  162. package/lib/ui/DeviceTable/DeviceHealthBar.js.map +0 -1
  163. package/lib/ui/DeviceTable/DeviceRow.js.map +0 -1
  164. package/lib/ui/DeviceTable/DeviceTable.d.ts +0 -8
  165. package/lib/ui/DeviceTable/DeviceTable.js +0 -65
  166. package/lib/ui/DeviceTable/DeviceTable.js.map +0 -1
  167. package/lib/ui/SupportedDeviceTable/FilterCategoryMenu.d.ts +0 -15
  168. package/lib/ui/SupportedDeviceTable/FilterCategoryMenu.js.map +0 -1
  169. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.d.ts +0 -4
  170. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.js +0 -46
  171. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.js.map +0 -1
  172. package/lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.d.ts +0 -11
  173. package/lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.js.map +0 -1
  174. package/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.d.ts +0 -1
  175. package/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.js +0 -5
  176. package/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.js.map +0 -1
  177. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.d.ts +0 -5
  178. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.js +0 -12
  179. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.js.map +0 -1
  180. package/lib/ui/SupportedDeviceTable/SupportedDeviceTable.d.ts +0 -3
  181. package/lib/ui/SupportedDeviceTable/SupportedDeviceTable.js +0 -8
  182. package/lib/ui/SupportedDeviceTable/SupportedDeviceTable.js.map +0 -1
  183. package/lib/ui/SupportedDeviceTable/types.d.ts +0 -15
  184. package/lib/ui/SupportedDeviceTable/types.js +0 -2
  185. package/lib/ui/SupportedDeviceTable/types.js.map +0 -1
  186. package/lib/ui/index.js.map +0 -1
  187. package/lib/use-interval.js.map +0 -1
  188. package/lib/use-is-date-in-past.js.map +0 -1
  189. package/lib/use-toggle.js.map +0 -1
  190. package/src/lib/copy-to-clipboard.ts +0 -3
  191. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceContent.tsx +0 -135
  192. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceHeader.tsx +0 -14
  193. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceRow.tsx +0 -35
  194. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceTable.tsx +0 -16
  195. package/src/lib/ui/SupportedDeviceTable/types.ts +0 -16
  196. /package/lib/{ui → seam/components}/AccessCodeDetails/AccessCodeDevice.d.ts +0 -0
  197. /package/lib/{ui → seam/components}/AccessCodeTable/CodeDetails.d.ts +0 -0
  198. /package/lib/{ui → seam/components}/DeviceDetails/DeviceModel.d.ts +0 -0
  199. /package/lib/{ui → seam/components}/DeviceDetails/DeviceModel.js +0 -0
  200. /package/lib/{ui → seam/components}/index.d.ts +0 -0
  201. /package/lib/{ui → seam/components}/index.js +0 -0
  202. /package/lib/{capitalize.d.ts → strings.d.ts} +0 -0
  203. /package/lib/{copy-to-clipboard.d.ts → ui/clipboard.d.ts} +0 -0
  204. /package/lib/{use-interval.d.ts → ui/use-interval.d.ts} +0 -0
  205. /package/lib/{use-interval.js → ui/use-interval.js} +0 -0
  206. /package/lib/{use-is-date-in-past.d.ts → ui/use-is-date-in-past.d.ts} +0 -0
  207. /package/lib/{use-toggle.d.ts → ui/use-toggle.d.ts} +0 -0
  208. /package/lib/{use-toggle.js → ui/use-toggle.js} +0 -0
  209. /package/src/lib/{ui → seam/components}/DeviceDetails/DeviceModel.tsx +0 -0
  210. /package/src/lib/{ui → seam/components}/index.ts +0 -0
  211. /package/src/lib/{capitalize.ts → strings.ts} +0 -0
  212. /package/src/lib/{use-toggle.ts → ui/use-toggle.ts} +0 -0
@@ -2,6 +2,24 @@ import { ChevronDownIcon } from 'lib/icons/ChevronDown.js'
2
2
  import { Menu } from 'lib/ui/Menu/Menu.js'
3
3
  import { MenuItem } from 'lib/ui/Menu/MenuItem.js'
4
4
 
5
+ export type FilterCategoryMenuProps =
6
+ | FilterCategoryMenuPropsWithAllOption
7
+ | FilterCategoryMenuPropsWithoutAllOption
8
+
9
+ interface FilterCategoryMenuPropsWithAllOption
10
+ extends FilterCategoryMenuBaseProps {
11
+ hideAllOption?: false
12
+ allLabel: string
13
+ onAllOptionSelect: () => void
14
+ }
15
+
16
+ interface FilterCategoryMenuPropsWithoutAllOption
17
+ extends FilterCategoryMenuBaseProps {
18
+ hideAllOption: true
19
+ allLabel: never
20
+ onAllOptionSelect?: never
21
+ }
22
+
5
23
  interface FilterCategoryMenuBaseProps {
6
24
  label: string
7
25
  options: string[]
@@ -9,24 +27,15 @@ interface FilterCategoryMenuBaseProps {
9
27
  buttonLabel?: string
10
28
  }
11
29
 
12
- export type FilterCategoryMenuProps =
13
- | (FilterCategoryMenuBaseProps & {
14
- hideAllOption?: false
15
- onAllOptionSelect: () => void
16
- })
17
- | (FilterCategoryMenuBaseProps & {
18
- hideAllOption: true
19
- onAllOptionSelect?: never
20
- })
21
-
22
30
  export function FilterCategoryMenu({
23
- label = 'Filter',
31
+ label = t.filter,
32
+ allLabel,
24
33
  options,
25
34
  hideAllOption = false,
26
35
  onSelect,
27
36
  onAllOptionSelect,
28
37
  buttonLabel,
29
- }: FilterCategoryMenuProps) {
38
+ }: FilterCategoryMenuProps): JSX.Element {
30
39
  const usableOptions = hideAllOption ? options : ['All', ...options]
31
40
 
32
41
  return (
@@ -44,7 +53,7 @@ export function FilterCategoryMenu({
44
53
  <MenuItem
45
54
  key={`${index}:${option}`}
46
55
  onClick={() => {
47
- if (option === 'All') {
56
+ if (option === allLabel) {
48
57
  onAllOptionSelect?.()
49
58
  } else {
50
59
  onSelect(option)
@@ -58,3 +67,7 @@ export function FilterCategoryMenu({
58
67
  </div>
59
68
  )
60
69
  }
70
+
71
+ const t = {
72
+ filter: 'Filter',
73
+ }
@@ -0,0 +1,120 @@
1
+ import { SupportedDeviceHeader } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js'
2
+ import { SupportedDeviceRow } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js'
3
+ import {
4
+ type DeviceModelFilters,
5
+ useFilteredDeviceModels,
6
+ } from 'lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js'
7
+ import { Button } from 'lib/ui/Button.js'
8
+
9
+ interface SupportedDeviceContentProps {
10
+ filterValue: string
11
+ resetFilterValue: () => void
12
+ filters: DeviceModelFilters
13
+ }
14
+
15
+ export function SupportedDeviceContent({
16
+ resetFilterValue,
17
+ filterValue,
18
+ filters,
19
+ }: SupportedDeviceContentProps): JSX.Element | null {
20
+ const { deviceModels, isLoading, isError, refetch } = useFilteredDeviceModels(
21
+ filterValue,
22
+ filters
23
+ )
24
+
25
+ if (isLoading) {
26
+ return (
27
+ <div className='seam-supported-device-table-content-state-block'>
28
+ <p>{t.loading}</p>
29
+ </div>
30
+ )
31
+ }
32
+
33
+ if (isError) {
34
+ return (
35
+ <div className='seam-supported-device-table-content-state-block'>
36
+ <p>{t.error}</p>
37
+ <Button
38
+ variant='solid'
39
+ size='small'
40
+ onClick={() => {
41
+ void refetch()
42
+ }}
43
+ >
44
+ {t.retry}
45
+ </Button>
46
+ </div>
47
+ )
48
+ }
49
+
50
+ if (deviceModels == null) {
51
+ return null
52
+ }
53
+
54
+ return (
55
+ <table className='seam-supported-device-table-content'>
56
+ <SupportedDeviceHeader />
57
+ <tbody>
58
+ {deviceModels.map((deviceModel, index) => (
59
+ <SupportedDeviceRow
60
+ key={[
61
+ deviceModel.main_category,
62
+ deviceModel.brand,
63
+ deviceModel.model_name,
64
+ deviceModel.manufacturer_model_id,
65
+ index,
66
+ ].join(':')}
67
+ deviceModel={deviceModel}
68
+ />
69
+ ))}
70
+ {deviceModels.length === 0 && (
71
+ <EmptyResult
72
+ filterValue={filterValue}
73
+ resetFilterValue={resetFilterValue}
74
+ />
75
+ )}
76
+ </tbody>
77
+ </table>
78
+ )
79
+ }
80
+
81
+ function EmptyResult({
82
+ filterValue,
83
+ resetFilterValue,
84
+ }: Pick<
85
+ SupportedDeviceContentProps,
86
+ 'filterValue' | 'resetFilterValue'
87
+ >): JSX.Element {
88
+ const noMatchingRows = (
89
+ <>
90
+ <p>{t.noMatch}</p>
91
+ <Button
92
+ variant='outline'
93
+ size='small'
94
+ onClick={resetFilterValue}
95
+ className='seam-supported-device-table-content-message-clear-search'
96
+ >
97
+ {t.clear}
98
+ </Button>
99
+ </>
100
+ )
101
+
102
+ return (
103
+ <tr className='seam-supported-device-table-content-message-row'>
104
+ <td colSpan={6}>
105
+ <div className='seam-supported-device-table-content-message'>
106
+ {filterValue.length === 0 ? <p>{t.noneFound}</p> : noMatchingRows}
107
+ </div>
108
+ </td>
109
+ </tr>
110
+ )
111
+ }
112
+
113
+ const t = {
114
+ loading: 'Loading device models...',
115
+ retry: 'Retry',
116
+ error: 'There was an error fetching device models.',
117
+ noneFound: 'No device models found.',
118
+ noMatch: 'No device models matched your search.',
119
+ clear: 'Clear search terms',
120
+ }
@@ -1,58 +1,45 @@
1
1
  import type { Dispatch, SetStateAction } from 'react'
2
+ import type { DeviceModel } from 'seamapi'
2
3
 
3
- import { capitalize } from 'lib/capitalize.js'
4
+ import { FilterCategoryMenu } from 'lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js'
5
+ import type { DeviceModelFilters } from 'lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js'
6
+ import { useDeviceModels } from 'lib/seam/device-models/use-device-models.js'
7
+ import { capitalize } from 'lib/strings.js'
4
8
  import { Button } from 'lib/ui/Button.js'
5
9
  import { Menu } from 'lib/ui/Menu/Menu.js'
6
- import { FilterCategoryMenu } from 'lib/ui/SupportedDeviceTable/FilterCategoryMenu.js'
7
- import type { DeviceModel, Filters } from 'lib/ui/SupportedDeviceTable/types.js'
8
10
  import { SearchTextField } from 'lib/ui/TextField/SearchTextField.js'
9
11
 
10
- interface SupportedDeviceFilterAreaProps {
11
- deviceModels: DeviceModel[]
12
+ export interface SupportedDeviceFilterAreaProps {
12
13
  filterValue: string
13
14
  setFilterValue: (filter: string) => void
14
- filters: Filters
15
- setFilters: Dispatch<SetStateAction<Filters>>
15
+ filters: DeviceModelFilters
16
+ setFilters: Dispatch<SetStateAction<DeviceModelFilters>>
16
17
  }
17
18
 
18
19
  export function SupportedDeviceFilterArea({
19
- deviceModels,
20
20
  filterValue,
21
21
  setFilterValue,
22
22
  filters,
23
23
  setFilters,
24
- }: SupportedDeviceFilterAreaProps) {
25
- const appliedFilters = Object.keys(filters).filter((key) => {
26
- const filterProperty = filters[key as keyof Filters]
27
- return (
28
- filterProperty !== undefined &&
29
- filterProperty !== null &&
30
- filterProperty !== false
31
- )
32
- })
33
- const appliedFiltersCount = appliedFilters.length
24
+ }: SupportedDeviceFilterAreaProps): JSX.Element {
25
+ const appliedFiltersCount = Object.values(filters).filter(
26
+ (v) => v != null && v !== false
27
+ ).length
34
28
 
35
- const getAvailablePropertiesFromDeviceModels = (
36
- property: keyof DeviceModel
37
- ) => {
38
- const properties = new Set<string>()
39
- deviceModels.forEach((deviceModel) => {
40
- properties.add(capitalize(deviceModel[property]))
41
- })
42
- return Array.from(properties)
43
- }
29
+ const filterProperty = 'brand'
30
+ const availableProperties = useAvailableProperties(filterProperty)
44
31
 
45
- const resetFilter = (filterType: keyof Filters) => {
32
+ const resetFilter = (filterType: keyof DeviceModelFilters): void => {
46
33
  setFilters((filters) => ({
47
34
  ...filters,
48
35
  [filterType]: null,
49
36
  }))
50
37
  }
51
38
 
52
- const isPlural = appliedFiltersCount > 1
53
- const filterButtonLabel = isPlural
54
- ? `Filters (${appliedFiltersCount})`
55
- : 'Filter'
39
+ const filterButtonLabel =
40
+ appliedFiltersCount > 0 ? `${t.filters} (${appliedFiltersCount})` : t.filter
41
+
42
+ const allLabel = t.all
56
43
 
57
44
  return (
58
45
  <div className='seam-supported-device-table-filter-area'>
@@ -77,27 +64,27 @@ export function SupportedDeviceFilterArea({
77
64
  >
78
65
  <div className='seam-filter-menu-row'>
79
66
  <FilterCategoryMenu
80
- label='Brand'
81
- options={getAvailablePropertiesFromDeviceModels('brand')}
67
+ label={t.brand}
68
+ allLabel={allLabel}
69
+ options={availableProperties}
82
70
  onSelect={(brand: string) => {
83
71
  setFilters((filters) => ({
84
72
  ...filters,
85
73
  brand,
86
74
  }))
87
75
  }}
88
- buttonLabel={filters.brand ?? 'All'}
76
+ buttonLabel={filters.brand ?? allLabel}
89
77
  onAllOptionSelect={() => {
90
- resetFilter('brand')
78
+ resetFilter(filterProperty)
91
79
  }}
92
80
  />
93
81
  </div>
94
-
95
82
  <div className='seam-filter-menu-row'>
96
83
  <label
97
84
  htmlFor='supportedOnly'
98
85
  className='seam-filter-checkbox-label'
99
86
  >
100
- <p>Supported</p>
87
+ <p>{t.supported}</p>
101
88
  <input
102
89
  id='supportedOnly'
103
90
  name='supportedOnly'
@@ -115,7 +102,6 @@ export function SupportedDeviceFilterArea({
115
102
  </div>
116
103
  </div>
117
104
  </Menu>
118
-
119
105
  <div className='seam-supported-device-table-filter-area-search-bar-wrap'>
120
106
  <SearchTextField
121
107
  value={filterValue}
@@ -129,3 +115,21 @@ export function SupportedDeviceFilterArea({
129
115
  </div>
130
116
  )
131
117
  }
118
+
119
+ const useAvailableProperties = (property: keyof DeviceModel): string[] => {
120
+ const { deviceModels } = useDeviceModels()
121
+ if (deviceModels == null) return []
122
+ const properties = new Set<string>()
123
+ for (const deviceModel of deviceModels) {
124
+ properties.add(capitalize(deviceModel[property]))
125
+ }
126
+ return Array.from(properties)
127
+ }
128
+
129
+ const t = {
130
+ all: 'All',
131
+ brand: 'Brand',
132
+ supported: 'Supported',
133
+ filter: 'Filter',
134
+ filters: 'Filters',
135
+ }
@@ -0,0 +1,22 @@
1
+ export function SupportedDeviceHeader(): JSX.Element {
2
+ return (
3
+ <thead>
4
+ <tr>
5
+ <th />
6
+ <th>{t.category}</th>
7
+ <th>{t.modelName}</th>
8
+ <th>{t.manufacturerModelId}</th>
9
+ <th>{t.connectionType}</th>
10
+ <th>{t.status}</th>
11
+ </tr>
12
+ </thead>
13
+ )
14
+ }
15
+
16
+ const t = {
17
+ category: 'Category',
18
+ modelName: 'Model Name',
19
+ manufacturerModelId: 'Manufacturer Model ID',
20
+ connectionType: 'Connection Type',
21
+ status: 'Status',
22
+ }
@@ -0,0 +1,48 @@
1
+ import classNames from 'classnames'
2
+ import type { DeviceModel } from 'seamapi'
3
+
4
+ import { capitalize } from 'lib/strings.js'
5
+
6
+ export interface SupportedDeviceRowProps {
7
+ deviceModel: DeviceModel
8
+ }
9
+
10
+ export function SupportedDeviceRow({
11
+ deviceModel,
12
+ }: SupportedDeviceRowProps): JSX.Element {
13
+ const statusColor = supportLevelColors[deviceModel.support_level] ?? 'gray'
14
+ return (
15
+ <tr>
16
+ <td>
17
+ <img width={40} src={deviceModel.icon_url} />
18
+ </td>
19
+ <td>{deviceModel.main_category}</td>
20
+ <td>{deviceModel.model_name}</td>
21
+ <td>{deviceModel.manufacturer_model_id}</td>
22
+ <td>{connectionTypeNames[deviceModel.connection_type]}</td>
23
+ <td>
24
+ <div
25
+ className={classNames('seam-status-pill', `status-${statusColor}`)}
26
+ >
27
+ <span>{capitalize(deviceModel.support_level)}</span>
28
+ </div>
29
+ </td>
30
+ </tr>
31
+ )
32
+ }
33
+
34
+ const supportLevelColors: Record<
35
+ DeviceModel['support_level'],
36
+ 'green' | 'blue' | 'orange'
37
+ > = {
38
+ live: 'green',
39
+ beta: 'blue',
40
+ unsupported: 'orange',
41
+ }
42
+
43
+ const connectionTypeNames: Record<DeviceModel['connection_type'], string> = {
44
+ wifi: 'Wifi',
45
+ zwave: 'Z-Wave',
46
+ zigbee: 'Zigbee',
47
+ unknown: 'Unknown',
48
+ }
@@ -0,0 +1,11 @@
1
+ import type { ElementProps } from 'lib/element.js'
2
+
3
+ import type { SupportedDeviceTableProps } from './SupportedDeviceTable.js'
4
+
5
+ export const name = 'seam-supported-device-table'
6
+
7
+ export const props: ElementProps<SupportedDeviceTableProps> = {
8
+ cannotFilter: 'boolean',
9
+ }
10
+
11
+ export { SupportedDeviceTable as Component } from './SupportedDeviceTable.js'
@@ -0,0 +1,48 @@
1
+ import classNames from 'classnames'
2
+ import { useState } from 'react'
3
+
4
+ import { SupportedDeviceContent } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js'
5
+ import { SupportedDeviceFilterArea } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js'
6
+ import type { DeviceModelFilters } from 'lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js'
7
+
8
+ export interface SupportedDeviceTableProps {
9
+ cannotFilter?: boolean
10
+ className?: string
11
+ }
12
+
13
+ export function SupportedDeviceTable({
14
+ cannotFilter = false,
15
+ className,
16
+ }: SupportedDeviceTableProps): JSX.Element {
17
+ const [filterValue, setFilterValue] = useState('')
18
+ const [filters, setFilters] = useState<DeviceModelFilters>({
19
+ supportedOnly: false,
20
+ category: null,
21
+ brand: null,
22
+ })
23
+
24
+ return (
25
+ <div
26
+ className={classNames(
27
+ 'seam-supported-device-table-content-wrap',
28
+ className
29
+ )}
30
+ >
31
+ {!cannotFilter && (
32
+ <SupportedDeviceFilterArea
33
+ filterValue={filterValue}
34
+ setFilterValue={setFilterValue}
35
+ filters={filters}
36
+ setFilters={setFilters}
37
+ />
38
+ )}
39
+ <SupportedDeviceContent
40
+ resetFilterValue={() => {
41
+ setFilterValue('')
42
+ }}
43
+ filterValue={filterValue}
44
+ filters={filters}
45
+ />
46
+ </div>
47
+ )
48
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ useDeviceModels,
3
+ type UseDeviceModelsParams,
4
+ } from 'lib/seam/device-models/use-device-models.js'
5
+
6
+ export interface DeviceModelFilters {
7
+ supportedOnly: boolean
8
+ category: string | null
9
+ brand: string | null
10
+ }
11
+
12
+ export const useFilteredDeviceModels = (
13
+ filterValue: string,
14
+ filters: DeviceModelFilters
15
+ ): ReturnType<typeof useDeviceModels> => {
16
+ const params: UseDeviceModelsParams = {}
17
+
18
+ if (filterValue.trim() !== '') {
19
+ params.text_search = filterValue.trim()
20
+ }
21
+
22
+ if (filters.supportedOnly) {
23
+ params.support_level = 'live'
24
+ }
25
+
26
+ if (filters.category !== null) {
27
+ params.main_category = filters.category
28
+ }
29
+
30
+ if (filters.brand !== null) {
31
+ params.brand = filters.brand
32
+ }
33
+
34
+ return useDeviceModels(params)
35
+ }
@@ -0,0 +1,5 @@
1
+ export * as AccessCodeDetails from './AccessCodeDetails/AccessCodeDetails.element.js'
2
+ export * as AccessCodeTable from './AccessCodeTable/AccessCodeTable.element.js'
3
+ export * as DeviceDetails from './DeviceDetails/DeviceDetails.element.js'
4
+ export * as DeviceTable from './DeviceTable/DeviceTable.element.js'
5
+ export * as SupportedDeviceTable from './SupportedDeviceTable/SupportedDeviceTable.element.js'
@@ -0,0 +1,34 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import type {
3
+ DeviceModel,
4
+ DeviceModelsListRequest,
5
+ DeviceModelsListResponse,
6
+ SeamError,
7
+ } from 'seamapi'
8
+
9
+ import { useSeamClient } from 'lib/seam/use-seam-client.js'
10
+ import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
11
+
12
+ export type UseDeviceModelsParams = DeviceModelsListRequest
13
+ export type UseDeviceModelsData = DeviceModel[]
14
+
15
+ export function useDeviceModels(
16
+ params?: UseDeviceModelsParams
17
+ ): UseSeamQueryResult<'deviceModels', UseDeviceModelsData> {
18
+ const { client } = useSeamClient()
19
+
20
+ const { data, ...rest } = useQuery<
21
+ DeviceModelsListResponse['device_models'],
22
+ SeamError
23
+ >({
24
+ enabled: client != null,
25
+ queryKey: ['device_models', 'list', params],
26
+ queryFn: async () => {
27
+ if (client == null) return []
28
+ const deviceModels = await client?.deviceModels.list(params)
29
+ return deviceModels
30
+ },
31
+ })
32
+
33
+ return { ...rest, deviceModels: data }
34
+ }
@@ -7,9 +7,9 @@ import type {
7
7
  SeamError,
8
8
  } from 'seamapi'
9
9
 
10
+ import { compareByCreatedAtDesc } from 'lib/dates.js'
10
11
  import { useSeamClient } from 'lib/seam/use-seam-client.js'
11
12
  import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
12
- import { byCreatedAt } from 'lib/sort-by.js'
13
13
 
14
14
  export type UseDevicesParams = DevicesListRequest
15
15
  export type UseDevicesData = Array<Device<CommonDeviceProperties>>
@@ -27,9 +27,9 @@ export function useDevices(
27
27
  queryFn: async () => {
28
28
  if (client == null) return []
29
29
  const devices = await client?.devices.list(params)
30
- return devices.sort(byCreatedAt)
30
+ return devices.sort(compareByCreatedAtDesc)
31
31
  },
32
- onSuccess(devices) {
32
+ onSuccess: (devices) => {
33
33
  // Prime cache for each device.
34
34
  for (const device of devices) {
35
35
  queryClient.setQueryData(
@@ -11,9 +11,8 @@ import type {
11
11
  SeamError,
12
12
  } from 'seamapi'
13
13
 
14
- import { useSeamClient } from 'lib/index.js'
15
-
16
14
  import { isLockDevice } from 'lib/seam/devices/types.js'
15
+ import { useSeamClient } from 'lib/seam/use-seam-client.js'
17
16
 
18
17
  export function useToggleLock({
19
18
  device_id: deviceId,
@@ -1,5 +1,6 @@
1
1
  export * from './access-codes/use-access-code.js'
2
2
  export * from './access-codes/use-access-codes.js'
3
+ export * from './device-models/use-device-models.js'
3
4
  export * from './devices/use-device.js'
4
5
  export * from './devices/use-devices.js'
5
6
  export * from './use-seam-client.js'
@@ -3,7 +3,7 @@ import { useEffect } from 'react'
3
3
  import { Seam } from 'seamapi'
4
4
  import { v4 as uuidv4 } from 'uuid'
5
5
 
6
- import { useSeamContext } from 'lib/SeamProvider.js'
6
+ import { useSeamContext } from 'lib/seam/SeamProvider.js'
7
7
 
8
8
  export function useSeamClient(): {
9
9
  client: Seam | null
@@ -3,7 +3,11 @@ import { useEffect } from 'react'
3
3
  const fontUrl =
4
4
  'https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap'
5
5
 
6
- export const useSeamFont = ({ disabled = false }: { disabled?: boolean }) => {
6
+ export const useSeamFont = ({
7
+ disabled = false,
8
+ }: {
9
+ disabled?: boolean
10
+ }): void => {
7
11
  useEffect(() => {
8
12
  if (disabled) return
9
13
  if (globalThis.document == null) return
@@ -10,7 +10,7 @@ export const useSeamStyles = ({
10
10
  }: {
11
11
  unminified?: boolean
12
12
  disabled?: boolean
13
- }) => {
13
+ }): void => {
14
14
  const ext = `${unminified ? '' : 'min.'}css`
15
15
  const cssUrl = `${origin}/v/${version ?? ''}/index.${ext}`
16
16
 
@@ -38,7 +38,7 @@ export function Menu({
38
38
  children,
39
39
  renderButton,
40
40
  backgroundProps,
41
- }: PropsWithChildren<MenuProps>) {
41
+ }: PropsWithChildren<MenuProps>): JSX.Element | null {
42
42
  const { Provider } = menuContext
43
43
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
44
44
  const [documentEl, setDocumentEl] = useState<null | Element>(null)
@@ -56,11 +56,11 @@ export function Menu({
56
56
  }
57
57
  }, [setDocumentEl])
58
58
 
59
- const handleClose = () => {
59
+ const handleClose = (): void => {
60
60
  setAnchorEl(null)
61
61
  }
62
62
 
63
- const handleOpen = (event: MouseEvent<HTMLElement>) => {
63
+ const handleOpen = (event: MouseEvent<HTMLElement>): void => {
64
64
  setAnchorEl(event.currentTarget)
65
65
  }
66
66
 
@@ -171,7 +171,7 @@ export function Menu({
171
171
  )
172
172
  }
173
173
 
174
- export function useMenu() {
174
+ export function useMenu(): MenuContextValue {
175
175
  const context = useContext(menuContext)
176
176
  if (context == null) {
177
177
  throw new Error('useMenu must be used within a Menu.')
@@ -9,7 +9,7 @@ interface MenuItemProps {
9
9
  export function MenuItem({
10
10
  onClick,
11
11
  children,
12
- }: PropsWithChildren<MenuItemProps>) {
12
+ }: PropsWithChildren<MenuItemProps>): JSX.Element {
13
13
  const { close: closeMenu } = useMenu()
14
14
 
15
15
  return (
@@ -11,7 +11,7 @@ interface MoreActionsMenuProps {
11
11
  export function MoreActionsMenu({
12
12
  children,
13
13
  menuProps,
14
- }: PropsWithChildren<MoreActionsMenuProps>) {
14
+ }: PropsWithChildren<MoreActionsMenuProps>): JSX.Element {
15
15
  return (
16
16
  <Menu
17
17
  renderButton={({ onOpen }) => (