-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from devtron-labs/feat/dashboard-common
Feat: dashboard common
- Loading branch information
Showing
9 changed files
with
227 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import React, { useEffect, useState } from 'react' | ||
import { useDebouncedEffect } from './Utils' | ||
import { ReactComponent as ICClear } from '../../Assets/Icon/ic-error-cross.svg' | ||
import { DebouncedSearchProps } from './Types' | ||
|
||
/** | ||
* @param onSearch - Callback function to be called on search | ||
* @param Icon - (Optional) Icon to be shown before the input | ||
* @param iconClass - (Optional) Class for the icon | ||
* @param children - (Optional) In case we want to add another button or any other element | ||
* @param placeholder - (Optional) Placeholder for the input | ||
* @param containerClass - (Optional) Class for the container | ||
* @param inputClass - (Optional) Class for the input field | ||
* @param debounceTimeout - (Optional) Timeout for the debounce with default value of 500ms | ||
* @param clearSearch - (Optional) To clear the search text | ||
* @param showClearIcon - (Optional) To show the clear icon default value is true | ||
*/ | ||
export default function DebouncedSearch({ | ||
onSearch, | ||
Icon, | ||
children, | ||
placeholder, | ||
containerClass = '', | ||
iconClass = '', | ||
inputClass = '', | ||
debounceTimeout = 500, | ||
clearSearch, | ||
showClearIcon = true, | ||
}: DebouncedSearchProps) { | ||
const [searchText, setSearchText] = useState<string>('') | ||
|
||
useEffect(() => { | ||
setSearchText('') | ||
}, [clearSearch]) | ||
|
||
const handleSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setSearchText(e.target.value) | ||
} | ||
|
||
const handleClearSearch = () => { | ||
setSearchText('') | ||
} | ||
|
||
useDebouncedEffect(() => onSearch(searchText), debounceTimeout, [searchText, onSearch]) | ||
|
||
return ( | ||
<div className={containerClass}> | ||
{Icon && <Icon className={iconClass} />} | ||
|
||
<input | ||
type="text" | ||
className={inputClass} | ||
placeholder={placeholder ?? 'Search'} | ||
value={searchText} | ||
onChange={handleSearchTextChange} | ||
autoFocus | ||
data-testid="debounced-search" | ||
/> | ||
|
||
{showClearIcon && !!searchText && ( | ||
<button | ||
type="button" | ||
className="dc__outline-none-imp dc__no-border p-0 bc-n50 flex" | ||
onClick={handleClearSearch} | ||
data-testid="clear-search" | ||
> | ||
<ICClear className="icon-dim-20 icon-n6" /> | ||
</button> | ||
)} | ||
|
||
{/* In case we want to add another button or something */} | ||
{children} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export interface DebouncedSearchProps { | ||
onSearch: (query: string) => void | ||
Icon?: React.FunctionComponent<React.SVGProps<SVGSVGElement>> | ||
placeholder?: string | ||
inputClass?: string | ||
containerClass?: string | ||
iconClass?: string | ||
children?: React.ReactNode | ||
debounceTimeout?: number | ||
clearSearch?: boolean | ||
showClearIcon?: boolean | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { useEffect, useRef } from 'react' | ||
|
||
export function useDebouncedEffect(callback, delay, deps: unknown[] = []) { | ||
// function will be executed only after the specified time once the user stops firing the event. | ||
const firstUpdate = useRef(true) | ||
useEffect(() => { | ||
if (firstUpdate.current) { | ||
firstUpdate.current = false | ||
return | ||
} | ||
const handler = setTimeout(() => { | ||
callback() | ||
}, delay) | ||
|
||
return () => { | ||
clearTimeout(handler) | ||
} | ||
}, [delay, ...deps]) | ||
} |
61 changes: 61 additions & 0 deletions
61
src/Common/DebouncedSearch/__tests__/DebouncedSearch.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import React from 'react' | ||
import { render, screen, fireEvent } from '@testing-library/react' | ||
import { ReactComponent as ICClear } from '../../../../assets/icons/ic-error.svg' | ||
import DebouncedSearch from '../DebouncedSearch' | ||
|
||
jest.mock('../../helpers/Helpers', () => ({ | ||
useDebouncedEffect: jest.fn().mockImplementation((fn) => fn()), | ||
})) | ||
|
||
describe('When DebouncedSearch mounts', () => { | ||
it('should have a input field', () => { | ||
render(<DebouncedSearch onSearch={jest.fn()} />) | ||
expect(screen.getByRole('textbox')).toBeTruthy() | ||
}) | ||
|
||
it('should have a clear icon when clearSearch is true and user has typed something', () => { | ||
const { getByTestId } = render(<DebouncedSearch onSearch={jest.fn()} clearSearch debounceTimeout={0} />) | ||
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } }) | ||
expect(getByTestId('clear-search')).toBeTruthy() | ||
}) | ||
|
||
it('should call onSearch when input value changes', () => { | ||
const onSearch = jest.fn() | ||
render(<DebouncedSearch onSearch={onSearch} debounceTimeout={0} />) | ||
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } }) | ||
expect(onSearch).toHaveBeenCalled() | ||
}) | ||
|
||
it('should clear input value when clear icon is clicked', () => { | ||
const { container } = render(<DebouncedSearch onSearch={jest.fn()} clearSearch debounceTimeout={0} />) | ||
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } }) | ||
const inputBar = container.querySelector('input') | ||
expect(inputBar.value).toBe('test') | ||
fireEvent.click(screen.getByRole('button')) | ||
expect(inputBar.value).toBe('') | ||
}) | ||
|
||
it('should have a placeholder', () => { | ||
render(<DebouncedSearch onSearch={jest.fn()} placeholder="test" />) | ||
expect(screen.getByPlaceholderText('test')).toBeTruthy() | ||
}) | ||
|
||
it('should not show clear icon when showClearIcon is false', () => { | ||
render(<DebouncedSearch onSearch={jest.fn()} showClearIcon={false} />) | ||
expect(screen.queryByRole('button')).toBeNull() | ||
}) | ||
|
||
it('should have a custom Icon', () => { | ||
const { container } = render(<DebouncedSearch onSearch={jest.fn()} Icon={ICClear} iconClass="icon-class" />) | ||
expect(container.querySelector('.icon-class')).toBeTruthy() | ||
}) | ||
|
||
it('should support children', () => { | ||
render( | ||
<DebouncedSearch onSearch={jest.fn()}> | ||
<div>test</div> | ||
</DebouncedSearch>, | ||
) | ||
expect(screen.getByText('test')).toBeTruthy() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React from 'react' | ||
import { GridProps } from './types' | ||
|
||
// 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 | ||
export default function Grid({ | ||
container, | ||
spacing = 0, | ||
item, | ||
xs, | ||
containerClass = '', | ||
itemClass = '', | ||
children, | ||
}: GridProps) { | ||
const containerStyles = container ? { gap: spacing + 'px' } : {} | ||
|
||
if (item) { | ||
const getColumnWidth = () => { | ||
const percentageWidth = (xs / 12) * 100 | ||
// DONT CHANGE IT FROM CALC SINCE CALC CONVERTS TO PX which is needed to handle text overflow | ||
return `calc(${percentageWidth}%)` | ||
} | ||
|
||
const itemStyles = { | ||
flex: `1 1 ${getColumnWidth()}`, | ||
} | ||
|
||
return ( | ||
<div className={`p-0 ${itemClass}`} style={itemStyles}> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
return ( | ||
<div className={`flex-wrap flexbox ${container ? containerClass : ''}`} style={containerStyles}> | ||
{children} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface GridProps { | ||
container?: boolean | ||
spacing?: number | ||
item?: boolean | ||
xs?: number | ||
containerClass?: string | ||
itemClass?: string | ||
children?: React.ReactNode | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters