@seamapi/react 1.2.0 → 1.4.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 (83) hide show
  1. package/README.md +56 -7
  2. package/index.css +43 -37
  3. package/index.css.map +1 -1
  4. package/index.min.css +1 -1
  5. package/index.min.css.map +1 -1
  6. package/lib/SeamProvider.d.ts +7 -5
  7. package/lib/SeamProvider.js +3 -3
  8. package/lib/SeamProvider.js.map +1 -1
  9. package/lib/seam/use-seam-font.d.ts +2 -2
  10. package/lib/seam/use-seam-font.js +11 -11
  11. package/lib/seam/use-seam-font.js.map +1 -1
  12. package/lib/seam/use-seam-styles.d.ts +3 -2
  13. package/lib/seam/use-seam-styles.js +17 -13
  14. package/lib/seam/use-seam-styles.js.map +1 -1
  15. package/lib/ui/AccessCodeDetails/AccessCodeDetails.d.ts +1 -0
  16. package/lib/ui/AccessCodeDetails/AccessCodeDevice.d.ts +1 -0
  17. package/lib/ui/AccessCodeTable/AccessCodeTable.d.ts +1 -0
  18. package/lib/ui/AccessCodeTable/CodeDetails.d.ts +1 -0
  19. package/lib/ui/Alert/Alert.js +1 -1
  20. package/lib/ui/Alert/Alert.js.map +1 -1
  21. package/lib/ui/Alert/Alerts.d.ts +2 -2
  22. package/lib/ui/Alert/Alerts.js.map +1 -1
  23. package/lib/ui/Button.js +1 -1
  24. package/lib/ui/Button.js.map +1 -1
  25. package/lib/ui/DeviceDetails/DeviceDetails.d.ts +1 -0
  26. package/lib/ui/DeviceDetails/DeviceDetails.js +10 -20
  27. package/lib/ui/DeviceDetails/DeviceDetails.js.map +1 -1
  28. package/lib/ui/DeviceDetails/DeviceModel.d.ts +1 -0
  29. package/lib/ui/DeviceTable/DeviceHealthBar.js +2 -2
  30. package/lib/ui/DeviceTable/DeviceHealthBar.js.map +1 -1
  31. package/lib/ui/DeviceTable/DeviceRow.d.ts +1 -0
  32. package/lib/ui/DeviceTable/DeviceTable.d.ts +1 -0
  33. package/lib/ui/IconButton.d.ts +1 -0
  34. package/lib/ui/Menu/Menu.js +1 -3
  35. package/lib/ui/Menu/Menu.js.map +1 -1
  36. package/lib/ui/SupportedDeviceTable/FilterCategoryMenu.d.ts +3 -9
  37. package/lib/ui/SupportedDeviceTable/FilterCategoryMenu.js +3 -7
  38. package/lib/ui/SupportedDeviceTable/FilterCategoryMenu.js.map +1 -1
  39. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.js +27 -34
  40. package/lib/ui/SupportedDeviceTable/SupportedDeviceContent.js.map +1 -1
  41. package/lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.js +3 -3
  42. package/lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.js.map +1 -1
  43. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.js +1 -1
  44. package/lib/ui/SupportedDeviceTable/SupportedDeviceRow.js.map +1 -1
  45. package/lib/ui/Table/EmptyPlaceholder.d.ts +1 -0
  46. package/lib/ui/Table/TableCell.d.ts +1 -0
  47. package/lib/ui/Table/TableFilterBar/TableFilterBar.d.ts +2 -2
  48. package/lib/ui/Table/TableFilterBar/TableFilterBar.js +2 -3
  49. package/lib/ui/Table/TableFilterBar/TableFilterBar.js.map +1 -1
  50. package/lib/ui/Table/TableRow.d.ts +1 -0
  51. package/lib/ui/TextButton.d.ts +1 -0
  52. package/lib/ui/TextField/SearchTextField.d.ts +1 -0
  53. package/lib/ui/TextField/TextField.js +1 -1
  54. package/lib/ui/TextField/TextField.js.map +1 -1
  55. package/lib/ui/device/BatteryStatus.d.ts +1 -0
  56. package/lib/ui/device/LockStatus.d.ts +1 -0
  57. package/lib/ui/device/OnlineStatus.d.ts +1 -0
  58. package/lib/ui/layout/ContentHeader.d.ts +1 -0
  59. package/lib/ui/layout/DotDivider.d.ts +1 -0
  60. package/lib/ui/typography/Title.d.ts +1 -0
  61. package/lib/version.d.ts +2 -0
  62. package/lib/version.js +1 -0
  63. package/lib/version.js.map +1 -0
  64. package/package.json +4 -3
  65. package/src/lib/SeamProvider.tsx +17 -10
  66. package/src/lib/seam/use-seam-font.ts +10 -11
  67. package/src/lib/seam/use-seam-styles.ts +23 -13
  68. package/src/lib/ui/Alert/Alert.tsx +1 -1
  69. package/src/lib/ui/Alert/Alerts.tsx +4 -2
  70. package/src/lib/ui/Button.tsx +1 -1
  71. package/src/lib/ui/DeviceDetails/DeviceDetails.tsx +11 -31
  72. package/src/lib/ui/DeviceTable/DeviceHealthBar.tsx +2 -2
  73. package/src/lib/ui/Menu/Menu.tsx +5 -4
  74. package/src/lib/ui/SupportedDeviceTable/FilterCategoryMenu.tsx +6 -11
  75. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceContent.tsx +42 -54
  76. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.tsx +4 -4
  77. package/src/lib/ui/SupportedDeviceTable/SupportedDeviceRow.tsx +1 -1
  78. package/src/lib/ui/Table/TableFilterBar/TableFilterBar.tsx +3 -6
  79. package/src/lib/ui/TextField/TextField.tsx +1 -1
  80. package/src/lib/version.ts +1 -0
  81. package/src/styles/_main.scss +2 -2
  82. package/src/styles/_menus.scss +9 -1
  83. package/src/styles/{_supported-devices.scss → _supported-device-table.scss} +8 -8
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import type { LockDevice } from 'seamapi';
2
3
  interface BatteryStatusProps {
3
4
  device: LockDevice;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import type { LockDevice } from 'seamapi';
2
3
  interface LockStatusProps {
3
4
  device: LockDevice;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import type { LockDevice } from 'seamapi';
2
3
  interface OnlineStatusProps {
3
4
  device: LockDevice;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  export declare function ContentHeader(props: {
2
3
  title?: string;
3
4
  onBack?: () => void;
@@ -1 +1,2 @@
1
+ /// <reference types="react" />
1
2
  export declare function DotDivider(): JSX.Element;
@@ -1,2 +1,3 @@
1
+ /// <reference types="react" />
1
2
  import type { SpanProps } from '../../../lib/ui/types.js';
2
3
  export declare function Title({ children, className, ...props }: SpanProps): JSX.Element;
@@ -0,0 +1,2 @@
1
+ declare const _default: null;
2
+ export default _default;
package/lib/version.js ADDED
@@ -0,0 +1 @@
1
+ export default '1.4.0'
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/lib/version.ts"],"names":[],"mappings":"AAAA,eAAe,IAAI,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamapi/react",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Seam React component library.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -65,6 +65,7 @@
65
65
  "lint": "eslint --ignore-path .gitignore --ext .js,.cjs,.mjs,.ts,.tsx .",
66
66
  "prelint": "prettier --check --ignore-path .gitignore .",
67
67
  "postlint": "stylelint '**/*.scss'",
68
+ "prepack": "echo \"export default '$(jq -r .version < package.json)'\" > lib/version.js",
68
69
  "postversion": "git push --follow-tags",
69
70
  "storybook": "storybook dev --port 6006",
70
71
  "storybook:docs": "storybook dev --docs --port 6007",
@@ -94,7 +95,7 @@
94
95
  "@tanstack/react-query": "^4.28.0",
95
96
  "classnames": "^2.3.2",
96
97
  "luxon": "^3.3.0",
97
- "seamapi": "^5.21.1",
98
+ "seamapi": "^6.1.0",
98
99
  "uuid": "^9.0.0"
99
100
  },
100
101
  "devDependencies": {
@@ -113,7 +114,7 @@
113
114
  "@storybook/react": "^7.0.2",
114
115
  "@storybook/react-webpack5": "^7.0.2",
115
116
  "@storybook/testing-library": "^0.1.0",
116
- "@svgr/cli": "^7.0.0",
117
+ "@svgr/cli": "^8.0.1",
117
118
  "@testing-library/react": "^14.0.0",
118
119
  "@types/jest": "^29.5.0",
119
120
  "@types/luxon": "^3.3.0",
@@ -25,14 +25,12 @@ export interface SeamContext {
25
25
  clientSessionToken?: string | undefined
26
26
  }
27
27
 
28
- type SeamProviderProps = {
29
- disableCssInjection?: boolean | undefined
30
- disableFontInjection?: boolean | undefined
31
- } & (
32
- | SeamProviderPropsWithClient
33
- | (SeamProviderPropsWithPublishableKey & AllowedSeamClientOptions)
34
- | (SeamProviderPropsWithClientSessionToken & AllowedSeamClientOptions)
35
- )
28
+ type SeamProviderProps = SeamProviderBaseProps &
29
+ (
30
+ | SeamProviderPropsWithClient
31
+ | (SeamProviderPropsWithPublishableKey & AllowedSeamClientOptions)
32
+ | (SeamProviderPropsWithClientSessionToken & AllowedSeamClientOptions)
33
+ )
36
34
 
37
35
  interface SeamProviderPropsWithClient {
38
36
  client: Seam
@@ -47,10 +45,19 @@ interface SeamProviderPropsWithClientSessionToken {
47
45
  clientSessionToken: string
48
46
  }
49
47
 
48
+ interface SeamProviderBaseProps {
49
+ disableCssInjection?: boolean | undefined
50
+ disableFontInjection?: boolean | undefined
51
+ useUnminifiedCss?: boolean | undefined
52
+ }
53
+
50
54
  type AllowedSeamClientOptions = Pick<SeamClientOptions, 'endpoint'>
51
55
 
52
56
  export function SeamProvider({
53
57
  children,
58
+ disableCssInjection = false,
59
+ disableFontInjection = false,
60
+ useUnminifiedCss = false,
54
61
  ...props
55
62
  }: PropsWithChildren<SeamProviderProps>): JSX.Element {
56
63
  const { Provider } = seamContext
@@ -76,8 +83,8 @@ export function SeamProvider({
76
83
  )
77
84
  }
78
85
 
79
- useSeamStyles({ enabled: !(props.disableCssInjection ?? false) })
80
- useSeamFont({ enabled: !(props.disableFontInjection ?? false) })
86
+ useSeamStyles({ disabled: disableCssInjection, unminified: useUnminifiedCss })
87
+ useSeamFont({ disabled: disableFontInjection })
81
88
 
82
89
  return (
83
90
  <div className='seam-components'>
@@ -3,19 +3,18 @@ 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 = ({ enabled }: { enabled: boolean }) => {
6
+ export const useSeamFont = ({ disabled = false }: { disabled?: boolean }) => {
7
7
  useEffect(() => {
8
+ if (disabled) return
8
9
  if (globalThis.document == null) return
9
- if (!enabled) return
10
- const linkEl = globalThis.document.querySelector(`link[href="${fontUrl}"]`)
11
10
 
12
- if (linkEl === null) {
13
- const link = globalThis.document.createElement('link')
14
- link.rel = 'stylesheet'
15
- link.type = 'text/css'
16
- link.href = fontUrl
11
+ const linkEl = globalThis.document.querySelector(`link[href="${fontUrl}"]`)
12
+ if (linkEl != null) return
17
13
 
18
- globalThis.document.head.appendChild(link)
19
- }
20
- }, [enabled])
14
+ const link = globalThis.document.createElement('link')
15
+ link.rel = 'stylesheet'
16
+ link.type = 'text/css'
17
+ link.href = fontUrl
18
+ globalThis.document.head.appendChild(link)
19
+ }, [disabled])
21
20
  }
@@ -1,21 +1,31 @@
1
1
  import { useEffect } from 'react'
2
2
 
3
- // TODO figure out the version automatically
4
- const cssUrl = 'https://www.unpkg.com/@seamapi/react@1.1.0/index.min.css'
3
+ import version from 'lib/version.js'
4
+
5
+ const origin = 'https://react.seam.co'
6
+
7
+ export const useSeamStyles = ({
8
+ disabled = false,
9
+ unminified = false,
10
+ }: {
11
+ unminified?: boolean
12
+ disabled?: boolean
13
+ }) => {
14
+ const ext = `${unminified ? '' : 'min.'}css`
15
+ const cssUrl = `${origin}/v/${version ?? ''}/index.${ext}`
5
16
 
6
- export const useSeamStyles = ({ enabled }: { enabled: boolean }) => {
7
17
  useEffect(() => {
18
+ if (version === null) return
19
+ if (disabled) return
8
20
  if (globalThis.document == null) return
9
- if (!enabled) return
10
- const linkEl = globalThis.document.querySelector(`link[href="${cssUrl}"]`)
11
21
 
12
- if (linkEl === null) {
13
- const link = globalThis.document.createElement('link')
14
- link.rel = 'stylesheet'
15
- link.type = 'text/css'
16
- link.href = cssUrl
22
+ const linkEl = globalThis.document.querySelector(`link[href="${cssUrl}"]`)
23
+ if (linkEl != null) return
17
24
 
18
- globalThis.document.head.appendChild(link)
19
- }
20
- }, [enabled])
25
+ const link = globalThis.document.createElement('link')
26
+ link.rel = 'stylesheet'
27
+ link.type = 'text/css'
28
+ link.href = cssUrl
29
+ globalThis.document.head.appendChild(link)
30
+ }, [disabled, cssUrl])
21
31
  }
@@ -35,7 +35,7 @@ export function Alert(props: AlertProps): JSX.Element {
35
35
  <p className='seam-alert-message'>{message}</p>
36
36
  </div>
37
37
  </div>
38
- {props.action == null ? null : <Action {...props.action} />}
38
+ {props.action != null && <Action {...props.action} />}
39
39
  </div>
40
40
  )
41
41
  }
@@ -1,14 +1,16 @@
1
1
  import classNames from 'classnames'
2
+ import type { PropsWithChildren } from 'react'
2
3
 
3
4
  import { Alert, type AlertProps } from 'lib/ui/Alert/Alert.js'
4
5
 
5
6
  interface AlertsProps {
6
7
  alerts?: AlertProps[]
7
- children?: JSX.Element
8
8
  className?: string
9
9
  }
10
10
 
11
- export function Alerts(props: AlertsProps): JSX.Element | null {
11
+ export function Alerts(
12
+ props: PropsWithChildren<AlertsProps>
13
+ ): JSX.Element | null {
12
14
  const { alerts, children, className } = props
13
15
 
14
16
  if (alerts?.length === 0) return null
@@ -13,7 +13,7 @@ export function Button({
13
13
  variant = 'outline',
14
14
  children,
15
15
  size = 'medium',
16
- disabled,
16
+ disabled = false,
17
17
  onClick,
18
18
  className,
19
19
  }: PropsWithChildren<ButtonProps>): JSX.Element {
@@ -7,7 +7,6 @@ import { useAccessCodes } from 'lib/seam/access-codes/use-access-codes.js'
7
7
  import { isLockDevice } from 'lib/seam/devices/types.js'
8
8
  import { useToggleLock } from 'lib/seam/devices/use-toggle-lock.js'
9
9
  import { AccessCodeTable } from 'lib/ui/AccessCodeTable/AccessCodeTable.js'
10
- import type { AlertProps } from 'lib/ui/Alert/Alert.js'
11
10
  import { Alerts } from 'lib/ui/Alert/Alerts.js'
12
11
  import { Button } from 'lib/ui/Button.js'
13
12
  import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
@@ -57,36 +56,6 @@ function LockDeviceDetails(props: { device: LockDevice; onBack?: () => void }) {
57
56
  const accessCodeLength =
58
57
  device.properties?.schlage_metadata?.access_code_length
59
58
 
60
- function generateDeviceAlerts() {
61
- if (device.errors.length === 0 && device.warnings.length === 0) {
62
- return []
63
- }
64
-
65
- const alerts: AlertProps[] = []
66
-
67
- if (device.errors.length > 0) {
68
- alerts.push(
69
- ...device.errors.map((error) => ({
70
- variant: 'error' as const,
71
- message: error.message,
72
- }))
73
- )
74
- }
75
-
76
- if (device.warnings.length > 0) {
77
- alerts.push(
78
- ...device.warnings.map((warning) => ({
79
- variant: 'warning' as const,
80
- message: warning.message,
81
- }))
82
- )
83
- }
84
-
85
- return alerts
86
- }
87
-
88
- const alerts = generateDeviceAlerts()
89
-
90
59
  if (accessCodes == null) {
91
60
  return null
92
61
  }
@@ -100,6 +69,17 @@ function LockDeviceDetails(props: { device: LockDevice; onBack?: () => void }) {
100
69
  )
101
70
  }
102
71
 
72
+ const alerts = [
73
+ ...device.errors.map((error) => ({
74
+ variant: 'error' as const,
75
+ message: error.message,
76
+ })),
77
+ ...device.warnings.map((warning) => ({
78
+ variant: 'warning' as const,
79
+ message: warning.message,
80
+ })),
81
+ ]
82
+
103
83
  return (
104
84
  <div className='seam-device-details'>
105
85
  <ContentHeader title='Device' onBack={onBack} />
@@ -32,7 +32,7 @@ export function DeviceHealthBar({
32
32
 
33
33
  if (issueCount === 0) {
34
34
  return (
35
- <TableFilterBar>
35
+ <TableFilterBar filterCleared>
36
36
  <TableFilterItem>
37
37
  <CheckIcon />
38
38
  {t.devicesOk}
@@ -43,7 +43,7 @@ export function DeviceHealthBar({
43
43
 
44
44
  return (
45
45
  <TableFilterBar
46
- isFilterCleared={filter == null}
46
+ filterCleared={filter == null}
47
47
  onFilterClear={() => {
48
48
  onFilterSelect(null)
49
49
  }}
@@ -135,10 +135,11 @@ export function Menu({
135
135
  {renderButton({ onOpen: handleOpen })}
136
136
  {createPortal(
137
137
  <div
138
- className={classNames('seam-menu-bg', backgroundProps?.className)}
139
- style={{
140
- display: visible ? 'flex' : 'none',
141
- }}
138
+ className={classNames(
139
+ 'seam-menu-bg',
140
+ backgroundProps?.className,
141
+ visible ? 'seam-menu-visible' : 'seam-menu-hidden'
142
+ )}
142
143
  onClick={(event) => {
143
144
  event.stopPropagation()
144
145
  handleClose()
@@ -11,26 +11,26 @@ interface FilterCategoryMenuBaseProps {
11
11
 
12
12
  export type FilterCategoryMenuProps =
13
13
  | (FilterCategoryMenuBaseProps & {
14
- addAllOption: true
14
+ hideAllOption?: false
15
15
  onAllOptionSelect: () => void
16
16
  })
17
17
  | (FilterCategoryMenuBaseProps & {
18
- addAllOption?: false
18
+ hideAllOption: true
19
19
  onAllOptionSelect?: never
20
20
  })
21
21
 
22
22
  export function FilterCategoryMenu({
23
- label,
23
+ label = 'Filter',
24
24
  options,
25
- addAllOption,
25
+ hideAllOption = false,
26
26
  onSelect,
27
27
  onAllOptionSelect,
28
28
  buttonLabel,
29
29
  }: FilterCategoryMenuProps) {
30
- const usableOptions = addAllOption !== null ? ['All', ...options] : options
30
+ const usableOptions = hideAllOption ? options : ['All', ...options]
31
31
 
32
32
  return (
33
- <div className='seam-supported-devices-filter-menu-wrap'>
33
+ <div className='seam-supported-device-table-filter-menu-wrap'>
34
34
  <p>{label}</p>
35
35
  <Menu
36
36
  renderButton={({ onOpen }) => (
@@ -58,8 +58,3 @@ export function FilterCategoryMenu({
58
58
  </div>
59
59
  )
60
60
  }
61
-
62
- FilterCategoryMenu.defaultProps = {
63
- addAllOption: true,
64
- buttonLabel: 'Filter',
65
- }
@@ -1,6 +1,5 @@
1
1
  import { useQuery } from '@tanstack/react-query'
2
- import axios from 'axios'
3
- import { useCallback, useEffect, useState } from 'react'
2
+ import { useState } from 'react'
4
3
 
5
4
  import { Button } from 'lib/ui/Button.js'
6
5
  import { SupportedDeviceFilterArea } from 'lib/ui/SupportedDeviceTable/SupportedDeviceFilterArea.js'
@@ -9,8 +8,6 @@ import type { DeviceModel, Filters } from 'lib/ui/SupportedDeviceTable/types.js'
9
8
  import { SupportedDeviceHeader } from './SupportedDeviceHeader.js'
10
9
  import { SupportedDeviceRow } from './SupportedDeviceRow.js'
11
10
 
12
- const BASE_URL = 'https://devicedb.seam.co/api/device_models/list'
13
-
14
11
  export interface SupportedDeviceContentProps {
15
12
  cannotFilter?: boolean
16
13
  }
@@ -18,7 +15,6 @@ export interface SupportedDeviceContentProps {
18
15
  export function SupportedDeviceContent({
19
16
  cannotFilter = false,
20
17
  }: SupportedDeviceContentProps) {
21
- const [allDeviceModels, setAllDeviceModels] = useState<DeviceModel[]>([])
22
18
  const [filterValue, setFilterValue] = useState('')
23
19
  const [filters, setFilters] = useState<Filters>({
24
20
  supportedOnly: false,
@@ -26,54 +22,46 @@ export function SupportedDeviceContent({
26
22
  brand: null,
27
23
  })
28
24
 
29
- const fetchDeviceModels = useCallback(async () => {
30
- const queries = []
31
-
32
- if (filterValue.trim() !== '') {
33
- queries.push(`text_search=${encodeURIComponent(filterValue.trim())}`)
34
- }
35
-
36
- if (filters.supportedOnly) {
37
- queries.push('support_level=live')
38
- }
39
-
40
- if (filters.category !== null) {
41
- queries.push(`main_category=${encodeURIComponent(filters.category)}`)
42
- }
43
-
44
- if (filters.brand !== null) {
45
- queries.push(`brand=${encodeURIComponent(filters.brand)}`)
46
- }
47
-
48
- const url = `${BASE_URL}?${queries.join('&')}`
49
- return await axios.get(url)
50
- }, [filterValue, filters])
51
-
52
- const { data, isLoading, isError, refetch } = useQuery<{
53
- data: {
54
- device_models?: DeviceModel[]
55
- }
56
- }>({
25
+ const {
26
+ data: deviceModels,
27
+ isLoading,
28
+ isError,
29
+ refetch,
30
+ } = useQuery<DeviceModel[]>({
57
31
  queryKey: ['supported_devices', filterValue, filters],
58
- queryFn: fetchDeviceModels,
32
+ queryFn: async () => {
33
+ const url = new URL('https://devicedb.seam.co/api/device_models/list')
34
+
35
+ if (filterValue.trim() !== '') {
36
+ url.searchParams.set('text_search', filterValue.trim())
37
+ }
38
+
39
+ if (filters.supportedOnly) {
40
+ url.searchParams.set('support_level', 'live')
41
+ }
42
+
43
+ if (filters.category !== null) {
44
+ url.searchParams.set('main_category', filters.category)
45
+ }
46
+
47
+ if (filters.brand !== null) {
48
+ url.searchParams.set('brand', filters.brand)
49
+ }
50
+
51
+ const res = await fetch(url)
52
+ if (!res.ok) {
53
+ throw new Error('Failed to load device models')
54
+ }
55
+ const data = await res.json()
56
+ return data?.device_models ?? []
57
+ },
59
58
  })
60
59
 
61
- useEffect(() => {
62
- if (
63
- data?.data?.device_models !== undefined &&
64
- allDeviceModels.length === 0
65
- ) {
66
- setAllDeviceModels(data.data.device_models)
67
- }
68
- }, [data, allDeviceModels.length])
69
-
70
- const deviceModels = data?.data?.device_models ?? []
71
-
72
60
  return (
73
- <div className='seam-supported-devices-table-wrap'>
61
+ <div className='seam-supported-device-table-content-wrap'>
74
62
  {!cannotFilter && (
75
63
  <SupportedDeviceFilterArea
76
- deviceModels={allDeviceModels}
64
+ deviceModels={deviceModels ?? []}
77
65
  filterValue={filterValue}
78
66
  setFilterValue={setFilterValue}
79
67
  filters={filters}
@@ -82,13 +70,13 @@ export function SupportedDeviceContent({
82
70
  )}
83
71
 
84
72
  {isLoading && (
85
- <div className='seam-supported-devices-table-state-block'>
73
+ <div className='seam-supported-device-table-content-state-block'>
86
74
  <p>Loading device models...</p>
87
75
  </div>
88
76
  )}
89
77
 
90
78
  {isError && (
91
- <div className='seam-supported-devices-table-state-block'>
79
+ <div className='seam-supported-device-table-content-state-block'>
92
80
  <p>There was an error fetching device models.</p>
93
81
  <Button
94
82
  variant='solid'
@@ -102,8 +90,8 @@ export function SupportedDeviceContent({
102
90
  </div>
103
91
  )}
104
92
 
105
- {!isLoading && !isError && data?.data?.device_models !== null && (
106
- <table className='seam-supported-devices-table'>
93
+ {!isLoading && !isError && deviceModels !== null && (
94
+ <table className='seam-supported-device-table-content'>
107
95
  <SupportedDeviceHeader />
108
96
  <tbody>
109
97
  {deviceModels.length !== 0 &&
@@ -115,9 +103,9 @@ export function SupportedDeviceContent({
115
103
  ))}
116
104
 
117
105
  {deviceModels.length === 0 && (
118
- <tr className='seam-supported-devices-table-message-row'>
106
+ <tr className='seam-supported-device-table-content-message-row'>
119
107
  <td colSpan={6}>
120
- <div className='seam-supported-devices-table-message'>
108
+ <div className='seam-supported-device-table-content-message'>
121
109
  {filterValue.length === 0 ? (
122
110
  <p>No device models found.</p>
123
111
  ) : (
@@ -129,7 +117,7 @@ export function SupportedDeviceContent({
129
117
  onClick={() => {
130
118
  setFilterValue('')
131
119
  }}
132
- className='seam-supported-devices-table-message-clear-search'
120
+ className='seam-supported-device-table-content-message-clear-search'
133
121
  >
134
122
  Clear search terms
135
123
  </Button>
@@ -55,7 +55,7 @@ export function SupportedDeviceFilterArea({
55
55
  : 'Filter'
56
56
 
57
57
  return (
58
- <div className='seam-supported-devices-filter-area'>
58
+ <div className='seam-supported-device-table-filter-area'>
59
59
  <div className='seam-deliberate-block' />
60
60
  <div className='seam-filters-wrap'>
61
61
  <Menu
@@ -70,7 +70,7 @@ export function SupportedDeviceFilterArea({
70
70
  )}
71
71
  >
72
72
  <div
73
- className='seam-supported-devices-filter-menu'
73
+ className='seam-supported-device-table-filter-menu'
74
74
  onClick={(event) => {
75
75
  event.stopPropagation()
76
76
  }}
@@ -116,13 +116,13 @@ export function SupportedDeviceFilterArea({
116
116
  </div>
117
117
  </Menu>
118
118
 
119
- <div className='seam-supported-devices-filter-area-search-bar-wrap'>
119
+ <div className='seam-supported-device-table-filter-area-search-bar-wrap'>
120
120
  <SearchTextField
121
121
  value={filterValue}
122
122
  onChange={(value) => {
123
123
  setFilterValue(value)
124
124
  }}
125
- className='seam-supported-devices-filter-area-search-bar'
125
+ className='seam-supported-device-table-filter-area-search-bar'
126
126
  />
127
127
  </div>
128
128
  </div>
@@ -11,7 +11,7 @@ export function SupportedDeviceRow({ deviceModel }: SupportedDeviceRowProps) {
11
11
  return (
12
12
  <tr>
13
13
  <td>
14
- <img style={{ width: 40 }} src={deviceModel.icon_url} />
14
+ <img width={40} src={deviceModel.icon_url} />
15
15
  </td>
16
16
  <td>{deviceModel.main_category}</td>
17
17
  <td>{deviceModel.model_name}</td>
@@ -3,23 +3,20 @@ import { IconButton } from 'lib/ui/IconButton.js'
3
3
  import type { ButtonProps, DivProps } from 'lib/ui/types.js'
4
4
 
5
5
  interface TableFilterBarProps extends DivProps {
6
- isFilterCleared?: boolean
6
+ filterCleared?: boolean
7
7
  onFilterClear?: () => void
8
8
  }
9
9
 
10
10
  export function TableFilterBar({
11
- isFilterCleared,
11
+ filterCleared = false,
12
12
  onFilterClear,
13
13
  children,
14
14
  ...props
15
15
  }: TableFilterBarProps) {
16
- const showClearFilterButton = isFilterCleared === false
17
16
  return (
18
17
  <div className='seam-table-filter-bar' {...props}>
19
18
  {children}{' '}
20
- {showClearFilterButton ? (
21
- <ClearFiltersButton onClick={onFilterClear} />
22
- ) : null}
19
+ {!filterCleared && <ClearFiltersButton onClick={onFilterClear} />}
23
20
  </div>
24
21
  )
25
22
  }
@@ -23,7 +23,7 @@ export function TextField(props: TextFieldProps): JSX.Element {
23
23
  startAdornment,
24
24
  endAdornment,
25
25
  inputProps,
26
- disabled,
26
+ disabled = false,
27
27
  } = props
28
28
 
29
29
  return (
@@ -0,0 +1 @@
1
+ export default null
@@ -12,7 +12,7 @@
12
12
  @use './device-details';
13
13
  @use './device-table';
14
14
  @use './access-code-details';
15
- @use './supported-devices';
15
+ @use './supported-device-table';
16
16
 
17
17
  .seam-components {
18
18
  // Reset
@@ -33,5 +33,5 @@
33
33
  @include access-code-table.all;
34
34
  @include access-code-details.all;
35
35
  @include alert.all;
36
- @include supported-devices.all;
36
+ @include supported-device-table.all;
37
37
  }
@@ -9,7 +9,15 @@
9
9
  position: fixed;
10
10
  inset: 0;
11
11
  z-index: 999999;
12
- display: none; // Toggled in JS (component)
12
+ display: none;
13
+ }
14
+
15
+ .seam-menu-hidden {
16
+ display: none;
17
+ }
18
+
19
+ .seam-menu-visible {
20
+ display: flex;
13
21
  }
14
22
 
15
23
  .seam-menu-content {