@seamapi/react 1.48.0 → 1.48.1

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 +1 -1
  2. package/dist/elements.js +3213 -3191
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +400 -1
  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/dates.d.ts +9 -0
  9. package/lib/dates.js +15 -0
  10. package/lib/dates.js.map +1 -1
  11. package/lib/icons/ArrowRight.d.ts +2 -0
  12. package/lib/icons/ArrowRight.js +7 -0
  13. package/lib/icons/ArrowRight.js.map +1 -0
  14. package/lib/icons/TemperatureAdd.d.ts +2 -0
  15. package/lib/icons/TemperatureAdd.js +7 -0
  16. package/lib/icons/TemperatureAdd.js.map +1 -0
  17. package/lib/icons/TemperatureSubtract.d.ts +2 -0
  18. package/lib/icons/TemperatureSubtract.js +7 -0
  19. package/lib/icons/TemperatureSubtract.js.map +1 -0
  20. package/lib/seam/SeamProvider.d.ts +2 -2
  21. package/lib/seam/SeamProvider.js.map +1 -1
  22. package/lib/seam/access-codes/use-generate-access-code-code.d.ts +6 -0
  23. package/lib/seam/access-codes/use-generate-access-code-code.js +13 -0
  24. package/lib/seam/access-codes/use-generate-access-code-code.js.map +1 -0
  25. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.d.ts +6 -0
  26. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js +41 -0
  27. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js.map +1 -0
  28. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.d.ts +4 -0
  29. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js +29 -0
  30. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js.map +1 -0
  31. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRow.js +2 -2
  32. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRow.js.map +1 -1
  33. package/lib/seam/components/ClimateSettingScheduleTable/{ClimateSettingScheduleDetails.d.ts → ClimateSettingScheduleRowDetails.d.ts} +1 -1
  34. package/lib/seam/components/ClimateSettingScheduleTable/{ClimateSettingScheduleDetails.js → ClimateSettingScheduleRowDetails.js} +2 -2
  35. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.js.map +1 -0
  36. package/lib/seam/use-seam-client.js +2 -2
  37. package/lib/seam/use-seam-client.js.map +1 -1
  38. package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js +1 -1
  39. package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js.map +1 -1
  40. package/lib/ui/Menu/Menu.js +5 -5
  41. package/lib/ui/Menu/Menu.js.map +1 -1
  42. package/lib/ui/Switch/Switch.d.ts +8 -0
  43. package/lib/ui/Switch/Switch.js +15 -0
  44. package/lib/ui/Switch/Switch.js.map +1 -0
  45. package/lib/ui/TextField/TextField.js +1 -3
  46. package/lib/ui/TextField/TextField.js.map +1 -1
  47. package/lib/ui/clipboard.js +1 -1
  48. package/lib/ui/clipboard.js.map +1 -1
  49. package/lib/ui/thermostat/TemperatureControl.d.ts +11 -0
  50. package/lib/ui/thermostat/TemperatureControl.js +42 -0
  51. package/lib/ui/thermostat/TemperatureControl.js.map +1 -0
  52. package/lib/ui/use-current-time.d.ts +2 -0
  53. package/lib/ui/use-current-time.js +12 -0
  54. package/lib/ui/use-current-time.js.map +1 -0
  55. package/lib/version.d.ts +1 -1
  56. package/lib/version.js +1 -1
  57. package/package.json +7 -7
  58. package/src/lib/dates.ts +17 -0
  59. package/src/lib/element.tsx +6 -4
  60. package/src/lib/icons/ArrowRight.tsx +36 -0
  61. package/src/lib/icons/TemperatureAdd.tsx +38 -0
  62. package/src/lib/icons/TemperatureSubtract.tsx +35 -0
  63. package/src/lib/seam/SeamProvider.tsx +2 -3
  64. package/src/lib/seam/access-codes/use-generate-access-code-code.ts +38 -0
  65. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx +101 -0
  66. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.tsx +58 -0
  67. package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRow.tsx +2 -2
  68. package/src/lib/seam/components/ClimateSettingScheduleTable/{ClimateSettingScheduleDetails.tsx → ClimateSettingScheduleRowDetails.tsx} +1 -1
  69. package/src/lib/seam/use-seam-client.ts +2 -2
  70. package/src/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.tsx +2 -2
  71. package/src/lib/ui/Menu/Menu.tsx +5 -6
  72. package/src/lib/ui/Switch/Switch.tsx +38 -0
  73. package/src/lib/ui/TextField/TextField.tsx +1 -3
  74. package/src/lib/ui/clipboard.ts +1 -1
  75. package/src/lib/ui/thermostat/TemperatureControl.tsx +121 -0
  76. package/src/lib/ui/use-current-time.ts +16 -0
  77. package/src/lib/version.ts +1 -1
  78. package/src/styles/_colors.scss +4 -0
  79. package/src/styles/_main.scss +2 -0
  80. package/src/styles/_switch.scss +53 -0
  81. package/src/styles/_thermostat.scss +384 -2
  82. package/src/styles/_tooltip.scss +3 -0
  83. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleDetails.js.map +0 -1
