@devtron-labs/devtron-fe-common-lib 0.0.6-beta-20 → 0.0.6-beta-22
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/Checkbox.d.ts +18 -0
- package/dist/Common/Constants.d.ts +16 -0
- package/dist/Common/CustomTagSelector/PropagateTagInfo.d.ts +3 -1
- package/dist/Common/CustomTagSelector/ResizableTagTextArea.d.ts +3 -0
- package/dist/Common/CustomTagSelector/TagDetails.d.ts +1 -1
- package/dist/Common/CustomTagSelector/TagLabelSelect.d.ts +1 -1
- package/dist/Common/CustomTagSelector/TagLabelValueSelector.d.ts +1 -1
- package/dist/Common/Error.d.ts +5 -3
- package/dist/Common/Helper.d.ts +2 -0
- package/dist/Common/Modals/Modal.d.ts +2 -1
- package/dist/Common/PopupMenu.d.ts +5 -3
- package/dist/Common/Types.d.ts +33 -8
- package/dist/Common/index.d.ts +1 -0
- package/dist/index.js +224 -70
- package/package.json +1 -1
- package/src/Assets/Icon/ic-cross.svg +3 -0
- package/src/Common/Checkbox.tsx +49 -0
- package/src/Common/Constants.ts +14 -1
- package/src/Common/CustomTagSelector/PropagateTagInfo.tsx +16 -7
- package/src/Common/CustomTagSelector/ResizableTagTextArea.tsx +74 -0
- package/src/Common/CustomTagSelector/TagDetails.tsx +19 -7
- package/src/Common/CustomTagSelector/TagLabelSelect.tsx +5 -3
- package/src/Common/CustomTagSelector/TagLabelValueSelector.tsx +49 -30
- package/src/Common/Error.tsx +12 -4
- package/src/Common/Helper.tsx +23 -0
- package/src/Common/Modals/Modal.tsx +39 -30
- package/src/Common/PopupMenu.tsx +114 -63
- package/src/Common/TippyCustomized.tsx +2 -2
- package/src/Common/Types.ts +42 -18
- package/src/Common/index.ts +2 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { Component } from 'react'
|
|
2
|
+
|
|
3
|
+
export enum CHECKBOX_VALUE {
|
|
4
|
+
CHECKED = 'CHECKED',
|
|
5
|
+
INTERMEDIATE = 'INTERMEDIATE',
|
|
6
|
+
}
|
|
7
|
+
export interface CheckboxProps {
|
|
8
|
+
onChange: (event) => void
|
|
9
|
+
isChecked: boolean
|
|
10
|
+
value: 'CHECKED' | 'INTERMEDIATE'
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
tabIndex?: number
|
|
13
|
+
rootClassName?: string
|
|
14
|
+
onClick?: (event) => void
|
|
15
|
+
id?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
Valid States of Checkbox:
|
|
20
|
+
1. disabled: true, checked: false, value: XXX
|
|
21
|
+
2. disabled: true, checked: true, value: INTERMIDIATE
|
|
22
|
+
3. disabled: true, checked: true, value: CHECKED
|
|
23
|
+
4. disabled: true, checked: false, value: XXX
|
|
24
|
+
5. disabled: false, checked: true, value: INTERMIDIATE
|
|
25
|
+
6. disabled: false, checked: true, value: CHECKED
|
|
26
|
+
*/
|
|
27
|
+
export class Checkbox extends Component<CheckboxProps> {
|
|
28
|
+
render() {
|
|
29
|
+
let rootClassName = `${this.props.rootClassName ? this.props.rootClassName : ''}`
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<label className={`dc__position-rel flex left cursor ${rootClassName}`} onClick={this.props?.onClick}>
|
|
33
|
+
<input
|
|
34
|
+
type="checkbox"
|
|
35
|
+
className={'form__checkbox'}
|
|
36
|
+
disabled={this.props.disabled}
|
|
37
|
+
value={this.props.value}
|
|
38
|
+
onChange={this.props.onChange}
|
|
39
|
+
tabIndex={this.props.tabIndex}
|
|
40
|
+
checked={this.props.isChecked}
|
|
41
|
+
id={this.props.id}
|
|
42
|
+
/>
|
|
43
|
+
<span className="form__checkbox-container"></span>
|
|
44
|
+
<span className={`form__checkbox-label`}>{this.props.children}</span>
|
|
45
|
+
</label>
|
|
46
|
+
</>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/Common/Constants.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export const RequestTimeout = 60000
|
|
2
2
|
export const Host = process.env.REACT_APP_ORCHESTRATOR_ROOT
|
|
3
3
|
|
|
4
|
+
export const DOCUMENTATION_HOME_PAGE = 'https://docs.devtron.ai'
|
|
5
|
+
export const DOCUMENTATION = {
|
|
6
|
+
APP_TAGS: `${DOCUMENTATION_HOME_PAGE}/v/v0.6/usage/applications/create-application#tags`,
|
|
7
|
+
APP_OVERVIEW_TAGS: `${DOCUMENTATION_HOME_PAGE}/v/v0.6/usage/applications/overview#manage-tags`,
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export const PATTERNS = {
|
|
5
11
|
KUBERNETES_KEY_PREFIX: /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/,
|
|
6
12
|
KUBERNETES_KEY_NAME: /^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$/,
|
|
@@ -10,4 +16,11 @@ export const PATTERNS = {
|
|
|
10
16
|
|
|
11
17
|
export const ROUTES = {
|
|
12
18
|
PROJECT_LIST_MIN: 'team/autocomplete',
|
|
13
|
-
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export enum KEY_VALUE {
|
|
22
|
+
KEY= 'key',
|
|
23
|
+
VALUE= 'value'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_TAG_DATA = { key: '', value: '', propagate: false, isInvalidKey: false, isInvalidValue: false }
|
|
@@ -3,12 +3,21 @@ import { ReactComponent as InjectTag } from '../../Assets/Icon/inject-tag.svg'
|
|
|
3
3
|
import { ReactComponent as Help } from '../../Assets/Icon/ic-help-outline.svg'
|
|
4
4
|
import { TippyCustomized } from '../TippyCustomized'
|
|
5
5
|
import { TippyTheme } from '../Types'
|
|
6
|
+
import { DOCUMENTATION } from '../Constants'
|
|
6
7
|
|
|
7
|
-
export default function PropagateTagInfo() {
|
|
8
|
+
export default function PropagateTagInfo({ isCreateApp }: { isCreateApp: boolean }) {
|
|
8
9
|
const additionalInfo = () => {
|
|
9
10
|
return (
|
|
10
11
|
<div className="p-12 fs-13">
|
|
11
|
-
|
|
12
|
+
<div className="mb-20">
|
|
13
|
+
<span className="flex left">
|
|
14
|
+
Add a tag and click on the
|
|
15
|
+
<InjectTag className="icon-dim-16 ml-4 mr-4" />
|
|
16
|
+
icon to
|
|
17
|
+
</span>
|
|
18
|
+
propagate tags as labels to Kubernetes resources
|
|
19
|
+
</div>
|
|
20
|
+
<div>Use these tags to filter/identify resources via CLI or in other Kubernetes tools.</div>
|
|
12
21
|
</div>
|
|
13
22
|
)
|
|
14
23
|
}
|
|
@@ -19,18 +28,18 @@ export default function PropagateTagInfo() {
|
|
|
19
28
|
placement="top"
|
|
20
29
|
Icon={InjectTag}
|
|
21
30
|
heading={'Propagate tags to K8s resources'}
|
|
22
|
-
infoText=
|
|
31
|
+
infoText=""
|
|
23
32
|
additionalContent={additionalInfo()}
|
|
24
33
|
showCloseButton={true}
|
|
25
34
|
trigger="click"
|
|
26
35
|
interactive={true}
|
|
27
|
-
documentationLink={
|
|
28
|
-
documentationLinkText=
|
|
36
|
+
documentationLink={isCreateApp ? DOCUMENTATION.APP_TAGS : DOCUMENTATION.APP_OVERVIEW_TAGS}
|
|
37
|
+
documentationLinkText="View Documentation"
|
|
29
38
|
>
|
|
30
|
-
<div className="flexbox">
|
|
39
|
+
<div className="flexbox cursor">
|
|
31
40
|
<InjectTag className="icon-dim-16 mt-2 mr-4" />
|
|
32
41
|
<span>Propagate tags</span>
|
|
33
|
-
<Help className="icon-dim-16 mt-2 ml-4
|
|
42
|
+
<Help className="icon-dim-16 mt-2 ml-4" />
|
|
34
43
|
</div>
|
|
35
44
|
</TippyCustomized>
|
|
36
45
|
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { useThrottledEffect } from '../Helper'
|
|
3
|
+
import { ResizableTagTextAreaProps } from '../Types'
|
|
4
|
+
|
|
5
|
+
export function ResizableTagTextArea({
|
|
6
|
+
className,
|
|
7
|
+
minHeight,
|
|
8
|
+
maxHeight,
|
|
9
|
+
value,
|
|
10
|
+
onChange,
|
|
11
|
+
onBlur,
|
|
12
|
+
onFocus,
|
|
13
|
+
placeholder,
|
|
14
|
+
tabIndex,
|
|
15
|
+
refVar,
|
|
16
|
+
dependentRef,
|
|
17
|
+
}: ResizableTagTextAreaProps) {
|
|
18
|
+
const [text, setText] = useState('')
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
setText(value)
|
|
22
|
+
}, [value])
|
|
23
|
+
|
|
24
|
+
const handleChange = (event) => {
|
|
25
|
+
setText(event.target.value)
|
|
26
|
+
onChange(event)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const reInitHeight = () => {
|
|
30
|
+
if (document.activeElement !== refVar.current) return
|
|
31
|
+
refVar.current.style.height = 'auto'
|
|
32
|
+
dependentRef.current.style.height = 'auto'
|
|
33
|
+
let nextHeight = refVar.current.scrollHeight
|
|
34
|
+
if (nextHeight < dependentRef.current.scrollHeight) {
|
|
35
|
+
nextHeight = dependentRef.current.scrollHeight
|
|
36
|
+
}
|
|
37
|
+
if (minHeight && nextHeight < minHeight) {
|
|
38
|
+
nextHeight = minHeight
|
|
39
|
+
}
|
|
40
|
+
if (maxHeight && nextHeight > maxHeight) {
|
|
41
|
+
nextHeight = maxHeight
|
|
42
|
+
}
|
|
43
|
+
refVar.current.style.height = nextHeight + 'px'
|
|
44
|
+
dependentRef.current.style.height = nextHeight + 'px'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
useThrottledEffect(reInitHeight, 500, [text])
|
|
48
|
+
|
|
49
|
+
const handleOnBlur = (event) => {
|
|
50
|
+
refVar.current.style.height = 'auto'
|
|
51
|
+
dependentRef.current.style.height = 'auto'
|
|
52
|
+
onBlur(event)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleOnFocus = (event) => {
|
|
56
|
+
reInitHeight()
|
|
57
|
+
onFocus(event)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<textarea
|
|
62
|
+
rows={1}
|
|
63
|
+
ref={refVar}
|
|
64
|
+
value={text}
|
|
65
|
+
placeholder={placeholder}
|
|
66
|
+
className={`${className || ''} lh-20`}
|
|
67
|
+
style={{ resize: 'none' }}
|
|
68
|
+
onChange={handleChange}
|
|
69
|
+
onBlur={handleOnBlur}
|
|
70
|
+
onFocus={handleOnFocus}
|
|
71
|
+
tabIndex={tabIndex}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useRef } from 'react'
|
|
2
2
|
import { ReactComponent as DeleteCross } from '../../Assets/Icon/ic-close.svg'
|
|
3
3
|
import { ReactComponent as InjectTag } from '../../Assets/Icon/inject-tag.svg'
|
|
4
4
|
import { TagDetailType } from '../Types'
|
|
5
5
|
import { TagLabelValueSelector } from './TagLabelValueSelector'
|
|
6
|
+
import { KEY_VALUE } from '../Constants'
|
|
7
|
+
import { stopPropagation } from '../Helper'
|
|
6
8
|
|
|
7
|
-
export default function TagDetails({ index, tagData, setTagData, removeTag }: TagDetailType) {
|
|
8
|
-
const
|
|
9
|
+
export default function TagDetails({ index, tagData, setTagData, removeTag, tabIndex = 1 }: TagDetailType) {
|
|
10
|
+
const keyRef = useRef(null)
|
|
11
|
+
const valueRef = useRef(null)
|
|
12
|
+
|
|
13
|
+
const deleteTag = (e): void => {
|
|
14
|
+
stopPropagation(e)
|
|
9
15
|
removeTag(index)
|
|
10
16
|
}
|
|
11
17
|
const propagateTagToResource = (): void => {
|
|
@@ -16,7 +22,7 @@ export default function TagDetails({ index, tagData, setTagData, removeTag }: Ta
|
|
|
16
22
|
return (
|
|
17
23
|
<div className="flexbox mb-8">
|
|
18
24
|
<div
|
|
19
|
-
className={`dc__border pl-4 pr-4 br-4 mr-8 pointer ${tagData.propagate ? 'bcn-7' : ''}`}
|
|
25
|
+
className={`dc__border h-30 pl-4 pr-4 br-4 mr-8 pointer ${tagData.propagate ? 'bcn-7' : ''}`}
|
|
20
26
|
onClick={propagateTagToResource}
|
|
21
27
|
>
|
|
22
28
|
<InjectTag className={`icon-dim-20 mt-4 ${tagData.propagate ? 'scn-0' : ''}`} />
|
|
@@ -25,17 +31,23 @@ export default function TagDetails({ index, tagData, setTagData, removeTag }: Ta
|
|
|
25
31
|
selectedTagIndex={index}
|
|
26
32
|
tagData={tagData}
|
|
27
33
|
setTagData={setTagData}
|
|
28
|
-
|
|
34
|
+
tagInputType={KEY_VALUE.KEY}
|
|
29
35
|
placeholder="Enter key"
|
|
36
|
+
tabIndex={tabIndex - 1}
|
|
37
|
+
refVar={keyRef}
|
|
38
|
+
dependentRef={valueRef}
|
|
30
39
|
/>
|
|
31
40
|
<TagLabelValueSelector
|
|
32
41
|
selectedTagIndex={index}
|
|
33
42
|
tagData={tagData}
|
|
34
43
|
setTagData={setTagData}
|
|
35
|
-
|
|
44
|
+
tagInputType={KEY_VALUE.VALUE}
|
|
36
45
|
placeholder="Enter value"
|
|
46
|
+
tabIndex={tabIndex}
|
|
47
|
+
refVar={valueRef}
|
|
48
|
+
dependentRef={keyRef}
|
|
37
49
|
/>
|
|
38
|
-
<div className="dc__border pl-4 pr-4 dc__right-radius-4 pointer" onClick={deleteTag}>
|
|
50
|
+
<div className="dc__border pl-4 pr-4 dc__right-radius-4 pointer flex top" onClick={deleteTag}>
|
|
39
51
|
<DeleteCross className="icon-dim-20 mt-4" />
|
|
40
52
|
</div>
|
|
41
53
|
</div>
|
|
@@ -3,8 +3,9 @@ import PropagateTagInfo from './PropagateTagInfo'
|
|
|
3
3
|
import { ReactComponent as Add } from '../../Assets/Icon/ic-add.svg'
|
|
4
4
|
import TagDetails from './TagDetails'
|
|
5
5
|
import { TagLabelSelectType } from '../Types'
|
|
6
|
+
import { DEFAULT_TAG_DATA } from '../Constants'
|
|
6
7
|
|
|
7
|
-
export const TagLabelSelect = ({ labelTags, setLabelTags }: TagLabelSelectType) => {
|
|
8
|
+
export const TagLabelSelect = ({ isCreateApp, labelTags, setLabelTags, tabIndex = 0 }: TagLabelSelectType) => {
|
|
8
9
|
const setTagData = (index, tagValue): void => {
|
|
9
10
|
const _tags = [...labelTags]
|
|
10
11
|
_tags[index] = tagValue
|
|
@@ -13,7 +14,7 @@ export const TagLabelSelect = ({ labelTags, setLabelTags }: TagLabelSelectType)
|
|
|
13
14
|
|
|
14
15
|
const addNewTag = (): void => {
|
|
15
16
|
const _tags = [...labelTags]
|
|
16
|
-
_tags.splice(0, 0,
|
|
17
|
+
_tags.splice(0, 0, DEFAULT_TAG_DATA)
|
|
17
18
|
setLabelTags(_tags)
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -27,7 +28,7 @@ export const TagLabelSelect = ({ labelTags, setLabelTags }: TagLabelSelectType)
|
|
|
27
28
|
<div>
|
|
28
29
|
<div className="flexbox dc__content-space mb-8">
|
|
29
30
|
<span>Tags</span>
|
|
30
|
-
<PropagateTagInfo />
|
|
31
|
+
<PropagateTagInfo isCreateApp={isCreateApp} />
|
|
31
32
|
</div>
|
|
32
33
|
<div>
|
|
33
34
|
<div className="dc_width-max-content cb-5 fw-6 fs-13 flexbox mr-20 mb-8 cursor" onClick={addNewTag}>
|
|
@@ -41,6 +42,7 @@ export const TagLabelSelect = ({ labelTags, setLabelTags }: TagLabelSelectType)
|
|
|
41
42
|
tagData={tagData}
|
|
42
43
|
setTagData={setTagData}
|
|
43
44
|
removeTag={removeTag}
|
|
45
|
+
tabIndex={tabIndex + (index + 2)}
|
|
44
46
|
/>
|
|
45
47
|
))}
|
|
46
48
|
</div>
|
|
@@ -2,8 +2,11 @@ import React, { useState, useEffect } from 'react'
|
|
|
2
2
|
import { TagLabelValueSelectorType } from '../Types'
|
|
3
3
|
import PopupMenu from '../PopupMenu'
|
|
4
4
|
import { ValidationRules } from './ValidationRules'
|
|
5
|
-
import { ReactComponent as ErrorCross } from '../../Assets/Icon/ic-
|
|
5
|
+
import { ReactComponent as ErrorCross } from '../../Assets/Icon/ic-cross.svg'
|
|
6
6
|
import { ReactComponent as Info } from '../../Assets/Icon/ic-info-outlined.svg'
|
|
7
|
+
import { KEY_VALUE } from '../Constants'
|
|
8
|
+
import { stopPropagation } from '../Helper'
|
|
9
|
+
import { ResizableTagTextArea } from './ResizableTagTextArea'
|
|
7
10
|
|
|
8
11
|
export const TagLabelValueSelector = ({
|
|
9
12
|
selectedTagIndex,
|
|
@@ -11,27 +14,35 @@ export const TagLabelValueSelector = ({
|
|
|
11
14
|
setTagData,
|
|
12
15
|
tagOptions,
|
|
13
16
|
isRequired,
|
|
14
|
-
|
|
17
|
+
tagInputType,
|
|
15
18
|
placeholder,
|
|
19
|
+
tabIndex = null,
|
|
20
|
+
refVar,
|
|
21
|
+
dependentRef,
|
|
16
22
|
}: TagLabelValueSelectorType) => {
|
|
17
23
|
const [selectedValue, setSelectedValue] = useState<string>('')
|
|
18
|
-
const [
|
|
19
|
-
|
|
24
|
+
const [activeElement, setActiveElement] = useState<string>('')
|
|
20
25
|
const validationRules = new ValidationRules()
|
|
21
26
|
|
|
22
27
|
useEffect(() => {
|
|
23
|
-
setSelectedValue(tagData?.[
|
|
24
|
-
}, [selectedTagIndex, tagData,
|
|
28
|
+
setSelectedValue(tagData?.[tagInputType] || '')
|
|
29
|
+
}, [selectedTagIndex, tagData, tagInputType])
|
|
25
30
|
|
|
31
|
+
const handleOnFocus = (e) => {
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
setActiveElement(`tag-${tagInputType}-${selectedTagIndex}`)
|
|
34
|
+
}, 300)
|
|
35
|
+
}
|
|
26
36
|
const handleOnBlur = (e) => {
|
|
37
|
+
setActiveElement('')
|
|
27
38
|
if (
|
|
28
39
|
!e.relatedTarget ||
|
|
29
40
|
!e.relatedTarget.classList.value ||
|
|
30
41
|
!e.relatedTarget.classList.value.includes(`tag-${selectedTagIndex}-class`)
|
|
31
42
|
) {
|
|
32
43
|
const _tagData = { ...tagData }
|
|
33
|
-
_tagData[
|
|
34
|
-
if (
|
|
44
|
+
_tagData[tagInputType] = selectedValue
|
|
45
|
+
if (tagInputType === KEY_VALUE.KEY) {
|
|
35
46
|
_tagData.isInvalidKey = selectedValue
|
|
36
47
|
? !validationRules.propagateTagKey(selectedValue).isValid
|
|
37
48
|
: _tagData.value !== ''
|
|
@@ -51,15 +62,16 @@ export const TagLabelValueSelector = ({
|
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
const onSelectValue = (e): void => {
|
|
65
|
+
stopPropagation(e)
|
|
54
66
|
const _tagData = { ...tagData }
|
|
55
|
-
_tagData[
|
|
67
|
+
_tagData[tagInputType] = e.currentTarget.dataset.key
|
|
56
68
|
setTagData(selectedTagIndex, _tagData)
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
const renderValidationsSuggestions = (): JSX.Element => {
|
|
60
72
|
let field = { isValid: true, messages: [] }
|
|
61
|
-
if (
|
|
62
|
-
if (selectedValue || tagData.value
|
|
73
|
+
if (tagInputType === KEY_VALUE.KEY) {
|
|
74
|
+
if (selectedValue || tagData.value) {
|
|
63
75
|
field = validationRules.propagateTagKey(selectedValue)
|
|
64
76
|
}
|
|
65
77
|
} else if (isRequired || selectedValue) {
|
|
@@ -67,19 +79,19 @@ export const TagLabelValueSelector = ({
|
|
|
67
79
|
}
|
|
68
80
|
if (!field.isValid) {
|
|
69
81
|
return (
|
|
70
|
-
<div className="p-4">
|
|
82
|
+
<div className="p-4" onClick={stopPropagation}>
|
|
71
83
|
{field.messages.map((error) => (
|
|
72
|
-
<div key={error} className="flexbox
|
|
84
|
+
<div key={error} className="flexbox pr-4 pl-4">
|
|
73
85
|
<span>
|
|
74
|
-
<ErrorCross className="icon-dim-
|
|
86
|
+
<ErrorCross className="icon-dim-14 scr-5 mt-3 mr-4" />
|
|
75
87
|
</span>
|
|
76
88
|
<span>{error}</span>
|
|
77
89
|
</div>
|
|
78
90
|
))}
|
|
79
|
-
{
|
|
80
|
-
<div className="flexbox
|
|
91
|
+
{tagInputType === KEY_VALUE.KEY && (
|
|
92
|
+
<div className="flexbox pr-4 pl-4">
|
|
81
93
|
<span>
|
|
82
|
-
<Info className="icon-dim-
|
|
94
|
+
<Info className="icon-dim-14 mt-3 mr-4" />
|
|
83
95
|
</span>
|
|
84
96
|
<span className="dc__italic-font-style">Key format: prefix/name or name</span>
|
|
85
97
|
</div>
|
|
@@ -111,25 +123,32 @@ export const TagLabelValueSelector = ({
|
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
return (
|
|
114
|
-
<PopupMenu
|
|
115
|
-
<PopupMenu.Button
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
<PopupMenu autoClose autoPosition>
|
|
127
|
+
<PopupMenu.Button rootClassName="dc__bg-n50 flex top dc__no-border">
|
|
128
|
+
<ResizableTagTextArea
|
|
129
|
+
minHeight={30}
|
|
130
|
+
maxHeight={80}
|
|
131
|
+
className={`form__input pt-4-imp pb-4-imp fs-13 ${
|
|
132
|
+
tagInputType === KEY_VALUE.KEY
|
|
133
|
+
? `dc__no-right-radius`
|
|
134
|
+
: `dc__no-border-radius dc__no-right-border dc__no-left-border`
|
|
135
|
+
} ${
|
|
136
|
+
tagData[tagInputType === KEY_VALUE.KEY ? 'isInvalidKey' : 'isInvalidValue']
|
|
137
|
+
? 'form__input--error'
|
|
138
|
+
: ''
|
|
139
|
+
}`}
|
|
125
140
|
value={selectedValue}
|
|
126
141
|
onChange={handleInputChange}
|
|
127
142
|
onBlur={handleOnBlur}
|
|
143
|
+
onFocus={handleOnFocus}
|
|
128
144
|
placeholder={placeholder}
|
|
145
|
+
tabIndex={tabIndex}
|
|
146
|
+
refVar={refVar}
|
|
147
|
+
dependentRef={dependentRef}
|
|
129
148
|
/>
|
|
130
149
|
</PopupMenu.Button>
|
|
131
|
-
<PopupMenu.Body rootClassName={`tag-${selectedTagIndex}-class`} autoWidth={true}>
|
|
132
|
-
{
|
|
150
|
+
<PopupMenu.Body rootClassName={`tag-${selectedTagIndex}-class`} autoWidth={true} preventWheelDisable={true}>
|
|
151
|
+
{activeElement === `tag-${tagInputType}-${selectedTagIndex}` && renderSuggestions()}
|
|
133
152
|
</PopupMenu.Body>
|
|
134
153
|
</PopupMenu>
|
|
135
154
|
)
|
package/src/Common/Error.tsx
CHANGED
|
@@ -3,7 +3,13 @@ import EmptyState from './EmptyState/EmptyState'
|
|
|
3
3
|
import notAuthorized from '../Assets/Img/ic-not-authorized.svg'
|
|
4
4
|
import Reload from './Reload'
|
|
5
5
|
|
|
6
|
-
export class ErrorScreenManager extends Component<{
|
|
6
|
+
export class ErrorScreenManager extends Component<{
|
|
7
|
+
code?: number
|
|
8
|
+
reload?: (...args) => any
|
|
9
|
+
subtitle?: React.ReactChild
|
|
10
|
+
subtitleClass?: string
|
|
11
|
+
reloadClass?: string
|
|
12
|
+
}> {
|
|
7
13
|
getMessage() {
|
|
8
14
|
switch (this.props.code) {
|
|
9
15
|
case 400:
|
|
@@ -11,7 +17,9 @@ export class ErrorScreenManager extends Component<{ code?: number; reload?: (...
|
|
|
11
17
|
case 401:
|
|
12
18
|
return 'Unauthorized'
|
|
13
19
|
case 403:
|
|
14
|
-
return
|
|
20
|
+
return (
|
|
21
|
+
<ErrorScreenNotAuthorized subtitle={this.props.subtitle} subtitleClass={this.props.subtitleClass} />
|
|
22
|
+
)
|
|
15
23
|
case 404:
|
|
16
24
|
return 'Not Found'
|
|
17
25
|
case 500:
|
|
@@ -34,7 +42,7 @@ export class ErrorScreenManager extends Component<{ code?: number; reload?: (...
|
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
export class ErrorScreenNotAuthorized extends Component<{ subtitle: string }> {
|
|
45
|
+
export class ErrorScreenNotAuthorized extends Component<{ subtitle: React.ReactChild; subtitleClass?: string }> {
|
|
38
46
|
render() {
|
|
39
47
|
return (
|
|
40
48
|
<EmptyState>
|
|
@@ -44,7 +52,7 @@ export class ErrorScreenNotAuthorized extends Component<{ subtitle: string }> {
|
|
|
44
52
|
<EmptyState.Title>
|
|
45
53
|
<h3 className="title">Not authorized</h3>
|
|
46
54
|
</EmptyState.Title>
|
|
47
|
-
<EmptyState.Subtitle>
|
|
55
|
+
<EmptyState.Subtitle className={this.props.subtitleClass}>
|
|
48
56
|
{this.props.subtitle
|
|
49
57
|
? this.props.subtitle
|
|
50
58
|
: "Looks like you don't have access to information on this page. Please contact your manager to request access."}
|
package/src/Common/Helper.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { ServerErrors } from './ServerError'
|
|
|
2
2
|
import { toast } from 'react-toastify'
|
|
3
3
|
import * as Sentry from '@sentry/browser'
|
|
4
4
|
import { toastAccessDenied } from './ToastBody'
|
|
5
|
+
import { useEffect, useRef } from 'react'
|
|
5
6
|
|
|
6
7
|
export function showError(serverError, showToastOnUnknownError = true, hideAccessError = false) {
|
|
7
8
|
if (serverError instanceof ServerErrors && Array.isArray(serverError.errors)) {
|
|
@@ -52,3 +53,25 @@ export function sortCallback(key: string, a: any, b: any, isCaseSensitive?: bool
|
|
|
52
53
|
}
|
|
53
54
|
return 0
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
export const stopPropagation = (event): void => {
|
|
58
|
+
event.stopPropagation()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function useThrottledEffect(callback, delay, deps = []) {
|
|
62
|
+
//function will be executed only once in a given time interval.
|
|
63
|
+
const lastRan = useRef(Date.now())
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const handler = setTimeout(function () {
|
|
67
|
+
if (Date.now() - lastRan.current >= delay) {
|
|
68
|
+
callback()
|
|
69
|
+
lastRan.current = Date.now()
|
|
70
|
+
}
|
|
71
|
+
}, delay - (Date.now() - lastRan.current))
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
clearTimeout(handler)
|
|
75
|
+
}
|
|
76
|
+
}, [delay, ...deps])
|
|
77
|
+
}
|
|
@@ -1,53 +1,62 @@
|
|
|
1
|
-
import React, { useEffect } from 'react'
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom'
|
|
3
|
-
export const Modal = ({
|
|
3
|
+
export const Modal = ({
|
|
4
|
+
style = {},
|
|
5
|
+
children,
|
|
6
|
+
modal = false,
|
|
7
|
+
rootClassName = '',
|
|
8
|
+
onClick = null,
|
|
9
|
+
callbackRef = null,
|
|
10
|
+
preventWheelDisable = false,
|
|
11
|
+
}) => {
|
|
4
12
|
const innerRef = React.useRef(null)
|
|
5
13
|
function handleClick(e) {
|
|
6
14
|
if (typeof onClick !== 'function') return
|
|
7
|
-
if (innerRef && innerRef.current
|
|
15
|
+
if (innerRef && innerRef.current?.contains(e.target)) {
|
|
8
16
|
onClick(e, 'in')
|
|
9
|
-
}
|
|
10
|
-
else {
|
|
17
|
+
} else {
|
|
11
18
|
onClick(e, 'out')
|
|
12
19
|
}
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
function disableWheel(e) {
|
|
16
|
-
if (
|
|
17
|
-
if (innerRef
|
|
18
|
-
|
|
23
|
+
if (!preventWheelDisable) {
|
|
24
|
+
if (innerRef?.current.contains(e.target)) {
|
|
25
|
+
if (innerRef.current.clientHeight === innerRef.current.scrollHeight) {
|
|
26
|
+
e.preventDefault()
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
e.preventDefault()
|
|
19
30
|
}
|
|
20
31
|
}
|
|
21
|
-
else {
|
|
22
|
-
e.preventDefault();
|
|
23
|
-
}
|
|
24
32
|
}
|
|
25
33
|
useEffect(() => {
|
|
26
34
|
document.addEventListener('click', handleClick)
|
|
27
|
-
let modal = document.getElementById(
|
|
28
|
-
if (modal) modal.classList.add(
|
|
29
|
-
document.body.addEventListener('wheel', disableWheel, { passive: false })
|
|
35
|
+
let modal = document.getElementById('visible-modal')
|
|
36
|
+
if (modal) modal.classList.add('show')
|
|
37
|
+
if (!preventWheelDisable) document.body.addEventListener('wheel', disableWheel, { passive: false })
|
|
30
38
|
return () => {
|
|
31
|
-
document.body.removeEventListener('wheel', disableWheel)
|
|
39
|
+
if (!preventWheelDisable) document.body.removeEventListener('wheel', disableWheel)
|
|
32
40
|
document.removeEventListener('click', handleClick)
|
|
33
|
-
if (modal) modal.classList.remove(
|
|
34
|
-
|
|
41
|
+
if (modal) modal.classList.remove('show')
|
|
35
42
|
}
|
|
36
|
-
}, []
|
|
37
|
-
)
|
|
43
|
+
}, [])
|
|
38
44
|
return ReactDOM.createPortal(
|
|
39
|
-
<div
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
innerRef.current = el;
|
|
45
|
+
<div
|
|
46
|
+
tabIndex={0}
|
|
47
|
+
onClick={onClick}
|
|
48
|
+
ref={(el) => {
|
|
49
|
+
if (typeof callbackRef === 'function') {
|
|
50
|
+
callbackRef(el)
|
|
46
51
|
}
|
|
47
|
-
|
|
52
|
+
innerRef.current = el
|
|
53
|
+
}}
|
|
48
54
|
id="popup"
|
|
49
55
|
className={`${rootClassName} popup ${modal ? 'modal' : ''}`}
|
|
50
|
-
style={{ ...style }}
|
|
56
|
+
style={{ ...style }}
|
|
57
|
+
>
|
|
51
58
|
{children}
|
|
52
|
-
</div>,
|
|
53
|
-
|
|
59
|
+
</div>,
|
|
60
|
+
document.getElementById('visible-modal'),
|
|
61
|
+
)
|
|
62
|
+
}
|