@seamapi/react 2.18.0 → 2.19.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 (49) hide show
  1. package/README.md +2 -2
  2. package/dist/elements.js +5498 -5316
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +117 -0
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.min.css +1 -1
  7. package/dist/index.min.css.map +1 -1
  8. package/lib/icons/Clock.d.ts +2 -0
  9. package/lib/icons/Clock.js +7 -0
  10. package/lib/icons/Clock.js.map +1 -0
  11. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js +12 -1
  12. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js.map +1 -1
  13. package/lib/seam/events/use-events.d.ts +8 -0
  14. package/lib/seam/events/use-events.js +23 -0
  15. package/lib/seam/events/use-events.js.map +1 -0
  16. package/lib/telemetry/client.js +1 -1
  17. package/lib/telemetry/client.js.map +1 -1
  18. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js +15 -12
  19. package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js.map +1 -1
  20. package/lib/ui/TabSet.d.ts +9 -0
  21. package/lib/ui/TabSet.js +37 -0
  22. package/lib/ui/TabSet.js.map +1 -0
  23. package/lib/ui/layout/ContentHeader.d.ts +1 -0
  24. package/lib/ui/layout/ContentHeader.js +3 -2
  25. package/lib/ui/layout/ContentHeader.js.map +1 -1
  26. package/lib/ui/noise-sensor/NoiseSensorActivityList.d.ts +7 -0
  27. package/lib/ui/noise-sensor/NoiseSensorActivityList.js +16 -0
  28. package/lib/ui/noise-sensor/NoiseSensorActivityList.js.map +1 -0
  29. package/lib/ui/noise-sensor/NoiseSensorEventItem.d.ts +7 -0
  30. package/lib/ui/noise-sensor/NoiseSensorEventItem.js +27 -0
  31. package/lib/ui/noise-sensor/NoiseSensorEventItem.js.map +1 -0
  32. package/lib/version.d.ts +1 -1
  33. package/lib/version.js +1 -1
  34. package/package.json +1 -3
  35. package/src/lib/icons/Clock.tsx +36 -0
  36. package/src/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.tsx +56 -24
  37. package/src/lib/seam/events/use-events.ts +44 -0
  38. package/src/lib/telemetry/client.ts +1 -1
  39. package/src/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.tsx +17 -11
  40. package/src/lib/ui/TabSet.tsx +110 -0
  41. package/src/lib/ui/layout/ContentHeader.tsx +5 -2
  42. package/src/lib/ui/noise-sensor/NoiseSensorActivityList.tsx +31 -0
  43. package/src/lib/ui/noise-sensor/NoiseSensorEventItem.tsx +67 -0
  44. package/src/lib/version.ts +1 -1
  45. package/src/styles/_device-details.scss +12 -0
  46. package/src/styles/_layout.scss +8 -0
  47. package/src/styles/_main.scss +4 -0
  48. package/src/styles/_noise-sensor.scss +72 -0
  49. package/src/styles/_tab-set.scss +52 -0
@@ -1,4 +1,5 @@
1
1
  import classNames from 'classnames'
2
+ import { useState } from 'react'
2
3
  import type { NoiseSensorDevice } from 'seamapi'
3
4
 
4
5
  import type { NestedSpecificDeviceDetailsProps } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
@@ -8,7 +9,11 @@ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
8
9
  import { NoiseLevelStatus } from 'lib/ui/device/NoiseLevelStatus.js'
9
10
  import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
10
11
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
12
+ import { NoiseSensorActivityList } from 'lib/ui/noise-sensor/NoiseSensorActivityList.js'
11
13
  import { NoiseThresholdsList } from 'lib/ui/noise-sensor/NoiseThresholdsList.js'
14
+ import { TabSet } from 'lib/ui/TabSet.js'
15
+
16
+ type TabType = 'details' | 'activity'
12
17
 
13
18
  interface NoiseSensorDeviceDetailsProps
