@devtron-labs/devtron-fe-common-lib 0.0.26-beta-2 → 0.0.26-beta-3
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/CustomTagSelector/ResizableTagTextArea.d.ts +1 -1
- package/dist/Common/CustomTagSelector/Types.d.ts +0 -1
- package/dist/Common/DebouncedSearch/DebouncedSearch.d.ts +15 -0
- package/dist/Common/DebouncedSearch/Types.d.ts +13 -0
- package/dist/Common/DebouncedSearch/Utils.d.ts +1 -0
- package/dist/Common/Grid/Grid.d.ts +3 -0
- package/dist/Common/Grid/types.d.ts +10 -0
- package/dist/Common/index.d.ts +2 -0
- package/dist/index.js +207 -122
- package/package.json +1 -1
- package/src/Assets/Icon/ic-error-cross.svg +8 -0
- package/src/Common/CustomTagSelector/ResizableTagTextArea.tsx +6 -14
- package/src/Common/CustomTagSelector/Types.ts +0 -1
- package/src/Common/DebouncedSearch/DebouncedSearch.tsx +75 -0
- package/src/Common/DebouncedSearch/Types.ts +12 -0
- package/src/Common/DebouncedSearch/Utils.ts +19 -0
- package/src/Common/DebouncedSearch/__tests__/DebouncedSearch.test.tsx +61 -0
- package/src/Common/Grid/Grid.tsx +39 -0
- package/src/Common/Grid/types.ts +9 -0
- package/src/Common/index.ts +3 -1
package/package.json
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
2
|
+
<g fill="none" fill-rule="evenodd">
|
|
3
|
+
<g transform="translate(2.667 2.667)">
|
|
4
|
+
<rect width="26.667" height="26.667" class="fill-color" fill="#596168" rx="13.333"/>
|
|
5
|
+
<path fill="#FFF" fill-rule="nonzero" d="M8.62 8.62c.52-.521 1.364-.521 1.885 0l2.828 2.827 2.829-2.828a1.334 1.334 0 0 1 1.777-.097l.108.097c.521.521.521 1.365 0 1.886l-2.828 2.828 2.828 2.829c.486.486.519 1.254.098 1.777l-.098.108c-.52.521-1.365.521-1.885 0l-2.83-2.828-2.827 2.828a1.334 1.334 0 0 1-1.777.098l-.109-.098a1.333 1.333 0 0 1 0-1.885l2.828-2.83-2.828-2.827a1.334 1.334 0 0 1-.097-1.777z"/>
|
|
6
|
+
</g>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -15,7 +15,6 @@ export function ResizableTagTextArea({
|
|
|
15
15
|
refVar,
|
|
16
16
|
dependentRef,
|
|
17
17
|
dataTestId,
|
|
18
|
-
handleKeyDown,
|
|
19
18
|
}: ResizableTagTextAreaProps) {
|
|
20
19
|
const [text, setText] = useState('')
|
|
21
20
|
|
|
@@ -30,12 +29,10 @@ export function ResizableTagTextArea({
|
|
|
30
29
|
|
|
31
30
|
const reInitHeight = () => {
|
|
32
31
|
if (document.activeElement !== refVar.current) return
|
|
33
|
-
refVar.current.style.height = minHeight
|
|
34
|
-
|
|
35
|
-
dependentRef.current.style.height = minHeight + 'px'
|
|
36
|
-
}
|
|
32
|
+
refVar.current.style.height = minHeight+ 'px'
|
|
33
|
+
dependentRef.current.style.height = minHeight+ 'px'
|
|
37
34
|
let nextHeight = refVar.current.scrollHeight
|
|
38
|
-
if (
|
|
35
|
+
if (nextHeight < dependentRef.current.scrollHeight) {
|
|
39
36
|
nextHeight = dependentRef.current.scrollHeight
|
|
40
37
|
}
|
|
41
38
|
if (minHeight && nextHeight < minHeight) {
|
|
@@ -45,18 +42,14 @@ export function ResizableTagTextArea({
|
|
|
45
42
|
nextHeight = maxHeight
|
|
46
43
|
}
|
|
47
44
|
refVar.current.style.height = nextHeight + 'px'
|
|
48
|
-
|
|
49
|
-
dependentRef.current.style.height = nextHeight + 'px'
|
|
50
|
-
}
|
|
45
|
+
dependentRef.current.style.height = nextHeight + 'px'
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
useThrottledEffect(reInitHeight, 500, [text])
|
|
54
49
|
|
|
55
50
|
const handleOnBlur = (event) => {
|
|
56
|
-
refVar.current.style.height = minHeight
|
|
57
|
-
|
|
58
|
-
dependentRef.current.style.height = minHeight + 'px'
|
|
59
|
-
}
|
|
51
|
+
refVar.current.style.height = minHeight+ 'px'
|
|
52
|
+
dependentRef.current.style.height = minHeight+ 'px'
|
|
60
53
|
onBlur && onBlur(event)
|
|
61
54
|
}
|
|
62
55
|
|
|
@@ -78,7 +71,6 @@ export function ResizableTagTextArea({
|
|
|
78
71
|
onFocus={handleOnFocus}
|
|
79
72
|
tabIndex={tabIndex}
|
|
80
73
|
data-testid={dataTestId}
|
|
81
|
-
onKeyDown={handleKeyDown}
|
|
82
74
|
/>
|
|
83
75
|
)
|
|
84
76
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { useDebouncedEffect } from './Utils'
|
|
3
|
+
import { ReactComponent as ICClear } from '../../Assets/Icon/ic-error-cross.svg'
|
|
4
|
+
import { DebouncedSearchProps } from './Types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param onSearch - Callback function to be called on search
|
|
8
|
+
* @param Icon - (Optional) Icon to be shown before the input
|
|
9
|
+
* @param iconClass - (Optional) Class for the icon
|
|
10
|
+
* @param children - (Optional) In case we want to add another button or any other element
|
|
11
|
+
* @param placeholder - (Optional) Placeholder for the input
|
|
12
|
+
* @param containerClass - (Optional) Class for the container
|
|
13
|
+
* @param inputClass - (Optional) Class for the input field
|
|
14
|
+
* @param debounceTimeout - (Optional) Timeout for the debounce with default value of 500ms
|
|
15
|
+
* @param clearSearch - (Optional) To clear the search text
|
|
16
|
+
* @param showClearIcon - (Optional) To show the clear icon default value is true
|
|
17
|
+
*/
|
|
18
|
+
export default function DebouncedSearch({
|
|
19
|
+
onSearch,
|
|
20
|
+
Icon,
|
|
21
|
+
children,
|
|
22
|
+
placeholder,
|
|
23
|
+
containerClass = '',
|
|
24
|
+
iconClass = '',
|
|
25
|
+
inputClass = '',
|
|
26
|
+
debounceTimeout = 500,
|
|
27
|
+
clearSearch,
|
|
28
|
+
showClearIcon = true,
|
|
29
|
+
}: DebouncedSearchProps) {
|
|
30
|
+
const [searchText, setSearchText] = useState<string>('')
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setSearchText('')
|
|
34
|
+
}, [clearSearch])
|
|
35
|
+
|
|
36
|
+
const handleSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
37
|
+
setSearchText(e.target.value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handleClearSearch = () => {
|
|
41
|
+
setSearchText('')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
useDebouncedEffect(() => onSearch(searchText), debounceTimeout, [searchText, onSearch])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={containerClass}>
|
|
48
|
+
{Icon && <Icon className={iconClass} />}
|
|
49
|
+
|
|
50
|
+
<input
|
|
51
|
+
type="text"
|
|
52
|
+
className={inputClass}
|
|
53
|
+
placeholder={placeholder ?? 'Search'}
|
|
54
|
+
value={searchText}
|
|
55
|
+
onChange={handleSearchTextChange}
|
|
56
|
+
autoFocus
|
|
57
|
+
data-testid="debounced-search"
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
{showClearIcon && !!searchText && (
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
className="dc__outline-none-imp dc__no-border p-0 bc-n50 flex"
|
|
64
|
+
onClick={handleClearSearch}
|
|
65
|
+
data-testid="clear-search"
|
|
66
|
+
>
|
|
67
|
+
<ICClear className="icon-dim-20 icon-n6" />
|
|
68
|
+
</button>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{/* In case we want to add another button or something */}
|
|
72
|
+
{children}
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DebouncedSearchProps {
|
|
2
|
+
onSearch: (query: string) => void
|
|
3
|
+
Icon?: React.FunctionComponent<React.SVGProps<SVGSVGElement>>
|
|
4
|
+
placeholder?: string
|
|
5
|
+
inputClass?: string
|
|
6
|
+
containerClass?: string
|
|
7
|
+
iconClass?: string
|
|
8
|
+
children?: React.ReactNode
|
|
9
|
+
debounceTimeout?: number
|
|
10
|
+
clearSearch?: boolean
|
|
11
|
+
showClearIcon?: boolean
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useDebouncedEffect(callback, delay, deps: unknown[] = []) {
|
|
4
|
+
// function will be executed only after the specified time once the user stops firing the event.
|
|
5
|
+
const firstUpdate = useRef(true)
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (firstUpdate.current) {
|
|
8
|
+
firstUpdate.current = false
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
const handler = setTimeout(() => {
|
|
12
|
+
callback()
|
|
13
|
+
}, delay)
|
|
14
|
+
|
|
15
|
+
return () => {
|
|
16
|
+
clearTimeout(handler)
|
|
17
|
+
}
|
|
18
|
+
}, [delay, ...deps])
|
|
19
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { ReactComponent as ICClear } from '../../../../assets/icons/ic-error.svg'
|
|
4
|
+
import DebouncedSearch from '../DebouncedSearch'
|
|
5
|
+
|
|
6
|
+
jest.mock('../../helpers/Helpers', () => ({
|
|
7
|
+
useDebouncedEffect: jest.fn().mockImplementation((fn) => fn()),
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
describe('When DebouncedSearch mounts', () => {
|
|
11
|
+
it('should have a input field', () => {
|
|
12
|
+
render(<DebouncedSearch onSearch={jest.fn()} />)
|
|
13
|
+
expect(screen.getByRole('textbox')).toBeTruthy()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should have a clear icon when clearSearch is true and user has typed something', () => {
|
|
17
|
+
const { getByTestId } = render(<DebouncedSearch onSearch={jest.fn()} clearSearch debounceTimeout={0} />)
|
|
18
|
+
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } })
|
|
19
|
+
expect(getByTestId('clear-search')).toBeTruthy()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should call onSearch when input value changes', () => {
|
|
23
|
+
const onSearch = jest.fn()
|
|
24
|
+
render(<DebouncedSearch onSearch={onSearch} debounceTimeout={0} />)
|
|
25
|
+
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } })
|
|
26
|
+
expect(onSearch).toHaveBeenCalled()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should clear input value when clear icon is clicked', () => {
|
|
30
|
+
const { container } = render(<DebouncedSearch onSearch={jest.fn()} clearSearch debounceTimeout={0} />)
|
|
31
|
+
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } })
|
|
32
|
+
const inputBar = container.querySelector('input')
|
|
33
|
+
expect(inputBar.value).toBe('test')
|
|
34
|
+
fireEvent.click(screen.getByRole('button'))
|
|
35
|
+
expect(inputBar.value).toBe('')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should have a placeholder', () => {
|
|
39
|
+
render(<DebouncedSearch onSearch={jest.fn()} placeholder="test" />)
|
|
40
|
+
expect(screen.getByPlaceholderText('test')).toBeTruthy()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should not show clear icon when showClearIcon is false', () => {
|
|
44
|
+
render(<DebouncedSearch onSearch={jest.fn()} showClearIcon={false} />)
|
|
45
|
+
expect(screen.queryByRole('button')).toBeNull()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should have a custom Icon', () => {
|
|
49
|
+
const { container } = render(<DebouncedSearch onSearch={jest.fn()} Icon={ICClear} iconClass="icon-class" />)
|
|
50
|
+
expect(container.querySelector('.icon-class')).toBeTruthy()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should support children', () => {
|
|
54
|
+
render(
|
|
55
|
+
<DebouncedSearch onSearch={jest.fn()}>
|
|
56
|
+
<div>test</div>
|
|
57
|
+
</DebouncedSearch>,
|
|
58
|
+
)
|
|
59
|
+
expect(screen.getByText('test')).toBeTruthy()
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { GridProps } from './types'
|
|
3
|
+
|
|
4
|
+
// This is meant to be a reusable component that will provide a grid like dynamic layout where xs is the number of columns out of 12 that the item will take up
|
|
5
|
+
export default function Grid({
|
|
6
|
+
container,
|
|
7
|
+
spacing = 0,
|
|
8
|
+
item,
|
|
9
|
+
xs,
|
|
10
|
+
containerClass = '',
|
|
11
|
+
itemClass = '',
|
|
12
|
+
children,
|
|
13
|
+
}: GridProps) {
|
|
14
|
+
const containerStyles = container ? { gap: spacing + 'px' } : {}
|
|
15
|
+
|
|
16
|
+
if (item) {
|
|
17
|
+
const getColumnWidth = () => {
|
|
18
|
+
const percentageWidth = (xs / 12) * 100
|
|
19
|
+
// DONT CHANGE IT FROM CALC SINCE CALC CONVERTS TO PX which is needed to handle text overflow
|
|
20
|
+
return `calc(${percentageWidth}%)`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const itemStyles = {
|
|
24
|
+
flex: `1 1 ${getColumnWidth()}`,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className={`p-0 ${itemClass}`} style={itemStyles}>
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={`flex-wrap flexbox ${container ? containerClass : ''}`} style={containerStyles}>
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
package/src/Common/index.ts
CHANGED
|
@@ -34,4 +34,6 @@ export * from './Policy.Types'
|
|
|
34
34
|
export { default as DeleteComponent } from './DeleteComponentModal/DeleteComponent'
|
|
35
35
|
export * from './ImageTags'
|
|
36
36
|
export * from './ImageTags.Types'
|
|
37
|
-
export * from './ResizableTextarea'
|
|
37
|
+
export * from './ResizableTextarea'
|
|
38
|
+
export { default as DebouncedSearch } from './DebouncedSearch/DebouncedSearch'
|
|
39
|
+
export { default as Grid } from './Grid/Grid'
|