@campxdev/shared 1.4.7 → 1.4.9
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/package.json +3 -2
- package/src/components/HookForm/MultiCheckbox.tsx +21 -14
- package/src/components/HookForm/MultiSelect.tsx +4 -1
- package/src/components/HookForm/RadioGroup.tsx +12 -7
- package/src/components/HookForm/SingleCheckbox.tsx +10 -4
- package/src/components/HookForm/SingleSelect.tsx +4 -4
- package/src/components/HookForm/TextField.tsx +5 -6
- package/src/components/Input/AsyncSearchSelect/AsyncSearchSelect.tsx +216 -0
- package/src/components/Input/AsyncSearchSelect/index.tsx +1 -0
- package/src/components/Input/AsyncSearchSelect/styles.tsx +105 -0
- package/src/components/Input/DateTimePicker.tsx +1 -1
- package/src/components/Input/FormLabel.tsx +1 -1
- package/src/components/Input/MultiCheckbox.tsx +50 -48
- package/src/components/Input/MultiSelect.tsx +1 -1
- package/src/components/Input/RadioGroup.tsx +10 -1
- package/src/components/Input/SingleCheckbox.tsx +6 -5
- package/src/components/Input/SingleSelect.tsx +12 -10
- package/src/components/Input/TextField.tsx +6 -2
- package/src/components/Input/TimePicker.tsx +1 -1
- package/src/components/Input/types.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campxdev/shared",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.9",
|
|
4
4
|
"main": "./exports.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "react-scripts start",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"react-toastify": "^9.0.1",
|
|
51
51
|
"styled-components": "^5.3.5",
|
|
52
52
|
"swiper": "^8.1.5",
|
|
53
|
+
"use-immer": "^0.8.1",
|
|
53
54
|
"yup": "^0.32.11"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
|
-
"@types/react-flatpickr": "^3.8.8",
|
|
57
57
|
"@storybook/addon-actions": "^6.5.14",
|
|
58
58
|
"@storybook/addon-essentials": "^6.5.14",
|
|
59
59
|
"@storybook/addon-interactions": "^6.5.14",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"@types/js-cookie": "^3.0.2",
|
|
68
68
|
"@types/node": "^18.11.8",
|
|
69
69
|
"@types/react": "^18.0.25",
|
|
70
|
+
"@types/react-flatpickr": "^3.8.8",
|
|
70
71
|
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
|
71
72
|
"@typescript-eslint/parser": "^5.35.1",
|
|
72
73
|
"eslint": "^8.23.0",
|
|
@@ -1,34 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
BoxProps,
|
|
4
|
+
FormGroup,
|
|
5
|
+
FormGroupProps,
|
|
6
|
+
FormHelperText,
|
|
7
|
+
} from '@mui/material'
|
|
2
8
|
import { ReactNode } from 'react'
|
|
3
9
|
import { Controller } from 'react-hook-form'
|
|
4
10
|
import { FormLabel, SingleCheckbox } from '../Input'
|
|
11
|
+
import { IOption } from '../Input/types'
|
|
5
12
|
|
|
6
13
|
interface Props extends FormGroupProps {
|
|
7
14
|
label?: ReactNode
|
|
8
15
|
name: string
|
|
9
16
|
control: any
|
|
10
|
-
options:
|
|
17
|
+
options: IOption[]
|
|
11
18
|
required?: boolean
|
|
12
19
|
row?: boolean
|
|
20
|
+
containerProps: BoxProps
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
export default function FormMultiCheckbox(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
export default function FormMultiCheckbox({
|
|
24
|
+
name,
|
|
25
|
+
control,
|
|
26
|
+
label = '',
|
|
27
|
+
options = [],
|
|
28
|
+
required = false,
|
|
29
|
+
row = true,
|
|
30
|
+
containerProps,
|
|
31
|
+
...rest
|
|
32
|
+
}: Props) {
|
|
26
33
|
return (
|
|
27
34
|
<Controller
|
|
28
35
|
name={name}
|
|
29
36
|
control={control}
|
|
30
37
|
render={({ field, fieldState: { error } }) => (
|
|
31
|
-
<Box width="100%">
|
|
38
|
+
<Box width="100%" {...containerProps}>
|
|
32
39
|
<FormLabel label={label} name={name} required={required} />
|
|
33
40
|
<FormGroup row={row} sx={{ flexWrap: 'wrap' }} {...rest}>
|
|
34
41
|
{options?.map((item, index) => (
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { ReactNode } from 'react'
|
|
2
2
|
import { Controller } from 'react-hook-form'
|
|
3
3
|
import { MultiSelect } from '../Input'
|
|
4
|
+
import { IOption } from '../Input/types'
|
|
4
5
|
|
|
5
6
|
interface MultiSelectProps {
|
|
6
7
|
control: any
|
|
7
8
|
label: ReactNode
|
|
8
9
|
name: string
|
|
9
|
-
options:
|
|
10
|
+
options: IOption[]
|
|
10
11
|
placeholder?: string
|
|
11
12
|
loading?: boolean
|
|
12
13
|
required?: boolean
|
|
14
|
+
value?: IOption
|
|
15
|
+
onChange?: (value: IOption[]) => void
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export default function FormMultiSelect({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box,
|
|
1
|
+
import { Box, BoxProps, RadioGroupProps } from '@mui/material'
|
|
2
2
|
import { ReactNode } from 'react'
|
|
3
3
|
import { Controller } from 'react-hook-form'
|
|
4
4
|
import { RadioGroup } from '../Input'
|
|
@@ -11,18 +11,24 @@ interface Props extends RadioGroupProps {
|
|
|
11
11
|
row?: boolean
|
|
12
12
|
required?: boolean
|
|
13
13
|
options: { value: any; label: string | ReactNode }[]
|
|
14
|
-
|
|
14
|
+
containerProps?: BoxProps
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export default function FormRadioGroup(
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export default function FormRadioGroup({
|
|
18
|
+
name,
|
|
19
|
+
control,
|
|
20
|
+
label,
|
|
21
|
+
options = [],
|
|
22
|
+
row = false,
|
|
23
|
+
containerProps,
|
|
24
|
+
...rest
|
|
25
|
+
}: Props) {
|
|
20
26
|
return (
|
|
21
27
|
<Controller
|
|
22
28
|
name={name}
|
|
23
29
|
control={control}
|
|
24
30
|
render={({ field, fieldState: { error } }) => (
|
|
25
|
-
<Box width="100%">
|
|
31
|
+
<Box width="100%" {...containerProps}>
|
|
26
32
|
<RadioGroup
|
|
27
33
|
row={row}
|
|
28
34
|
name={name}
|
|
@@ -32,7 +38,6 @@ export default function FormRadioGroup(props: Props) {
|
|
|
32
38
|
onChange={field.onChange}
|
|
33
39
|
{...rest}
|
|
34
40
|
/>
|
|
35
|
-
{error?.message && <FormHelperText>{error.message}</FormHelperText>}
|
|
36
41
|
</Box>
|
|
37
42
|
)}
|
|
38
43
|
/>
|
|
@@ -9,16 +9,22 @@ interface Props extends CheckboxProps {
|
|
|
9
9
|
control: any
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export default function FormSingleCheckbox(
|
|
13
|
-
|
|
12
|
+
export default function FormSingleCheckbox({
|
|
13
|
+
name,
|
|
14
|
+
control,
|
|
15
|
+
label,
|
|
16
|
+
onChange,
|
|
17
|
+
checked,
|
|
18
|
+
...rest
|
|
19
|
+
}: Props) {
|
|
14
20
|
return (
|
|
15
21
|
<Controller
|
|
16
22
|
name={name}
|
|
17
23
|
control={control}
|
|
18
24
|
render={({ field, fieldState: { error } }) => (
|
|
19
25
|
<SingleCheckbox
|
|
20
|
-
checked={field.value}
|
|
21
|
-
onChange={field.onChange}
|
|
26
|
+
checked={checked ?? field.value}
|
|
27
|
+
onChange={onChange ?? field.onChange}
|
|
22
28
|
label={label}
|
|
23
29
|
{...rest}
|
|
24
30
|
/>
|
|
@@ -16,8 +16,9 @@ export default function FormSingleSelect(props: Props) {
|
|
|
16
16
|
options = [],
|
|
17
17
|
control,
|
|
18
18
|
label,
|
|
19
|
+
onChange,
|
|
20
|
+
value,
|
|
19
21
|
firstItemEmpty = false,
|
|
20
|
-
size = 'medium',
|
|
21
22
|
} = props
|
|
22
23
|
|
|
23
24
|
const inputOptions = firstItemEmpty
|
|
@@ -30,11 +31,10 @@ export default function FormSingleSelect(props: Props) {
|
|
|
30
31
|
control={control}
|
|
31
32
|
render={({ field, fieldState: { error } }) => (
|
|
32
33
|
<SingleSelect
|
|
33
|
-
size={size}
|
|
34
34
|
label={label}
|
|
35
35
|
name={name}
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
onChange={onChange ?? field.onChange}
|
|
37
|
+
value={value ?? field?.value}
|
|
38
38
|
options={inputOptions}
|
|
39
39
|
error={!!error}
|
|
40
40
|
helperText={error?.message}
|
|
@@ -8,9 +8,6 @@ type MyTextFieldProps = MuiTextFieldProps & {
|
|
|
8
8
|
label?: string
|
|
9
9
|
name: string
|
|
10
10
|
required?: boolean
|
|
11
|
-
handleChange?: React.ChangeEventHandler<
|
|
12
|
-
HTMLTextAreaElement | HTMLInputElement
|
|
13
|
-
>
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
export default function FormTextField({
|
|
@@ -18,18 +15,20 @@ export default function FormTextField({
|
|
|
18
15
|
control,
|
|
19
16
|
label,
|
|
20
17
|
required,
|
|
18
|
+
onChange,
|
|
19
|
+
value,
|
|
21
20
|
...rest
|
|
22
21
|
}: MyTextFieldProps) {
|
|
23
22
|
return (
|
|
24
23
|
<Controller
|
|
25
24
|
control={control}
|
|
26
25
|
name={name}
|
|
27
|
-
render={({ field
|
|
26
|
+
render={({ field, fieldState: { error } }) => (
|
|
28
27
|
<TextField
|
|
29
28
|
name={name}
|
|
30
29
|
label={label}
|
|
31
|
-
onChange={onChange}
|
|
32
|
-
value={value}
|
|
30
|
+
onChange={onChange ?? field.onChange}
|
|
31
|
+
value={value ?? field?.value}
|
|
33
32
|
required={required}
|
|
34
33
|
error={error ? true : false}
|
|
35
34
|
helperText={error ? error.message : null}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Close } from '@mui/icons-material'
|
|
2
|
+
import { Box, Checkbox, CircularProgress, Typography } from '@mui/material'
|
|
3
|
+
import _ from 'lodash'
|
|
4
|
+
import { ReactNode, useCallback, useEffect, useState } from 'react'
|
|
5
|
+
import { useQuery } from 'react-query'
|
|
6
|
+
import { useImmer } from 'use-immer'
|
|
7
|
+
import FormLabel from '../FormLabel'
|
|
8
|
+
import { BpCheckedIcon, BpIcon } from '../SingleCheckbox'
|
|
9
|
+
import TextField from '../TextField'
|
|
10
|
+
import { IOption } from '../types'
|
|
11
|
+
import {
|
|
12
|
+
StyledAnchor,
|
|
13
|
+
StyledListContainer,
|
|
14
|
+
StyledListItem,
|
|
15
|
+
StyledPopover,
|
|
16
|
+
StyledSelectedChip,
|
|
17
|
+
} from './styles'
|
|
18
|
+
|
|
19
|
+
const Anchor = ({ handleClick, id, label, required }) => {
|
|
20
|
+
return (
|
|
21
|
+
<Box>
|
|
22
|
+
<FormLabel label={label} name={id ?? ''} required={required} />
|
|
23
|
+
<StyledAnchor id={id} onClick={handleClick}>
|
|
24
|
+
<Typography variant="subtitle1">Click to search</Typography>
|
|
25
|
+
</StyledAnchor>
|
|
26
|
+
</Box>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SelectedItems = ({ selectedItems, selectedLabel, onDelete }) => {
|
|
31
|
+
if (!selectedItems?.length) return null
|
|
32
|
+
return (
|
|
33
|
+
<Box marginTop="10px">
|
|
34
|
+
<FormLabel label={selectedLabel} name="form-label" required={false} />
|
|
35
|
+
<Box sx={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
|
36
|
+
{selectedItems?.map((item, index) => (
|
|
37
|
+
<StyledSelectedChip
|
|
38
|
+
deleteIcon={<Close />}
|
|
39
|
+
key={index}
|
|
40
|
+
label={item?.label}
|
|
41
|
+
variant={'outlined'}
|
|
42
|
+
onDelete={() => onDelete(item)}
|
|
43
|
+
/>
|
|
44
|
+
))}
|
|
45
|
+
</Box>
|
|
46
|
+
</Box>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const OptionsList = ({ loading, menuItems, onSelected, selected }) => {
|
|
51
|
+
const isIncluded = (id) => {
|
|
52
|
+
return selected?.options?.find((item) => item.value == id)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleClick = (selectedOption) => {
|
|
56
|
+
if (isIncluded(selectedOption.value)) {
|
|
57
|
+
onSelected(
|
|
58
|
+
selected?.options?.filter(
|
|
59
|
+
(item) => item.value !== selectedOption.value,
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
} else {
|
|
63
|
+
onSelected([...selected.options, selectedOption])
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (loading)
|
|
68
|
+
return <CircularProgress size={16} sx={{ textAlign: 'center' }} />
|
|
69
|
+
|
|
70
|
+
if (!menuItems)
|
|
71
|
+
return (
|
|
72
|
+
<Typography
|
|
73
|
+
sx={{ padding: '10px' }}
|
|
74
|
+
variant="subtitle1"
|
|
75
|
+
textAlign={'center'}
|
|
76
|
+
>
|
|
77
|
+
Start typing to search
|
|
78
|
+
</Typography>
|
|
79
|
+
)
|
|
80
|
+
if (!menuItems?.length)
|
|
81
|
+
return (
|
|
82
|
+
<Typography
|
|
83
|
+
sx={{ padding: '10px' }}
|
|
84
|
+
variant="subtitle1"
|
|
85
|
+
textAlign={'center'}
|
|
86
|
+
>
|
|
87
|
+
No results found
|
|
88
|
+
</Typography>
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
{menuItems?.map((option, index) => (
|
|
94
|
+
<StyledListItem key={index} onClick={() => handleClick(option)}>
|
|
95
|
+
<Checkbox
|
|
96
|
+
icon={isIncluded(option?.value) ? <BpCheckedIcon /> : <BpIcon />}
|
|
97
|
+
/>
|
|
98
|
+
{option.label}
|
|
99
|
+
</StyledListItem>
|
|
100
|
+
))}
|
|
101
|
+
</>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type SearchInputProps = {
|
|
106
|
+
onChange: (value: IOption[]) => void
|
|
107
|
+
initialOptions?: IOption[]
|
|
108
|
+
searchQueryKey?: string
|
|
109
|
+
selectedLabel?: ReactNode
|
|
110
|
+
label: ReactNode
|
|
111
|
+
fetchFn: (params: { [x: string]: string }) => Promise<IOption[]>
|
|
112
|
+
required?: boolean
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default function AsyncSearchSelect({
|
|
116
|
+
label,
|
|
117
|
+
onChange,
|
|
118
|
+
initialOptions = [],
|
|
119
|
+
searchQueryKey = 'search',
|
|
120
|
+
selectedLabel = 'Selected Items',
|
|
121
|
+
fetchFn,
|
|
122
|
+
required,
|
|
123
|
+
}: SearchInputProps) {
|
|
124
|
+
const [search, setSearch] = useState('')
|
|
125
|
+
const [width, setWidth] = useState(300)
|
|
126
|
+
|
|
127
|
+
const { data: searchResults, isLoading } = useQuery(
|
|
128
|
+
['search', search],
|
|
129
|
+
(params: any) => fetchFn({ ...params, [searchQueryKey]: search }),
|
|
130
|
+
{
|
|
131
|
+
enabled: Boolean(search),
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const [selected, setSelected] = useImmer({
|
|
136
|
+
options: [],
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const [open, setOpen] = useState(false)
|
|
140
|
+
const [anchorEl, setAnchorEl] = useState<any>(null)
|
|
141
|
+
const id = open ? 'simple-popover' : undefined
|
|
142
|
+
|
|
143
|
+
const onSearchChange = (e) => {
|
|
144
|
+
setSearch(e.target.value)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const handleClick = (event) => {
|
|
148
|
+
const anchorWidth = event.target.getBoundingClientRect().width
|
|
149
|
+
setWidth(anchorWidth)
|
|
150
|
+
setOpen(true)
|
|
151
|
+
setAnchorEl(event.currentTarget)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const handleClose = () => {
|
|
155
|
+
setOpen(false)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const onSelected = (newValues) => {
|
|
159
|
+
setSelected((s) => {
|
|
160
|
+
s.options = newValues
|
|
161
|
+
})
|
|
162
|
+
if (onChange) onChange(newValues)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const onDeleteSelectedItem = (option) => {
|
|
166
|
+
onSelected(selected?.options?.filter((item) => item.value !== option.value))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const debouncedChange = useCallback(_.debounce(onSearchChange, 300), [])
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
setSelected((s) => {
|
|
173
|
+
s.options = initialOptions
|
|
174
|
+
})
|
|
175
|
+
}, [initialOptions?.length])
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<Box>
|
|
179
|
+
<Anchor
|
|
180
|
+
handleClick={handleClick}
|
|
181
|
+
id={id}
|
|
182
|
+
label={label}
|
|
183
|
+
required={required}
|
|
184
|
+
/>
|
|
185
|
+
<SelectedItems
|
|
186
|
+
onDelete={onDeleteSelectedItem}
|
|
187
|
+
selectedItems={selected?.options}
|
|
188
|
+
selectedLabel={selectedLabel}
|
|
189
|
+
/>
|
|
190
|
+
<StyledPopover
|
|
191
|
+
popoverWidth={width}
|
|
192
|
+
open={open}
|
|
193
|
+
id={id}
|
|
194
|
+
anchorEl={anchorEl}
|
|
195
|
+
onClose={handleClose}
|
|
196
|
+
>
|
|
197
|
+
<Box sx={{ padding: '14px' }}>
|
|
198
|
+
<TextField
|
|
199
|
+
size="small"
|
|
200
|
+
onChange={debouncedChange}
|
|
201
|
+
placeholder="Search"
|
|
202
|
+
autoFocus={true}
|
|
203
|
+
/>
|
|
204
|
+
<StyledListContainer>
|
|
205
|
+
<OptionsList
|
|
206
|
+
loading={isLoading}
|
|
207
|
+
menuItems={searchResults}
|
|
208
|
+
onSelected={onSelected}
|
|
209
|
+
selected={selected}
|
|
210
|
+
/>
|
|
211
|
+
</StyledListContainer>
|
|
212
|
+
</Box>
|
|
213
|
+
</StyledPopover>
|
|
214
|
+
</Box>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './AsyncSearchSelect'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
alpha,
|
|
3
|
+
Box,
|
|
4
|
+
Chip,
|
|
5
|
+
ListItem,
|
|
6
|
+
Popover,
|
|
7
|
+
PopoverProps,
|
|
8
|
+
styled,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
|
|
11
|
+
export const StyledPopover = styled((props: PopoverProps) => (
|
|
12
|
+
<Popover
|
|
13
|
+
elevation={0}
|
|
14
|
+
anchorPosition={{
|
|
15
|
+
left: 0,
|
|
16
|
+
top: 0,
|
|
17
|
+
}}
|
|
18
|
+
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
19
|
+
transitionDuration={150}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
))<{ popoverWidth: number }>(({ theme, popoverWidth }) => ({
|
|
23
|
+
'& .MuiPaper-root': {
|
|
24
|
+
width: popoverWidth,
|
|
25
|
+
borderRadius: '10px',
|
|
26
|
+
boxShadow: '0px 4px 16px #0000000F',
|
|
27
|
+
marginTop: '1px',
|
|
28
|
+
'& .MuiList-root': {
|
|
29
|
+
padding: 0,
|
|
30
|
+
'& li': {
|
|
31
|
+
height: '60px',
|
|
32
|
+
borderBottom: (theme) => theme.borders.grayLight,
|
|
33
|
+
':hover': {
|
|
34
|
+
backgroundColor: 'rgba(0, 0, 0, 0.025)',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
'& > :last-child': {
|
|
38
|
+
borderBottom: 'none',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
export const StyledListContainer = styled(Box)(({ theme }) => ({
|
|
45
|
+
maxHeight: 'calc(100vh - 400px)',
|
|
46
|
+
overflowY: 'auto',
|
|
47
|
+
paddingTop: '10px',
|
|
48
|
+
'&::-webkit-scrollbar': {
|
|
49
|
+
width: '0.5em',
|
|
50
|
+
height: '0.5em',
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
'&::-webkit-scrollbar-thumb': {
|
|
54
|
+
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
|
55
|
+
borderRadius: '3px',
|
|
56
|
+
|
|
57
|
+
'&:hover': {
|
|
58
|
+
background: 'rgba(0, 0, 0, 0.2)',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
export const StyledListItem = styled(ListItem)(({ theme }) => ({
|
|
64
|
+
height: '40px',
|
|
65
|
+
cursor: 'pointer',
|
|
66
|
+
padding: '10px',
|
|
67
|
+
background: 'none',
|
|
68
|
+
'&.Mui-focused': {
|
|
69
|
+
background: 'none',
|
|
70
|
+
},
|
|
71
|
+
'& .MuiCheckbox-root': {
|
|
72
|
+
padding: 0,
|
|
73
|
+
marginRight: '10px',
|
|
74
|
+
},
|
|
75
|
+
}))
|
|
76
|
+
|
|
77
|
+
export const StyledAnchor = styled(Box)(({ theme }) => ({
|
|
78
|
+
height: '50px',
|
|
79
|
+
border: theme.borders.grayLight,
|
|
80
|
+
borderRadius: '10px',
|
|
81
|
+
padding: '14px',
|
|
82
|
+
cursor: 'pointer',
|
|
83
|
+
':hover': {
|
|
84
|
+
borderColor: alpha(theme.palette.common.black, 0.3),
|
|
85
|
+
},
|
|
86
|
+
transition: 'border-color 200ms ease',
|
|
87
|
+
'& > .MuiTypography-root': {
|
|
88
|
+
fontSize: '14px',
|
|
89
|
+
pointerEvents: 'none',
|
|
90
|
+
},
|
|
91
|
+
}))
|
|
92
|
+
|
|
93
|
+
export const StyledSelectedChip = styled(Chip)(({ theme }) => ({
|
|
94
|
+
height: '40px',
|
|
95
|
+
background: '#F8F8F8',
|
|
96
|
+
border: '1px solid #D6D6D6',
|
|
97
|
+
'& .MuiSvgIcon-root': {
|
|
98
|
+
color: theme.palette.secondary.main,
|
|
99
|
+
fontSize: '15px',
|
|
100
|
+
margin: '0 14px',
|
|
101
|
+
'&:hover': {
|
|
102
|
+
color: theme.palette.secondary.main,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
}))
|
|
@@ -1,62 +1,64 @@
|
|
|
1
|
-
import { Box, FormGroup, FormHelperText } from '@mui/material'
|
|
1
|
+
import { Box, BoxProps, FormGroup, FormHelperText } from '@mui/material'
|
|
2
|
+
import { ReactNode } from 'react'
|
|
2
3
|
import { Controller } from 'react-hook-form'
|
|
3
4
|
import FormLabel from './FormLabel'
|
|
4
5
|
import SingleCheckbox from './SingleCheckbox'
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
|
-
label?:
|
|
8
|
+
label?: ReactNode
|
|
8
9
|
name: string
|
|
9
10
|
size?: 'small' | 'medium'
|
|
10
|
-
|
|
11
|
-
options: Array<{ label: string; value: string }>
|
|
11
|
+
options: Array<{ label: ReactNode; value: any }>
|
|
12
12
|
required?: boolean
|
|
13
13
|
row?: boolean
|
|
14
|
+
error?: boolean
|
|
15
|
+
helperText?: ReactNode
|
|
16
|
+
value: { label: string; value: any }[]
|
|
17
|
+
onChange: (value: { label: ReactNode; value: any }[]) => void
|
|
18
|
+
containerProps?: BoxProps
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
export default function MultiCheckbox(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
export default function MultiCheckbox({
|
|
22
|
+
name,
|
|
23
|
+
label,
|
|
24
|
+
options = [],
|
|
25
|
+
required = false,
|
|
26
|
+
value = [],
|
|
27
|
+
onChange,
|
|
28
|
+
error,
|
|
29
|
+
helperText,
|
|
30
|
+
row = true,
|
|
31
|
+
containerProps,
|
|
32
|
+
...rest
|
|
33
|
+
}: Props) {
|
|
26
34
|
return (
|
|
27
|
-
<
|
|
28
|
-
name={name}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
))}
|
|
56
|
-
</FormGroup>
|
|
57
|
-
{error && <FormHelperText>{error.message}</FormHelperText>}
|
|
58
|
-
</Box>
|
|
59
|
-
)}
|
|
60
|
-
/>
|
|
35
|
+
<Box width="100%" {...containerProps}>
|
|
36
|
+
<FormLabel label={label} required={required} name={name} />
|
|
37
|
+
<FormGroup row={row} sx={{ flexWrap: 'wrap' }}>
|
|
38
|
+
{options?.map((item, index) => (
|
|
39
|
+
<SingleCheckbox
|
|
40
|
+
name={name}
|
|
41
|
+
key={index}
|
|
42
|
+
label={item.label}
|
|
43
|
+
checked={value
|
|
44
|
+
?.map((item: any) => item?.value)
|
|
45
|
+
?.includes(item?.value)}
|
|
46
|
+
onChange={(e) => {
|
|
47
|
+
if (e.target.checked) {
|
|
48
|
+
let newValue = [...value, item]
|
|
49
|
+
onChange(newValue)
|
|
50
|
+
} else {
|
|
51
|
+
let filteredValue = value.filter(
|
|
52
|
+
(opt: any) => opt?.value !== item.value,
|
|
53
|
+
)
|
|
54
|
+
onChange(filteredValue)
|
|
55
|
+
}
|
|
56
|
+
}}
|
|
57
|
+
{...rest}
|
|
58
|
+
/>
|
|
59
|
+
))}
|
|
60
|
+
</FormGroup>
|
|
61
|
+
{error && <FormHelperText>{helperText}</FormHelperText>}
|
|
62
|
+
</Box>
|
|
61
63
|
)
|
|
62
64
|
}
|
|
@@ -62,6 +62,7 @@ const StyledPopper = styled(Popper)(({ theme }) => ({
|
|
|
62
62
|
},
|
|
63
63
|
'& .MuiCheckbox-root': {
|
|
64
64
|
padding: 0,
|
|
65
|
+
marginRight: '10px',
|
|
65
66
|
},
|
|
66
67
|
},
|
|
67
68
|
'&::-webkit-scrollbar': {
|
|
@@ -114,7 +115,6 @@ export default function MultiSelect({
|
|
|
114
115
|
<Checkbox
|
|
115
116
|
icon={<BpIcon />}
|
|
116
117
|
checkedIcon={<BpCheckedIcon />}
|
|
117
|
-
style={{ marginRight: 8 }}
|
|
118
118
|
checked={selected}
|
|
119
119
|
/>
|
|
120
120
|
{option.label}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Box,
|
|
3
|
+
BoxProps,
|
|
3
4
|
FormControlLabel,
|
|
5
|
+
FormHelperText,
|
|
4
6
|
Radio,
|
|
5
7
|
RadioGroup as MuiRadioGroup,
|
|
6
8
|
RadioGroupProps,
|
|
@@ -56,6 +58,9 @@ interface Props extends RadioGroupProps {
|
|
|
56
58
|
row?: boolean
|
|
57
59
|
required?: boolean
|
|
58
60
|
options: { value: any; label: string | ReactNode }[]
|
|
61
|
+
containerProps?: BoxProps
|
|
62
|
+
error?: boolean
|
|
63
|
+
helperText?: string | null
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export default function RadioGroup(props: Props) {
|
|
@@ -67,10 +72,13 @@ export default function RadioGroup(props: Props) {
|
|
|
67
72
|
required = false,
|
|
68
73
|
value,
|
|
69
74
|
onChange,
|
|
75
|
+
containerProps,
|
|
76
|
+
error,
|
|
77
|
+
helperText,
|
|
70
78
|
...rest
|
|
71
79
|
} = props
|
|
72
80
|
return (
|
|
73
|
-
<Box width="100%">
|
|
81
|
+
<Box width="100%" {...containerProps}>
|
|
74
82
|
<FormLabel label={label} name={name} required={required} />
|
|
75
83
|
<MuiRadioGroup
|
|
76
84
|
value={value}
|
|
@@ -90,6 +98,7 @@ export default function RadioGroup(props: Props) {
|
|
|
90
98
|
/>
|
|
91
99
|
))}
|
|
92
100
|
</MuiRadioGroup>
|
|
101
|
+
{error && <FormHelperText>{helperText}</FormHelperText>}
|
|
93
102
|
</Box>
|
|
94
103
|
)
|
|
95
104
|
}
|
|
@@ -51,19 +51,20 @@ interface Props extends CheckboxProps {
|
|
|
51
51
|
disabled?: boolean
|
|
52
52
|
label: ReactNode
|
|
53
53
|
name?: string
|
|
54
|
-
size?: 'small' | 'medium'
|
|
55
|
-
sx?: any
|
|
56
54
|
checked: boolean
|
|
57
55
|
onChange: (e) => void
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
export default function SingleCheckbox(
|
|
61
|
-
|
|
58
|
+
export default function SingleCheckbox({
|
|
59
|
+
checked,
|
|
60
|
+
label,
|
|
61
|
+
onChange,
|
|
62
|
+
...props
|
|
63
|
+
}: Props) {
|
|
62
64
|
return (
|
|
63
65
|
<FormControlLabel
|
|
64
66
|
control={
|
|
65
67
|
<Checkbox
|
|
66
|
-
size={size}
|
|
67
68
|
checked={checked}
|
|
68
69
|
onChange={onChange}
|
|
69
70
|
checkedIcon={<BpCheckedIcon />}
|
|
@@ -7,18 +7,11 @@ import {
|
|
|
7
7
|
Select,
|
|
8
8
|
FormHelperText,
|
|
9
9
|
Box,
|
|
10
|
+
BoxProps,
|
|
10
11
|
} from '@mui/material'
|
|
11
12
|
import { ReactNode } from 'react'
|
|
12
13
|
import FormLabel from './FormLabel'
|
|
13
14
|
|
|
14
|
-
type Props = {
|
|
15
|
-
options: Array<{ label: ReactNode; value: any }>
|
|
16
|
-
onChange?: (value: any) => void
|
|
17
|
-
required?: boolean
|
|
18
|
-
firstItemEmpty?: boolean
|
|
19
|
-
helperText?: string
|
|
20
|
-
} & SelectProps
|
|
21
|
-
|
|
22
15
|
const StyledFormControl = styled(FormControl)(({ theme }) => ({
|
|
23
16
|
'& .MuiInputBase-root': {
|
|
24
17
|
'& legend': { display: 'none' },
|
|
@@ -39,7 +32,6 @@ const PaperProps = {
|
|
|
39
32
|
maxHeight: 360,
|
|
40
33
|
marginTop: '1px',
|
|
41
34
|
'& .MuiList-root': {
|
|
42
|
-
minWidth: '240px',
|
|
43
35
|
padding: 0,
|
|
44
36
|
'& li': {
|
|
45
37
|
height: '60px',
|
|
@@ -55,6 +47,15 @@ const PaperProps = {
|
|
|
55
47
|
},
|
|
56
48
|
}
|
|
57
49
|
|
|
50
|
+
type Props = {
|
|
51
|
+
options: Array<{ label: ReactNode; value: any }>
|
|
52
|
+
onChange?: (value: any) => void
|
|
53
|
+
required?: boolean
|
|
54
|
+
firstItemEmpty?: boolean
|
|
55
|
+
helperText?: string
|
|
56
|
+
containerProps?: BoxProps
|
|
57
|
+
} & SelectProps
|
|
58
|
+
|
|
58
59
|
export default function SingleSelect({
|
|
59
60
|
name = 'select',
|
|
60
61
|
options = [],
|
|
@@ -63,6 +64,7 @@ export default function SingleSelect({
|
|
|
63
64
|
required,
|
|
64
65
|
value,
|
|
65
66
|
helperText,
|
|
67
|
+
containerProps,
|
|
66
68
|
firstItemEmpty = false,
|
|
67
69
|
...props
|
|
68
70
|
}: Props) {
|
|
@@ -71,7 +73,7 @@ export default function SingleSelect({
|
|
|
71
73
|
: options
|
|
72
74
|
|
|
73
75
|
return (
|
|
74
|
-
<Box width="100%">
|
|
76
|
+
<Box width="100%" {...containerProps}>
|
|
75
77
|
<FormLabel required={required} label={label} name={name} />
|
|
76
78
|
<StyledFormControl fullWidth>
|
|
77
79
|
<Select
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Box,
|
|
3
|
+
BoxProps,
|
|
3
4
|
styled,
|
|
4
5
|
TextField as MuiTextField,
|
|
5
6
|
TextFieldProps as MuiTextFieldProps,
|
|
@@ -17,7 +18,9 @@ const StyledTextField = styled(MuiTextField)(({ theme }) => ({
|
|
|
17
18
|
},
|
|
18
19
|
}))
|
|
19
20
|
|
|
20
|
-
type TextFieldProps = {
|
|
21
|
+
type TextFieldProps = {
|
|
22
|
+
containerProps?: BoxProps
|
|
23
|
+
} & MuiTextFieldProps
|
|
21
24
|
|
|
22
25
|
export default function TextField({
|
|
23
26
|
name,
|
|
@@ -25,10 +28,11 @@ export default function TextField({
|
|
|
25
28
|
value,
|
|
26
29
|
onChange,
|
|
27
30
|
required = false,
|
|
31
|
+
containerProps,
|
|
28
32
|
...rest
|
|
29
33
|
}: TextFieldProps) {
|
|
30
34
|
return (
|
|
31
|
-
<Box width="100%">
|
|
35
|
+
<Box width="100%" {...containerProps}>
|
|
32
36
|
<FieldLabel label={label} required={required} name={name} />
|
|
33
37
|
<StyledTextField
|
|
34
38
|
id={name}
|