@seamapi/react 1.28.0 → 1.29.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 (81) hide show
  1. package/README.md +1 -0
  2. package/dist/elements.js +5755 -5574
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +105 -17
  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/Add.d.ts +2 -0
  9. package/lib/icons/Add.js +7 -0
  10. package/lib/icons/Add.js.map +1 -0
  11. package/lib/icons/DotsEllipsisMore.js +2 -2
  12. package/lib/icons/DotsEllipsisMore.js.map +1 -1
  13. package/lib/seam/SeamProvider.d.ts +2 -2
  14. package/lib/seam/SeamProvider.js.map +1 -1
  15. package/lib/seam/access-codes/use-create-access-code.d.ts +6 -0
  16. package/lib/seam/access-codes/use-create-access-code.js +18 -0
  17. package/lib/seam/access-codes/use-create-access-code.js.map +1 -0
  18. package/lib/seam/components/AccessCodeForm/AccessCodeForm.d.ts +7 -0
  19. package/lib/seam/components/AccessCodeForm/AccessCodeForm.js +52 -0
  20. package/lib/seam/components/AccessCodeForm/AccessCodeForm.js.map +1 -0
  21. package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -1
  22. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +10 -2
  23. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
  24. package/lib/ui/Alert/Alerts.d.ts +2 -2
  25. package/lib/ui/Alert/Alerts.js.map +1 -1
  26. package/lib/ui/Button.d.ts +3 -2
  27. package/lib/ui/Button.js +2 -2
  28. package/lib/ui/Button.js.map +1 -1
  29. package/lib/ui/FormField.d.ts +7 -0
  30. package/lib/ui/FormField.js +37 -0
  31. package/lib/ui/FormField.js.map +1 -0
  32. package/lib/ui/InputLabel.d.ts +7 -0
  33. package/lib/ui/InputLabel.js +7 -0
  34. package/lib/ui/InputLabel.js.map +1 -0
  35. package/lib/ui/Menu/Menu.d.ts +2 -2
  36. package/lib/ui/Menu/Menu.js.map +1 -1
  37. package/lib/ui/Menu/MenuItem.d.ts +2 -2
  38. package/lib/ui/Menu/MenuItem.js +1 -1
  39. package/lib/ui/Menu/MenuItem.js.map +1 -1
  40. package/lib/ui/Menu/MoreActionsMenu.d.ts +2 -2
  41. package/lib/ui/Menu/MoreActionsMenu.js.map +1 -1
  42. package/lib/ui/TextField/SearchTextField.js +1 -48
  43. package/lib/ui/TextField/SearchTextField.js.map +1 -1
  44. package/lib/ui/TextField/TextField.d.ts +8 -2
  45. package/lib/ui/TextField/TextField.js +56 -5
  46. package/lib/ui/TextField/TextField.js.map +1 -1
  47. package/lib/ui/layout/ContentHeader.d.ts +5 -3
  48. package/lib/ui/layout/ContentHeader.js +2 -2
  49. package/lib/ui/layout/ContentHeader.js.map +1 -1
  50. package/lib/version.d.ts +1 -1
  51. package/lib/version.js +1 -1
  52. package/package.json +1 -1
  53. package/src/lib/icons/Add.tsx +33 -0
  54. package/src/lib/icons/DotsEllipsisMore.tsx +7 -7
  55. package/src/lib/seam/SeamProvider.tsx +3 -2
  56. package/src/lib/seam/access-codes/use-create-access-code.ts +34 -0
  57. package/src/lib/seam/components/AccessCodeForm/AccessCodeForm.element.ts +12 -0
  58. package/src/lib/seam/components/AccessCodeForm/AccessCodeForm.tsx +113 -0
  59. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +1 -0
  60. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +30 -3
  61. package/src/lib/ui/Alert/Alerts.tsx +2 -4
  62. package/src/lib/ui/Button.tsx +5 -2
  63. package/src/lib/ui/FormField.tsx +56 -0
  64. package/src/lib/ui/InputLabel.tsx +21 -0
  65. package/src/lib/ui/Menu/Menu.tsx +2 -2
  66. package/src/lib/ui/Menu/MenuItem.tsx +2 -5
  67. package/src/lib/ui/Menu/MoreActionsMenu.tsx +2 -2
  68. package/src/lib/ui/TextField/SearchTextField.tsx +1 -70
  69. package/src/lib/ui/TextField/TextField.tsx +132 -19
  70. package/src/lib/ui/layout/ContentHeader.tsx +13 -5
  71. package/src/lib/version.ts +1 -1
  72. package/src/styles/_access-code-form.scss +55 -0
  73. package/src/styles/_access-code-table.scss +11 -0
  74. package/src/styles/_buttons.scss +11 -1
  75. package/src/styles/_colors.scss +1 -0
  76. package/src/styles/_form-field.scss +15 -0
  77. package/src/styles/_input-label.scss +11 -0
  78. package/src/styles/_inputs.scss +47 -22
  79. package/src/styles/_layout.scss +9 -1
  80. package/src/styles/_main.scss +6 -0
  81. package/src/styles/_tables.scss +1 -1