@@ -0,0 +1,58 @@
1
+ import { isThermostatDevice, type ThermostatDevice } from 'seamapi'
2
+
3
+ import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
4
+ import { useDevice } from 'lib/seam/devices/use-device.js'
5
+ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
6
+ import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
7
+
8
+ export function ClimateSettingScheduleDeviceBar({
9
+ deviceId,
10
+ }: {
11
+ deviceId: string
12
+ }): JSX.Element | null {
13
+ const { isLoading, device } = useDevice({
14
+ device_id: deviceId,
15
+ })
16
+
17
+ if (isLoading) {
18
+ return null
19
+ }
20
+
21
+ if (device == null) {
22
+ return null
23
+ }
24
+
25
+ if (!isThermostatDevice(device)) {
26
+ return null
27
+ }
28
+
29
+ return <Content device={device} />
30
+ }
31
+
32
+ function Content(props: { device: ThermostatDevice }): JSX.Element {
33
+ const { device } = props
34
+
35
+ return (
36
+ <div className='seam-climate-setting-schedule-device-bar'>
37
+ <div className='seam-climate-setting-schedule-device-content'>
38
+ <div className='seam-device-image'>
39
+ <DeviceImage device={device} />
40
+ </div>
41
+ <div className='seam-climate-setting-schedule-device-details'>
42
+ <div className='seam-device-name'>{device.properties.name}</div>
43
+ <div className='seam-device-current-climate-setting'>
44
+ {t.currentClimate}:
45
+ <ClimateSettingStatus
46
+ climateSetting={device.properties.current_climate_setting}
47
+ />
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <ChevronRightIcon />
52
+ </div>
53
+ )
54
+ }
55
+
56
+ const t = {
57
+ currentClimate: 'Current climate',
58
+ }
@@ -4,7 +4,7 @@ import { TableCell } from 'lib/ui/Table/TableCell.js'
4
4
  import { TableRow } from 'lib/ui/Table/TableRow.js'
5
5
  import { Title } from 'lib/ui/typography/Title.js'
6
6
 
7
- import { ClimateSettingScheduleDetails } from './ClimateSettingScheduleDetails.js'
7
+ import { ClimateSettingScheduleRowDetails } from './ClimateSettingScheduleRowDetails.js'
8
8
 
