@seamapi/react 4.2.0 → 4.3.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 (46) hide show
  1. package/README.md +2 -2
  2. package/dist/elements.js +4656 -4472
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +49 -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/Edit.js +2 -4
  9. package/lib/icons/Edit.js.map +1 -1
  10. package/lib/seam/components/AccessCodeTable/AccessCodeMainIcon.js +6 -3
  11. package/lib/seam/components/AccessCodeTable/AccessCodeMainIcon.js.map +1 -1
  12. package/lib/seam/components/DeviceDetails/DeviceDetails.js +15 -3
  13. package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -1
  14. package/lib/seam/components/DeviceDetails/LockDeviceDetails.d.ts +2 -1
  15. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js +3 -2
  16. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js.map +1 -1
  17. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.d.ts +2 -1
  18. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js +3 -2
  19. package/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js.map +1 -1
  20. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.d.ts +2 -1
  21. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js +2 -2
  22. package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js.map +1 -1
  23. package/lib/seam/devices/use-update-device-name.d.ts +8 -0
  24. package/lib/seam/devices/use-update-device-name.js +43 -0
  25. package/lib/seam/devices/use-update-device-name.js.map +1 -0
  26. package/lib/ui/device/EditableDeviceName.d.ts +9 -0
  27. package/lib/ui/device/EditableDeviceName.js +85 -0
  28. package/lib/ui/device/EditableDeviceName.js.map +1 -0
  29. package/lib/ui/thermostat/ThermostatCard.d.ts +2 -1
  30. package/lib/ui/thermostat/ThermostatCard.js +4 -3
  31. package/lib/ui/thermostat/ThermostatCard.js.map +1 -1
  32. package/lib/version.d.ts +1 -1
  33. package/lib/version.js +1 -1
  34. package/package.json +1 -1
  35. package/src/lib/icons/Edit.tsx +6 -19
  36. package/src/lib/seam/components/AccessCodeTable/AccessCodeMainIcon.tsx +7 -3
  37. package/src/lib/seam/components/DeviceDetails/DeviceDetails.tsx +35 -4
  38. package/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx +9 -1
  39. package/src/lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.tsx +8 -1
  40. package/src/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.tsx +3 -1
  41. package/src/lib/seam/devices/use-update-device-name.ts +93 -0
  42. package/src/lib/ui/device/EditableDeviceName.tsx +213 -0
  43. package/src/lib/ui/thermostat/ThermostatCard.tsx +11 -6
  44. package/src/lib/version.ts +1 -1
  45. package/src/styles/_main.scss +2 -0
  46. package/src/styles/_seam-editable-device-name.scss +62 -0
@@ -6,6 +6,7 @@ import { LockDeviceDetails } from 'lib/seam/components/DeviceDetails/LockDeviceD
6
6
  import { NoiseSensorDeviceDetails } from 'lib/seam/components/DeviceDetails/NoiseSensorDeviceDetails.js'
7
7
  import { ThermostatDeviceDetails } from 'lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js'
8
8
  import { useDevice } from 'lib/seam/devices/use-device.js'
9
+ import { useUpdateDeviceName } from 'lib/seam/devices/use-update-device-name.js'
9
10
  import { isLockDevice } from 'lib/seam/locks/lock-device.js'
10
11
  import { isNoiseSensorDevice } from 'lib/seam/noise-sensors/noise-sensor-device.js'
11
12
  import { isThermostatDevice } from 'lib/seam/thermostats/thermostat-device.js'
@@ -16,7 +17,6 @@ export interface DeviceDetailsProps extends CommonProps {
16
17
  }
17
18
 
18
19
  export const NestedDeviceDetails = withRequiredCommonProps(DeviceDetails)
19
-
20
20
  export interface NestedSpecificDeviceDetailsProps