14
19
  extends NestedSpecificDeviceDetailsProps {
@@ -22,38 +27,63 @@ export function NoiseSensorDeviceDetails({
22
27
  onBack,
23
28
  className,
24
29
  }: NoiseSensorDeviceDetailsProps): JSX.Element | null {
30
+ const [tab, setTab] = useState<TabType>('details')
31
+
25
32
  return (
26
33
  <div className={classNames('seam-device-details', className)}>
27
- <ContentHeader title={t.noiseSensor} onBack={onBack} />
28
-
29
- <div className='seam-body'>
30
- <div className='seam-summary'>
31
- <div className='seam-content'>
32
- <div className='seam-image'>
33
- <DeviceImage device={device} />
34
- </div>
35
- <div className='seam-info'>
36
- <span className='seam-label'>{t.noiseSensor}</span>
37
- <h4 className='seam-device-name'>{device.properties.name}</h4>
38
- <div className='seam-properties'>
39
- <span className='seam-label'>{t.status}:</span>{' '}
40
- <OnlineStatus device={device} />
41
- <NoiseLevelStatus device={device} />
42
- <DeviceModel device={device} />
34
+ <div className='seam-body seam-body-no-margin'>
35
+ <div className='seam-contained-summary'>
36
+ <ContentHeader
37
+ title={t.noiseSensor}
38
+ onBack={onBack}
39
+ className='seam-content-header-contained'
40
+ />
41
+ <div className='seam-summary'>
42
+ <div className='seam-content'>
43
+ <div className='seam-image'>
44
+ <DeviceImage device={device} />
45
+ </div>
46
+ <div className='seam-info'>
47
+ <span className='seam-label'>{t.noiseSensor}</span>
48
+ <h4 className='seam-device-name'>{device.properties.name}</h4>
49
+ <div className='seam-properties'>
50
+ <span className='seam-label'>{t.status}:</span>{' '}
51
+ <OnlineStatus device={device} />
52
+ <NoiseLevelStatus device={device} />
53
+ <DeviceModel device={device} />
54
+ </div>
43
55
  </div>
44
56
  </div>
45
57
  </div>
58
+
59
+ <TabSet<TabType>
60
+ tabs={['details', 'activity']}
61
+ tabTitles={{
62
+ details: t.details,
63
+ activity: t.activity,
64
+ }}
65
+ activeTab={tab}
66
+ onTabChange={(tab) => {
67
+ setTab(tab)
68
+ }}
69
+ />
46
70
  </div>
47
71
 
48
- <NoiseThresholdsList device={device} />
72
+ {tab === 'details' && (
73
+ <div className='seam-padded-container'>
74
+ <NoiseThresholdsList device={device} />
75
+
76
+ <DeviceInfo
77
+ device={device}
78
+ disableConnectedAccountInformation={
79
+ disableConnectedAccountInformation
80
+ }
81
+ disableResourceIds={disableResourceIds}
82
+ />
83
+ </div>
84
+ )}
49
85
 
50
- <DeviceInfo
51
- device={device}
52
- disableConnectedAccountInformation={
53
- disableConnectedAccountInformation
54
- }
55
- disableResourceIds={disableResourceIds}
56
- />
86
+ {tab === 'activity' && <NoiseSensorActivityList device={device} />}
57
87
  </div>
58
88
  </div>
59
89
  )
@@ -63,4 +93,6 @@ const t = {
63
93
  noiseSensor: 'Noise Sensor',
64
94
  status: 'Status',
65
95
  noiseLevel: 'Noise level',
96
+ details: 'Details',
97
+ activity: 'Activity',
66
98
  }
@@ -0,0 +1,44 @@
1
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
2
+ import type {
3
+ Event,
4
+ EventsListRequest,
5
+ EventsListResponse,
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 UseEventsParams = EventsListRequest
13
+ export type UseEventsData = Event[]
14
+ export interface UseEventsOptions {
15
+ refetchInterval?: number
16
+ }
17
+
18
+ export function useEvents(
19
+ params?: UseEventsParams,
20
+ options?: UseEventsOptions
21
+ ): UseSeamQueryResult<'events', UseEventsData> {
22
+ const { client } = useSeamClient()
23
+ const queryClient = useQueryClient()
24
+
25
+ const { data, ...rest } = useQuery<EventsListResponse['events'], SeamError>({
26
+ enabled: client != null,
27
+ queryKey: ['events', 'list', params],
28
+ queryFn: async () => {
29
+ if (client == null) return []
30
+ return await client.events.list(params)
31
+ },
32
+ onSuccess: (events) => {
33
+ for (const event of events) {
34
+ queryClient.setQueryData(
35
+ ['events', 'get', { event_id: event.event_id }],
36
+ event
37
+ )
38
+ }
39
+ },
40
+ refetchInterval: options?.refetchInterval ?? 30_000,
41
+ })
42
+
43
+ return { ...rest, events: data }
44
+ }
@@ -29,7 +29,7 @@ export class TelemetryClient {
29
29
  }: TelemetryClientOptions = {}) {
30
30
  this.#queue = new Queue()
31
31
  this.#anonymousId = uuidv4()
32
- this.#endpoint = endpoint
32
+ this.#endpoint = endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint
33
33
  this.#debug = debug
34
34
  this.#disabled = disabled
35
35
  }
@@ -8,7 +8,7 @@ import {
8
8
  } from 'seamapi'
9
9
 
10
10
  import { getSystemTimeZone } from 'lib/dates.js'
11
- import { useDevice } from 'lib/index.js'
11
+ import { useSeamClient } from 'lib/index.js'
12
12
  import { ClimateSettingScheduleFormClimateSetting } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormClimateSetting.js'
13
13
  import { ClimateSettingScheduleFormDefaultClimateSetting } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormDefaultClimateSetting.js'
14
14
  import { ClimateSettingScheduleFormDeviceSelect } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormDeviceSelect.js'
@@ -61,6 +61,7 @@ export function ClimateSettingScheduleForm({
61
61
  function Content({
62
62
  onBack,
63
63
  }: Omit<ClimateSettingScheduleFormProps, 'className'>): JSX.Element {
64
+ const { client } = useSeamClient()
64
65
  const { control, watch, resetField } =
65
66
  useForm<ClimateSettingScheduleFormFields>({
66
67
  defaultValues: {
@@ -80,10 +81,6 @@ function Content({
80
81
  const deviceId = watch('deviceId')
81
82
  const timeZone = watch('timeZone')
82
83
 
83
- const { device } = useDevice({
84
- device_id: deviceId,
85
- })
86
-
87
84
  const [page, setPage] = useState<
88
85
  | 'device_select'
89
86
  | 'default_setting'
@@ -98,13 +95,22 @@ function Content({
98
95
  }
99
96
 
100
97
  useEffect(() => {
101
- if (page === 'device_select' && device != null) {
102
- if (!isThermostatDevice(device)) return
103
- const defaultSetting = device.properties.default_climate_setting
104
- if (defaultSetting != null) setPage('name_and_schedule')
105
- else setPage('default_setting')
98
+ if (page === 'device_select' && deviceId !== '' && client != null) {
99
+ client.devices
100
+ .get({ device_id: deviceId })
101
+ .then((device) => {
102
+ if (!isThermostatDevice(device)) return
103
+
104
+ if (device.properties.default_climate_setting != null) {
105
+ setPage('name_and_schedule')
106
+ return
107
+ }
108
+
109
+ setPage('default_setting')
110
+ })
111
+ .catch(() => {})
106
112
  }
107
- }, [device, page, setPage])
113
+ }, [client, deviceId, page, setPage])
108
114
 
109
115
  if (page === 'device_select') {
110
116
  return (
@@ -0,0 +1,110 @@
1
+ import classNames from 'classnames'
2
+ import {
3
+ type MouseEventHandler,
4
+ useCallback,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useState,
8
+ } from 'react'
9
+
10
+ interface TabSetProps<TabType extends string> {
11
+ tabs: TabType[]
12
+ tabTitles: Record<TabType, string>
13
+ activeTab: TabType
14
+ onTabChange: (tab: TabType) => void
15
+ }
16
+
17
+ interface HighlightStyle {
18
+ left: number
19
+ width: number
20
+ }
21
+
22
+ export function TabSet<TabType extends string>({
23
+ tabs,
24
+ tabTitles,
25
+ activeTab,
26
+ onTabChange,
27
+ }: TabSetProps<TabType>): JSX.Element {
28
+ const [highlightStyle, setHighlightStyle] = useState<HighlightStyle>({
29
+ left: 0,
30
+ width: 140,
31
+ })
32
+
33
+ const calculateHighlightStyle = useCallback(() => {
34
+ const tabButton: HTMLButtonElement | null =
35
+ globalThis.document?.querySelector(
36
+ `.seam-tab-button:nth-of-type(${tabs.indexOf(activeTab) + 1})`
37
+ )
38
+
39
+ setHighlightStyle({
40
+ left: tabButton?.offsetLeft ?? 0,
41
+ width: tabButton?.offsetWidth ?? 140,
42
+ })
43
+ }, [activeTab, tabs])
44
+
45
+ useLayoutEffect(() => {
46
+ calculateHighlightStyle()
47
+ }, [activeTab, calculateHighlightStyle])
48
+
49
+ useEffect(() => {
50
+ globalThis.addEventListener?.('resize', calculateHighlightStyle)
51
+ return () => {
52
+ globalThis.removeEventListener?.('resize', calculateHighlightStyle)
53
+ }
54
+ }, [calculateHighlightStyle])
55
+
56
+ return (
57
+ <div className='seam-tab-set'>
58
+ <div className='seam-tab-set-buttons'>
59
+ <div className='seam-tab-set-highlight' style={highlightStyle} />
60
+
61
+ {tabs.map((tab) => (
62
+ <TabButton<TabType>
63
+ key={tab}
64
+ tab={tab}
65
+ title={tabTitles[tab]}
66
+ isActive={activeTab === tab}
67
+ onTabChange={onTabChange}
68
+ setHighlightStyle={setHighlightStyle}
69
+ />
70
+ ))}
71
+ </div>
72
+ </div>
73
+ )
74
+ }
75
+
76
+ interface TabButtonProps<TabType> {
77
+ tab: TabType
78
+ title: string
79
+ isActive: boolean
80
+ onTabChange: (tab: TabType) => void
81
+ setHighlightStyle: (style: HighlightStyle) => void
82
+ }
83
+
84
+ function TabButton<TabType extends string>({
85
+ tab,
86
+ title,
87
+ isActive,
88
+ onTabChange,
89
+ setHighlightStyle,
90
+ }: TabButtonProps<TabType>): JSX.Element {
91
+ const handleClick: MouseEventHandler<HTMLButtonElement> = (ev) => {
92
+ onTabChange(tab)
93
+ setHighlightStyle({
94
+ left: ev.currentTarget.offsetLeft,
95
+ width: ev.currentTarget.offsetWidth,
96
+ })
97
+ }
98
+
99
+ return (
100
+ <button
101
+ className={classNames(
102
+ 'seam-tab-button',
103
+ isActive && 'seam-tab-button-active'
104
+ )}
105
+ onClick={handleClick}
106
+ >
107
+ <p className='seam-tab-button-label'>{title}</p>
108
+ </button>
109
+ )
110
+ }
@@ -1,19 +1,22 @@
1
+ import classNames from 'classnames'
2
+
1
3
  import { ArrowBackIcon } from 'lib/icons/ArrowBack.js'
2
4
 
3
5
  interface ContentHeaderProps {
4
6
  onBack: (() => void) | undefined
5
7
  title?: string
6
8
  subheading?: string
9
+ className?: string
7
10
  }
8
11
 
9
12
  export function ContentHeader(props: ContentHeaderProps): JSX.Element | null {
10
- const { title, onBack, subheading } = props
13
+ const { title, onBack, subheading, className } = props
11
14
  if (title == null && onBack == null) {
12
15
  return null
13
16
  }
14
17
 
15
18
  return (
16
- <div className='seam-content-header'>
19
+ <div className={classNames('seam-content-header', className)}>
17
20
  <BackIcon onClick={onBack} />
18
21
  <div>
19
22
  <span className='seam-title'>{title}</span>
@@ -0,0 +1,31 @@
1
+ import { useState } from 'react'
2
+ import type { NoiseSensorDevice } from 'seamapi'
3
+
4
+ import { useEvents } from 'lib/seam/events/use-events.js'
5
+ import { NoiseSensorEventItem } from 'lib/ui/noise-sensor/NoiseSensorEventItem.js'
6
+ import { useNow } from 'lib/ui/use-now.js'
7
+
8
+ interface NoiseSensorActivityListProps {
9
+ device: NoiseSensorDevice
10
+ }
11
+
12
+ export function NoiseSensorActivityList({
13
+ device,
14
+ }: NoiseSensorActivityListProps): JSX.Element {
15
+ const now = useNow()
16
+ const [mountedAt] = useState(now)
17
+
18
+ const { events } = useEvents({
19
+ device_id: device.device_id,
20
+ event_type: 'noise_sensor.noise_threshold_triggered',
21
+ since: mountedAt.minus({ months: 1 }).toString(),
22
+ })
23
+
24
+ return (
25
+ <div className='seam-noise-sensor-activity-list'>
26
+ {events?.map((event) => (
27
+ <NoiseSensorEventItem key={event.event_id} event={event} />
28
+ ))}
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,67 @@
1
+ import { DateTime } from 'luxon'
2
+ import type { Event } from 'seamapi'
3
+
4
+ import { ClockIcon } from 'lib/icons/Clock.js'
5
+
6
+ interface NoiseSensorEventItemProps {
7
+ event: Event
8
+ }
9
+
10
+ export function NoiseSensorEventItem({
11
+ event,
12
+ }: NoiseSensorEventItemProps): JSX.Element {
13
+ const date = formatDate(event.created_at)
14
+ const time = formatTime(event.created_at)
15
+
16
+ return (
17
+ <div className='seam-noise-sensor-event-item'>
18
+ <div className='seam-noise-sensor-event-item-column-wrap'>
19
+ <ClockIcon />
20
+
21
+ <div className='seam-noise-sensor-event-item-datetime-wrap'>
22
+ <p className='seam-noise-sensor-event-item-date'>{date}</p>
23
+ <p className='seam-noise-sensor-event-item-time'>{time}</p>
24
+ </div>
25
+ </div>
26
+
27
+ <div className='seam-noise-sensor-event-item-context-wrap'>
28
+ <p className='seam-noise-sensor-event-item-context-label'>
29
+ {t.noiseThresholdTriggered}
30
+ </p>
31
+ {getContextSublabel(event) != null && (
32
+ <p className='seam-noise-sensor-event-item-context-sublabel'>
33
+ {getContextSublabel(event)}
34
+ </p>
35
+ )}
36
+ </div>
37
+
38
+ <div className='seam-noise-sensor-event-item-right-block' />
39
+ </div>
40
+ )
41
+ }
42
+
43
+ function getContextSublabel(event: Event): string | null {
44
+ if ('noise_threshold_name' in event) {
45
+ // @ts-expect-error UPSTREAM: Shallow event type
46
+ // https://github.com/seamapi/react/issues/611
47
+ return event.noise_threshold_name
48
+ }
49
+
50
+ if ('noise_level_decibels' in event) {
51
+ // UPSTREAM: Shallow event types.
52
+ return `${event.noise_level_decibels as string} ${t.decibel}`
53
+ }
54
+
55
+ return null
56
+ }
57
+
58
+ const formatDate = (dateTime: string): string =>
59
+ DateTime.fromISO(dateTime).toLocaleString(DateTime.DATE_FULL)
60
+
61
+ const formatTime = (dateTime: string): string =>
62
+ DateTime.fromISO(dateTime).toLocaleString(DateTime.TIME_SIMPLE)
63
+
64
+ const t = {
65
+ decibel: 'dB',
66
+ noiseThresholdTriggered: 'Noise threshold triggered',
67
+ }
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '2.18.0'
1
+ const seamapiReactVersion = '2.19.1'
2
2
 
3
3
  export default seamapiReactVersion
@@ -8,6 +8,11 @@
8
8
  gap: 16px;
9
9
  margin: 0 24px 24px;
10
10
 
11
+ &.seam-body-no-margin {
12
+ margin: 0;
13
+ gap: 0;
14
+ }
15
+
11
16
  .seam-box {
12
17
  border: 1px solid colors.$text-gray-3;
13
18
  border-radius: 8px;
@@ -56,6 +61,13 @@
56
61
  }
57
62
  }
58
63
 
64
+ .seam-contained-summary {
65
+ padding: 0 24px;
66
+ box-shadow:
67
+ 0 2px 8px 0 rgb(0 0 0 / 8%),
68
+ 0 1px 0 0 rgb(0 0 0 / 10%);
69
+ }
70
+
59
71
  .seam-summary {
60
72
  padding: 24px 16px;
61
73
  border-radius: 16px;
@@ -9,6 +9,10 @@
9
9
  justify-content: center;
10
10
  margin: 0 16px;
11
11
 
12
+ &.seam-content-header-contained {
13
+ margin: 0;
14
+ }
15
+
12
16
  .seam-back-icon {
13
17
  position: absolute;
14
18
  top: 50%;
@@ -34,6 +38,10 @@
34
38
  }
35
39
 
36
40
  @mixin detail-section {
41
+ .seam-padded-container {
42
+ padding: 0 16px;
43
+ }
44
+
37
45
  .seam-detail-sections {
38
46
  display: flex;
39
47
  flex-direction: column;
@@ -29,6 +29,8 @@
29
29
  @use './climate-setting-schedule-form';
30
30
  @use './climate-setting-schedule-details';
31
31
  @use './time-zone-picker';
32
+ @use './tab-set';
33
+ @use './noise-sensor';
32
34
 
33
35
  .seam-components {
34
36
  // Reset
@@ -53,6 +55,7 @@
53
55
  @include spinner.all;
54
56
  @include switch.all;
55
57
  @include time-zone-picker.all;
58
+ @include tab-set.all;
56
59
 
57
60
  // Components
58
61
  @include device-details.all;
@@ -66,4 +69,5 @@
66
69
  @include thermostat.all;
67
70
  @include seam-table.all;
68
71
  @include climate-setting-schedule-details.all;
72
+ @include noise-sensor.all;
69
73
  }
@@ -0,0 +1,72 @@
1
+ @use './colors';
2
+
3
+ @mixin all {
4
+ .seam-noise-sensor-activity-list {
5
+ width: 100%;
6
+ display: flex;
7
+ justify-content: center;
8
+ align-items: center;
9
+ flex-direction: column;
10
+ }
11
+
12
+ .seam-noise-sensor-event-item {
13
+ width: 100%;
14
+ min-height: 64px;
15
+ padding: 12px 8px 12px 16px;
16
+ border-bottom: 1px solid colors.$divider-stroke-light;
17
+ display: flex;
18
+ gap: 40px;
19
+ }
20
+
21
+ .seam-noise-sensor-event-item-column-wrap {
22
+ display: flex;
23
+ justify-content: flex-start;
24
+ align-items: center;
25
+ flex-direction: row;
26
+ gap: 18px;
27
+ flex: 0.5;
28
+ }
29
+
30
+ .seam-noise-sensor-event-item-datetime-wrap {
31
+ color: colors.$text-gray-1;
32
+ font-size: 14px;
33
+ line-height: 134%;
34
+ }
35
+
36
+ .seam-noise-sensor-event-item-date,
37
+ .seam-noise-sensor-event-item-time,
38
+ .seam-noise-sensor-event-item-context-label,
39
+ .seam-noise-sensor-event-item-context-sublabel {
40
+ white-space: nowrap;
41
+ }
42
+
43
+ .seam-noise-sensor-event-item-context-wrap {
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: flex-start;
47
+ flex-direction: column;
48
+ flex: 1;
49
+ }
50
+
51
+ .seam-noise-sensor-event-item-context-label {
52
+ color: colors.$text-default;
53
+ font-size: 14px;
54
+ white-space: nowrap;
55
+ max-width: 100%;
56
+ overflow: hidden;
57
+ text-overflow: ellipsis;
58
+ }
59
+
60
+ .seam-noise-sensor-event-item-context-sublabel {
61
+ color: colors.$text-gray-2;
62
+ font-size: 14px;
63
+ }
64
+
65
+ .seam-noise-sensor-event-item-right-block {
66
+ display: flex;
67
+ justify-content: flex-end;
68
+ align-items: center;
69
+ gap: 18px;
70
+ flex: 0.5;
71
+ }
72
+ }
@@ -0,0 +1,52 @@
1
+ @use './colors';
2
+
3
+ @mixin all {
4
+ .seam-tab-set {
5
+ width: 100%;
6
+ display: flex;
7
+ justify-content: center;
8
+ align-items: center;
9
+ flex-direction: row;
10
+
11
+ .seam-tab-set-buttons {
12
+ width: 100%;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ flex-direction: row;
17
+ position: relative;
18
+ padding-top: 8px;
19
+ }
20
+
21
+ .seam-tab-set-highlight {
22
+ height: 4px;
23
+ border-radius: 6px 6px 0 0;
24
+ background-color: colors.$primary;
25
+ position: absolute;
26
+ bottom: 0;
27
+ transition: 240ms cubic-bezier(0.2, 0, 0.38, 0.9);
28
+ }
29
+
30
+ .seam-tab-button {
31
+ appearance: none;
32
+ width: 140px;
33
+ padding-top: 4px;
34
+ padding-bottom: 12px;
35
+ display: flex;
36
+ justify-content: center;
37
+ align-items: center;
38
+ font-size: 14px;
39
+ cursor: pointer;
40
+ background-color: transparent;
41
+ box-shadow: none;
42
+ border: none;
43
+ color: colors.$text-gray-2;
44
+ transition: 240ms ease-in-out;
45
+
46
+ &:hover,
47
+ &.seam-tab-button-active {
48
+ color: colors.$text-default;
49
+ }
50
+ }
51
+ }
52
+ }