@@ -25,10 +25,11 @@ export interface SeamContext {
25
25
  clientSessionToken?: string | undefined
26
26
  }
27
27
 
28
- export type SeamProviderProps =
28
+ export type SeamProviderProps = PropsWithChildren<
29
29
  | SeamProviderPropsWithClient
30
30
  | SeamProviderPropsWithPublishableKey
31
31
  | SeamProviderPropsWithClientSessionToken
32
+ >
32
33
 
33
34
  export interface SeamProviderPropsWithClient extends SeamProviderBaseProps {
34
35
  client: Seam
@@ -65,7 +66,7 @@ export function SeamProvider({
65
66
  unminifiyCss = false,
66
67
  queryClient,
67
68
  ...props
68
- }: PropsWithChildren<SeamProviderProps>): JSX.Element {
69
+ }: SeamProviderProps): JSX.Element {
69
70
  useSeamStyles({ disabled: disableCssInjection, unminified: unminifiyCss })
70
71
  useSeamFont({ disabled: disableFontInjection })
71
72
 
@@ -0,0 +1,34 @@
1
+ import {
2
+ useMutation,
3
+ type UseMutationResult,
4
+ useQueryClient,
5
+ } from '@tanstack/react-query'
6
+ import type { AccessCode, AccessCodeCreateRequest, SeamError } from 'seamapi'
7
+
8
+ import { useSeamClient } from 'lib/seam/use-seam-client.js'
9
+
10
+ export type UseCreateAccessCodeParams = never
11
+ export type UseCreateAccessCodeData = AccessCode
12
+ export type UseCreateAccessCodeMutationParams = AccessCodeCreateRequest
13
+
14
+ export function useCreateAccessCode(): UseMutationResult<
15
+ UseCreateAccessCodeData,
16
+ SeamError,
17
+ UseCreateAccessCodeMutationParams
18
+ > {
19
+ const { client } = useSeamClient()
20
+ const queryClient = useQueryClient()
21
+
22
+ return useMutation({
23
+ mutationFn: async (mutationParams) => {
24
+ if (client === null) {
25
+ throw new Error('Missing seam client')
26
+ }
27
+
28
+ return await client.accessCodes.create(mutationParams)
29
+ },
30
+ onSuccess: () => {
31
+ void queryClient.invalidateQueries(['access_codes', 'list'])
32
+ },
33
+ })
34
+ }
@@ -0,0 +1,12 @@
1
+ import type { ElementProps } from 'lib/element.js'
2
+ import type { AccessCodeFormProps } from 'lib/seam/components/AccessCodeForm/AccessCodeForm.js'
3
+
4
+ export const name = 'seam-access-code-form'
5
+
6
+ export const props: ElementProps<AccessCodeFormProps> = {
7
+ deviceId: 'string',
8
+ onBack: 'function',
9
+ className: 'string',
10
+ }
11
+
12
+ export { AccessCodeForm as Component } from './AccessCodeForm.js'
@@ -0,0 +1,113 @@
1
+ import classNames from 'classnames'
2
+ import { useState } from 'react'
3
+
4
+ import { useCreateAccessCode } from 'lib/seam/access-codes/use-create-access-code.js'
5
+ import { useDevice, type UseDeviceData } from 'lib/seam/devices/use-device.js'
6
+ import { Button } from 'lib/ui/Button.js'
7
+ import { FormField } from 'lib/ui/FormField.js'
8
+ import { InputLabel } from 'lib/ui/InputLabel.js'
9
+ import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
10
+ import { TextField } from 'lib/ui/TextField/TextField.js'
11
+
12
+ export interface AccessCodeFormProps {
13
+ deviceId: string
14
+ onBack?: () => void
15
+ className?: string
16
+ }
17
+
18
+ export function AccessCodeForm({
19
+ deviceId,
20
+ onBack,
21
+ className,
22
+ }: AccessCodeFormProps): JSX.Element | null {
23
+ const { device } = useDevice({
24
+ device_id: deviceId,
25
+ })
26
+
27
+ if (device == null) {
28
+ return null
29
+ }
30
+
31
+ return (
32
+ <div className={classNames('seam-access-code-form', className)}>
33
+ <Content className={className} onBack={onBack} device={device} />
34
+ </div>
35
+ )
36
+ }
37
+
38
+ function Content({
39
+ onBack,
40
+ device,
41
+ }: Omit<AccessCodeFormProps, 'deviceId'> & {
42
+ device: NonNullable<UseDeviceData>
43
+ }): JSX.Element {
44
+ const [name, setName] = useState('')
45
+
46
+ const createAccessCode = useCreateAccessCode()
47
+
48
+ const save = (): void => {
49
+ if (name === '') {
50
+ return
51
+ }
52
+
53
+ if (createAccessCode.isLoading) {
54
+ return
55
+ }
56
+
57
+ createAccessCode.mutate(
58
+ {
59
+ name,
60
+ device_id: device.device_id,
61
+ },
62
+ {
63
+ onSuccess: () => {
64
+ onBack?.()
65
+ },
66
+ }
67
+ )
68
+ }
69
+
70
+ const nameError = name.length > 60 ? t.overCharacterLimitError : undefined
71
+
72
+ const isFormValid =
73
+ name.trim().length > 0 &&
74
+ nameError === undefined &&
75
+ !createAccessCode.isLoading
76
+
77
+ return (
78
+ <>
79
+ <ContentHeader
80
+ title={t.addNewAccessCode}
81
+ subheading={device.properties.name}
82
+ onBack={onBack}
83
+ />
84
+ <div className='seam-main'>
85
+ <FormField>
86
+ <InputLabel>{t.nameInputLabel}</InputLabel>
87
+ <TextField
88
+ size='large'
89
+ clearable
90
+ value={name}
91
+ onChange={setName}
92
+ hasError={nameError != null}
93
+ helperText={nameError}
94
+ />
95
+ </FormField>
96
+ <div className='seam-actions'>
97
+ <Button onClick={onBack}>{t.cancel}</Button>
98
+ <Button variant='solid' disabled={!isFormValid} onMouseDown={save}>
99
+ {t.save}
100
+ </Button>
101
+ </div>
102
+ </div>
103
+ </>
104
+ )
105
+ }
106
+
107
+ const t = {
108
+ addNewAccessCode: 'Add new access code',
109
+ overCharacterLimitError: '60 characters max',
110
+ nameInputLabel: 'Name the new code',
111
+ cancel: 'Cancel',
112
+ save: 'Save',
113
+ }
@@ -7,6 +7,7 @@ export const name = 'seam-access-code-table'
7
7
  export const props: ElementProps<AccessCodeTableProps> = {
8
8
  deviceId: 'string',
9
9
  disableLockUnlock: 'boolean',
10
+ disableCreateAccessCode: 'boolean',
10
11
  accessCodeFilter: 'function',
11
12
  accessCodeComparator: 'function',
12
13
  onAccessCodeClick: 'function',
@@ -3,6 +3,7 @@ import { useCallback, useMemo, useState } from 'react'
3
3
 
4
4
  import { compareByCreatedAtDesc } from 'lib/dates.js'
5
5
  import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
6
+ import { AddIcon } from 'lib/icons/Add.js'
6
7
  import { CopyIcon } from 'lib/icons/Copy.js'
7
8
  import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
8
9
  import { TriangleWarningOutlineIcon } from 'lib/icons/TriangleWarningOutline.js'
@@ -11,12 +12,14 @@ import {
11
12
  type UseAccessCodesData,
12
13
  } from 'lib/seam/access-codes/use-access-codes.js'
13
14
  import { AccessCodeDetails } from 'lib/seam/components/AccessCodeDetails/AccessCodeDetails.js'
15
+ import { AccessCodeForm } from 'lib/seam/components/AccessCodeForm/AccessCodeForm.js'
14
16
  import {
15
17
  type AccessCodeFilter,
16
18
  AccessCodeHealthBar,
17
19
  } from 'lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js'
18
20
  import { CodeDetails } from 'lib/seam/components/AccessCodeTable/CodeDetails.js'
19
21
  import { copyToClipboard } from 'lib/ui/clipboard.js'
22
+ import { IconButton } from 'lib/ui/IconButton.js'
20
23
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
21
24
  import { MenuItem } from 'lib/ui/Menu/MenuItem.js'
22
25
  import { MoreActionsMenu } from 'lib/ui/Menu/MoreActionsMenu.js'
@@ -29,10 +32,12 @@ import { TableTitle } from 'lib/ui/Table/TableTitle.js'
29
32
  import { SearchTextField } from 'lib/ui/TextField/SearchTextField.js'
30
33
  import { Caption } from 'lib/ui/typography/Caption.js'
31
34
  import { Title } from 'lib/ui/typography/Title.js'
35
+ import { useToggle } from 'lib/ui/use-toggle.js'
32
36
 
33
37
  export interface AccessCodeTableProps {
34
38
  deviceId: string
35
39
  disableLockUnlock?: boolean
40
+ disableCreateAccessCode?: boolean
36
41
  accessCodeFilter?: (
37
42
  accessCode: AccessCode,
38
43
  searchInputValue: string
@@ -65,6 +70,7 @@ const defaultAccessCodeFilter = (
65
70
  export function AccessCodeTable({
66
71
  deviceId,
67
72
  disableLockUnlock = false,
73
+ disableCreateAccessCode = false,
68
74
  onAccessCodeClick = () => {},
69
75
  preventDefaultOnAccessCodeClick = false,
70
76
  onBack,
@@ -81,6 +87,7 @@ export function AccessCodeTable({
81
87
  >(null)
82
88
 
83
89
  const [searchInputValue, setSearchInputValue] = useState('')
90
+ const [accessCodeFormVisible, toggleAddAccessCodeForm] = useToggle()
84
91
 
85
92
  const filteredAccessCodes = useMemo(
86
93
  () =>
@@ -116,13 +123,33 @@ export function AccessCodeTable({
116
123
  )
117
124
  }
118
125
 
126
+ if (accessCodeFormVisible) {
127
+ return (
128
+ <AccessCodeForm
129
+ className={className}
130
+ onBack={toggleAddAccessCodeForm}
131
+ deviceId={deviceId}
132
+ />
133
+ )
134
+ }
135
+
119
136
  return (
120
137
  <div className={classNames('seam-access-code-table', className)}>
121
138
  <ContentHeader onBack={onBack} />
122
139
  <TableHeader>
123
- <TableTitle>
124
- {t.accessCodes} <Caption>({filteredAccessCodes.length})</Caption>
125
- </TableTitle>
140
+ <div className='seam-left'>
141
+ <TableTitle>
142
+ {t.accessCodes} <Caption>({filteredAccessCodes.length})</Caption>{' '}
143
+ </TableTitle>
144
+ {!disableCreateAccessCode && (
145
+ <IconButton
146
+ onClick={toggleAddAccessCodeForm}
147
+ className='seam-add-access-code-button'
148
+ >
149
+ <AddIcon />
150
+ </IconButton>
151
+ )}
152
+ </div>
126
153
  <SearchTextField
127
154
  value={searchInputValue}
128
155
  onChange={setSearchInputValue}
@@ -3,14 +3,12 @@ import type { PropsWithChildren } from 'react'
3
3
 
4
4
  import { Alert, type AlertProps } from 'lib/ui/Alert/Alert.js'
5
5
 
6
- interface AlertsProps {
6
+ interface AlertsProps extends PropsWithChildren {
7
7
  alerts?: AlertProps[]
8
8
  className?: string
9
9
  }
10
10
 
11
- export function Alerts(
12
- props: PropsWithChildren<AlertsProps>
13
- ): JSX.Element | null {
11
+ export function Alerts(props: AlertsProps): JSX.Element | null {
14
12
  const { alerts, children, className } = props
15
13
 
16
14
  if (alerts?.length === 0) return null
@@ -1,12 +1,13 @@
1
1
  import classNames from 'classnames'
2
2
  import type { MouseEventHandler, PropsWithChildren } from 'react'
3
3
 
4
- interface ButtonProps {
4
+ interface ButtonProps extends PropsWithChildren {
5
5
  variant?: 'solid' | 'outline' | 'neutral'
6
6
  size?: 'small' | 'medium' | 'large'
7
7
  disabled?: boolean
8
8
  onClick?: MouseEventHandler<HTMLButtonElement>
9
9
  className?: string
10
+ onMouseDown?: MouseEventHandler<HTMLButtonElement>
10
11
  }
11
12
 
12
13
  export function Button({
@@ -16,7 +17,8 @@ export function Button({
16
17
  disabled = false,
17
18
  onClick,
18
19
  className,
19
- }: PropsWithChildren<ButtonProps>): JSX.Element {
20
+ onMouseDown,
21
+ }: ButtonProps): JSX.Element {
20
22
  return (
21
23
  <button
22
24
  className={classNames(
@@ -28,6 +30,7 @@ export function Button({
28
30
  )}
29
31
  disabled={disabled}
30
32
  onClick={onClick}
33
+ onMouseDown={onMouseDown}
31
34
  >
32
35
  {children}
33
36
  </button>
@@ -0,0 +1,56 @@
1
+ import classNames from 'classnames'
2
+ import { cloneElement, useRef } from 'react'
3
+
4
+ import { InputLabel } from 'lib/ui/InputLabel.js'
5
+ import { TextField } from 'lib/ui/TextField/TextField.js'
6
+
7
+ interface FormFieldProps {
8
+ children: JSX.Element | JSX.Element[]
9
+ className?: string
10
+ }
11
+
12
+ export function FormField({
13
+ children,
14
+ className,
15
+ }: FormFieldProps): JSX.Element {
16
+ const inputRef = useRef<HTMLInputElement>()
17
+
18
+ const focusInput = (): void => {
19
+ if (inputRef.current != null) {
20
+ inputRef.current.focus()
21
+ }
22
+ }
23
+
24
+ const components = Array.isArray(children) ? children : [children]
25
+
26
+ // Map children to automatically focus on input when clicking
27
+ // on an InputLabel.
28
+ const clonedChildren = components.map((component, index) => {
29
+ const baseProps = {
30
+ ...component.props,
31
+ key: index,
32
+ }
33
+
34
+ if (component.type === InputLabel) {
35
+ return cloneElement(component, {
36
+ ...baseProps,
37
+ onClick: focusInput,
38
+ })
39
+ }
40
+
41
+ if (component.type === TextField) {
42
+ return cloneElement(component, {
43
+ ...baseProps,
44
+ ref: inputRef,
45
+ })
46
+ }
47
+
48
+ return cloneElement(component, baseProps)
49
+ })
50
+
51
+ return (
52
+ <div className={classNames('seam-form-field', className)}>
53
+ {clonedChildren}
54
+ </div>
55
+ )
56
+ }
@@ -0,0 +1,21 @@
1
+ import classNames from 'classnames'
2
+ import { forwardRef, type PropsWithChildren } from 'react'
3
+
4
+ interface InputLabelProps extends PropsWithChildren {
5
+ className?: string
6
+ onClick?: () => void
7
+ }
8
+
9
+ export const InputLabel = forwardRef<HTMLDivElement, InputLabelProps>(
10
+ function InputLabel({ children, className, onClick }, ref) {
11
+ return (
12
+ <div
13
+ className={classNames('seam-input-label', className)}
14
+ ref={ref}
15
+ onClick={onClick}
16
+ >
17
+ {children}
18
+ </div>
19
+ )
20
+ }
21
+ )
@@ -11,7 +11,7 @@ import {
11
11
  } from 'react'
12
12
  import { createPortal } from 'react-dom'
13
13
 
14
- export interface MenuProps {
14
+ export interface MenuProps extends PropsWithChildren {
15
15
  verticalOffset?: number
16
16
  horizontalOffset?: number
17
17
  edgeOffset?: number
@@ -38,7 +38,7 @@ export function Menu({
38
38
  children,
39
39
  renderButton,
40
40
  backgroundProps,
41
- }: PropsWithChildren<MenuProps>): JSX.Element | null {
41
+ }: MenuProps): JSX.Element | null {
42
42
  const { Provider } = menuContext
43
43
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
44
44
  const [documentEl, setDocumentEl] = useState<null | Element>(null)
@@ -2,14 +2,11 @@ import type { PropsWithChildren } from 'react'
2
2
 
3
3
  import { useMenu } from 'lib/ui/Menu/Menu.js'
4
4
 
5
- interface MenuItemProps {
5
+ interface MenuItemProps extends PropsWithChildren {
6
6
  onClick: () => void
7
7
  }
8
8
 
9
- export function MenuItem({
10
- onClick,
11
- children,
12
- }: PropsWithChildren<MenuItemProps>): JSX.Element {
9
+ export function MenuItem({ onClick, children }: MenuItemProps): JSX.Element {
13
10
  const { close: closeMenu } = useMenu()
14
11
 
15
12
  return (
@@ -4,14 +4,14 @@ import { DotsEllipsisMoreIcon } from 'lib/icons/DotsEllipsisMore.js'
4
4
  import { IconButton } from 'lib/ui/IconButton.js'
5
5
  import { Menu, type MenuProps } from 'lib/ui/Menu/Menu.js'
6
6
 
7
- interface MoreActionsMenuProps {
7
+ interface MoreActionsMenuProps extends PropsWithChildren {
8
8
  menuProps?: Partial<MenuProps>
9
9
  }
10
10
 
11
11
  export function MoreActionsMenu({
12
12
  children,
13
13
  menuProps,
14
- }: PropsWithChildren<MoreActionsMenuProps>): JSX.Element {
14
+ }: MoreActionsMenuProps): JSX.Element {
15
15
  return (
16
16
  <Menu
17
17
  renderButton={({ onOpen }) => (
@@ -1,7 +1,5 @@
1
1
  import classNames from 'classnames'
2
- import { useEffect, useState } from 'react'
3
2
 
4
- import { CloseIcon } from 'lib/icons/Close.js'
5
3
  import { SearchIcon } from 'lib/icons/Search.js'
6
4
  import { TextField, type TextFieldProps } from 'lib/ui/TextField/TextField.js'
7
5
 
@@ -11,20 +9,6 @@ export function SearchTextField({
11
9
  onChange,
12
10
  ...props
13
11
  }: TextFieldProps): JSX.Element {
14
- const [inputEl, setInputEl] = useState<HTMLInputElement | null>(null)
15
-
16
- const valueIsEmpty = useValueIsEmpty(value, inputEl)
17
- const clearInput = (): void => {
18
- if (onChange != null) {
19
- onChange('')
20
- return
21
- }
22
-
23
- if (inputEl != null) {
24
- inputEl.value = ''
25
- }
26
- }
27
-
28
12
  return (
29
13
  <TextField
30
14
  value={value}
@@ -33,62 +17,9 @@ export function SearchTextField({
33
17
  className={classNames(className, 'seam-search-text-field')}
34
18
  startAdornment={<SearchIcon />}
35
19
  inputProps={{
36
- ref: setInputEl,
37
20
  placeholder: 'Search',
38
21
  }}
39
- endAdornment={
40
- <button
41
- className={classNames({
42
- 'seam-hidden': valueIsEmpty,
43
- })}
44
- onClick={clearInput}
45
- >
46
- <CloseIcon />
47
- </button>
48
- }
22
+ clearable
49
23
  />
50
24
  )
51
25
  }
52
-
53
- function useValueIsEmpty(
54
- value: string | undefined,
55
- inputEl: HTMLInputElement | null
56
- ): boolean {
57
- const [valueIsEmpty, setValueIsEmpty] = useState(true)
58
-
59
- // If this is a controlled element, we'll just look at `value`
60
- useEffect(() => {
61
- setValueIsEmpty(value == null || value === '')
62
- }, [value])
63
-
64
- // If this is not a controlled element, we'll need to listen to `input`
65
- // events.
66
- useEffect(() => {
67
- if (inputEl == null) {
68
- return
69
- }
70
-
71
- const handler = (event: Event): void => {
72
- if (value !== undefined) {
73
- return
74
- }
75
-
76
- if (event.target == null || !('value' in event.target)) {
77
- return
78
- }
79
-
80
- const inputValue = event.target.value
81
- if (value === undefined) {
82
- setValueIsEmpty(inputValue === '')
83
- }
84
- }
85
-
86
- inputEl.addEventListener('input', handler)
87
-
88
- return () => {
89
- inputEl.removeEventListener('input', handler)
90
- }
91
- }, [inputEl, value])
92
-
93
- return valueIsEmpty
94
- }