21
21
  extends Required<Omit<CommonProps, 'onBack' | 'className'>> {
22
22
  onBack: (() => void) | undefined
@@ -42,6 +42,19 @@ export function DeviceDetails({
42
42
  device_id: deviceId,
43
43
  })
44
44
 
45
+ const { mutate: setDeviceName } = useUpdateDeviceName({
46
+ device_id: deviceId,
47
+ })
48
+
49
+ const updateDeviceName = (newName: string): void => {
50
+ if (device != null) {
51
+ setDeviceName({
52
+ device_id: device.device_id,
53
+ name: newName,
54
+ })
55
+ }
56
+ }
57
+
45
58
  if (device == null) {
46
59
  return null
47
60
  }
@@ -60,15 +73,33 @@ export function DeviceDetails({
60
73
  }
61
74
 
62
75
  if (isLockDevice(device)) {
63
- return <LockDeviceDetails device={device} {...props} />
76
+ return (
77
+ <LockDeviceDetails
78
+ device={device}
79
+ onEditName={updateDeviceName}
80
+ {...props}
81
+ />
82
+ )
64
83
  }
65
84
 
66
85
  if (isThermostatDevice(device)) {
67
- return <ThermostatDeviceDetails device={device} {...props} />
86
+ return (
87
+ <ThermostatDeviceDetails
88
+ device={device}
89
+ onEditName={updateDeviceName}
90
+ {...props}
91
+ />
92
+ )
68
93
  }
69
94
 
70
95
  if (isNoiseSensorDevice(device)) {
71
- return <NoiseSensorDeviceDetails device={device} {...props} />
96
+ return (
97
+ <NoiseSensorDeviceDetails
98
+ device={device}
99
+ onEditName={updateDeviceName}
100
+ {...props}
101
+ />
102
+ )
72
103
  }
73
104
 
74
105
  return null
@@ -13,12 +13,14 @@ import { Alerts } from 'lib/ui/Alert/Alerts.js'
13
13
  import { Button } from 'lib/ui/Button.js'
14
14
  import { BatteryStatusIndicator } from 'lib/ui/device/BatteryStatusIndicator.js'
15
15
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
16
+ import { EditableDeviceName } from 'lib/ui/device/EditableDeviceName.js'
16
17
  import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
17
18
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
18
19
  import { useToggle } from 'lib/ui/use-toggle.js'
19
20
 
20
21
  interface LockDeviceDetailsProps extends NestedSpecificDeviceDetailsProps {
21
22
  device: LockDevice
23
+ onEditName?: (newName: string) => void | Promise<void>
22
24
  }
23
25
 
24
26
  export function LockDeviceDetails({
@@ -33,6 +35,7 @@ export function LockDeviceDetails({
33
35
  disableConnectedAccountInformation,
34
36
  onBack,
35
37
  className,
38
+ onEditName,
36
39
  }: LockDeviceDetailsProps): JSX.Element | null {
37
40
  const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
38
41
  const toggleLock = useToggleLock()
@@ -95,7 +98,12 @@ export function LockDeviceDetails({
95
98
  </div>
96
99
  <div className='seam-info'>
97
100
  <span className='seam-label'>{t.device}</span>
98
- <h4 className='seam-device-name'>{device.properties.name}</h4>
101
+ <EditableDeviceName
102
+ tagName='h4'
103
+ value={device.properties.name}
104
+ className='seam-device-name'
105
+ onEdit={onEditName}
106
+ />
99
107
  <div className='seam-properties'>
100
108
  <span className='seam-label'>{t.status}:</span>{' '}
101
109
  <OnlineStatus device={device} />
@@ -6,6 +6,7 @@ import { DeviceInfo } from 'lib/seam/components/DeviceDetails/DeviceInfo.js'
6
6
  import { DeviceModel } from 'lib/seam/components/DeviceDetails/DeviceModel.js'
7
7
  import type { NoiseSensorDevice } from 'lib/seam/noise-sensors/noise-sensor-device.js'
8
8
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
9
+ import { EditableDeviceName } from 'lib/ui/device/EditableDeviceName.js'
9
10
  import { NoiseLevelStatus } from 'lib/ui/device/NoiseLevelStatus.js'
10
11
  import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
11
12
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
@@ -18,6 +19,7 @@ type TabType = 'details' | 'activity'
18
19
  interface NoiseSensorDeviceDetailsProps
19
20
  extends NestedSpecificDeviceDetailsProps {
20
21
  device: NoiseSensorDevice
22
+ onEditName?: (newName: string) => void | Promise<void>
21
23
  }
22
24
 
23
25
  export function NoiseSensorDeviceDetails({
@@ -26,6 +28,7 @@ export function NoiseSensorDeviceDetails({
26
28
  disableResourceIds,
27
29
  onBack,
28
30
  className,
31
+ onEditName,
29
32
  }: NoiseSensorDeviceDetailsProps): JSX.Element | null {
30
33
  const [tab, setTab] = useState<TabType>('details')
31
34
 
@@ -45,7 +48,11 @@ export function NoiseSensorDeviceDetails({
45
48
  </div>
46
49
  <div className='seam-info'>
47
50
  <span className='seam-label'>{t.noiseSensor}</span>
48
- <h4 className='seam-device-name'>{device.properties.name}</h4>
51
+ <EditableDeviceName
52
+ onEdit={onEditName}
53
+ tagName='h4'
54
+ value={device.properties.name}
55
+ />
49
56
  <div className='seam-properties'>
50
57
  <span className='seam-label'>{t.status}:</span>{' '}
51
58
  <OnlineStatus device={device} />
@@ -29,6 +29,7 @@ import { ThermostatCard } from 'lib/ui/thermostat/ThermostatCard.js'
29
29
  interface ThermostatDeviceDetailsProps
30
30
  extends NestedSpecificDeviceDetailsProps {
31
31
  device: ThermostatDevice
32
+ onEditName?: (newName: string) => void | Promise<void>
32
33
  }
33
34
 
34
35
  export function ThermostatDeviceDetails({
@@ -37,6 +38,7 @@ export function ThermostatDeviceDetails({
37
38
  disableConnectedAccountInformation,
38
39
  onBack,
39
40
  className,
41
+ onEditName,
40
42
  }: ThermostatDeviceDetailsProps): JSX.Element | null {
41
43
  if (device == null) {
42
44
  return null
@@ -47,7 +49,7 @@ export function ThermostatDeviceDetails({
47
49
  <ContentHeader title={t.thermostat} onBack={onBack} />
48
50
 
49
51
  <div className='seam-body'>
50
- <ThermostatCard device={device} />
52
+ <ThermostatCard device={device} onEditName={onEditName} />
51
53
 
52
54
  <div className='seam-thermostat-device-details'>
53
55
  <DetailSectionGroup>
@@ -0,0 +1,93 @@
1
+ import type {
2
+ DevicesGetParams,
3
+ DevicesUpdateBody,
4
+ SeamHttpApiError,
5
+ } from '@seamapi/http/connect'
6
+ import type { Device } from '@seamapi/types/connect'
7
+ import {
8
+ useMutation,
9
+ type UseMutationResult,
10
+ useQueryClient,
11
+ } from '@tanstack/react-query'
12
+
13
+ import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js'
14
+
15
+ export type UseUpdateDeviceNameParams = never
16
+
17
+ export type UseUpdateDeviceNameData = undefined
18
+
19
+ export type UseUpdateDeviceNameMutationVariables = Pick<
20
+ DevicesUpdateBody,
21
+ 'device_id' | 'name'
22
+ >
23
+
24
+ type MutationError = SeamHttpApiError
25
+
26
+ export function useUpdateDeviceName(
27
+ params: DevicesGetParams
28
+ ): UseMutationResult<
29
+ UseUpdateDeviceNameData,
30
+ MutationError,
31
+ UseUpdateDeviceNameMutationVariables
32
+ > {
33
+ const { client } = useSeamClient()
34
+ const queryClient = useQueryClient()
35
+
36
+ return useMutation<
37
+ UseUpdateDeviceNameData,
38
+ MutationError,
39
+ UseUpdateDeviceNameMutationVariables
40
+ >({
41
+ mutationFn: async (variables) => {
42
+ if (client === null) throw new NullSeamClientError()
43
+ await client.devices.update(variables)
44
+ },
45
+ onSuccess: (_data, variables) => {
46
+ queryClient.setQueryData<Device | null>(
47
+ ['devices', 'get', params],
48
+ (device) => {
49
+ if (device == null) {
50
+ return
51
+ }
52
+
53
+ return getUpdatedDevice(
54
+ device,
55
+ variables.name ?? device.properties.name
56
+ )
57
+ }
58
+ )
59
+
60
+ queryClient.setQueryData<Device[]>(
61
+ ['devices', 'list', { device_id: variables.device_id }],
62
+ (devices): Device[] => {
63
+ if (devices == null) {
64
+ return []
65
+ }
66
+
67
+ return devices.map((device) => {
68
+ if (device.device_id === variables.device_id) {
69
+ return getUpdatedDevice(
70
+ device,
71
+ variables.name ?? device.properties.name
72
+ )
73
+ }
74
+
75
+ return device
76
+ })
77
+ }
78
+ )
79
+ },
80
+ })
81
+ }
82
+
83
+ const getUpdatedDevice = (device: Device, name: string): Device => {
84
+ const { properties } = device
85
+
86
+ return {
87
+ ...device,
88
+ properties: {
89
+ ...properties,
90
+ name,
91
+ },
92
+ }
93
+ }
@@ -0,0 +1,213 @@
1
+ import classNames from 'classnames'
2
+ import {
3
+ type ChangeEvent,
4
+ type HTMLAttributes,
5
+ type KeyboardEvent,
6
+ type PropsWithChildren,
7
+ useCallback,
8
+ useState,
9
+ } from 'react'
10
+
11
+ import { CheckIcon } from 'lib/icons/Check.js'
12
+ import { CloseIcon } from 'lib/icons/Close.js'
13
+ import { EditIcon } from 'lib/icons/Edit.js'
14
+
15
+ type EditableDeviceNameProps = {
16
+ onEdit?: (newName: string) => void
17
+ editable?: boolean
18
+ tagName?: string
19
+ value: string
20
+ } & HTMLAttributes<HTMLElement>
21
+
22
+ export function EditableDeviceName({
23
+ onEdit,
24
+ editable = true,
25
+ tagName,
26
+ value,
27
+ ...props
28
+ }: EditableDeviceNameProps): JSX.Element {
29
+ const [editing, setEditing] = useState(false)
30
+ const [errorText, setErrorText] = useState<null | string>(null)
31
+ const [currentValue, setCurrentValue] = useState(value)
32
+ const Tag = (tagName ?? 'span') as 'div'
33
+
34
+ const handleCheck = useCallback(() => {
35
+ const fixedName = fixName(currentValue)
36
+ const valid = isValidName(fixedName)
37
+
38
+ if (valid.type === 'error') {
39
+ setErrorText(valid.message)
40
+ return
41
+ }
42
+
43
+ setEditing(false)
44
+ setCurrentValue(fixedName)
45
+ onEdit?.(fixedName)
46
+ }, [currentValue, onEdit])
47
+
48
+ const handleChange = useCallback(
49
+ (event: ChangeEvent<HTMLInputElement>): void => {
50
+ setCurrentValue(event.target.value)
51
+ setErrorText(null)
52
+ },
53
+ []
54
+ )
55
+
56
+ const handleCancel = useCallback(() => {
57
+ setEditing(false)
58
+ setCurrentValue(value)
59
+ setErrorText(null)
60
+ }, [value])
61
+
62
+ const handleInputKeydown = useCallback(
63
+ (e: KeyboardEvent<HTMLInputElement>): void => {
64
+ if (e.repeat) return
65
+
66
+ if (e.key === 'Enter') {
67
+ handleCheck()
68
+ } else if (e.key === 'Escape') {
69
+ handleCancel()
70
+ }
71
+ },
72
+ [handleCheck, handleCancel]
73
+ )
74
+
75
+ return (
76
+ <Tag
77
+ {...props}
78
+ className={classNames('seam-editable-device-name', props.className)}
79
+ >
80
+ <NameView
81
+ editing={editing}
82
+ value={currentValue}
83
+ onChange={handleChange}
84
+ onKeyDown={handleInputKeydown}
85
+ errorText={errorText}
86
+ />
87
+
88
+ {editable && (
89
+ <span className='seam-editable-device-name-icon-wrapper'>
90
+ <ActionButtons
91
+ editing={editing}
92
+ onEdit={() => {
93
+ setEditing(true)
94
+ }}
95
+ onCancel={handleCancel}
96
+ onCheck={handleCheck}
97
+ />
98
+ </span>
99
+ )}
100
+ </Tag>
101
+ )
102
+ }
103
+
104
+ interface NameViewProps {
105
+ editing: boolean
106
+ value: string
107
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void
108
+ onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void
109
+ errorText?: string | null
110
+ }
111
+
112
+ function NameView(props: NameViewProps): JSX.Element {
113
+ if (!props.editing) {
114
+ return <span>{props.value}</span>
115
+ }
116
+
117
+ return (
118
+ <span className='seam-editable-device-name-input-wrapper'>
119
+ <input
120
+ type='text'
121
+ defaultValue={props.value}
122
+ onChange={props.onChange}
123
+ onKeyDown={props.onKeyDown}
124
+ ref={(el) => {
125
+ setTimeout(() => {
126
+ el?.focus()
127
+ }, 0)
128
+ }}
129
+ />
130
+
131
+ {props.errorText != null && (
132
+ <span className='seam-editable-device-name-input-error'>
133
+ {props.errorText}
134
+ </span>
135
+ )}
136
+ </span>
137
+ )
138
+ }
139
+
140
+ interface ActionButtonsProps {
141
+ onEdit: () => void
142
+ onCancel: () => void
143
+ onCheck: () => void
144
+ editing: boolean
145
+ }
146
+
147
+ function ActionButtons(props: ActionButtonsProps): JSX.Element {
148
+ if (props.editing) {
149
+ return (
150
+ <>
151
+ <IconButton onClick={props.onCheck}>
152
+ <CheckIcon width='1em' height='1em' viewBox='0 0 24 24' />
153
+ </IconButton>
154
+ <IconButton onClick={props.onCancel}>
155
+ <CloseIcon width='1em' height='1em' viewBox='0 0 24 24' />
156
+ </IconButton>
157
+ </>
158
+ )
159
+ }
160
+
161
+ return (
162
+ <IconButton onClick={props.onEdit}>
163
+ <EditIcon width='1em' height='1em' viewBox='0 0 24 24' />
164
+ </IconButton>
165
+ )
166
+ }
167
+
168
+ function IconButton(
169
+ props: PropsWithChildren<HTMLAttributes<HTMLButtonElement>>
170
+ ): JSX.Element {
171
+ return (
172
+ <button
173
+ {...props}
174
+ className={classNames(
175
+ 'seam-editable-device-name-icon-button',
176
+ props.className
177
+ )}
178
+ >
179
+ {props.children}
180
+ </button>
181
+ )
182
+ }
183
+
184
+ const fixName = (name: string): string => {
185
+ return name.replace(/\s+/g, ' ').trim()
186
+ }
187
+
188
+ type Result =
189
+ | { type: 'success' }
190
+ | {
191
+ type: 'error'
192
+ message: string
193
+ }
194
+
195
+ const isValidName = (name: string): Result => {
196
+ if (name.length < 2) {
197
+ return {
198
+ type: 'error',
199
+ message: 'Name must be at least 2 characters long',
200
+ }
201
+ }
202
+
203
+ if (name.length > 64) {
204
+ return {
205
+ type: 'error',
206
+ message: 'Name must be at most 64 characters long',
207
+ }
208
+ }
209
+
210
+ return {
211
+ type: 'success',
212
+ } as const
213
+ }
@@ -5,22 +5,24 @@ import { FanIcon } from 'lib/icons/Fan.js'
5
5
  import { OffIcon } from 'lib/icons/Off.js'
6
6
  import type { ThermostatDevice } from 'lib/seam/thermostats/thermostat-device.js'
7
7
  import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
8
+ import { EditableDeviceName } from 'lib/ui/device/EditableDeviceName.js'
8
9
  import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
9
10
  import { Temperature } from 'lib/ui/thermostat/Temperature.js'
10
11
 
11
12
  interface ThermostatCardProps {
12
13
  device: ThermostatDevice
14
+ onEditName?: (newName: string) => void
13
15
  }
14
16
 
15
- export function ThermostatCard({ device }: ThermostatCardProps): JSX.Element {
17
+ export function ThermostatCard(props: ThermostatCardProps): JSX.Element {
16
18
  return (
17
19
  <div className='seam-thermostat-card'>
18
- <Content device={device} />
20
+ <Content device={props.device} onEditName={props.onEditName} />
19
21
  </div>
20
22
  )
21
23
  }
22
24
 
23
- function Content(props: { device: ThermostatDevice }): JSX.Element | null {
25
+ function Content(props: ThermostatCardProps): JSX.Element | null {
24
26
  const { device } = props
25
27
 
26
28
  const [temperatureUnit, setTemperatureUnit] = useState<
@@ -50,9 +52,12 @@ function Content(props: { device: ThermostatDevice }): JSX.Element | null {
50
52
  </div>
51
53
  <div className='seam-thermostat-card-details'>
52
54
  <div className='seam-thermostat-heading-wrap'>
53
- <h4 className='seam-thermostat-card-heading'>
54
- {device.properties.name}
55
- </h4>
55
+ <EditableDeviceName
56
+ value={device.properties.name}
57
+ tagName='h4'
58
+ className='seam-thermostat-card-heading'
59
+ onEdit={props.onEditName}
60
+ />
56
61
  <button
57
62
  onClick={toggleTemperatureUnit}
58
63
  className='seam-thermostat-temperature-toggle'
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '4.2.0'
1
+ const seamapiReactVersion = '4.3.1'
2
2
 
3
3
  export default seamapiReactVersion
@@ -29,6 +29,7 @@
29
29
  @use './time-zone-picker';
30
30
  @use './tab-set';
31
31
  @use './noise-sensor';
32
+ @use './seam-editable-device-name';
32
33
 
33
34
  .seam-components {
34
35
  // Reset
@@ -54,6 +55,7 @@
54
55
  @include switch.all;
55
56
  @include time-zone-picker.all;
56
57
  @include tab-set.all;
58
+ @include seam-editable-device-name.all;
57
59
 
58
60
  // Components
59
61
  @include device-details.all;
@@ -0,0 +1,62 @@
1
+ @use './colors';
2
+
3
+ @mixin all {
4
+ .seam-editable-device-name {
5
+ input {
6
+ border: none;
7
+ background-color: transparent;
8
+ font-size: inherit;
9
+ font-weight: inherit;
10
+ letter-spacing: inherit;
11
+ line-height: inherit;
12
+ font-family: inherit;
13
+ margin: 0;
14
+ padding: 0;
15
+ border-bottom: 1px dashed currentcolor;
16
+ outline: none;
17
+ }
18
+
19
+ &-input-wrapper {
20
+ display: inline-flex;
21
+ flex-flow: column nowrap;
22
+ }
23
+
24
+ &-input-error {
25
+ color: colors.$status-red;
26
+ font-size: 0.8em;
27
+ margin-top: 5px;
28
+ }
29
+
30
+ &-icon-wrapper {
31
+ margin-left: 10px;
32
+ display: inline-flex;
33
+ flex-flow: row nowrap;
34
+ gap: 5px;
35
+ }
36
+
37
+ &-icon-button {
38
+ display: inline-flex;
39
+ padding: 0;
40
+ margin: 0;
41
+ background: none;
42
+ border: none;
43
+ width: fit-content;
44
+ cursor: pointer;
45
+ transform-origin: center;
46
+ border-radius: 2px;
47
+
48
+ path {
49
+ fill: currentcolor;
50
+ }
51
+
52
+ &:hover {
53
+ background-color: currentcolor;
54
+ box-shadow: 0 0 0 2px currentcolor;
55
+
56
+ path {
57
+ fill: white;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }