@seamapi/react 0.23.0 → 0.25.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 (77) hide show
  1. package/index.css +140 -0
  2. package/index.css.map +1 -1
  3. package/index.min.css +1 -1
  4. package/index.min.css.map +1 -1
  5. package/lib/icons/ArrowRestart.d.ts +2 -0
  6. package/lib/icons/ArrowRestart.js +5 -0
  7. package/lib/icons/ArrowRestart.js.map +1 -0
  8. package/lib/icons/Check.d.ts +2 -0
  9. package/lib/icons/Check.js +3 -0
  10. package/lib/icons/Check.js.map +1 -0
  11. package/lib/icons/ExclamationCircle.d.ts +2 -0
  12. package/lib/icons/ExclamationCircle.js +5 -0
  13. package/lib/icons/ExclamationCircle.js.map +1 -0
  14. package/lib/icons/ExclamationCircleOutline.d.ts +2 -0
  15. package/lib/icons/ExclamationCircleOutline.js +5 -0
  16. package/lib/icons/ExclamationCircleOutline.js.map +1 -0
  17. package/lib/icons/TriangleWarning.d.ts +2 -0
  18. package/lib/icons/TriangleWarning.js +5 -0
  19. package/lib/icons/TriangleWarning.js.map +1 -0
  20. package/lib/ui/AccessCodeTable/AccessCodeTable.js +24 -11
  21. package/lib/ui/AccessCodeTable/AccessCodeTable.js.map +1 -1
  22. package/lib/ui/Alert/Alert.d.ts +11 -0
  23. package/lib/ui/Alert/Alert.js +12 -0
  24. package/lib/ui/Alert/Alert.js.map +1 -0
  25. package/lib/ui/Alert/Alerts.d.ts +8 -0
  26. package/lib/ui/Alert/Alerts.js +8 -0
  27. package/lib/ui/Alert/Alerts.js.map +1 -0
  28. package/lib/ui/DeviceDetails/DeviceDetails.js +23 -1
  29. package/lib/ui/DeviceDetails/DeviceDetails.js.map +1 -1
  30. package/lib/ui/DeviceTable/DeviceHealthBar.d.ts +9 -0
  31. package/lib/ui/DeviceTable/DeviceHealthBar.js +29 -0
  32. package/lib/ui/DeviceTable/DeviceHealthBar.js.map +1 -0
  33. package/lib/ui/DeviceTable/DeviceRow.d.ts +11 -0
  34. package/lib/ui/DeviceTable/DeviceRow.js +24 -0
  35. package/lib/ui/DeviceTable/DeviceRow.js.map +1 -0
  36. package/lib/ui/DeviceTable/DeviceTable.js +23 -19
  37. package/lib/ui/DeviceTable/DeviceTable.js.map +1 -1
  38. package/lib/ui/IconButton.d.ts +1 -1
  39. package/lib/ui/IconButton.js +3 -2
  40. package/lib/ui/IconButton.js.map +1 -1
  41. package/lib/ui/SupportedDevices/SupportedDevices.d.ts +1 -1
  42. package/lib/ui/SupportedDevices/SupportedDevices.js +1 -1
  43. package/lib/ui/SupportedDevices/SupportedDevices.js.map +1 -1
  44. package/lib/ui/Table/EmptyPlaceholder.d.ts +2 -0
  45. package/lib/ui/Table/EmptyPlaceholder.js +5 -0
  46. package/lib/ui/Table/EmptyPlaceholder.js.map +1 -0
  47. package/lib/ui/Table/TableFilterBar/TableFilterBar.d.ts +6 -0
  48. package/lib/ui/Table/TableFilterBar/TableFilterBar.js +11 -0
  49. package/lib/ui/Table/TableFilterBar/TableFilterBar.js.map +1 -0
  50. package/lib/ui/Table/TableFilterBar/TableFilterItem.d.ts +5 -0
  51. package/lib/ui/Table/TableFilterBar/TableFilterItem.js +9 -0
  52. package/lib/ui/Table/TableFilterBar/TableFilterItem.js.map +1 -0
  53. package/lib/ui/index.d.ts +1 -0
  54. package/lib/ui/index.js +1 -0
  55. package/lib/ui/index.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/lib/icons/ArrowRestart.tsx +32 -0
  58. package/src/lib/icons/Check.tsx +15 -0
  59. package/src/lib/icons/ExclamationCircle.tsx +32 -0
  60. package/src/lib/icons/ExclamationCircleOutline.tsx +32 -0
  61. package/src/lib/icons/TriangleWarning.tsx +32 -0
  62. package/src/lib/ui/AccessCodeTable/AccessCodeTable.tsx +73 -40
  63. package/src/lib/ui/Alert/Alert.tsx +52 -0
  64. package/src/lib/ui/Alert/Alerts.tsx +22 -0
  65. package/src/lib/ui/DeviceDetails/DeviceDetails.tsx +37 -0
  66. package/src/lib/ui/DeviceTable/DeviceHealthBar.tsx +66 -0
  67. package/src/lib/ui/DeviceTable/DeviceRow.tsx +60 -0
  68. package/src/lib/ui/DeviceTable/DeviceTable.tsx +43 -42
  69. package/src/lib/ui/IconButton.tsx +6 -2
  70. package/src/lib/ui/SupportedDevices/SupportedDevices.tsx +1 -1
  71. package/src/lib/ui/Table/EmptyPlaceholder.tsx +12 -0
  72. package/src/lib/ui/Table/TableFilterBar/TableFilterBar.tsx +31 -0
  73. package/src/lib/ui/Table/TableFilterBar/TableFilterItem.tsx +24 -0
  74. package/src/lib/ui/index.ts +1 -0
  75. package/src/styles/_alert.scss +115 -0
  76. package/src/styles/_main.scss +2 -0
  77. package/src/styles/_tables.scss +65 -0
@@ -0,0 +1,52 @@
1
+ import classNames from 'classnames'
2
+ import type { MouseEventHandler } from 'react'
3
+
4
+ import { ExclamationCircleIcon } from 'lib/icons/ExclamationCircle.js'
5
+ import { TriangleWarningIcon } from 'lib/icons/TriangleWarning.js'
6
+
7
+ export interface AlertProps {
8
+ variant: 'warning' | 'error'
9
+ message: string
10
+ action?: {
11
+ label: string
12
+ onClick: MouseEventHandler
13
+ }
14
+ className?: string
15
+ }
16
+
17
+ export function Alert(props: AlertProps): JSX.Element {
18
+ const { variant, message, action, className, ...rest } = props
19
+
20
+ const handleClick: MouseEventHandler<HTMLButtonElement> = (ev) => {
21
+ action?.onClick(ev)
22
+ }
23
+
24
+ return (
25
+ <div
26
+ className={classNames('seam-alert', `seam-${variant}-alert`, className)}
27
+ {...rest}
28
+ >
29
+ <div className='seam-alert-content'>
30
+ <div className='seam-alert-icon'>
31
+ {variant === 'warning' ? (
32
+ <TriangleWarningIcon />
33
+ ) : (
34
+ <ExclamationCircleIcon />
35
+ )}
36
+ </div>
37
+
38
+ <div className='seam-alert-message-wrap'>
39
+ <p className='seam-alert-message'>{message}</p>
40
+ </div>
41
+ </div>
42
+
43
+ {action != null && (
44
+ <div className='seam-alert-action-wrap'>
45
+ <button onClick={handleClick} className='seam-alert-action'>
46
+ {action.label}
47
+ </button>
48
+ </div>
49
+ )}
50
+ </div>
51
+ )
52
+ }
@@ -0,0 +1,22 @@
1
+ import classNames from 'classnames'
2
+
3
+ import { Alert, type AlertProps } from 'lib/ui/Alert/Alert.js'
4
+
5
+ export interface AlertsProps {
6
+ alerts?: AlertProps[]
7
+ children?: React.ReactNode
8
+ className?: string
9
+ }
10
+
11
+ export function Alerts(props: AlertsProps): JSX.Element {
12
+ const { alerts, children, className, ...rest } = props
13
+
14
+ return (
15
+ <div className={classNames('seam-alerts', className)} {...rest}>
16
+ {alerts?.map((alert, index) => (
17
+ <Alert key={`${index}:${alert.message}`} {...alert} />
18
+ ))}
19
+ {children}
20
+ </div>
21
+ )
22
+ }
@@ -7,6 +7,8 @@ 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
+ import { Alerts } from 'lib/ui/Alert/Alerts.js'
10
12
  import { Button } from 'lib/ui/Button.js'
11
13
  import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
12
14
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
@@ -40,6 +42,7 @@ export function DeviceDetails(props: DeviceDetailsProps): JSX.Element | null {
40
42
 
41
43
  function LockDeviceDetails(props: { device: LockDevice; onBack?: () => void }) {
42
44
  const { device, onBack } = props
45
+
43
46
  const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
44
47
  const toggleLock = useToggleLock(device)
45
48
  const { accessCodes } = useAccessCodes({
@@ -54,6 +57,36 @@ function LockDeviceDetails(props: { device: LockDevice; onBack?: () => void }) {
54
57
  const accessCodeLength =
55
58
  device.properties?.schlage_metadata?.access_code_length
56
59
 
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
+
57
90
  if (accessCodes == null) {
58
91
  return null
59
92
  }
@@ -88,6 +121,10 @@ function LockDeviceDetails(props: { device: LockDevice; onBack?: () => void }) {
88
121
  </div>
89
122
  </div>
90
123
  </div>
124
+
125
+ {alerts.length > 0 && (
126
+ <Alerts alerts={alerts} className='seam-alerts-space-top' />
127
+ )}
91
128
  </div>
92
129
  <div className='seam-box'>
93
130
  <div
@@ -0,0 +1,66 @@
1
+ import { CheckIcon } from 'lib/icons/Check.js'
2
+ import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
3
+ import type { UseDevicesData } from 'lib/seam/devices/use-devices.js'
4
+ import { TableFilterBar } from 'lib/ui/Table/TableFilterBar/TableFilterBar.js'
5
+ import { TableFilterItem } from 'lib/ui/Table/TableFilterBar/TableFilterItem.js'
6
+
7
+ export type DeviceFilter = 'device_issues'
8
+
9
+ interface DeviceHealthBarProps {
10
+ devices: Array<UseDevicesData[number]>
11
+ filter: DeviceFilter | null
12
+ onFilterSelect: (filter: DeviceFilter | null) => void
13
+ }
14
+
15
+ export function DeviceHealthBar({
16
+ devices,
17
+ filter,
18
+ onFilterSelect,
19
+ }: DeviceHealthBarProps) {
20
+ const erroredDevices = devices.filter((device) => device.errors.length > 0)
21
+ const issueCount = erroredDevices.length
22
+
23
+ const toggle = (target: DeviceFilter) => () => {
24
+ const newFilter = target === filter ? null : target
25
+ onFilterSelect(newFilter)
26
+ }
27
+
28
+ const isPlural = issueCount === 0 || issueCount > 1
29
+ const label = isPlural
30
+ ? `${issueCount} ${t.deviceIssues}`
31
+ : `${issueCount} ${t.deviceIssue}`
32
+
33
+ if (issueCount === 0) {
34
+ return (
35
+ <TableFilterBar>
36
+ <TableFilterItem>
37
+ <CheckIcon />
38
+ {t.devicesOk}
39
+ </TableFilterItem>
40
+ </TableFilterBar>
41
+ )
42
+ }
43
+
44
+ return (
45
+ <TableFilterBar
46
+ isFilterCleared={filter == null}
47
+ onFilterClear={() => {
48
+ onFilterSelect(null)
49
+ }}
50
+ >
51
+ <TableFilterItem
52
+ onClick={toggle('device_issues')}
53
+ selected={filter === 'device_issues'}
54
+ >
55
+ <ExclamationCircleOutlineIcon />
56
+ {label}
57
+ </TableFilterItem>
58
+ </TableFilterBar>
59
+ )
60
+ }
61
+
62
+ const t = {
63
+ devicesOk: 'Devices OK',
64
+ deviceIssue: 'device issue',
65
+ deviceIssues: 'device issues',
66
+ }
@@ -0,0 +1,60 @@
1
+ import { isLockDevice } from 'lib/seam/devices/types.js'
2
+ import {
3
+ type UseDevicesData,
4
+ type UseDevicesParams,
5
+ } from 'lib/seam/devices/use-devices.js'
6
+ import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
7
+ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
8
+ import { LockStatus } from 'lib/ui/device/LockStatus.js'
9
+ import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
10
+ import { getDeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
11
+ import { TableCell } from 'lib/ui/Table/TableCell.js'
12
+ import { TableRow } from 'lib/ui/Table/TableRow.js'
13
+ import { Title } from 'lib/ui/typography/Title.js'
14
+
15
+ export type DeviceTableProps = Props & UseDevicesParams
16
+
17
+ interface Props {
18
+ onBack?: () => void
19
+ }
20
+
21
+ export interface DeviceRowProps {
22
+ device: UseDevicesData[number]
23
+ onClick: () => void
24
+ }
25
+
26
+ export function DeviceRow({
27
+ device,
28
+ onClick,
29
+ }: DeviceRowProps): JSX.Element | null {
30
+ if (!isLockDevice(device)) {
31
+ return null
32
+ }
33
+
34
+ const deviceModel = getDeviceModel(device) ?? t.unknownLock
35
+
36
+ return (
37
+ <TableRow key={device.device_id} onClick={onClick}>
38
+ <TableCell className='seam-image-cell'>
39
+ <DeviceImage device={device} />
40
+ </TableCell>
41
+ <TableCell className='seam-body-cell'>
42
+ <Title>{device.properties.name}</Title>
43
+ <div className='seam-bottom'>
44
+ <span className='seam-device-model'>{deviceModel}</span>
45
+ <div className='seam-device-statuses'>
46
+ <OnlineStatus device={device} />
47
+ <BatteryStatus device={device} />
48
+ <LockStatus device={device} />
49
+ </div>
50
+ </div>
51
+ </TableCell>
52
+ </TableRow>
53
+ )
54
+ }
55
+
56
+ const t = {
57
+ devices: 'Devices',
58
+ unknownLock: 'Unknown Lock',
59
+ noDevicesMessage: 'Sorry, no devices were found',
60
+ }
@@ -1,25 +1,22 @@
1
1
  import { useState } from 'react'
2
2
 
3
- import { isLockDevice } from 'lib/seam/devices/types.js'
4
3
  import {
5
4
  useDevices,
6
5
  type UseDevicesData,
7
6
  type UseDevicesParams,
8
7
  } from 'lib/seam/devices/use-devices.js'
9
- import { BatteryStatus } from 'lib/ui/device/BatteryStatus.js'
10
- import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
11
- import { LockStatus } from 'lib/ui/device/LockStatus.js'
12
- import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
13
8
  import { DeviceDetails } from 'lib/ui/DeviceDetails/DeviceDetails.js'
14
- import { getDeviceModel } from 'lib/ui/DeviceDetails/DeviceModel.js'
9
+ import {
10
+ type DeviceFilter,
11
+ DeviceHealthBar,
12
+ } from 'lib/ui/DeviceTable/DeviceHealthBar.js'
13
+ import { DeviceRow } from 'lib/ui/DeviceTable/DeviceRow.js'
15
14
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
15
+ import { EmptyPlaceholder } from 'lib/ui/Table/EmptyPlaceholder.js'
16
16
  import { TableBody } from 'lib/ui/Table/TableBody.js'
17
- import { TableCell } from 'lib/ui/Table/TableCell.js'
18
17
  import { TableHeader } from 'lib/ui/Table/TableHeader.js'
19
- import { TableRow } from 'lib/ui/Table/TableRow.js'
20
18
  import { TableTitle } from 'lib/ui/Table/TableTitle.js'
21
19
  import { Caption } from 'lib/ui/typography/Caption.js'
22
- import { Title } from 'lib/ui/typography/Title.js'
23
20
 
24
21
  export type DeviceTableProps = Props & UseDevicesParams
25
22
 
@@ -69,53 +66,57 @@ export function DeviceTable({
69
66
  </TableTitle>
70
67
  </TableHeader>
71
68
  <TableBody>
72
- {devices.map((device) => (
73
- <DeviceRow
74
- device={device}
75
- key={device.device_id}
76
- onClick={() => {
77
- selectDevice(device.device_id)
78
- }}
79
- />
80
- ))}
69
+ <Body devices={devices} selectDevice={selectDevice} />
81
70
  </TableBody>
82
71
  </div>
83
72
  )
84
73
  }
85
74
 
86
- function DeviceRow(props: {
87
- device: UseDevicesData[number]
88
- onClick: () => void
89
- }): JSX.Element | null {
90
- const { device, onClick } = props
75
+ function Body(props: {
76
+ devices: Array<UseDevicesData[number]>
77
+ selectDevice: (id: string) => void
78
+ }) {
79
+ const { devices, selectDevice } = props
80
+ const [filter, setFilter] = useState<DeviceFilter | null>(null)
91
81
 
92
- if (!isLockDevice(device)) {
93
- return null
82
+ if (devices.length === 0) {
83
+ return <EmptyPlaceholder>{t.noDevicesMessage}</EmptyPlaceholder>
94
84
  }
95
85
 
96
- const deviceModel = getDeviceModel(device) ?? t.unknownLock
86
+ const filteredDevices = devices.filter((device) => {
87
+ if (filter === null) {
88
+ return true
89
+ }
90
+
91
+ if (filter === 'device_issues') {
92
+ return device.errors.length > 0
93
+ }
94
+
95
+ return true
96
+ })
97
97
 
98
98
  return (
99
- <TableRow key={device.device_id} onClick={onClick}>
100
- <TableCell className='seam-image-cell'>
101
- <DeviceImage device={device} />
102
- </TableCell>
103
- <TableCell className='seam-body-cell'>
104
- <Title>{device.properties.name}</Title>
105
- <div className='seam-bottom'>
106
- <span className='seam-device-model'>{deviceModel}</span>
107
- <div className='seam-device-statuses'>
108
- <OnlineStatus device={device} />
109
- <BatteryStatus device={device} />
110
- <LockStatus device={device} />
111
- </div>
112
- </div>
113
- </TableCell>
114
- </TableRow>
99
+ <>
100
+ <DeviceHealthBar
101
+ devices={devices}
102
+ filter={filter}
103
+ onFilterSelect={setFilter}
104
+ />
105
+ {filteredDevices.map((device) => (
106
+ <DeviceRow
107
+ device={device}
108
+ key={device.device_id}
109
+ onClick={() => {
110
+ selectDevice(device.device_id)
111
+ }}
112
+ />
113
+ ))}
114
+ </>
115
115
  )
116
116
  }
117
117
 
118
118
  const t = {
119
119
  devices: 'Devices',
120
120
  unknownLock: 'Unknown Lock',
121
+ noDevicesMessage: 'Sorry, no devices were found',
121
122
  }
@@ -1,5 +1,9 @@
1
+ import classNames from 'classnames'
2
+
1
3
  import type { ButtonProps } from 'lib/ui/types.js'
2
4
 
3
- export function IconButton(props: ButtonProps): JSX.Element {
4
- return <button {...props} className='seam-icon-btn' />
5
+ export function IconButton({ className, ...props }: ButtonProps): JSX.Element {
6
+ return (
7
+ <button {...props} className={classNames('seam-icon-btn', className)} />
8
+ )
5
9
  }
@@ -7,7 +7,7 @@ import type { DeviceModel } from 'lib/ui/SupportedDevices/types.js'
7
7
  import SupportedDeviceRow from './SupportedDeviceRow.js'
8
8
  import SupportedDevicesHeader from './SupportedDevicesHeader.js'
9
9
 
10
- export default function SupportedDevices() {
10
+ export function SupportedDevices() {
11
11
  const { data, isLoading, isError, refetch } = useQuery<{
12
12
  data: {
13
13
  device_models?: DeviceModel[]
@@ -0,0 +1,12 @@
1
+ import type { DivProps } from 'lib/ui/types.js'
2
+
3
+ export function EmptyPlaceholder({
4
+ children,
5
+ ...props
6
+ }: DivProps): JSX.Element {
7
+ return (
8
+ <div className='seam-table-empty-placeholder' {...props}>
9
+ {children}
10
+ </div>
11
+ )
12
+ }
@@ -0,0 +1,31 @@
1
+ import { ArrowRestartIcon } from 'lib/icons/ArrowRestart.js'
2
+ import { IconButton } from 'lib/ui/IconButton.js'
3
+ import type { ButtonProps, DivProps } from 'lib/ui/types.js'
4
+
5
+ export interface TableFiltersProps extends DivProps {
6
+ isFilterCleared?: boolean
7
+ onFilterClear?: () => void
8
+ }
9
+
10
+ export function TableFilterBar({
11
+ isFilterCleared,
12
+ onFilterClear,
13
+ children,
14
+ ...props
15
+ }: TableFiltersProps) {
16
+ const showClearFilterButton = isFilterCleared === false
17
+ return (
18
+ <div className='seam-table-filter-bar' {...props}>
19
+ {children}{' '}
20
+ {showClearFilterButton && <ClearFiltersButton onClick={onFilterClear} />}
21
+ </div>
22
+ )
23
+ }
24
+
25
+ function ClearFiltersButton(props: ButtonProps) {
26
+ return (
27
+ <IconButton {...props} className='seam-clear-filters-button'>
28
+ <ArrowRestartIcon />
29
+ </IconButton>
30
+ )
31
+ }
@@ -0,0 +1,24 @@
1
+ import classNames from 'classnames'
2
+
3
+ import type { DivProps } from 'lib/ui/types.js'
4
+
5
+ export interface TableFilterItemProps extends DivProps {
6
+ selected?: boolean
7
+ }
8
+
9
+ export function TableFilterItem({
10
+ onClick,
11
+ selected,
12
+ ...props
13
+ }: TableFilterItemProps) {
14
+ return (
15
+ <div
16
+ onClick={onClick}
17
+ className={classNames('seam-table-filter-item', {
18
+ 'seam-clickable': onClick !== undefined,
19
+ 'seam-selected': selected,
20
+ })}
21
+ {...props}
22
+ />
23
+ )
24
+ }
@@ -2,3 +2,4 @@ export * from './AccessCodeDetails/AccessCodeDetails.js'
2
2
  export * from './AccessCodeTable/AccessCodeTable.js'
3
3
  export * from './DeviceDetails/DeviceDetails.js'
4
4
  export * from './DeviceTable/DeviceTable.js'
5
+ export * from './SupportedDevices/SupportedDevices.js'
@@ -0,0 +1,115 @@
1
+ @use './colors';
2
+
3
+ @mixin all {
4
+ // Alerts
5
+ .seam-alerts {
6
+ width: 100%;
7
+ display: flex;
8
+ justify-content: flex-start;
9
+ align-items: flex-start;
10
+ flex-direction: column;
11
+ gap: 8px;
12
+
13
+ &.seam-alerts-space-top {
14
+ margin-top: 16px;
15
+ }
16
+ }
17
+
18
+ // Alert
19
+ .seam-alert {
20
+ width: 100%;
21
+ min-height: 70px;
22
+ padding: 8px 16px;
23
+ border-radius: 8px;
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ flex-direction: row;
28
+ gap: 16px;
29
+
30
+ .seam-alert-action {
31
+ appearance: none;
32
+ height: 32px;
33
+ padding: 0 10px;
34
+ border-radius: 6px;
35
+ background-color: transparent;
36
+ font-size: 14px;
37
+ line-height: 1;
38
+ font-weight: 600;
39
+ white-space: nowrap;
40
+ cursor: pointer;
41
+
42
+ &:hover {
43
+ background-color: colors.$item-hover-bg;
44
+ }
45
+ }
46
+
47
+ &.seam-warning-alert {
48
+ background-color: colors.$alert-warn-fill;
49
+ border: 1px solid colors.$alert-warn-line;
50
+
51
+ .seam-alert-action {
52
+ border: 1px solid colors.$alert-warn-dark;
53
+ color: colors.$alert-warn-dark;
54
+ }
55
+ }
56
+
57
+ &.seam-error-alert {
58
+ background-color: colors.$alert-error-fill;
59
+ border: 1px solid colors.$alert-error-line;
60
+
61
+ .seam-alert-action {
62
+ border: 1px solid colors.$alert-error-dark;
63
+ color: colors.$alert-error-dark;
64
+ }
65
+ }
66
+
67
+ &.seam-alert-space-top {
68
+ margin-top: 16px;
69
+ }
70
+
71
+ .seam-alert-icon {
72
+ width: 24px;
73
+ height: 24px;
74
+ }
75
+
76
+ .seam-alert-content {
77
+ display: flex;
78
+ justify-content: flex-start;
79
+ align-items: center;
80
+ flex-direction: row;
81
+ gap: 8px;
82
+ }
83
+
84
+ .seam-alert-message-wrap {
85
+ .seam-alert-message {
86
+ font-size: 14px;
87
+ font-weight: 400;
88
+ line-height: 134%;
89
+ color: colors.$text-default;
90
+ }
91
+ }
92
+ }
93
+
94
+ @media only screen and (width <= 500px) {
95
+ .seam-alert {
96
+ justify-content: flex-start;
97
+ flex-direction: column;
98
+
99
+ .seam-alert-content {
100
+ width: 100%;
101
+ align-items: flex-start;
102
+ flex-direction: column;
103
+ gap: 4px;
104
+ }
105
+
106
+ .seam-alert-action-wrap {
107
+ width: 100%;
108
+
109
+ .seam-alert-action {
110
+ width: 100%;
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
@@ -2,6 +2,7 @@
2
2
  @use './typography';
3
3
  @use './tables';
4
4
  @use './inputs';
5
+ @use './alert';
5
6
  @use './buttons';
6
7
  @use './layout';
7
8
  @use './access-code-table';
@@ -31,5 +32,6 @@
31
32
  @include device-table.all;
32
33
  @include access-code-table.all;
33
34
  @include access-code-details.all;
35
+ @include alert.all;
34
36
  @include supported-devices.all;
35
37
  }
@@ -47,8 +47,73 @@
47
47
  }
48
48
  }
49
49
 
50
+ @mixin empty-placeholder {
51
+ .seam-table-empty-placeholder {
52
+ height: 300px;
53
+ font-size: 18px;
54
+ line-height: 132%;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ }
59
+ }
60
+
61
+ @mixin table-filters {
62
+ .seam-table-filter-bar {
63
+ display: flex;
64
+ align-items: center;
65
+ padding: 6px 16px;
66
+ background: colors.$bg-a;
67
+
68
+ .seam-clear-filters-button {
69
+ margin-left: 6px;
70
+ border: 0;
71
+ background: transparent !important;
72
+ padding: 0;
73
+
74
+ svg {
75
+ scale: 0.8333;
76
+
77
+ * {
78
+ fill: colors.$text-gray-1;
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ .seam-table-filter-item {
85
+ display: flex;
86
+ align-items: center;
87
+ padding: 2px 8px 2px 5px;
88
+ border-radius: 5px;
89
+ border: 1px solid colors.$bg-a;
90
+ font-size: 14px;
91
+ line-height: 134%;
92
+
93
+ svg {
94
+ scale: 0.75;
95
+ }
96
+
97
+ &.seam-clickable {
98
+ cursor: pointer;
99
+
100
+ &:hover {
101
+ background: rgb(255 255 255 / 65%);
102
+ border: 1px solid colors.$text-gray-3;
103
+ }
104
+ }
105
+
106
+ &.seam-selected {
107
+ background: #fff;
108
+ border: 1px solid colors.$text-gray-3;
109
+ }
110
+ }
111
+ }
112
+
50
113
  @mixin all {
51
114
  @include header;
52
115
  @include row;
53
116
  @include cell;
117
+ @include empty-placeholder;
118
+ @include table-filters;
54
119
  }