9
9
  interface ClimateSettingScheduleRowProps {
10
10
  climateSettingSchedule: UseClimateSettingSchedulesData[number]
@@ -26,7 +26,7 @@ export function ClimateSettingScheduleRow({
26
26
  <Title className='seam-truncated-text'>
27
27
  {climateSettingSchedule.name}
28
28
  </Title>
29
- <ClimateSettingScheduleDetails
29
+ <ClimateSettingScheduleRowDetails
30
30
  climateSettingSchedule={climateSettingSchedule}
31
31
  />
32
32
  </TableCell>
@@ -6,7 +6,7 @@ import { DotDivider } from 'lib/ui/layout/DotDivider.js'
6
6
  import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
7
7
  import { useIsDateInPast } from 'lib/ui/use-is-date-in-past.js'
8
8
 
9
- export function ClimateSettingScheduleDetails(props: {
9
+ export function ClimateSettingScheduleRowDetails(props: {
10
10
  climateSettingSchedule: ClimateSettingSchedule
11
11
  }): JSX.Element {
12
12
  const { climateSettingSchedule } = props
@@ -102,10 +102,10 @@ This is not recommended because the client session is now bound to this machine
102
102
  }
103
103
 
104
104
  const fingerprint =
105
- globalThis?.localStorage?.getItem('seam_user_fingerprint') ??
105
+ globalThis.localStorage?.getItem('seam_user_fingerprint') ??
106
106
  `fingerprint_${uuidv4()}`
107
107
 
108
- globalThis?.localStorage?.setItem('seam_user_fingerprint', fingerprint)
108
+ globalThis.localStorage?.setItem('seam_user_fingerprint', fingerprint)
109
109
 
110
110
  return fingerprint
111
111
  }
@@ -42,7 +42,7 @@ export function AccessCodeFormDatePicker({
42
42
  <FormField>
43
43
  <InputLabel>{t.startTimeLabel}</InputLabel>
44
44
  <DateTimePicker
45
- value={startDate == null ? '' : startDate}
45
+ value={startDate ?? ''}
46
46
  onChange={setStartDate}
47
47
  size='large'
48
48
  />
@@ -50,7 +50,7 @@ export function AccessCodeFormDatePicker({
50
50
  <FormField>
51
51
  <InputLabel>{t.endTimeLabel}</InputLabel>
52
52
  <DateTimePicker
53
- value={endDate == null ? '' : endDate}
53
+ value={endDate ?? ''}
54
54
  onChange={setEndDate}
55
55
  size='large'
56
56
  />
@@ -47,8 +47,7 @@ export function Menu({
47
47
  const [left, setLeft] = useState(0)
48
48
 
49
49
  useEffect(() => {
50
- const containers =
51
- globalThis?.document?.querySelectorAll('.seam-components')
50
+ const containers = globalThis.document?.querySelectorAll('.seam-components')
52
51
  if (containers == null) return
53
52
  const el = containers[containers.length - 1]
54
53
  if (el != null) {
@@ -110,11 +109,11 @@ export function Menu({
110
109
 
111
110
  useLayoutEffect(() => {
112
111
  setPositions()
113
- globalThis?.addEventListener('scroll', setPositions)
114
- globalThis?.addEventListener('resize', setPositions)
112
+ globalThis.addEventListener('scroll', setPositions)
113
+ globalThis.addEventListener('resize', setPositions)
115
114
  return () => {
116
- globalThis?.removeEventListener('scroll', setPositions)
117
- globalThis?.removeEventListener('resize', setPositions)
115
+ globalThis.removeEventListener('scroll', setPositions)
116
+ globalThis.removeEventListener('resize', setPositions)
118
117
  }
119
118
  }, [setPositions])
120
119
 
@@ -0,0 +1,38 @@
1
+ import classNames from 'classnames'
2
+
3
+ export interface SwitchProps {
4
+ checked: boolean
5
+ onChange: (checked: boolean) => void
6
+ enableLabel?: boolean
7
+ }
8
+
9
+ function Switch({
10
+ checked,
11
+ onChange,
12
+ enableLabel = false,
13
+ }: SwitchProps): JSX.Element {
14
+ return (
15
+ <div className='seam-switch-container'>
16
+ {enableLabel && (
17
+ <label className='seam-switch-label'>{checked ? t.on : t.off}</label>
18
+ )}
19
+ <div
20
+ className={classNames('seam-switch', {
21
+ 'seam-switch-checked': checked,
22
+ })}
23
+ onClick={() => {
24
+ onChange(!checked)
25
+ }}
26
+ >
27
+ <div className='seam-switch-slider' />
28
+ </div>
29
+ </div>
30
+ )
31
+ }
32
+
33
+ const t = {
34
+ on: 'On',
35
+ off: 'Off',
36
+ }
37
+
38
+ export default Switch
@@ -74,9 +74,7 @@ export const TextField = forwardRef<
74
74
  }
75
75
 
76
76
  // Maintain a local ref, and still forward it along
77
- useImperativeHandle(ref, () => (inputEl != null ? inputEl : undefined), [
78
- inputEl,
79
- ])
77
+ useImperativeHandle(ref, () => inputEl ?? undefined, [inputEl])
80
78
 
81
79
  const endAdornmentVisible = endAdornment != null || clearable
82
80
 
@@ -1,3 +1,3 @@
1
1
  export async function copyToClipboard(value: string): Promise<void> {
2
- await globalThis?.navigator?.clipboard?.writeText(value)
2
+ await globalThis.navigator?.clipboard?.writeText(value)
3
3
  }
@@ -0,0 +1,121 @@
1
+ import { type ChangeEventHandler, useEffect, useRef } from 'react'
2
+
3
+ import { TemperatureAddIcon } from 'lib/icons/TemperatureAdd.js'
4
+ import { TemperatureSubtractIcon } from 'lib/icons/TemperatureSubtract.js'
5
+
6
+ interface TemperatureControlProps {
7
+ variant: 'heat' | 'cool'
8
+ value: number
9
+ onChange: (temperature: number) => void
10
+ min?: number
11
+ max?: number
12
+ unit?: 'fahrenheit' | 'celsius'
13
+ }
14
+
15
+ export function TemperatureControl({
16
+ variant,
17
+ value,
18
+ onChange,
19
+ min = 50,
20
+ max = 90,
21
+ unit = 'fahrenheit',
22
+ }: TemperatureControlProps): JSX.Element {
23
+ const increment = (): void => {
24
+ onChange(Math.min(value + 1, max))
25
+ }
26
+
27
+ const decrement = (): void => {
28
+ onChange(Math.max(value - 1, min))
29
+ }
30
+
31
+ return (
32
+ <div className='seam-temperature-control'>
33
+ <button className='seam-temperature-stepper-button' onClick={decrement}>
34
+ <TemperatureSubtractIcon />
35
+ </button>
36
+ <RangeSlider
37
+ variant={variant}
38
+ min={min}
39
+ max={max}
40
+ value={value}
41
+ onChange={(temperature) => {
42
+ onChange(temperature)
43
+ }}
44
+ unit={unit}
45
+ />
46
+ <button className='seam-temperature-stepper-button' onClick={increment}>
47
+ <TemperatureAddIcon />
48
+ </button>
49
+ </div>
50
+ )
51
+ }
52
+
53
+ interface RangeSliderProps {
54
+ variant: 'heat' | 'cool'
55
+ value: number
56
+ onChange: (temperature: number) => void
57
+ min: number
58
+ max: number
59
+ unit: 'fahrenheit' | 'celsius'
60
+ }
61
+
62
+ function RangeSlider({
63
+ variant,
64
+ value,
65
+ onChange,
66
+ min,
67
+ max,
68
+ unit,
69
+ }: RangeSliderProps): JSX.Element {
70
+ const wrapRef = useRef<HTMLDivElement>(null)
71
+ const inputRef = useRef<HTMLInputElement>(null)
72
+
73
+ useEffect(() => {
74
+ if (wrapRef.current == null || inputRef.current == null) return
75
+
76
+ const wrap = wrapRef.current
77
+ const input = inputRef.current
78
+
79
+ wrap.style.setProperty('--temperature-current', input.value)
80
+ wrap.style.setProperty('--temperature-min', input.min)
81
+ wrap.style.setProperty('--temperature-max', input.max)
82
+ }, [value])
83
+
84
+ const handleRangeChange: ChangeEventHandler<HTMLInputElement> = (
85
+ event
86
+ ): void => {
87
+ const wrap = wrapRef.current
88
+
89
+ if (wrap == null) return
90
+ const temperature = event.target.value
91
+ wrap.style.setProperty('--temperature-current', temperature)
92
+ onChange(Number(temperature))
93
+ }
94
+
95
+ return (
96
+ <div ref={wrapRef} className='seam-temperature-range-wrap'>
97
+ <input
98
+ ref={inputRef}
99
+ type='range'
100
+ min={min}
101
+ max={max}
102
+ value={value}
103
+ onChange={handleRangeChange}
104
+ className='seam-temperature-range'
105
+ data-variant={variant}
106
+ />
107
+
108
+ <div className='seam-floating-temperature'>
109
+ <span className='seam-floating-temperature-value'>{value}</span>
110
+ <span className='seam-floating-temperature-unit'>
111
+ {unit === 'fahrenheit' ? t.degreeFahrenheit : t.degreeCelsius}
112
+ </span>
113
+ </div>
114
+ </div>
115
+ )
116
+ }
117
+
118
+ const t = {
119
+ degreeFahrenheit: 'ºF',
120
+ degreeCelsius: 'ºC',
121
+ }
@@ -0,0 +1,16 @@
1
+ import { DateTime } from 'luxon'
2
+ import { useCallback, useState } from 'react'
3
+
4
+ import { useInterval } from 'lib/ui/use-interval.js'
5
+
6
+ export function useCurrentTime(): DateTime {
7
+ const [date, setDate] = useState<DateTime>(DateTime.now())
8
+
9
+ const update = useCallback(() => {
10
+ setDate(DateTime.now())
11
+ }, [setDate])
12
+
13
+ useInterval(update)
14
+
15
+ return date
16
+ }
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '1.48.0'
1
+ const seamapiReactVersion = '1.48.1'
2
2
 
3
3
  export default seamapiReactVersion
@@ -34,3 +34,7 @@ $pill-green-bg: rgb(98 232 139 / 30%);
34
34
  $pill-green-text: #0d8841;
35
35
  $pill-blue-bg: rgb(125 192 241 / 30%);
36
36
  $pill-blue-text: #1653b8;
37
+ $thermo-orange: #fc8e28;
38
+ $thermo-orange-faded: #fff2e0;
39
+ $thermo-blue: #6b95ff;
40
+ $thermo-blue-faded: #e7f2ff;
@@ -20,6 +20,7 @@
20
20
  @use './thermostat';
21
21
  @use './tooltip';
22
22
  @use './seam-table';
23
+ @use './switch';
23
24
 
24
25
  .seam-components {
25
26
  // Reset
@@ -38,6 +39,7 @@
38
39
  @include checkbox.all;
39
40
  @include radio-field.all;
40
41
  @include tooltip.all;
42
+ @include switch.all;
41
43
 
42
44
  // Components
43
45
  @include device-details.all;
@@ -0,0 +1,53 @@
1
+ @use './colors';
2
+
3
+ @mixin all {
4
+ .seam-switch-container {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 8px;
8
+
9
+ .seam-switch-label {
10
+ font-style: normal;
11
+ font-weight: 400;
12
+ line-height: 118%;
13
+ font-size: 14px;
14
+ color: colors.$text-gray-2-5;
15
+ }
16
+
17
+ .seam-switch {
18
+ position: relative;
19
+ cursor: pointer;
20
+
21
+ .seam-switch-slider::before {
22
+ position: absolute;
23
+ content: '';
24
+ height: 20px;
25
+ width: 20px;
26
+ left: 0;
27
+ bottom: -3px;
28
+ background-color: colors.$bg-aa;
29
+ transition: 0.4s;
30
+ border-radius: 50%;
31
+ filter: drop-shadow(0 1px 4px rgb(0 0 0 / 25%));
32
+ }
33
+
34
+ .seam-switch-slider {
35
+ inset: 0;
36
+ width: 34px;
37
+ height: 14px;
38
+ background-color: #ccc;
39
+ transition: 0.4s;
40
+ border-radius: 20px;
41
+ }
42
+
43
+ &.seam-switch-checked .seam-switch-slider::before {
44
+ transform: translateX(14px);
45
+ background-color: colors.$primary;
46
+ }
47
+
48
+ &.seam-switch-checked .seam-switch-slider {
49
+ background-color: colors.$primary-dim;
50
+ }
51
+ }
52
+ }
53
+ }