@devtron-labs/devtron-fe-common-lib 0.0.7 → 0.0.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/dist/Common/BreadCrumb/BreadCrumb.d.ts +1 -29
- package/dist/Common/BreadCrumb/Types.d.ts +28 -0
- package/dist/Common/Checkbox.d.ts +1 -14
- package/dist/Common/Constants.d.ts +13 -0
- package/dist/Common/CustomTagSelector/ResizableTagTextArea.d.ts +1 -1
- package/dist/Common/CustomTagSelector/TagLabelValueSelector.d.ts +1 -1
- package/dist/Common/CustomTagSelector/Types.d.ts +11 -3
- package/dist/Common/CustomTagSelector/ValidationRules.d.ts +1 -1
- package/dist/Common/DeleteComponentModal/DeleteComponent.d.ts +4 -0
- package/dist/Common/DeleteComponentModal/types.d.ts +15 -0
- package/dist/Common/Dialogs/ConfirmationDialog.d.ts +5 -16
- package/dist/Common/Dialogs/DialogForm.d.ts +1 -9
- package/dist/Common/Dialogs/Types.d.ts +27 -0
- package/dist/Common/EmptyState/EmptyState.d.ts +2 -1
- package/dist/Common/EmptyState/GenericEmptyState.d.ts +5 -0
- package/dist/Common/Error.d.ts +2 -2
- package/dist/Common/Helper.d.ts +31 -1
- package/dist/Common/InfoColorBar/InfoColourbar.d.ts +2 -15
- package/dist/Common/Modals/Modal.d.ts +2 -9
- package/dist/Common/MultiSelectCustomization.d.ts +1 -2
- package/dist/Common/PopupMenu.d.ts +4 -21
- package/dist/Common/Progressing.d.ts +4 -13
- package/dist/Common/RadioGroup/RadioGroup.d.ts +17 -0
- package/dist/Common/RadioGroup.d.ts +1 -10
- package/dist/Common/Reload.d.ts +2 -4
- package/dist/Common/Security/ScanVulnerabilitiesTable.d.ts +4 -0
- package/dist/Common/TippyCustomized.d.ts +2 -30
- package/dist/Common/Toggle/Toggle.d.ts +12 -0
- package/dist/Common/Types.d.ts +266 -0
- package/dist/Common/index.d.ts +6 -1
- package/dist/index.js +2388 -1943
- package/package.json +2 -2
- package/rollup.config.js +3 -3
- package/src/Assets/Icon/ic-info-filled.svg +7 -0
- package/src/Assets/Icon/ic-warning.svg +7 -0
- package/src/Assets/Img/app-not-deployed.png +0 -0
- package/src/Common/Api.ts +7 -1
- package/src/Common/BreadCrumb/BreadCrumb.tsx +1 -30
- package/src/Common/BreadCrumb/Types.ts +30 -0
- package/src/Common/Checkbox.tsx +3 -16
- package/src/Common/Constants.ts +15 -0
- package/src/Common/CustomTagSelector/ResizableTagTextArea.tsx +6 -4
- package/src/Common/CustomTagSelector/TagDetails.tsx +15 -2
- package/src/Common/CustomTagSelector/TagLabelSelect.tsx +7 -3
- package/src/Common/CustomTagSelector/TagLabelValueSelector.tsx +66 -26
- package/src/Common/CustomTagSelector/Types.ts +11 -3
- package/src/Common/CustomTagSelector/ValidationRules.ts +21 -18
- package/src/Common/DeleteComponentModal/DeleteComponent.tsx +95 -0
- package/src/Common/DeleteComponentModal/types.ts +15 -0
- package/src/Common/Dialogs/ConfirmationDialog.tsx +6 -4
- package/src/Common/Dialogs/DeleteDialog.tsx +2 -2
- package/src/Common/Dialogs/DialogForm.tsx +2 -11
- package/src/Common/Dialogs/Types.ts +28 -0
- package/src/Common/Drawer/Drawer.scss +12 -11
- package/src/Common/EmptyState/EmptyState.tsx +5 -5
- package/src/Common/EmptyState/GenericEmptyState.tsx +41 -0
- package/src/Common/EmptyState/emptyState.scss +4 -15
- package/src/Common/Error.tsx +13 -16
- package/src/Common/ErrorScreenNotFound.tsx +23 -30
- package/src/Common/Helper.tsx +258 -2
- package/src/Common/InfoColorBar/InfoColourbar.tsx +6 -18
- package/src/Common/Modals/Modal.tsx +7 -1
- package/src/Common/Modals/OpaqueModal.tsx +2 -2
- package/src/Common/Modals/VisibleModal.tsx +1 -1
- package/src/Common/Modals/VisibleModal2.tsx +1 -1
- package/src/Common/MultiSelectCustomization.tsx +3 -3
- package/src/Common/PopupMenu.tsx +23 -6
- package/src/Common/Progressing.tsx +8 -15
- package/src/Common/RadioGroup/RadioGroup.tsx +88 -0
- package/src/Common/RadioGroup/radioGroup.scss +46 -0
- package/src/Common/RadioGroup.tsx +3 -13
- package/src/Common/Reload.tsx +15 -10
- package/src/Common/Security/ScanVulnerabilitiesTable.tsx +55 -0
- package/src/Common/Security/scanVulnerabilities.css +67 -0
- package/src/Common/TippyCustomized.tsx +3 -31
- package/src/Common/Toggle/Toggle.scss +70 -0
- package/src/Common/Toggle/Toggle.tsx +38 -0
- package/src/Common/Types.ts +290 -0
- package/src/Common/index.ts +6 -1
- package/dist/27bfa7a87d0b7039.png +0 -0
- package/dist/b5d52f036bdcd3ed.png +0 -0
- package/src/Assets/Icon/ic-close.svg +0 -9
package/src/Common/Helper.tsx
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
1
2
|
import { ServerErrors } from './ServerError'
|
|
2
3
|
import { toast } from 'react-toastify'
|
|
3
4
|
import * as Sentry from '@sentry/browser'
|
|
4
5
|
import { toastAccessDenied } from './ToastBody'
|
|
5
|
-
import {
|
|
6
|
+
import { ERROR_EMPTY_SCREEN, TOKEN_COOKIE_NAME } from './Constants'
|
|
7
|
+
import { ReactComponent as FormError } from '../Assets/Icon/ic-warning.svg'
|
|
8
|
+
|
|
9
|
+
toast.configure({
|
|
10
|
+
autoClose: 3000,
|
|
11
|
+
hideProgressBar: true,
|
|
12
|
+
pauseOnHover: true,
|
|
13
|
+
pauseOnFocusLoss: true,
|
|
14
|
+
closeOnClick: false,
|
|
15
|
+
newestOnTop: true,
|
|
16
|
+
toastClassName: 'devtron-toast',
|
|
17
|
+
bodyClassName: 'devtron-toast__body',
|
|
18
|
+
})
|
|
6
19
|
|
|
7
20
|
export function showError(serverError, showToastOnUnknownError = true, hideAccessError = false) {
|
|
8
21
|
if (serverError instanceof ServerErrors && Array.isArray(serverError.errors)) {
|
|
9
22
|
serverError.errors.map(({ userMessage, internalMessage }) => {
|
|
10
|
-
if (
|
|
23
|
+
if (
|
|
24
|
+
serverError.code === 403 &&
|
|
25
|
+
(userMessage === ERROR_EMPTY_SCREEN.UNAUTHORIZED || userMessage === ERROR_EMPTY_SCREEN.FORBIDDEN)
|
|
26
|
+
) {
|
|
11
27
|
if (!hideAccessError) {
|
|
12
28
|
toastAccessDenied()
|
|
13
29
|
}
|
|
@@ -75,3 +91,243 @@ export function useThrottledEffect(callback, delay, deps = []) {
|
|
|
75
91
|
}
|
|
76
92
|
}, [delay, ...deps])
|
|
77
93
|
}
|
|
94
|
+
|
|
95
|
+
const colors = [
|
|
96
|
+
'#FFB900',
|
|
97
|
+
'#D83B01',
|
|
98
|
+
'#B50E0E',
|
|
99
|
+
'#E81123',
|
|
100
|
+
'#B4009E',
|
|
101
|
+
'#5C2D91',
|
|
102
|
+
'#0078D7',
|
|
103
|
+
'#00B4FF',
|
|
104
|
+
'#008272',
|
|
105
|
+
'#107C10',
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
export function getRandomColor(email: string): string {
|
|
109
|
+
let sum = 0
|
|
110
|
+
for (let i = 0; i < email.length; i++) {
|
|
111
|
+
sum += email.charCodeAt(i)
|
|
112
|
+
}
|
|
113
|
+
return colors[sum % colors.length]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const getAlphabetIcon = (str: string) => {
|
|
117
|
+
if (!str) return null
|
|
118
|
+
return (
|
|
119
|
+
<span
|
|
120
|
+
className="alphabet-icon__initial fs-13 icon-dim-20 flex cn-0 mr-8"
|
|
121
|
+
style={{ backgroundColor: getRandomColor(str) }}
|
|
122
|
+
>
|
|
123
|
+
{str[0]}
|
|
124
|
+
</span>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const getEmptyArrayOfLength = (length: number) => {
|
|
129
|
+
return Array.from({ length })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function noop(...args): any {}
|
|
133
|
+
|
|
134
|
+
export function not(e) {
|
|
135
|
+
return !e
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function useEffectAfterMount(cb, dependencies) {
|
|
139
|
+
const justMounted = React.useRef(true)
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
if (!justMounted.current) {
|
|
142
|
+
return cb()
|
|
143
|
+
}
|
|
144
|
+
justMounted.current = false
|
|
145
|
+
}, dependencies)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getCookie(sKey) {
|
|
149
|
+
if (!sKey) {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
return (
|
|
153
|
+
document.cookie.replace(
|
|
154
|
+
new RegExp('(?:(?:^|.*;)\\s*' + sKey.replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'),
|
|
155
|
+
'$1',
|
|
156
|
+
) || null
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getLoginInfo() {
|
|
161
|
+
const argocdToken = getCookie(TOKEN_COOKIE_NAME)
|
|
162
|
+
if (argocdToken) {
|
|
163
|
+
const jwts = argocdToken.split('.')
|
|
164
|
+
try {
|
|
165
|
+
return JSON.parse(atob(jwts[1]))
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error('error in setting user ', err)
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function useForm(stateSchema, validationSchema = {}, callback) {
|
|
174
|
+
const [state, setState] = useState(stateSchema)
|
|
175
|
+
const [disable, setDisable] = useState(true)
|
|
176
|
+
const [isDirty, setIsDirty] = useState(false)
|
|
177
|
+
|
|
178
|
+
// Disable button in initial render.
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
setDisable(true)
|
|
181
|
+
}, [])
|
|
182
|
+
|
|
183
|
+
// For every changed in our state this will be fired
|
|
184
|
+
// To be able to disable the button
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (isDirty) {
|
|
187
|
+
setDisable(validateState(state))
|
|
188
|
+
}
|
|
189
|
+
}, [state, isDirty])
|
|
190
|
+
|
|
191
|
+
// Used to disable submit button if there's an error in state
|
|
192
|
+
// or the required field in state has no value.
|
|
193
|
+
// Wrapped in useCallback to cached the function to avoid intensive memory leaked
|
|
194
|
+
// in every re-render in component
|
|
195
|
+
const validateState = useCallback(
|
|
196
|
+
(state) => {
|
|
197
|
+
//check errors in all fields
|
|
198
|
+
const hasErrorInState = Object.keys(validationSchema).some((key) => {
|
|
199
|
+
const isInputFieldRequired = validationSchema[key].required
|
|
200
|
+
const stateValue = state[key].value // state value
|
|
201
|
+
const stateError = state[key].error // state error
|
|
202
|
+
return (isInputFieldRequired && !stateValue) || stateError
|
|
203
|
+
})
|
|
204
|
+
return hasErrorInState
|
|
205
|
+
},
|
|
206
|
+
[state, validationSchema],
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
function validateField(name, value): string | string[] {
|
|
210
|
+
if (validationSchema[name].required) {
|
|
211
|
+
if (!value) {
|
|
212
|
+
return 'This is a required field.'
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function _validateSingleValidator(validator, value) {
|
|
217
|
+
if (value && !validator.regex.test(value)) {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// single validator
|
|
224
|
+
let _validator = validationSchema[name].validator
|
|
225
|
+
if (_validator && typeof _validator === 'object') {
|
|
226
|
+
if (!_validateSingleValidator(_validator, value)) {
|
|
227
|
+
return _validator.error
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// multiple validators
|
|
232
|
+
let _validators = validationSchema[name].validators
|
|
233
|
+
if (_validators && typeof _validators === 'object' && Array.isArray(_validators)) {
|
|
234
|
+
let errors = []
|
|
235
|
+
_validators.forEach((_validator) => {
|
|
236
|
+
if (!_validateSingleValidator(_validator, value)) {
|
|
237
|
+
errors.push(_validator.error)
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
if (errors.length > 0) {
|
|
241
|
+
return errors
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return ''
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const handleOnChange = useCallback(
|
|
249
|
+
(event) => {
|
|
250
|
+
setIsDirty(true)
|
|
251
|
+
|
|
252
|
+
const { name, value } = event.target
|
|
253
|
+
let error = validateField(name, value)
|
|
254
|
+
setState((prevState) => ({
|
|
255
|
+
...prevState,
|
|
256
|
+
[name]: { value, error },
|
|
257
|
+
}))
|
|
258
|
+
},
|
|
259
|
+
[validationSchema],
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const handleOnSubmit = (event) => {
|
|
263
|
+
event.preventDefault()
|
|
264
|
+
const newState = Object.keys(validationSchema).reduce((agg, curr) => {
|
|
265
|
+
agg[curr] = { ...state[curr], error: validateField(curr, state[curr].value) }
|
|
266
|
+
return agg
|
|
267
|
+
}, state)
|
|
268
|
+
if (!validateState(newState)) {
|
|
269
|
+
callback(state)
|
|
270
|
+
} else {
|
|
271
|
+
setState({ ...newState })
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { state, disable, handleOnChange, handleOnSubmit }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function handleError(error: any): any[] {
|
|
278
|
+
if (!error) {
|
|
279
|
+
return []
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!Array.isArray(error)) {
|
|
283
|
+
return [error]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return error
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function CustomInput({
|
|
290
|
+
name,
|
|
291
|
+
value,
|
|
292
|
+
error,
|
|
293
|
+
onChange,
|
|
294
|
+
onBlur = (e) => {},
|
|
295
|
+
onFocus = (e) => {},
|
|
296
|
+
label,
|
|
297
|
+
type = 'text',
|
|
298
|
+
disabled = false,
|
|
299
|
+
autoComplete = 'off',
|
|
300
|
+
labelClassName = '',
|
|
301
|
+
placeholder = '',
|
|
302
|
+
tabIndex = 1,
|
|
303
|
+
dataTestid = '',
|
|
304
|
+
}) {
|
|
305
|
+
return (
|
|
306
|
+
<div className="flex column left top">
|
|
307
|
+
<label className={`form__label ${labelClassName}`}>{label}</label>
|
|
308
|
+
<input
|
|
309
|
+
data-testid={dataTestid}
|
|
310
|
+
type={type}
|
|
311
|
+
name={name}
|
|
312
|
+
autoComplete="off"
|
|
313
|
+
className="form__input"
|
|
314
|
+
onChange={(e) => {
|
|
315
|
+
e.persist()
|
|
316
|
+
onChange(e)
|
|
317
|
+
}}
|
|
318
|
+
onBlur={onBlur}
|
|
319
|
+
onFocus={onFocus}
|
|
320
|
+
placeholder={placeholder}
|
|
321
|
+
value={value}
|
|
322
|
+
disabled={disabled}
|
|
323
|
+
tabIndex={tabIndex}
|
|
324
|
+
/>
|
|
325
|
+
{handleError(error).map((err) => (
|
|
326
|
+
<div className="form__error">
|
|
327
|
+
<FormError className="form__icon form__icon--error" />
|
|
328
|
+
{err}
|
|
329
|
+
</div>
|
|
330
|
+
))}
|
|
331
|
+
</div>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
@@ -1,22 +1,8 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
import { Link } from 'react-router-dom'
|
|
3
|
+
import { InfoColourBarType } from '../Types'
|
|
3
4
|
import './infoColourBar.scss'
|
|
4
5
|
|
|
5
|
-
interface InfoColourBarType {
|
|
6
|
-
message: React.ReactNode
|
|
7
|
-
classname: string
|
|
8
|
-
Icon
|
|
9
|
-
iconClass?: string
|
|
10
|
-
iconSize?: number // E.g. 16, 20, etc.. Currently, there are around 12 sizes supported. Check `icons.css` or `base.scss` for supported sizes or add new size (class names starts with `icon-dim-`).
|
|
11
|
-
renderActionButton?: () => JSX.Element
|
|
12
|
-
linkText?: React.ReactNode
|
|
13
|
-
redirectLink?: string
|
|
14
|
-
linkOnClick?: () => void
|
|
15
|
-
linkClass?: string
|
|
16
|
-
internalLink?: boolean
|
|
17
|
-
styles?: CSSProperties
|
|
18
|
-
}
|
|
19
|
-
|
|
20
6
|
function InfoColourBar({
|
|
21
7
|
message,
|
|
22
8
|
classname,
|
|
@@ -40,6 +26,7 @@ function InfoColourBar({
|
|
|
40
26
|
<Link
|
|
41
27
|
to={redirectLink}
|
|
42
28
|
onClick={linkOnClick}
|
|
29
|
+
data-testid="info-bar-internal-link"
|
|
43
30
|
className="cursor dc__link dc__underline-onhover mr-5"
|
|
44
31
|
>
|
|
45
32
|
{linkText}
|
|
@@ -52,6 +39,7 @@ function InfoColourBar({
|
|
|
52
39
|
href={redirectLink}
|
|
53
40
|
target="_blank"
|
|
54
41
|
onClick={linkOnClick}
|
|
42
|
+
data-testid="info-bar-redirectLink"
|
|
55
43
|
className="cursor dc__link dc__underline-onhover mr-5"
|
|
56
44
|
>
|
|
57
45
|
{linkText}
|
|
@@ -61,7 +49,7 @@ function InfoColourBar({
|
|
|
61
49
|
|
|
62
50
|
return (
|
|
63
51
|
linkOnClick && (
|
|
64
|
-
<div onClick={linkOnClick} className="cursor dc__link dc__underline-onhover">
|
|
52
|
+
<div onClick={linkOnClick} className="cursor dc__link dc__underline-onhover" data-testid="info-bar-linkText">
|
|
65
53
|
{linkText}
|
|
66
54
|
</div>
|
|
67
55
|
)
|
|
@@ -71,7 +59,7 @@ function InfoColourBar({
|
|
|
71
59
|
return (
|
|
72
60
|
<div className="info-bar-container">
|
|
73
61
|
<div
|
|
74
|
-
className={`${classname} info_text flex dc__content-space pt-8 pb-8 pl-
|
|
62
|
+
className={`${classname} info_text flex dc__content-space pt-8 pb-8 pl-12 pr-12 br-4 top fs-13 fw-4`}
|
|
75
63
|
style={styles}
|
|
76
64
|
>
|
|
77
65
|
<div className={`flex top ${typeof renderActionButton === 'function' ? 'mr-5' : ''}`}>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React, { useEffect } from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom'
|
|
3
|
+
import { ModalType } from '../Types'
|
|
4
|
+
|
|
3
5
|
export const Modal = ({
|
|
4
6
|
style = {},
|
|
5
7
|
children,
|
|
@@ -8,7 +10,8 @@ export const Modal = ({
|
|
|
8
10
|
onClick = null,
|
|
9
11
|
callbackRef = null,
|
|
10
12
|
preventWheelDisable = false,
|
|
11
|
-
|
|
13
|
+
noBackDrop,
|
|
14
|
+
}: ModalType) => {
|
|
12
15
|
const innerRef = React.useRef(null)
|
|
13
16
|
function handleClick(e) {
|
|
14
17
|
if (typeof onClick !== 'function') return
|
|
@@ -34,17 +37,20 @@ export const Modal = ({
|
|
|
34
37
|
document.addEventListener('click', handleClick)
|
|
35
38
|
let modal = document.getElementById('visible-modal')
|
|
36
39
|
if (modal) modal.classList.add('show')
|
|
40
|
+
if (noBackDrop) modal.classList.add('no-back-drop')
|
|
37
41
|
if (!preventWheelDisable) document.body.addEventListener('wheel', disableWheel, { passive: false })
|
|
38
42
|
return () => {
|
|
39
43
|
if (!preventWheelDisable) document.body.removeEventListener('wheel', disableWheel)
|
|
40
44
|
document.removeEventListener('click', handleClick)
|
|
41
45
|
if (modal) modal.classList.remove('show')
|
|
46
|
+
if (noBackDrop) modal.classList.remove('no-back-drop')
|
|
42
47
|
}
|
|
43
48
|
}, [])
|
|
44
49
|
return ReactDOM.createPortal(
|
|
45
50
|
<div
|
|
46
51
|
tabIndex={0}
|
|
47
52
|
onClick={onClick}
|
|
53
|
+
data-testid="common-modal"
|
|
48
54
|
ref={(el) => {
|
|
49
55
|
if (typeof callbackRef === 'function') {
|
|
50
56
|
callbackRef(el)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom';
|
|
3
|
-
import closeIcon from '../../Assets/Icon/ic-
|
|
3
|
+
import closeIcon from '../../Assets/Icon/ic-cross.svg'
|
|
4
4
|
|
|
5
5
|
export interface OpaqueModalProps {
|
|
6
6
|
className?: string;
|
|
@@ -28,7 +28,7 @@ export class OpaqueModal extends React.Component<OpaqueModalProps> {
|
|
|
28
28
|
return ReactDOM.createPortal(
|
|
29
29
|
<div className={`full-screen-modal__body-container ${className}`}>
|
|
30
30
|
{this.props.children}
|
|
31
|
-
{typeof onHide === 'function' && <div className="close-btn icon-dim-24" onClick={e => onHide(false)}>
|
|
31
|
+
{typeof onHide === 'function' && <div className="close-btn icon-dim-24" onClick={e => onHide(false)} data-testid="opaque-modal-close">
|
|
32
32
|
<img className="close-img" src={closeIcon} alt="close" />
|
|
33
33
|
</div>}
|
|
34
34
|
</div>, document.getElementById('full-screen-modal'))
|
|
@@ -46,7 +46,7 @@ export class VisibleModal extends React.Component<{
|
|
|
46
46
|
|
|
47
47
|
render() {
|
|
48
48
|
return ReactDOM.createPortal(
|
|
49
|
-
<div className={`visible-modal__body ${this.props.className}`} onClick={this.props?.close}>
|
|
49
|
+
<div className={`visible-modal__body ${this.props.className}`} onClick={this.props?.close} data-testid="visible-modal-close">
|
|
50
50
|
{this.props.children}
|
|
51
51
|
</div>,
|
|
52
52
|
document.getElementById('visible-modal'),
|
|
@@ -28,7 +28,7 @@ export class VisibleModal2 extends React.Component<{ className: string; close?:
|
|
|
28
28
|
|
|
29
29
|
render() {
|
|
30
30
|
return ReactDOM.createPortal(
|
|
31
|
-
<div className={`visible-modal__body ${this.props.className}`} onClick={this.props?.close}>
|
|
31
|
+
<div className={`visible-modal__body ${this.props.className}`} onClick={this.props?.close} data-testid="visible-modal2-close">
|
|
32
32
|
{this.props.children}
|
|
33
33
|
</div>, document.getElementById('visible-modal-2'))
|
|
34
34
|
}
|
|
@@ -87,9 +87,9 @@ export const MultiValueChipContainer = ({ validator, isAllSelected = false, ...p
|
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
89
|
<components.MultiValueContainer {...{ data, innerProps, selectProps }}>
|
|
90
|
-
<div className=
|
|
90
|
+
<div className="flex left fs-12 pl-4 pr-4 dc__ellipsis-right">
|
|
91
91
|
{!isValidEmail && <RedWarning className="mr-4 icon-dim-16" />}
|
|
92
|
-
<div className={
|
|
92
|
+
<div className={`dc__ellipsis-right ${isValidEmail ? 'cn-9' : 'cr-5'}`}>{label}</div>
|
|
93
93
|
</div>
|
|
94
94
|
{children[1]}
|
|
95
95
|
</components.MultiValueContainer>
|
|
@@ -124,7 +124,7 @@ export const multiSelectStyles = {
|
|
|
124
124
|
}),
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
interface CustomSelect {
|
|
127
|
+
export interface CustomSelect {
|
|
128
128
|
sortSelected?: boolean
|
|
129
129
|
options: any[]
|
|
130
130
|
onChange: (...args) => void
|
package/src/Common/PopupMenu.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React, { useEffect } from 'react'
|
|
2
|
-
import {Modal} from './Modals/Modal'
|
|
3
|
-
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
import { Modal } from './Modals/Modal'
|
|
3
|
+
import { PopupMenuBodyType, PopupMenuButtonType, PopupMenuType } from './Types'
|
|
4
|
+
|
|
5
|
+
const PopupContext = React.createContext(null)
|
|
4
6
|
|
|
5
7
|
function usePopupContext() {
|
|
6
8
|
const context = React.useContext(PopupContext)
|
|
@@ -10,7 +12,12 @@ function usePopupContext() {
|
|
|
10
12
|
return context
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
function PopupMenu({
|
|
15
|
+
function PopupMenu({
|
|
16
|
+
children = null,
|
|
17
|
+
onToggleCallback = null,
|
|
18
|
+
autoClose = false,
|
|
19
|
+
autoPosition = false,
|
|
20
|
+
}: PopupMenuType) {
|
|
14
21
|
const [popupPosition, setPopupPosition] = React.useState(null)
|
|
15
22
|
const [opacity, setOpacity] = React.useState(0)
|
|
16
23
|
const observer = React.useRef<IntersectionObserver | null>(null)
|
|
@@ -135,7 +142,8 @@ function Button({
|
|
|
135
142
|
tabIndex = 0,
|
|
136
143
|
onHover = false,
|
|
137
144
|
isKebab = false,
|
|
138
|
-
|
|
145
|
+
dataTestId = "",
|
|
146
|
+
}: PopupMenuButtonType) {
|
|
139
147
|
const { handleOpen, popupPosition, buttonRef, initialiseButtonWidth } = usePopupContext()
|
|
140
148
|
return (
|
|
141
149
|
<button
|
|
@@ -148,13 +156,21 @@ function Button({
|
|
|
148
156
|
}`}
|
|
149
157
|
onMouseEnter={onHover ? (disabled ? null : handleOpen) : () => {}}
|
|
150
158
|
onClick={disabled ? null : handleOpen}
|
|
159
|
+
data-testid={dataTestId}
|
|
151
160
|
>
|
|
152
161
|
{children}
|
|
153
162
|
</button>
|
|
154
163
|
)
|
|
155
164
|
}
|
|
156
165
|
|
|
157
|
-
function Body({
|
|
166
|
+
function Body({
|
|
167
|
+
children = null,
|
|
168
|
+
rootClassName = '',
|
|
169
|
+
style = {},
|
|
170
|
+
autoWidth = false,
|
|
171
|
+
preventWheelDisable = false,
|
|
172
|
+
noBackDrop,
|
|
173
|
+
}: PopupMenuBodyType) {
|
|
158
174
|
const { handleClose, popupPosition, opacity, callbackRef, buttonWidth } = usePopupContext()
|
|
159
175
|
return popupPosition ? (
|
|
160
176
|
<Modal
|
|
@@ -170,6 +186,7 @@ function Body({ children = null, rootClassName = '', style = {}, autoWidth = fal
|
|
|
170
186
|
opacity: opacity,
|
|
171
187
|
}}
|
|
172
188
|
preventWheelDisable={preventWheelDisable}
|
|
189
|
+
noBackDrop={noBackDrop}
|
|
173
190
|
>
|
|
174
191
|
{children}
|
|
175
192
|
</Modal>
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
import { ProgressingProps } from './Types'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
pageLoader?: boolean
|
|
5
|
-
loadingText?: string
|
|
6
|
-
size?: number
|
|
7
|
-
fullHeight?: boolean
|
|
8
|
-
theme?: 'white' | 'default'
|
|
9
|
-
styles?: React.CSSProperties
|
|
10
|
-
children?: React.ReactNode
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function Progressing({ pageLoader, size, theme, styles }: ProgressingProps): JSX.Element {
|
|
4
|
+
export function Progressing({ pageLoader, size, theme, styles, fillColor }: ProgressingProps): JSX.Element {
|
|
14
5
|
const loaderSize = size ? `${size}px` : pageLoader ? '48px' : '20px'
|
|
15
6
|
return (
|
|
16
|
-
<div className={`loader ${theme || 'default'}-background`} style={styles}>
|
|
7
|
+
<div className={`loader ${theme || 'default'}-background`} style={styles} data-testid="progressing">
|
|
17
8
|
<div style={{ width: loaderSize, height: loaderSize }}>
|
|
18
9
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className="loader__svg">
|
|
19
10
|
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
|
@@ -27,7 +18,7 @@ export function Progressing({ pageLoader, size, theme, styles }: ProgressingProp
|
|
|
27
18
|
type="rotate"
|
|
28
19
|
/>
|
|
29
20
|
<path
|
|
30
|
-
fill=
|
|
21
|
+
fill={fillColor || '#06C'}
|
|
31
22
|
fillRule="nonzero"
|
|
32
23
|
d="M12 2.5A9.5 9.5 0 1 1 2.5 12a1.5 1.5 0 0 1 3 0A6.5 6.5 0 1 0 12 5.5a1.5 1.5 0 0 1 0-3z"
|
|
33
24
|
/>
|
|
@@ -42,16 +33,18 @@ export function DetailsProgressing({
|
|
|
42
33
|
loadingText,
|
|
43
34
|
size = 24,
|
|
44
35
|
fullHeight = false,
|
|
45
|
-
children,
|
|
46
36
|
styles,
|
|
37
|
+
fillColor,
|
|
38
|
+
children,
|
|
47
39
|
}: ProgressingProps): JSX.Element {
|
|
48
40
|
return (
|
|
49
41
|
<div
|
|
50
42
|
className={`details-loader bcn-0 flex column fs-14 fw-6 ${fullHeight ? 'h-100' : 'details-loader-height'}`}
|
|
51
43
|
style={styles}
|
|
44
|
+
data-testid="details-progressing"
|
|
52
45
|
>
|
|
53
46
|
<span style={{ width: `${size}px`, height: `${size}px` }}>
|
|
54
|
-
<Progressing size={size} />
|
|
47
|
+
<Progressing size={size} fillColor={fillColor} />
|
|
55
48
|
</span>
|
|
56
49
|
{loadingText && <span className="mt-10">{loadingText}</span>}
|
|
57
50
|
{children}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import Tippy from '@tippyjs/react'
|
|
2
|
+
import React, { useState, useEffect } from 'react'
|
|
3
|
+
import { ConditionalWrap } from '../Helper'
|
|
4
|
+
import './radioGroup.scss'
|
|
5
|
+
|
|
6
|
+
const RadioContext = React.createContext(null)
|
|
7
|
+
|
|
8
|
+
function useRadioContext() {
|
|
9
|
+
const context = React.useContext(RadioContext)
|
|
10
|
+
if (!context) {
|
|
11
|
+
throw new Error(`Radio compound components cannot be rendered outside the Toggle component`)
|
|
12
|
+
}
|
|
13
|
+
return context
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RadioGroupInterface {
|
|
17
|
+
name: string
|
|
18
|
+
onChange: any
|
|
19
|
+
className?: string
|
|
20
|
+
initialTab: string
|
|
21
|
+
disabled: boolean
|
|
22
|
+
showTippy?: boolean
|
|
23
|
+
tippyContent?: any
|
|
24
|
+
dataTestId?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RadioGroupComposition {
|
|
28
|
+
Radio?: React.FC<any>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RadioGroup: React.FC<RadioGroupInterface> & RadioGroupComposition = React.memo(function RadioGroup({
|
|
32
|
+
name,
|
|
33
|
+
onChange,
|
|
34
|
+
children,
|
|
35
|
+
className = '',
|
|
36
|
+
initialTab,
|
|
37
|
+
disabled = false,
|
|
38
|
+
dataTestId
|
|
39
|
+
}) {
|
|
40
|
+
const [selected, select] = useState(null)
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (initialTab === selected) return
|
|
44
|
+
select(initialTab)
|
|
45
|
+
}, [initialTab])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<RadioContext.Provider value={{ name, selected, select, disabled, onChange }}>
|
|
49
|
+
<div className={`${className} radio-group`}>{children}</div>
|
|
50
|
+
</RadioContext.Provider>
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
function Radio({ value, children, className = '', showTippy = false, tippyContent = '', canSelect = true, isDisabled = false, tippyClass = '', dataTestId }) {
|
|
55
|
+
const { name, selected, select, disabled, onChange } = useRadioContext()
|
|
56
|
+
return (
|
|
57
|
+
<ConditionalWrap
|
|
58
|
+
condition={showTippy}
|
|
59
|
+
wrap={(children) => (
|
|
60
|
+
<Tippy className={`default-tt w-250 ${tippyClass}`} arrow={false} placement="bottom" content={tippyContent}>
|
|
61
|
+
{children}
|
|
62
|
+
</Tippy>
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
<label className={`${className} radio ${(isDisabled || disabled)? 'disabled': ''}`}>
|
|
66
|
+
<input
|
|
67
|
+
type="checkbox"
|
|
68
|
+
value={value}
|
|
69
|
+
name={name}
|
|
70
|
+
|
|
71
|
+
checked={value === selected}
|
|
72
|
+
onChange={(e) => {
|
|
73
|
+
e.persist()
|
|
74
|
+
if (canSelect) {
|
|
75
|
+
select(e.target.value)
|
|
76
|
+
}
|
|
77
|
+
onChange(e)
|
|
78
|
+
}}
|
|
79
|
+
disabled={isDisabled || disabled}
|
|
80
|
+
/>
|
|
81
|
+
<span className="radio__item-label" data-testid={dataTestId}>{children}</span>
|
|
82
|
+
</label>
|
|
83
|
+
</ConditionalWrap>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
RadioGroup.Radio = Radio
|
|
88
|
+
export default RadioGroup
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.radio-group {
|
|
2
|
+
.label,
|
|
3
|
+
.radio,
|
|
4
|
+
.checkbox {
|
|
5
|
+
margin: 0;
|
|
6
|
+
}
|
|
7
|
+
height: 30px;
|
|
8
|
+
background: white;
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
align-items: center;
|
|
12
|
+
padding: 4px;
|
|
13
|
+
border-radius: 4px;
|
|
14
|
+
.radio {
|
|
15
|
+
height: 100%;
|
|
16
|
+
width: auto;
|
|
17
|
+
color: var(--N400);
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
}
|
|
21
|
+
.radio+.radio {
|
|
22
|
+
margin-top: 0;
|
|
23
|
+
}
|
|
24
|
+
input[type=checkbox] {
|
|
25
|
+
display: contents;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
max-width: 0px;
|
|
28
|
+
height: 0;
|
|
29
|
+
opacity: 0;
|
|
30
|
+
margin: unset;
|
|
31
|
+
}
|
|
32
|
+
input+.radio__item-label {
|
|
33
|
+
padding: 4px 6px;
|
|
34
|
+
height: 100%;
|
|
35
|
+
width: 100%;
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
transition: all 0.5s;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
41
|
+
input[type=checkbox]:checked+.radio__item-label {
|
|
42
|
+
background: #2c3354;
|
|
43
|
+
color: white;
|
|
44
|
+
border-radius: 4px;
|
|
45
|
+
}
|
|
46
|
+
}
|