@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.
- package/README.md +1 -0
- package/dist/elements.js +5755 -5574
- package/dist/elements.js.map +1 -1
- package/dist/index.css +105 -17
- package/dist/index.css.map +1 -1
- package/dist/index.min.css +1 -1
- package/dist/index.min.css.map +1 -1
- package/lib/icons/Add.d.ts +2 -0
- package/lib/icons/Add.js +7 -0
- package/lib/icons/Add.js.map +1 -0
- package/lib/icons/DotsEllipsisMore.js +2 -2
- package/lib/icons/DotsEllipsisMore.js.map +1 -1
- package/lib/seam/SeamProvider.d.ts +2 -2
- package/lib/seam/SeamProvider.js.map +1 -1
- package/lib/seam/access-codes/use-create-access-code.d.ts +6 -0
- package/lib/seam/access-codes/use-create-access-code.js +18 -0
- package/lib/seam/access-codes/use-create-access-code.js.map +1 -0
- package/lib/seam/components/AccessCodeForm/AccessCodeForm.d.ts +7 -0
- package/lib/seam/components/AccessCodeForm/AccessCodeForm.js +52 -0
- package/lib/seam/components/AccessCodeForm/AccessCodeForm.js.map +1 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +10 -2
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
- package/lib/ui/Alert/Alerts.d.ts +2 -2
- package/lib/ui/Alert/Alerts.js.map +1 -1
- package/lib/ui/Button.d.ts +3 -2
- package/lib/ui/Button.js +2 -2
- package/lib/ui/Button.js.map +1 -1
- package/lib/ui/FormField.d.ts +7 -0
- package/lib/ui/FormField.js +37 -0
- package/lib/ui/FormField.js.map +1 -0
- package/lib/ui/InputLabel.d.ts +7 -0
- package/lib/ui/InputLabel.js +7 -0
- package/lib/ui/InputLabel.js.map +1 -0
- package/lib/ui/Menu/Menu.d.ts +2 -2
- package/lib/ui/Menu/Menu.js.map +1 -1
- package/lib/ui/Menu/MenuItem.d.ts +2 -2
- package/lib/ui/Menu/MenuItem.js +1 -1
- package/lib/ui/Menu/MenuItem.js.map +1 -1
- package/lib/ui/Menu/MoreActionsMenu.d.ts +2 -2
- package/lib/ui/Menu/MoreActionsMenu.js.map +1 -1
- package/lib/ui/TextField/SearchTextField.js +1 -48
- package/lib/ui/TextField/SearchTextField.js.map +1 -1
- package/lib/ui/TextField/TextField.d.ts +8 -2
- package/lib/ui/TextField/TextField.js +56 -5
- package/lib/ui/TextField/TextField.js.map +1 -1
- package/lib/ui/layout/ContentHeader.d.ts +5 -3
- package/lib/ui/layout/ContentHeader.js +2 -2
- package/lib/ui/layout/ContentHeader.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/lib/icons/Add.tsx +33 -0
- package/src/lib/icons/DotsEllipsisMore.tsx +7 -7
- package/src/lib/seam/SeamProvider.tsx +3 -2
- package/src/lib/seam/access-codes/use-create-access-code.ts +34 -0
- package/src/lib/seam/components/AccessCodeForm/AccessCodeForm.element.ts +12 -0
- package/src/lib/seam/components/AccessCodeForm/AccessCodeForm.tsx +113 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +1 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +30 -3
- package/src/lib/ui/Alert/Alerts.tsx +2 -4
- package/src/lib/ui/Button.tsx +5 -2
- package/src/lib/ui/FormField.tsx +56 -0
- package/src/lib/ui/InputLabel.tsx +21 -0
- package/src/lib/ui/Menu/Menu.tsx +2 -2
- package/src/lib/ui/Menu/MenuItem.tsx +2 -5
- package/src/lib/ui/Menu/MoreActionsMenu.tsx +2 -2
- package/src/lib/ui/TextField/SearchTextField.tsx +1 -70
- package/src/lib/ui/TextField/TextField.tsx +132 -19
- package/src/lib/ui/layout/ContentHeader.tsx +13 -5
- package/src/lib/version.ts +1 -1
- package/src/styles/_access-code-form.scss +55 -0
- package/src/styles/_access-code-table.scss +11 -0
- package/src/styles/_buttons.scss +11 -1
- package/src/styles/_colors.scss +1 -0
- package/src/styles/_form-field.scss +15 -0
- package/src/styles/_input-label.scss +11 -0
- package/src/styles/_inputs.scss +47 -22
- package/src/styles/_layout.scss +9 -1
- package/src/styles/_main.scss +6 -0
- 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
|
-
}:
|
|
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
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
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
|
package/src/lib/ui/Button.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
+
)
|
package/src/lib/ui/Menu/Menu.tsx
CHANGED
|
@@ -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
|
-
}:
|
|
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
|
-
}:
|
|
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
|
-
|
|
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
|
-
}
|