@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devtron-labs/devtron-fe-common-lib",
3
- "version": "0.0.26-beta-2",
3
+ "version": "0.0.26-beta-3",
4
4
  "description": "Supporting common component library",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -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 + 'px'
34
- if (dependentRef) {
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 (dependentRef && nextHeight < dependentRef.current.scrollHeight) {
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
- if (dependentRef) {
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 + 'px'
57
- if (dependentRef) {
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
  }
@@ -68,5 +68,4 @@ export interface ResizableTagTextAreaProps {
68
68
  refVar?: React.MutableRefObject<HTMLTextAreaElement>
69
69
  dependentRef?: React.MutableRefObject<HTMLTextAreaElement>
70
70
  dataTestId?: string
71
- handleKeyDown?: any
72
71
  }
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ export interface GridProps {
2
+ container?: boolean
3
+ spacing?: number
4
+ item?: boolean
5
+ xs?: number
6
+ containerClass?: string
7
+ itemClass?: string
8
+ children?: React.ReactNode
9
+ }
@@ -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'