@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.
@@ -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
+ }
@@ -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
- Use these tags to filter/identify resources via CLI or in other Kubernetes tools.
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={`Add a tag and click on the ⬡ icon to propagate tags as labels to Kubernetes resources.`}
31
+ infoText=""
23
32
  additionalContent={additionalInfo()}
24
33
  showCloseButton={true}
25
34
  trigger="click"
26
35
  interactive={true}
27
- documentationLink={''}
28
- documentationLinkText={'View Documentation'}
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 cursor" />
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 deleteTag = (): void => {
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
- type="key"
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
- type="value"
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, { key: '', value: '', propagate: false, isInvalidKey: false, isInvalidValue: false })
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-close.svg'
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
- type,
17
+ tagInputType,
15
18
  placeholder,
19
+ tabIndex = null,
20
+ refVar,
21
+ dependentRef,
16
22
  }: TagLabelValueSelectorType) => {
17
23
  const [selectedValue, setSelectedValue] = useState<string>('')
18
- const [isPopupOpen, togglePopup] = useState<boolean>(false)
19
-
24
+ const [activeElement, setActiveElement] = useState<string>('')
20
25
  const validationRules = new ValidationRules()
21
26
 
22
27
  useEffect(() => {
23
- setSelectedValue(tagData?.[type] || '')
24
- }, [selectedTagIndex, tagData, type])
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[type] = selectedValue
34
- if (type === 'key') {
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[type] = e.currentTarget.dataset.key
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 (type === 'key') {
62
- if (selectedValue || tagData.value || tagData.description) {
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 p-4">
84
+ <div key={error} className="flexbox pr-4 pl-4">
73
85
  <span>
74
- <ErrorCross className="icon-dim-16 fcr-5 mt-3 mr-4" />
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
- {type === 'key' && (
80
- <div className="flexbox p-4">
91
+ {tagInputType === KEY_VALUE.KEY && (
92
+ <div className="flexbox pr-4 pl-4">
81
93
  <span>
82
- <Info className="icon-dim-16 mt-3 mr-4" />
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 onToggleCallback={(isOpen) => togglePopup(isOpen)} autoClose>
115
- <PopupMenu.Button
116
- rootClassName={`h-32 ${
117
- type === 'key'
118
- ? `dc__no-right-radius`
119
- : `dc__no-border-radius dc__no-right-border dc__no-left-border`
120
- } ${tagData[type === 'key' ? 'isInvalidKey' : 'isInvalidValue'] ? 'er-5 bw-1' : ''}`}
121
- >
122
- <input
123
- type="text"
124
- className="form__input pt-4-imp pb-4-imp dc__no-border"
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
- {isPopupOpen && renderSuggestions()}
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
  )
@@ -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<{ code?: number; reload?: (...args) => any; subtitle?: string ;reloadClass?: string}> {
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 <ErrorScreenNotAuthorized subtitle={this.props.subtitle} />
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."}
@@ -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 = ({ style = {}, children, modal = false, rootClassName = "", onClick = null, callbackRef = null }) => {
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.contains(e.target)) {
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 (innerRef && innerRef.current.contains(e.target)) {
17
- if (innerRef.current.clientHeight === innerRef.current.scrollHeight) {
18
- e.preventDefault();
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("visible-modal");
28
- if (modal) modal.classList.add("show")
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("show")
34
-
41
+ if (modal) modal.classList.remove('show')
35
42
  }
36
- }, []
37
- )
43
+ }, [])
38
44
  return ReactDOM.createPortal(
39
- <div tabIndex={0} onClick={onClick}
40
- ref={
41
- el => {
42
- if (typeof callbackRef === 'function') {
43
- callbackRef(el);
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>, document.getElementById('visible-modal'));
53
- }
59
+ </div>,
60
+ document.getElementById('visible-modal'),
61
+ )
62
+ }